27 votes

Firestore: Comment obtenir des documents aléatoires dans une collection

Il est essentiel pour mon application pour être en mesure de sélectionner plusieurs documents au hasard à partir d'une collection de firebase.

Depuis il n'y a pas de fonction native intégrée dans Firebase (que je connais) pour atteindre une requête qui n'a tout simplement cela, ma première pensée a été d'utiliser la requête curseurs pour sélectionner au hasard un de début et de fin de l'index à la condition que j'ai le nombre de documents dans la collection.

Cette approche pourrait fonctionner, mais seulement de façon limitée, car chaque document sera servi dans la séquence de avec ses voisins documents à tout moment; cependant, si j'étais en mesure de sélectionner un document par son index dans sa collection parent je pouvais atteindre un hasard document de requête, mais le problème est que je ne trouve pas de documentation qui décrit comment vous pouvez le faire ou même si vous pouvez le faire.

Voici ce que j'aimerais être en mesure de le faire, considérez les points suivants firestore schéma:

root/
  posts/
     docA
     docB
     docC
     docD

Ensuite dans mon client (je suis dans un environnement Swift) je voudrais écrire une requête qui peut faire ceci:

db.collection("posts")[0, 1, 3] // would return: docA, docB, docD

Est-il de toute façon je peux faire quelque chose le long des lignes de cette situation? Ou, est-il un autre moyen que je peux sélectionner des documents aléatoires dans un mode similaire?

S'il vous plaît aider.

53voto

Dan McGrath Points 9839

À l'aide de généré de façon aléatoire, des index et des requêtes simples, vous pouvez choisir au hasard des documents à partir d'une collection ou un groupe de collection dans le Cloud Firestore.

Cette réponse est divisé en 4 sections avec différentes options dans chaque section:

  1. Comment générer de façon aléatoire à l'index
  2. Comment interroger le hasard index
  3. Sélection de plusieurs documents aléatoires
  4. Le réensemencement pour les cours de l'aléatoire

Comment générer de façon aléatoire à l'index

La base de cette réponse, c'est la création d'un champ indexé que sur ordre croissant ou décroissant, les résultats dans le document en cours de façon aléatoire commandé. Il y a différentes façons pour créer cette, regardons donc 2, en commençant par le plus facilement disponible.

Auto-Id de la version

Si vous utilisez générés aléatoirement automatique des identifiants fournis dans nos bibliothèques client, vous pouvez utiliser ce même système, de sélectionner au hasard un document. Dans ce cas, le hasard a ordonné l'indice est l'id de document.

Plus tard dans notre section requête, la valeur aléatoire vous générez est une nouvelle auto-id (iOS, Android, Web) et le champ de recherche est l' __name__ champ, et le "faible valeur", évoqué plus tard est une chaîne vide. C'est de loin la méthode la plus simple pour générer de l'aléatoire, de l'index et fonctionne indépendamment de la langue et de la plateforme.

Par défaut, le nom du document (__name__) est seulement indexés par ordre croissant, et vous ne pouvez pas renommer un document existant court de suppression et recréation. Si vous avez besoin de l'un de ces, vous pouvez toujours utiliser cette méthode et de l'enregistrer dans un auto-id comme un champ appelé random plutôt que de surcharger le nom du document à cet effet.

Entier aléatoire version

Lorsque vous écrivez un document, d'abord générer un entier aléatoire dans un délimitée gamme et de le définir comme un champ appelé" random. En fonction du nombre de documents que vous attendez, vous pouvez utiliser un autre délimitée gamme pour économiser de l'espace ou de réduire le risque de collisions (qui réduisent l'efficacité de cette technique).

Vous devez envisager les langues dont vous avez besoin, il y aura différentes considérations. Alors que Swift est facile, JavaScript notamment peut avoir un gotcha:

  • Entier de 32 bits: parfait pour les petits (~10K peu probable d'avoir une collision) des ensembles de données
  • 64-bit integer: Grands ensembles de données (remarque: JavaScript n'est pas nativement en charge, encore)

Cela va créer un index avec vos documents, classés de manière aléatoire. Plus tard dans notre section requête, la valeur aléatoire vous générez en aura un autre de ces valeurs, et la "faible valeur", évoqué plus tard sera -1.

Comment interroger le hasard index

Maintenant que vous avez un hasard index, vous aurez envie de l'interroger. Ci-dessous, nous examinons certaines de simples variantes pour sélectionner un 1 aléatoire document, ainsi que des options pour sélectionner plus de 1.

Pour toutes ces options, vous aurez envie de générer une nouvelle valeur aléatoire dans la même forme que les valeurs indexées vous avez créé lors de l'écriture du document, désigné par la variable random ci-dessous. Nous allons utiliser cette valeur pour trouver un endroit aléatoire sur l'index.

Wrap-around

Maintenant que vous avez une valeur aléatoire, vous pouvez demander un document unique:

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

Vérifiez que cela a renvoyé un document. Si ce n'est pas, la requête de nouveau, mais l'utilisation de la "faible valeur" pour vos aléatoire de l'index. Par exemple, si vous n'avez Entiers Aléatoires alors lowValue est 0:

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
                   .order(by: "random")
                   .limit(to: 1)

Tant que vous avez un document unique, vous serez assuré de revenir au moins 1 document.

Bi-directionnel

Le wrap-around méthode est simple à mettre en œuvre et permet d'optimiser le stockage avec seulement un ordre croissant de l'indice de permis. Un inconvénient est la possibilité de valeurs injustement blindé. E. g si les 3 premiers documents (A,B,C) et hors de 10K avez des valeurs de l'indice d'Un:409496, B:436496, C:818992, alors A et C ont un peu moins de 1/10K chance d'être sélectionné, tandis que B est effectivement protégé par la proximité de l'Un et seulement environ un 1/160K chance.

Plutôt que d'interroger dans une seule direction et d'emballage autour si une valeur n'est pas trouvée, vous pouvez à la place choisir au hasard entre >= et <=, ce qui réduit la probabilité de injustement blindé valeurs par moitié, au prix de doubler le stockage des index.

Si une direction ne retourne pas de résultat, passez à l'autre direction:

queryRef = postsRef.whereField("random", isLessThanOrEqualTo: random)
                   .order(by: "random", descending: true)
                   .limit(to: 1)

queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

Sélection de plusieurs documents aléatoires

Souvent, vous aurez envie de les sélectionner plus de 1 aléatoire document à la fois. Il y a 2 méthodes pour ajuster les techniques ci-dessus en fonction de ce compromis que vous voulez.

Rincer Et Répéter

Cette méthode est simple. Il suffit de répéter le processus, y compris la sélection d'un nouveau nombre entier aléatoire à chaque fois.

Cette méthode vous donnera des séquences aléatoires de documents sans se soucier de voir les mêmes modèles à plusieurs reprises.

En échange, il sera plus lente que la méthode suivante, car il nécessite un aller-retour pour le service pour chaque document.

Garder à venir

Dans cette approche, il suffit d'augmenter le nombre dans la limite du document désiré. C'est un peu plus complexe que vous pourriez retourner 0..limit documents de l'appel. Vous aurez alors besoin d'obtenir les documents manquants de la même manière, mais avec la limite réduits à la différence. Si vous savez qu'il ya plus de documents au total que le numéro que vous demandez, vous pouvez optimiser en ignorant le cas limite de ne jamais avoir assez de recul documents sur le second appel (mais pas le premier).

L'inconvénient de cette solution est de séquences répétées. Alors que les documents sont ordonnés aléatoirement, si jamais vous en fin de chevauchement des plages, vous verrez le même modèle que vous avez vu avant. Il existe des moyens pour atténuer cette préoccupation discuté dans la section suivante sur le réensemencement.

Cette approche est plus rapide 'Rincer et Répéter" que vous serez en demandant à tous les documents dans le meilleur des cas, un seul appel ou un pire des cas 2 appels.

Le réensemencement pour les cours de l'aléatoire

Bien que cette méthode vous donne des documents au hasard si le document est statique de la probabilité de chaque document retournée sera statique ainsi. C'est un problème que certaines valeurs pourraient avoir injustement bas ou des probabilités élevées sur la base de valeurs aléatoires qu'ils ont obtenu. Dans de nombreux cas d'utilisation, c'est bien, mais dans certains cas, vous souhaiterez peut-être augmenter à long terme le caractère aléatoire de la plus uniforme de la chance de retourner tout document 1.

Notez que insérée documents sera à la fin tissé entre-deux, en train de changer progressivement les probabilités, comme la suppression des documents. Si l'insérer/supprimer des taux est trop petite compte tenu du nombre de documents, il existe quelques stratégies pour remédier à cela.

Multi-Aléatoire

Plutôt que de s'inquiéter de sortir le réensemencement, vous pouvez toujours créer plusieurs aléatoire index par le document, puis choisissez au hasard un de ces indices à chaque fois. Par exemple, le champ random être une carte avec des sous-champs 1 à 3:

{'random': {'1': 32456, '2':3904515723, '3': 766958445}}

Maintenant, vous allez être d'interroger de façon aléatoire.1, aléatoire.2, aléatoire.3 au hasard, la création d'une plus grande propagation de l'aléatoire. Il s'agit essentiellement des métiers de stockage accrue pour économiser de l'augmentation de calcul (document écrit) d'avoir à effectuer un réamorçage.

Réamorçage sur l'écrit

Toute mise à jour d'un document de re-générer de la valeur aléatoire(s) de l' random champ. Cela vous permettra de déplacer le document dans l'aléatoire de l'index.

Réamorçage sur le lit

Si les valeurs aléatoires générées ne sont pas uniformément réparties (ils sont aléatoires, donc c'est prévu), puis le même document peut être choisi un dispropriate montant de l'époque. Ceci est facilement compensé par la mise à jour de l'choisis au hasard document avec de nouvelles valeurs aléatoires après sa lecture.

Depuis les écritures sont plus cher et peut hotspot, vous pouvez choisir de mettre à jour uniquement sur la lecture d'un sous-ensemble de la période (e.g, if random(0,100) === 0) update;).

7voto

ajzbc Points 317

Ce détachement pour aider quelqu'un qui a ce problème à l'avenir.

Si vous utilisez Auto Id, vous pouvez générer une nouvelle identification Automatique et de la requête pour les plus proches Auto ID comme mentionné dans Dan McGrath Réponse.

J'ai récemment créé une citation aléatoire de l'api et nécessaires pour obtenir des citations aléatoires à partir d'un firestore collection.
Voilà comment j'ai résolu le problème:

var db = admin.firestore();
var quotes = db.collection("quotes");

var key = quotes.doc().id;

quotes.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
    if(snapshot.size > 0) {
        snapshot.forEach(doc => {
            console.log(doc.id, '=>', doc.data());
        });
    }
    else {
        var quote = quotes.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
        .then(snapshot => {
            snapshot.forEach(doc => {
                console.log(doc.id, '=>', doc.data());
            });
        })
        .catch(err => {
            console.log('Error getting documents', err);
        });
    }
})
.catch(err => {
    console.log('Error getting documents', err);
});

La clé de la requête est ceci:

.where(admin.firestore.FieldPath.documentId(), '>', key)

Et en l'appelant de nouveau à l'opération en sens inverse si aucun document n'est trouvé.

J'espère que cela aide!
Si vous êtes intéressé, vous pouvez trouver cette partie de mon API sur GitHub

2voto

MartinJH Points 1593

Viens de faire ce travail dans Angulaire 7 + RxJS, donc partager ici avec des gens qui veulent un exemple.

J'ai utilisé @Dan McGrath 's réponse, et j'ai choisi ces options: nombre Entier Aléatoire version + Rincer et Répéter l'opération pour plusieurs numéros. J'ai aussi utilisé les trucs expliqué dans cet article: RxJS, où est le Si-Autre Opérateur? pour faire des if/else sur le niveau d'un cours d' (seulement si vous avez besoin d'un apprêt sur que).

Notez également que j'ai utilisé angularfire2 pour facile Firebase intégration Angulaire.

Voici le code:

import { Component, OnInit } from '@angular/core';
import { Observable, merge, pipe } from 'rxjs';
import { map, switchMap, filter, take } from 'rxjs/operators';
import { AngularFirestore, QuerySnapshot } from '@angular/fire/firestore';

@Component({
  selector: 'pp-random',
  templateUrl: './random.component.html',
  styleUrls: ['./random.component.scss']
})
export class RandomComponent implements OnInit {

  constructor(
    public afs: AngularFirestore,
  ) { }

  ngOnInit() {
  }

  public buttonClicked(): void {
    this.getRandom().pipe(take(1)).subscribe();
  }

  public getRandom(): Observable<any[]> {
    const randomNumber = this.getRandomNumber();
    const request$ = this.afs.collection('your-collection', ref => ref.where('random', '>=', randomNumber).orderBy('random').limit(1)).get();
    const retryRequest$ = this.afs.collection('your-collection', ref => ref.where('random', '<=', randomNumber).orderBy('random', 'desc').limit(1)).get();

    const docMap = pipe(
      map((docs: QuerySnapshot<any>) => {
        return docs.docs.map(e => {
          return {
            id: e.id,
            ...e.data()
          } as any;
        });
      })
    );

    const random$ = request$.pipe(docMap).pipe(filter(x => x !== undefined && x[0] !== undefined));

    const retry$ = request$.pipe(docMap).pipe(
      filter(x => x === undefined || x[0] === undefined),
      switchMap(() => retryRequest$),
      docMap
    );

    return merge(random$, retry$);
  }

  public getRandomNumber(): number {
    const min = Math.ceil(Number.MIN_VALUE);
    const max = Math.ceil(Number.MAX_VALUE);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
}

0voto

HVA Software Points 11

J'ai une manière d'obtenir aléatoires une liste de documents dans Firebase Firestore, il est vraiment facile. Quand j'ai télécharger les données sur Firestore j'ai créer un nom de champ "position" avec une valeur aléatoire de 1 à 1 millions. Quand je reçois les données de l'Incendie du magasin, je vais définir la Commande par champ "Position" et la mise à jour de la valeur pour elle, beaucoup de charge de travail des utilisateurs de données et des données toujours à jour et il sera aléatoire.

0voto

choopage - Jek Bao Points 1540

Pour ceux qui utilisent Angulaire + Firestore, en s'appuyant sur @Dan McGrath techniques, voici l'extrait de code.

Ci-dessous extrait de code renvoie 1 document.

  getDocumentRandomlyParent(): Observable<any> {
    return this.getDocumentRandomlyChild()
      .pipe(
        expand((document: any) => document === null ? this.getDocumentRandomlyChild() : EMPTY),
      );
  }

  getDocumentRandomlyChild(): Observable<any> {
      const random = this.afs.createId();
      return this.afs
        .collection('my_collection', ref =>
          ref
            .where('random_identifier', '>', random)
            .limit(1))
        .valueChanges()
        .pipe(
          map((documentArray: any[]) => {
            if (documentArray && documentArray.length) {
              return documentArray[0];
            } else {
              return null;
            }
          }),
        );
  }

1) .développez() est une rxjs fonctionnement de la récursivité pour nous assurer que nous certainement obtenir un document à partir de la sélection aléatoire.

2) la récursivité à fonctionner comme prévu nous avons besoin d'avoir 2 fonctions distinctes.

3) Nous utilisons le VIDE à la fin .développez() de l'opérateur.

import { Observable, EMPTY } from 'rxjs';

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X