web-dev-qa-db-fra.com

Nombre de collections Fireestore Firestore

Est-il possible de compter le nombre d'éléments d'une collection à l'aide de la nouvelle base de données firebase, firestore?

Si oui, comment je fais ça?


Mise à jour (avril 2019) - FieldValue.increment (voir la solution de collecte à grande échelle)


Comme pour beaucoup de questions, la réponse est - . Cela dépend .

Vous devez faire très attention lorsque vous manipulez de grandes quantités de données en amont. Firestore également vous charge 0,60 $ par million de lectures vous faites.


Petite collection (moins de 100 documents)

à utiliser avec précaution - L'expérience utilisateur frontale peut prendre un coup

Gérer cela sur le front-end devrait aller très bien tant que vous ne faites pas trop de logique avec ce tableau retourné.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});

Collection moyenne (100 à 1000 documents)

à utiliser avec précaution - Les invocations en lecture Firestore peuvent coûter cher

Cela n’est pas faisable en amont car cela risque trop de ralentir le système des utilisateurs. Nous devrions gérer ce côté serveur logique et ne renvoyer que la taille.

L'inconvénient de cette méthode est que vous invoquez toujours des lectures Firestore (égales à la taille de votre collection), ce qui, à long terme, risque de vous coûter plus cher que prévu.

Fonction Cloud:

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});

Front End:

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})

Grande collection (plus de 1000 documents)

Solution la plus évolutive


FieldValue.increment ()

À partir d'avril 2019, Firestore permet maintenant d'incrémenter les compteurs, de manière totalement atomique, et sans lire les données auparavant . Cela garantit que nous avons bien Contre des valeurs même lors de la mise à jour simultanée de plusieurs sources (précédemment résolues à l'aide de transactions), tout en réduisant le nombre de lectures de base de données que nous effectuons.


En écoutant tout document supprimé ou créé, nous pouvons ajouter ou supprimer un champ de comptage situé dans la base de données.

Voir la documentation Firestore - Compteurs distribués Ou jetez un oeil à Agrégation de données par Jeff Delaney. Ses guides sont vraiment fantastiques pour quiconque utilise AngularFire, mais ses leçons devraient également s'appliquer à d'autres cadres.

Fonction Cloud:

export const documentWriteListener = 
    functions.firestore.document('collection/{documentUid}')
    .onWrite((change, context) => {

    if (!change.before.exists) {
        // New document Created : add one to count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});

    } else if (change.before.exists && change.after.exists) {
        // Updating existing document : Do nothing

    } else if (!change.after.exists) {
        // Deleting document : subtract one from count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});

    }

return;
});

Maintenant, sur le frontend, vous pouvez simplement interroger ce champ numberOfDocs pour obtenir la taille de la collection.

116
Matthew Mullin

Le moyen le plus simple de le faire est de lire la taille d'un "querySnapshot".

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

Vous pouvez également lire la longueur du tableau docs dans "querySnapshot".

querySnapshot.docs.length;

Ou si un "querySnapshot" est vide en lisant la valeur vide, ce qui retournera une valeur booléenne.

querySnapshot.empty;
21
Ompel

Autant que je sache, il n’existe pas de solution intégrée à ce problème et cela n’est possible que dans le noeud sdk pour le moment. Si tu as un

db.collection ('someCollection')

vous pouvez utiliser

.sélectionner ([champs])

pour définir le champ que vous souhaitez sélectionner. Si vous faites un select () vide, vous obtiendrez simplement un tableau de références de documents.

exemple:

db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );

Cette solution n’est qu’une optimisation pour le cas le plus défavorable du téléchargement de tous les documents et ne s’adapte pas aux grandes collections!

Regardez aussi ceci:
Comment obtenir le nombre de documents dans une collection avec Cloud Firestore

13
jbb

Soyez prudent en comptant le nombre de documents pour les grandes collections . 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.

7
Ferran Verdés

Non, il n'y a pas de support intégré pour les requêtes d'agrégation pour le moment. Cependant, vous pouvez faire certaines choses.

Le premier est documenté ici . Vous pouvez utiliser des transactions ou des fonctions cloud pour gérer des informations globales:

Cet exemple montre comment utiliser une fonction pour suivre le nombre de notations dans une sous-collection, ainsi que la notation moyenne.

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

La solution mentionnée par jbb est également utile si vous souhaitez uniquement compter les documents rarement. Veillez à utiliser l'instruction select() pour éviter de télécharger tous les documents (c'est beaucoup de bande passante lorsque vous n'avez besoin que d'un décompte). select() n'est disponible que dans les kits de développement du serveur pour l'instant, de sorte que la solution ne fonctionne pas dans une application mobile.

4
Sam Stern

Je suis d'accord avec @Matthew, ce sera coûtera beaucoup si vous effectuez une telle requête.

[[CONSEILS AUX DÉVELOPPEURS AVANT DE COMMENCER LEURS PROJETS])

Comme nous avons prévu cette situation au début, nous pouvons créer une collection, à savoir des compteurs avec un document, pour stocker tous les compteurs dans un champ de type number.

Par exemple:

Pour chaque opération CRUD sur la collection, mettez à jour le document compteur:

  1. Lorsque vous créez une nouvelle collection/sous-collection: (+ 1 dans le compteur)[1 opération d'écriture]
  2. Lorsque vous supprimez une collection/sous-collection: (- 1 dans le compteur)[1 opération d'écriture]
  3. Lorsque vous mettez à jour une collection/sous-collection existante, ne faites rien sur le document compteur: (0)
  4. Lorsque vous lisez une collection/sous-collection existante, ne faites rien sur le document compteur: (0)

La prochaine fois, lorsque vous souhaitez obtenir le numéro de la collection, il vous suffit de faire une requête/de pointer vers le champ de document. [1 lecture]

De plus, vous pouvez stocker le nom des collections dans un tableau, mais cela sera compliqué, la condition de array dans firebase est illustrée ci-dessous:

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

Ainsi, si vous n'allez pas supprimer la collection, vous pouvez réellement utiliser array pour stocker une liste de noms de collections au lieu d'interroger toute la collection à chaque fois.

J'espère que ça aide!

4
Angus Tay

Incrémenter un compteur en utilisant admin.firestore.FieldValue.increment :

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

Dans cet exemple, nous incrémentons un champ instanceCount dans le projet chaque fois qu'un document est ajouté à la sous-collection instances. Si le champ n'existe pas encore, il sera créé et incrémenté à 1.

L'incrémentation est transactionnelle en interne, mais vous devez utiliser un compteur distribué si vous avez besoin d'incrémenter plus souvent que toutes les 1 secondes.

Il est souvent préférable de mettre en œuvre onCreate et onDelete plutôt que onWrite, car vous appellerez onWrite pour les mises à jour, ce qui signifie que vous dépensez plus d'argent en invocations de fonctions inutiles (si vous mettez à jour le fichier). docs dans votre collection).

3
Dominic

Il n'y a pas d'option directe disponible. Vous ne pouvez pas faire db.collection("CollectionName").count(). Vous trouverez ci-dessous les deux méthodes permettant de trouver le nombre de documents dans une collection.

1: - Obtenez tous les documents de la collection, puis sa taille. (Pas la meilleure solution)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

En utilisant le code ci-dessus, vos lectures de documents seront égales à la taille des documents d'une collection. C'est la raison pour laquelle vous devez éviter d'utiliser la solution ci-dessus.

2: - Créez un document séparé dans votre collection qui stockera le nombre de documents dans la collection. (Meilleure solution)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

Ci-dessus, nous avons créé un document avec des comptes de noms pour stocker toutes les informations de comptage. Vous pouvez mettre à jour le document de comptage de la manière suivante: -

  • Créer un déclencheur de mémoire sur les décomptes de documents
  • Incrémentez la propriété count du document count lors de la création d'un nouveau document.
  • Décrémentez la propriété count du document count lorsqu'un document est supprimé.

w.r.t price (Document Read = 1) et une récupération rapide des données, la solution ci-dessus est bonne.

0
Nipun Madan

Il m'a fallu un certain temps pour que cela fonctionne sur la base de certaines des réponses ci-dessus, alors j'ai pensé le partager avec d'autres personnes. J'espère que c'est utile.

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});
0
Rob Phillips