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?
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.
à 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
});
à 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
})
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.
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;
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
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
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.
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.
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:
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!
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).
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.
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.
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: -
w.r.t price (Document Read = 1) et une récupération rapide des données, la solution ci-dessus est bonne.
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;
});