Google Firestore - Comment obtenir un document par plusieurs identifiants en un aller-retour?

98

Je me demande s'il est possible d'obtenir plusieurs documents par liste d'identifiants en un aller-retour (appel réseau) au Firestore.

Joon
la source
4
Vous semblez supposer que les allers-retours entraînent des problèmes de performances dans votre application. Je ne le supposerais pas. Firebase a l'habitude de fonctionner correctement dans de tels cas, car il achemine les requêtes . Bien que je n'ai pas vérifié le comportement de Firestore dans ce scénario, j'aimerais voir la preuve d'un problème de performances avant de supposer qu'il existe.
Frank van Puffelen
1
Disons que je besoin de documents a, b, cde faire quelque chose. Je demande les trois en parallèle dans des demandes distinctes. aprend b100 ms, cprend 150 ms et prend 3000 ms. En conséquence, je dois attendre 3000 ms pour faire la tâche. Ça va être maxd'eux. Ce sera plus risqué lorsque le nombre de documents à récupérer sera important. Cela dépend de l'état du réseau, je pense que cela peut devenir un problème.
Joon
1
Les envoyer tous comme un seul ne SELECT * FROM docs WHERE id IN (a,b,c)prendrait-il pas le même temps? Je ne vois pas la différence, car la connexion est établie une fois et le reste est acheminé à ce sujet. Le temps (après l'établissement initial de la connexion) est le temps de chargement de tous les documents + 1 aller-retour, identique pour les deux approches. S'il se comporte différemment pour vous, pouvez-vous partager un échantillon (comme dans ma question liée)?
Frank van Puffelen
Je pense que je t'ai perdu. Lorsque vous dites qu'il est en pipeline, voulez-vous dire que Firestore regroupe et envoie automatiquement des requêtes à leur serveur en un aller-retour vers la base de données?
Joon
FYI, ce que je veux dire par un aller-retour est un appel réseau à la base de données du client. Je demande si plusieurs requêtes sont automatiquement regroupées en un aller-retour par Firestore, ou si plusieurs requêtes sont effectuées en plusieurs allers-retours en parallèle.
Joon

Réponses:

90

si vous êtes dans Node:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L701

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

Ceci est spécifiquement pour le SDK du serveur

MISE À JOUR: «Cloud Firestore [sdk côté client] prend désormais en charge les requêtes IN!»

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])

Nick Franceschina
la source
29
Pour tous ceux qui cherchent à appeler cette méthode avec un tableau de références de documents généré dynamiquement, vous pouvez le faire comme ceci: firestore.getAll (... arrayOfReferences) .then ()
Horea
1
Je suis désolé @KamanaKisinga ... Je n'ai pas fait de trucs sur Firebase depuis près d'un an et je ne peux pas vraiment aider pour le moment (hé regarde, j'ai en fait posté cette réponse il y a un an aujourd'hui!)
Nick Franceschina
2
Les SDK côté client offrent désormais également cette fonctionnalité. voir la réponse de jeodonara pour un exemple: stackoverflow.com/a/58780369
Frank van Puffelen
4
attention: le filtre d'entrée est limité à 10 éléments actuellement. Vous découvrirez donc probablement que c'est inutile lorsque vous êtes sur le point de passer à la production.
Martin Cremer le
6
en fait, vous devez utiliser firebase.firestore.FieldPath.documentId()et non'id'
Maddocks
20

Ils viennent d'annoncer cette fonctionnalité, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Vous pouvez maintenant utiliser des requêtes comme, mais sachez que la taille d'entrée ne peut pas être supérieure à 10.

userCollection.where('uid', 'in', ["1231","222","2131"])

Jeadonara
la source
Il existe une requête whereIn plutôt que where. Et je ne sais pas comment concevoir une requête pour plusieurs documents à partir d'une liste d'identifiants de documents appartenant à une collection spécifique. Veuillez aider.
Erreur de compilation se
17
@Compileerrorend pourrais-tu essayer ça? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara
merci, en particulier pour lefirebase.firestore.FieldPath.documentId()
Cherniv
10

Non, pour le moment, il n'existe aucun moyen de regrouper plusieurs demandes de lecture à l'aide du SDK Cloud Firestore et donc aucun moyen de garantir que vous pouvez lire toutes les données à la fois.

Cependant, comme Frank van Puffelen l'a dit dans les commentaires ci-dessus, cela ne signifie pas que la récupération de 3 documents sera 3 fois plus lente que la récupération d'un document. Il est préférable d'effectuer vos propres mesures avant d'arriver à une conclusion ici.

Sam Stern
la source
1
Le fait est que je veux connaître les limites théoriques des performances de Firestore avant de migrer vers Firestore. Je ne veux pas migrer et me rendre compte que ce n'est pas assez bon pour mon cas d'utilisation.
Joon
2
Salut, il y a aussi une considération de cose ici. Disons que j'ai stocké la liste de tous les identifiants de mes amis et que le nombre est de 500. Je peux obtenir la liste en 1 coût de lecture, mais pour afficher leur nom et leur photoURL, cela me coûtera 500 lectures.
Tapas Mukherjee
1
Si vous essayez de lire 500 documents, cela prend 500 lectures. Si vous combinez les informations dont vous avez besoin sur les 500 documents dans un seul document supplémentaire, cela ne prend qu'une seule lecture. C'est ce qu'on appelle une sorte de duplication de données est tout à fait normale dans la plupart des bases de données NoSQL, y compris Cloud Firestore.
Frank van Puffelen
1
@FrankvanPuffelen Par exemple, dans mongoDb, vous pouvez utiliser ObjectId comme suit stackoverflow.com/a/32264630/648851 .
Sitian Liu
2
Comme @FrankvanPuffelen l'a dit, la duplication de données est assez courante dans la base de données NoSQL. Ici, vous devez vous demander à quelle fréquence ces données doivent être lues et à quel point elles doivent être à jour. Si vous stockez 500 informations sur les utilisateurs, disons leur nom + photo + identifiant, vous pouvez les obtenir en une seule lecture. Mais si vous en avez besoin à jour, vous devrez probablement utiliser une fonction cloud pour mettre à jour ces références chaque fois qu'un utilisateur met à jour son nom / photo, exécutant donc une fonction cloud + effectuant des opérations d'écriture. Il n'y a pas d'implémentation «bonne» / «meilleure», cela dépend simplement de votre cas d'utilisation.
schankam le
10

En pratique, vous utiliseriez firestore.getAll comme ça

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

ou avec la syntaxe de promesse

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}
Sébastien
la source
3
cela devrait vraiment être la réponse sélectionnée car elle vous permet d'utiliser plus de 10 identifiants
sshah98
9

Vous pouvez utiliser une fonction comme celle-ci:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

Il peut être appelé avec un seul identifiant:

getById('collection', 'some_id')

ou un tableau d'identifiants:

getById('collection', ['some_id', 'some_other_id'])
JP de la Torre
la source
5

La meilleure façon de le faire est sûrement d'implémenter la requête réelle de Firestore dans une fonction cloud? Il n'y aurait alors qu'un seul appel aller-retour du client à Firebase, ce qui semble être ce que vous demandez.

Vous voulez vraiment conserver toute votre logique d'accès aux données comme ce côté serveur de toute façon.

En interne, il y aura probablement le même nombre d'appels à Firebase lui-même, mais ils seraient tous via les interconnexions ultra-rapides de Google, plutôt que le réseau externe, et combinés avec le pipeline que Frank van Puffelen a expliqué, vous devriez obtenir d'excellentes performances de cette approche.

Chris Wilson
la source
3
Stocker la mise en œuvre dans une fonction cloud est la bonne décision dans certains cas où vous avez une logique complexe, mais probablement pas dans le cas où vous souhaitez simplement fusionner une liste avec plusieurs identifiants. Ce que vous perdez, c'est la mise en cache côté client et le formatage de retour normalisé des appels normaux. Cela a causé plus de problèmes de performances qu'il n'en a résolu dans certains cas dans mes applications lorsque j'ai utilisé l'approche.
Jérémie
3

Si vous utilisez le flutter, vous pouvez effectuer les opérations suivantes:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

Cela renverra un Future contenant List<DocumentSnapshot>que vous pourrez itérer à votre guise.

Monis
la source
2

Voici comment vous feriez quelque chose comme ça dans Kotlin avec le SDK Android.
Cela peut ne pas être nécessairement en un aller-retour, mais cela regroupe efficacement le résultat et évite de nombreux rappels imbriqués.

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

Notez que la récupération de documents spécifiques est bien meilleure que la récupération de tous les documents et le filtrage du résultat. En effet, Firestore vous facture le jeu de résultats de la requête.

Markymark
la source
1
Fonctionne bien, exactement ce que je cherchais!
Georgi
0

Cela ne semble pas possible dans Firestore pour le moment. Je ne comprends pas pourquoi la réponse d'Alexandre est acceptée, la solution qu'il propose renvoie simplement tous les documents de la collection «utilisateurs».

En fonction de ce que vous devez faire, vous devriez envisager de dupliquer les données pertinentes que vous devez afficher et ne demander un document complet que lorsque cela est nécessaire.

Horea
la source
0

Le mieux que vous puissiez faire est de ne pas utiliser Promise.allcar votre client doit alors attendre .allles lectures avant de continuer.

Répétez les lectures et laissez-les se résoudre indépendamment. Du côté client, cela se résume probablement à l'interface utilisateur ayant plusieurs images du chargeur de progression résolues en valeurs indépendamment. Cependant, c'est mieux que de geler l'ensemble du client jusqu'à .allce que les lectures soient résolues.

Par conséquent, videz immédiatement tous les résultats synchrones dans la vue, puis laissez les résultats asynchrones entrer au fur et à mesure de leur résolution, individuellement. Cela peut sembler insignifiant, mais si votre client a une mauvaise connectivité Internet (comme je l'ai actuellement dans ce café), geler toute l'expérience client pendant plusieurs secondes entraînera probablement une expérience `` cette application est nul ''.

Ronnie Royston
la source
2
Il est asynchrone, il existe de nombreux cas d'utilisation Promise.all... il n'est pas nécessaire de "geler" quoi que ce soit - vous devrez peut-être attendre toutes les données avant de pouvoir faire quelque chose de significatif
Ryan Taylor
Il existe plusieurs cas d'utilisation où vous avez besoin de charger toutes vos données, donc l'attente (comme un spinner avec un message approprié, pas besoin de "geler" une interface utilisateur comme vous le dites) peut être totalement nécessaire par Promise.all .. Cela dépend vraiment du type de produits que vous construisez ici. Ce genre de commentaires est à mon avis très hors de propos et il ne devrait pas y avoir de «meilleurs» mots. Cela dépend vraiment de tous les cas d'utilisation différents auxquels vous pouvez faire face et de ce que votre application fait pour l'utilisateur.
schankam
0

J'espère que cela vous aide, cela fonctionne pour moi.

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
Muhammad Mabrouk
la source