web-dev-qa-db-fra.com

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

Il est essentiel pour mon application de pouvoir sélectionner plusieurs documents au hasard dans une collection dans firebase.

Comme il n’existe pas de fonction native intégrée à Firebase (à ma connaissance) pour réaliser une requête répondant à cet objectif, ma première idée a été d’utiliser des curseurs de requête pour sélectionner un index de début et de fin aléatoires, à condition que je dispose du nombre de documents. la collection.

Cette approche ne fonctionnerait que de manière limitée car chaque document serait servi dans l’ordre suivant avec les documents voisins à chaque fois; Cependant, si je pouvais sélectionner un document à partir de son index dans sa collection parente, je pourrais réaliser une requête de document aléatoire, mais le problème est que je ne trouve pas de documentation décrivant comment vous pouvez le faire ou même si vous le pouvez.

Voici ce que j'aimerais pouvoir faire. Examinons le schéma suivant:

root/
  posts/
     docA
     docB
     docC
     docD

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

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

Est-ce que je peux faire quelque chose dans ce sens? Ou, y a-t-il une façon différente de sélectionner des documents aléatoires de la même manière?

S'il vous plaît aider.

14
Garret Kaye

Version agnostique d'ID de document

Lorsque vous écrivez un document, commencez par générer un entier aléatoire de 64 bits et ajoutez-le sous forme de champ appelé random.

Cela créera un index avec vos documents triés au hasard.

Maintenant, pour sélectionner un document aléatoire, générez un autre entier aléatoire de 64 bits. Nous allons utiliser cela dans une requête pour choisir une position aléatoire dans l'index et lire le document suivant:

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

Vérifiez que cela a renvoyé un document. Si ce n'est pas le cas, inversez la direction et essayez à nouveau:

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

Version d'identification automatique

Si vous utilisez les identifiants automatiques générés de manière aléatoire fournis dans nos bibliothèques clientes, vous pouvez utiliser ce même système pour sélectionner un document de manière aléatoire.

Dans ce système, générez un nouvel identifiant automatique à utiliser dans la requête. Sélectionnez maintenant dans la collection les documents (limite 1) supérieurs à votre nouvel identifiant automatique.

Une légère différence avec la version agnostique est que vous ne pouvez pas commander d'ID de document par ordre décroissant. Cela signifie que si votre requête ne renvoie pas de document, vous devez générer un nouvel identifiant automatique, puis réessayer.

Plusieurs nombres aléatoires

Souvent, vous voudrez sélectionner plus d'un document aléatoire à la fois. Il existe 2 façons différentes d’ajuster les techniques ci-dessus en fonction des compromis que vous souhaitez.

Rincer et répéter

Cette méthode est simple. Répétez simplement le processus, en sélectionnant chaque fois un nouvel entier aléatoire.

Cette méthode vous donnera des séquences aléatoires de documents sans vous soucier de voir les mêmes modèles de manière répétée.

Le compromis est qu'il sera plus lent que la méthode suivante car il nécessite un aller-retour séparé vers le service pour chaque document.

Laisse le venir

Dans cette approche, augmentez simplement le nombre dans la limite des documents souhaités. C'est un peu plus complexe car vous pourriez renvoyer des documents 0..limit lors de l'appel. Vous devrez ensuite récupérer les documents manquants de la même manière, mais en réduisant la limite à la seule différence. Si vous savez qu'il y a plus de documents que le nombre demandé, vous pouvez ignorer le cas Edge qui consiste à ne jamais récupérer suffisamment de documents.

Le compromis avec cette solution est en séquences répétées. Bien que les documents soient classés de manière aléatoire, si vous vous retrouvez avec des plages superposées, vous verrez le même motif que précédemment. Cependant, il existe deux façons d’atténuer ce problème. Premièrement, les documents insérés finiront par être entrelacés, modifiant progressivement la séquence. Deuxièmement, vous pouvez accélérer le processus en mettant à jour le champ random avec un nouveau numéro à chaque mise à jour.

Cette approche est plus rapide que l'option "Rinse & Repeat", car vous demanderez tous les documents en un seul appel.

36
Dan McGrath

Publier ceci pour aider ceux qui ont ce problème à l’avenir.

Si vous utilisez des identifiants automatiques, vous pouvez générer un nouvel identifiant automatique et interroger l'identifiant automatique le plus proche, comme indiqué dans Réponse de Dan McGrath .

J'ai récemment créé une API de citation aléatoire et je devais obtenir des citations aléatoires à partir d'une collection de produits de stockage.
Voici comment j'ai résolu ce 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 la suivante:

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

Et l'appelant à nouveau avec l'opération inversée si aucun document n'est trouvé.

J'espère que ça aide!
Si cela vous intéresse, vous pouvez trouver cette partie de mon API sur GitHub

3
ajzbc

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

L'extrait de code ci-dessous 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) .expand () est une opération rxjs pour la récursivité afin de nous assurer que nous obtenons un document à partir de la sélection aléatoire. 

2) Pour que la récursivité fonctionne comme prévu, nous avons besoin de 2 fonctions distinctes. 

3) Nous utilisons EMPTY pour mettre fin à l'opérateur .expand (). 

import { Observable, EMPTY } from 'rxjs';
0
choopage - Jek Bao

J'ai un moyen d'obtenir au hasard un document de liste dans Firebase Firestore, c'est très simple. Lorsque je télécharge des données sur Firestore, je crée un nom de champ "position" avec une valeur aléatoire de 1 à 1 milions. Lorsque je reçois des données de Fire Store, je vais définir le champ Ordre par "Position" et mettre à jour la valeur correspondante. De nombreuses données de charge utilisateur et des données sont toujours mises à jour et ce sera une valeur aléatoire.

0
HVA Software