web-dev-qa-db-fra.com

Comment obtenir un nombre de documents dans une collection avec Cloud Firestore

Dans Firestore, comment puis-je obtenir le nombre total de documents d'une collection?

Par exemple si j'ai

/people
    /123456
        /name - 'John'
    /456789
        /name - 'Jane'

Je veux interroger le nombre de personnes que j'ai et obtenir 2.

Je pourrais faire une requête sur/people et obtenir ensuite la longueur des résultats renvoyés, mais cela semble être une perte de temps, surtout parce que je le ferai sur de plus grands ensembles de données.

36
justinbc820

Vous avez actuellement 3 options:

Option 1: côté client

C'est fondamentalement l'approche que vous avez mentionnée. Sélectionnez tout dans la collection et comptez du côté client. Cela fonctionne assez bien pour les petits jeux de données mais ne fonctionne évidemment pas si le jeu de données est plus grand.

Option 2: Meilleur effort d'écriture

Avec cette approche, vous pouvez utiliser les fonctions de cloud pour mettre à jour un compteur pour chaque ajout et suppression de la collection.

Cela fonctionne bien pour toute taille de jeu de données, tant que les ajouts/suppressions ne se produisent qu'à un taux inférieur ou égal à 1 par seconde. Cela vous donne un seul document à lire pour vous donner le nombre presque actuel immédiatement.

Si le besoin doit dépasser 1 par seconde, vous devez implémenter compteurs distribués selon notre documentation .

Option 3: temps d'écriture exact

Plutôt que d'utiliser Cloud Functions, dans votre client, vous pouvez mettre à jour le compteur en même temps que vous ajoutez ou supprimez un document. Cela signifie que le compteur sera également à jour, mais vous devrez vous assurer d'inclure cette logique partout où vous ajoutez ou supprimez des documents.

Comme l'option 2, vous devrez implémenter des compteurs distribués si vous souhaitez dépasser le nombre de secondes.

32
Dan McGrath

Si vous utilisez AngulareFire2, vous pouvez faire (en supposant que private afs: AngularFirestore Est injecté dans votre constructeur):

this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));

Ici, values est un tableau de tous les éléments de myCollection. Vous n'avez pas besoin de métadonnées, vous pouvez donc utiliser directement la méthode valueChanges().

4
Johan Chouquet

Les agrégations sont la voie à suivre (les fonctions de firebase ressemblent au moyen recommandé de les mettre à jour car le côté client expose des informations à l'utilisateur que vous ne souhaitez peut-être pas exposer) https://firebase.google.com/docs/firestore/ solutions/agrégation

Une autre manière (non recommandée) qui n’est pas adaptée aux grandes listes et qui implique le téléchargement de la liste complète: res.size comme cet exemple:

db.collection('products').get().then(res => console.log(res.size))
3
Ben Cochrane

Soyez prudent en comptant le nombre de documents pour des collections volumineuses avec une fonction cloud. C'est un peu complexe avec la base de données firestore si vous voulez avoir un compteur précalculé pour chaque collection.

Un tel code ne fonctionne pas dans ce cas:

export const customerCounterListener = 
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

La raison en est que chaque déclencheur d’incendie de cloud doit être idempotent, comme le dit la documentation d’incendie: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Solution

Par conséquent, pour éviter les exécutions multiples de votre code, vous devez gérer les événements et les transactions. C'est ma manière particulière de gérer les grands compteurs de collection:

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

Cas d'utilisation ici:

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

Comme vous pouvez le constater, la clé permettant d’empêcher l’exécution multiple est la propriété appelée eventId dans l’objet de contexte. Si la fonction a été gérée plusieurs fois pour le même événement, l'identifiant de l'événement sera le même dans tous les cas. Malheureusement, vous devez avoir une collection "events" dans votre base de données.

1
Ferran Verdés

Réponse suivante de Dan : Vous pouvez avoir un compteur séparé dans votre base de données et utiliser les fonctions de cloud pour le maintenir. ( Meilleur effort d'écriture )

// Example of performing an increment when item is added
module.exports.incrementIncomesCounter = collectionRef.onCreate(event => {
  const counterRef = event.data.ref.firestore.doc('counters/incomes')

  counterRef.get()
  .then(documentSnapshot => {
    const currentCount = documentSnapshot.exists ? documentSnapshot.data().count : 0

    counterRef.set({
      count: Number(currentCount) + 1
    })
    .then(() => {
      console.log('counter has increased!')
    })
  })
})

Ce code vous montre l'exemple complet de la procédure à suivre: https://Gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7

0
Saint Play