Nombre de collections Cloud Firestore

Réponses:

191

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


Comme pour de nombreuses questions, la réponse est: cela dépend .

Vous devez être très prudent lorsque vous manipulez de grandes quantités de données sur le front-end. En plus de rendre votre frontal lent, Firestore vous facture également 0,60 USD par million de lectures que vous effectuez.


Petite collection (moins de 100 documents)

À utiliser avec précaution - L'expérience utilisateur frontend peut en prendre un coup

La gestion de cela sur le front-end devrait être correcte tant que vous ne faites pas trop de logique avec ce tableau renvoyé.

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

Collection moyenne (100 à 1000 documents)

À utiliser avec précaution - Les appels de lecture Firestore peuvent coûter cher

Gérer cela sur le front-end n'est pas faisable car il a trop de potentiel pour ralentir le système des utilisateurs. Nous devons 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 de firestore (égales à la taille de votre collection), qui à long terme peuvent finir par vous coûter plus cher que prévu.

Fonction Cloud:

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

L'extrémité avant:

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

Grande collection (plus de 1000 documents)

La solution la plus évolutive


FieldValue.increment ()

Depuis avril 2019, Firestore permet désormais d'incrémenter les compteurs, de manière complètement atomique, et sans lire les données auparavant . Cela garantit que nous avons des valeurs de compteur correctes même lors de la mise à jour à partir de plusieurs sources simultanément (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 les suppressions ou les créations de documents, nous pouvons ajouter ou supprimer d'un champ de comptage qui se trouve dans la base de données.

Consultez la documentation Firestore - Compteurs distribués ou jetez un œil à l' agrégation de données par Jeff Delaney. Ses guides sont vraiment fantastiques pour quiconque utilise AngularFire, mais ses leçons devraient également être appliquées à d'autres frameworks.

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.

Matthew Mullin
la source
17
Excellente solution pour les grandes collections! Je voudrais juste ajouter que les implémenteurs devraient envelopper la lecture et l'écriture dans un firestore.runTransaction { ... }bloc. Cela corrige les problèmes de concurrence d'accès numberOfDocs.
efemoney
2
Ces méthodes utilisent un recompte du nombre d'enregistrements. Si vous utilisez un compteur et l'incrémentez à l'aide d'une transaction, cela ne permettrait-il pas d'obtenir le même résultat sans le coût supplémentaire et le besoin d'une fonction cloud?
user3836415
10
La solution pour les grandes collections n'est pas idempotente et ne fonctionne à aucune échelle. Les déclencheurs de document Firestore sont garantis de s'exécuter au moins une fois, mais peuvent s'exécuter plusieurs fois. Lorsque cela se produit, même le maintien de la mise à jour dans une transaction peut s'exécuter plusieurs fois, ce qui vous donnera un faux numéro. Lorsque j'ai essayé cela, j'ai rencontré des problèmes avec moins d'une douzaine de créations de documents à la fois.
Tym Pollack
2
Salut @TymPollack. J'ai remarqué un comportement incohérent lors de l'utilisation de déclencheurs cloud. Avez-vous une chance de me relier à un article ou à un forum pour expliquer le comportement que vous avez vécu?
Matthew Mullin
2
@cmprogram vous lisez toute la collection et les données lorsque vous utilisez db.collection ('...') ... donc lorsque vous n'avez pas besoin des données, vous avez raison - vous pouvez facilement demander une liste de ID de collection (pas de données de documents de collection) et cela compte comme une lecture.
atereshkov le
25

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 renverra une valeur booléenne.

querySnapshot.empty;
Ompel
la source
73
Sachez que chaque document «coûte» une lecture. Donc, si vous comptez 100 articles dans une collection de cette façon, vous êtes facturé pour 100 lectures!
Georg
Correct, mais il n'y a pas d'autre moyen de résumer le nombre de documents dans une collection. Et si vous avez déjà récupéré la collection, la lecture du tableau «docs» ne nécessitera plus de récupération, donc ne «coûtera» pas plus de lectures.
Ompel
5
Cela lit tous les documents en mémoire! Bonne chance pour les grands ensembles de données ...
Dan Dascalescu
85
c'est vraiment incroyable que Firebase Firestore n'en ait pas db.collection.count(). Penser à les laisser tomber uniquement pour cela
Blue Bot
8
Surtout pour les grandes collections, il n'est pas juste de nous facturer comme si nous avions effectivement téléchargé et utilisé tous les documents. Compter pour une table (collection) est une fonctionnalité de base. Compte tenu de leur modèle de tarification et du lancement de Firestore en 2017, il est tout simplement incroyable que Google ne propose pas d'autre moyen d'obtenir la taille d'une collection. Jusqu'à ce qu'ils ne le mettent pas en œuvre, ils devraient au moins éviter de le facturer.
nibbana
23

Pour autant que je sache, il n'y a pas de solution intégrée pour cela et cela n'est possible que dans le nœud sdk pour le moment. Si tu as un

db.collection('someCollection')

vous pouvez utiliser

.select([fields])

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

exemple:

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

Cette solution n'est qu'une optimisation pour le pire des cas de téléchargement de tous les documents et ne s'adapte pas aux grandes collections!

Jetez également un œil à ceci:
Comment obtenir un nombre de documents dans une collection avec Cloud Firestore

jbb
la source
D'après mon expérience, select(['_id'])c'est plus rapide queselect()
JAnton
13

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 code comme celui-ci 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 Cloud Firestore doit être idempotent, comme le dit la documentation Firestore: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Solution

Ainsi, afin d'éviter plusieurs exécutions de votre code, vous devez gérer les événements et les transactions. C'est ma façon particulière de gérer les grands compteurs de collecte:

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;
    });

Utilisez des cas 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 voir, la clé pour 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 «événements» dans votre base de données.

Ferran Verdés
la source
2
Ils expriment cela comme si ce problème serait corrigé dans la version 1.0. Les fonctions Amazon AWS souffrent du même problème. Quelque chose d'aussi simple que de compter les champs devient complexe et coûteux.
MarcG
Je vais essayer cela maintenant car cela semble être une meilleure solution. Revenez-vous en arrière et purgez-vous votre collection d'événements? Je pensais simplement ajouter un champ de date et purger plus d'un jour ou quelque chose juste pour garder l'ensemble de données petit (peut-être 1 mil + événements / jour). À moins qu'il n'y ait un moyen facile dans FS de le faire ... n'utilise FS que depuis quelques mois.
Tym Pollack
1
Pouvons-nous vérifier que ce context.eventIdsera toujours le même sur plusieurs invocations du même déclencheur? Dans mes tests, cela semble cohérent, mais je ne trouve aucune documentation «officielle» indiquant cela.
Mike McLin le
2
Donc, après avoir utilisé cela pendant un certain temps, j'ai constaté que, bien que cette solution fonctionne avec exactement une écriture, ce qui est génial, si trop de déclencheurs se déclenchent à partir de plusieurs documents écrits à la fois et essayant de mettre à jour le même document de comptage, vous pouvez obtenir des erreurs de contention de Firestore. Les avez-vous rencontrés et comment les avez-vous contournés? (Erreur: 10 ABORTED: Trop de conflit sur ces documents. Veuillez réessayer.)
Tym Pollack
1
@TymPollack regarde les compteurs distribués Les écritures de documents sont limitées à environ une mise à jour par seconde
Jamie
8

En 2020, cela n'est toujours pas disponible dans le SDK Firebase, mais il est disponible dans les extensions Firebase (bêta), mais c'est assez complexe à configurer et à utiliser ...

Une approche raisonnable

Helpers ... (créer / supprimer semble redondant mais est moins cher que onUpdate)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

Crochets Firestore exportés


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

En action

La collection racine du bâtiment et toutes les sous-collections seront suivies.

entrez la description de l'image ici

Ici sous le /counters/chemin racine

entrez la description de l'image ici

Désormais, le décompte des collections sera mis à jour automatiquement et éventuellement! Si vous avez besoin d'un décompte, utilisez simplement le chemin de la collection et ajoutez-lui le préfixe counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));
Ben Winding
la source
N'est-ce pas soumis à la même limitation "1 mise à jour de document par seconde"?
Ayyappa
Oui, mais c'est finalement cohérent, ce qui signifie que le nombre de collections s'alignera finalement sur le nombre de collections réel, c'est la solution la plus simple à implémenter et dans de nombreux cas, un bref décalage dans le nombre est acceptable.
Ben Winding
7

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

[CONSEILS AUX DÉVELOPPEURS AVANT DE DÉMARRER LEURS PROJETS]

Puisque nous avons prévu cette situation au début, nous pouvons en fait faire 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 nombre de collections, il vous suffit d'interroger / pointer sur le champ du document. [1 opération de lecture]

De plus, vous pouvez stocker le nom des collections dans un tableau, mais ce sera délicat, la condition du tableau dans firebase est indiqué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'}

Donc, si vous n'allez pas supprimer la collection, vous pouvez en fait utiliser un tableau pour stocker la liste des noms de collections au lieu d'interroger toute la collection à chaque fois.

J'espère que ça aide!

Angus Tay
la source
Pour une petite collection, peut-être. Mais gardez à l'esprit que la limite de taille du document Firestore est d'environ 1 Mo, ce qui, si les ID de document d'une collection sont générés automatiquement (20 octets), vous ne pourrez en stocker que ~ 52425 avant le document contenant le tableau. C est trop gros. Je suppose que pour contourner ce problème, vous pourriez créer un nouveau document tous les 50 000 éléments, mais maintenir ces tableaux serait tout à fait ingérable. De plus, à mesure que la taille du document augmente, la lecture et la mise à jour prendront plus de temps, ce qui finira par faire en sorte que toutes les autres opérations sur celui-ci expirent en conflit.
Tym Pollack
5

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 agrégées:

Cet exemple montre comment utiliser une fonction pour suivre le nombre de notes dans une sous-collection, ainsi que la note 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 ne souhaitez compter que rarement les documents. Assurez-vous d'utiliser l' select()instruction pour éviter de télécharger la totalité de chaque document (c'est beaucoup de bande passante lorsque vous n'avez besoin que d'un décompte). select()n'est disponible que dans les SDK du serveur pour le moment afin que la solution ne fonctionne pas dans une application mobile.

Sam Stern
la source
1
Cette solution n'est pas idempotente, de sorte que tout déclencheur qui se déclenche plus d'une fois entraînera une perte de votre nombre de notes et de votre moyenne.
Tym Pollack
4

Il n'y a pas d'option directe disponible. Tu ne peux pas faire db.collection("CollectionName").count(). Vous trouverez ci-dessous les deux façons dont vous pouvez trouver le nombre de documents dans une collection.

1: - Obtenez tous les documents de la collection, puis obtenez leur taille (ce n'est pas la meilleure solution)

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

En utilisant le code ci-dessus, vos lectures de document seront égales à la taille des documents dans une collection et c'est la raison pour laquelle il faut éviter d'utiliser la solution ci-dessus.

2: - Créez un document séparé avec 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 le nombre de noms pour stocker toutes les informations de comptage.Vous pouvez mettre à jour le document de comptage de la manière suivante: -

  • Créer des déclencheurs de Firestore sur le nombre de documents
  • Incrémentez la propriété count du document count lorsqu'un nouveau document est créé.
  • Décrémente la propriété count du document count lorsqu'un document est supprimé.

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

Nipun Madan
la source
3

Incrémentez 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 instanceCountchamp dans le projet chaque fois qu'un document est ajouté à la instancessous-collection. 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 devez incrémenter plus fréquemment que toutes les 1 seconde.

Il est souvent préférable de mettre en œuvre onCreateet onDeleteplutôt que onWritede demander onWritedes mises à jour, ce qui signifie que vous dépensez plus d'argent pour des appels de fonctions inutiles (si vous mettez à jour les documents de votre collection).

Dominique
la source
2

Une solution de contournement consiste à:

écrire un compteur dans un document Firebase, que vous incrémentez dans une transaction à chaque fois que vous créez une nouvelle entrée

Vous stockez le décompte dans un champ de votre nouvelle entrée (ex: position: 4).

Ensuite, vous créez un index sur ce champ (position DESC).

Vous pouvez faire un saut + limite avec une requête.Where ("position", "<" x) .OrderBy ("position", DESC)

J'espère que cela t'aides!

Kathan Shah
la source
1

J'ai créé une fonction universelle utilisant toutes ces idées pour gérer toutes les situations de compteur (sauf les requêtes).

La seule exception serait lorsque vous faites autant d'écritures par seconde, cela vous ralentit. Un exemple serait les likes sur un article tendance. C'est exagéré sur un article de blog, par exemple, et vous coûtera plus cher. Je suggère de créer une fonction distincte dans ce cas en utilisant des fragments: https://firebase.google.com/docs/firestore/solutions/counters

// trigger collections
exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// trigger sub-collections
exports.mySubFunction = functions.firestore
    .document('{colId}/{docId}/{subColId}/{subDocId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// add change the count
const runCounter = async function (change: any, context: any) {

    const col = context.params.colId;

    const eventsDoc = '_events';
    const countersDoc = '_counters';

    // ignore helper collections
    if (col.startsWith('_')) {
        return null;
    }
    // simplify event types
    const createDoc = change.after.exists && !change.before.exists;
    const updateDoc = change.before.exists && change.after.exists;

    if (updateDoc) {
        return null;
    }
    // check for sub collection
    const isSubCol = context.params.subDocId;

    const parentDoc = `${countersDoc}/${context.params.colId}`;
    const countDoc = isSubCol
        ? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
        : `${parentDoc}`;

    // collection references
    const countRef = db.doc(countDoc);
    const countSnap = await countRef.get();

    // increment size if doc exists
    if (countSnap.exists) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.update(countRef, { count: i });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        const colRef = db.collection(change.after.ref.parent.path);
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(colRef);
            return t.set(countRef, { count: colSnap.size });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}

Cela gère les événements, les incréments et les transactions. La beauté de ceci, c'est que si vous n'êtes pas sûr de l'exactitude d'un document (probablement pendant qu'il est encore en version bêta), vous pouvez supprimer le compteur pour qu'il les additionne automatiquement au prochain déclencheur. Oui, cela coûte, alors ne le supprimez pas autrement.

Même genre de chose pour obtenir le décompte:

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

Vous pouvez également créer une tâche cron (fonction planifiée) pour supprimer les anciens événements afin d'économiser de l'argent sur le stockage de la base de données. Vous avez besoin d'au moins un plan Blaze, et il peut y avoir plus de configuration. Vous pouvez l'exécuter tous les dimanches à 23 heures, par exemple. https://firebase.google.com/docs/functions/schedule-functions

Ceci n'est pas testé , mais devrait fonctionner avec quelques ajustements:

exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
    .timeZone('America/New_York')
    .onRun(async (context) => {

        // get yesterday
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
        const eventFilterSnap = await eventFilter.get();
        eventFilterSnap.forEach(async (doc: any) => {
            await doc.ref.delete();
        });
        return null;
    });

Enfin, n'oubliez pas de protéger les collections dans firestore.rules :

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}

Mise à jour: requêtes

En ajoutant à mon autre réponse si vous souhaitez également automatiser le nombre de requêtes, vous pouvez utiliser ce code modifié dans votre fonction cloud:

    if (col === 'posts') {

        // counter reference - user doc ref
        const userRef = after ? after.userDoc : before.userDoc;
        // query reference
        const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
        // add the count - postsCount on userDoc
        await addCount(change, context, postsQuery, userRef, 'postsCount');

    }
    return delEvents();

Ce qui mettra automatiquement à jour le postsCount dans le userDocument. Vous pouvez facilement en ajouter un autre à de nombreux comptes de cette façon. Cela vous donne simplement des idées sur la façon dont vous pouvez automatiser les choses. Je vous ai également donné un autre moyen de supprimer les événements. Vous devez lire chaque date pour la supprimer, donc cela ne vous évitera pas vraiment de les supprimer plus tard, cela ralentit simplement la fonction.

/**
 * Adds a counter to a doc
 * @param change - change ref
 * @param context - context ref
 * @param queryRef - the query ref to count
 * @param countRef - the counter document ref
 * @param countName - the name of the counter on the counter document
 */
const addCount = async function (change: any, context: any, 
  queryRef: any, countRef: any, countName: string) {

    // events collection
    const eventsDoc = '_events';

    // simplify event type
    const createDoc = change.after.exists && !change.before.exists;

    // doc references
    const countSnap = await countRef.get();

    // increment size if field exists
    if (countSnap.get(countName)) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.set(countRef, { [countName]: i }, { merge: true });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(queryRef);
            return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}
/**
 * Deletes events over a day old
 */
const delEvents = async function () {

    // get yesterday
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
    const eventFilterSnap = await eventFilter.get();
    eventFilterSnap.forEach(async (doc: any) => {
        await doc.ref.delete();
    });
    return null;
}

Je dois également vous avertir que les fonctions universelles fonctionneront à chaque période d'appel onWrite. Il peut être moins coûteux d'exécuter la fonction uniquement sur onCreate et sur les instances onDelete de vos collections spécifiques. Comme la base de données noSQL que nous utilisons, le code et les données répétés peuvent vous faire économiser de l'argent.

Jonathan
la source
rédigez un article à ce sujet sur support pour un accès facile.
ahmadalibaloch le
0

Il m'a fallu un certain temps pour que cela fonctionne en fonction de certaines des réponses ci-dessus, alors j'ai pensé que je le partagerais pour que d'autres puissent l'utiliser. 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;
});
Rob Phillips
la source
0

J'ai beaucoup essayé avec différentes approches. Et enfin, j'améliore une des méthodes. Vous devez d'abord créer une collection séparée et y enregistrer tous les événements. Deuxièmement, vous devez créer un nouveau lambda qui sera déclenché par le temps. Ce lambda comptera les événements dans la collection d'événements et effacera les documents d'événements. Détails du code dans l'article. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

Ihor Malaniuk
la source
Veuillez inclure les détails et le code pertinents dans la réponse elle - même , pointer les gens vers vos articles de blog n'est pas vraiment le but de StackOverflow.
DBS
0

Cette requête aboutira au nombre de documents.

this.db.collection(doc).get().subscribe((data) => {
      count = data.docs.length;
    });

console.log(count)
Aravin
la source
1
Ce n'est pas une bonne solution car vous récupérez tous les documents de la collection à chaque fois. Cela coûtera cher. Une meilleure approche consiste à configurer un compteur chaque fois qu'un nouveau document est ajouté à cette collection afin que vous puissiez simplement récupérer un document au lieu de quelques milliers.
Corentin Houdayer
0

Cela utilise le comptage pour créer un identifiant numérique unique. Dans mon utilisation, je ne décrémenterai jamais , même si documentl'ID pour lequel l'ID est nécessaire est supprimé.

Sur une collectioncréation qui a besoin d'une valeur numérique unique

  1. Désigner une collection appDataavec un document, setavec .docidentifiantonly
  2. Réglé uniqueNumericIDAmountsur 0 dans lefirebase firestore console
  3. Utiliser doc.data().uniqueNumericIDAmount + 1comme identifiant numérique unique
  4. Mettre à jour la appDatacollection uniqueNumericIDAmountavecfirebase.firestore.FieldValue.increment(1)
firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

        // your collection that needs a unique ID
        firebase
            .firestore()
            .collection("uniqueNumericIDs")
            .doc(user.uid)// user id in my case
            .set({// I use this in login, so this document doesn't
                  // exist yet, otherwise use update instead of set
                phone: this.state.phone,// whatever else you need
                uniqueNumericID: foo.uniqueNumericIDAmount + 1
            })
            .then(() => {

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });
Nick Carducci
la source
-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
Sampath Patro
la source
1
Cette réponse pourrait utiliser plus de contexte que l'exemple de code.
ShellNinja