Recuento de recopilación de Cloud Firestore
¿Es posible contar cuántos elementos tiene una colección utilizando la nueva base de datos de Firebase, Cloud Firestore?
Si es así, ¿cómo hago eso?
Actualización 2023
Firestore ahora admite consultas de agregación .
SDK de nodo
const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
SDK web v9
const coll = collection(db, "cities");
const snapshot = await getCountFromServer(coll);
console.log('count: ', snapshot.data().count);
Limitación notable : actualmente no puede utilizarcount()
consultas con escuchas en tiempo real ni consultas sin conexión. (Consulte las alternativas a continuación)
Precios : los precios dependen del número de entradas de índice coincidentes y no del número de documentos. Una entrada de índice contiene varios documentos, lo que hace que esto sea más económico que contar los documentos individualmente.
Antigua respuesta
Como ocurre con muchas preguntas, la respuesta es: Depende .
Debe tener mucho cuidado al manejar grandes cantidades de datos en el front-end. Además de hacer que su interfaz se sienta lenta, Firestore también le cobra $0,60 por millón de lecturas que realiza.
Pequeña colección (menos de 100 documentos)
Úselo con cuidado: la experiencia del usuario frontend puede verse afectada
Manejar esto en el front-end debería estar bien siempre y cuando no esté haciendo demasiada lógica con esta matriz devuelta.
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
Colección mediana (100 a 1000 documentos)
Úselo con cuidado: las invocaciones de lectura de Firestore pueden costar mucho
Manejar esto en el front-end no es factible, ya que tiene demasiado potencial para ralentizar el sistema del usuario. Deberíamos manejar este lado del servidor lógico y solo devolver el tamaño.
El inconveniente de este método es que todavía estás invocando lecturas de Firestore (igual al tamaño de tu colección), lo que a la larga puede terminar costándote más de lo esperado.
Función de nube:
db.collection('...').get().then(snap => {
res.status(200).send({length: snap.size});
});
Interfaz:
yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
size = snap.length // will return the collection size
})
Gran colección (más de 1000 documentos)
La solución más escalable
ValorCampo.incremento()
A partir de abril de 2019, Firestore ahora permite incrementar contadores, de forma completamente atómica y sin leer los datos previamente . Esto garantiza que tengamos valores de contador correctos incluso cuando actualicemos desde múltiples fuentes simultáneamente (previamente resuelto mediante transacciones), al mismo tiempo que reduce la cantidad de lecturas de bases de datos que realizamos.
Al escuchar la eliminación o creación de cualquier documento, podemos agregarlo o eliminarlo de un campo de recuento que se encuentra en la base de datos.
Consulte los documentos de Firestore: Contadores distribuidos o eche un vistazo a Agregación de datos de Jeff Delaney. Sus guías son realmente fantásticas para cualquiera que use AngularFire, pero sus lecciones también deberían aplicarse a otros marcos.
Función de nube:
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;
});
Ahora, en la interfaz, puede consultar este campo numberOfDocs para obtener el tamaño de la colección.
La forma más sencilla de hacerlo es leer el tamaño de una "querySnapshot".
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
También puede leer la longitud de la matriz de documentos dentro de "querySnapshot".
querySnapshot.docs.length;
O si una "querySnapshot" está vacía leyendo el valor vacío, se devolverá un valor booleano.
querySnapshot.empty;
Hasta donde yo sé, no existe una solución integrada para esto y solo es posible en el SDK del nodo en este momento. Si tienes un
db.collection('someCollection')
puedes usar
.select([fields])
para definir qué campo desea seleccionar. Si realiza un select() vacío, obtendrá una serie de referencias de documentos.
ejemplo:
db.collection('someCollection').select().get().then(
(snapshot) => console.log(snapshot.docs.length)
);
¡Esta solución es solo una optimización para el peor de los casos de descargar todos los documentos y no se adapta a colecciones grandes!
También eche un vistazo a esto:
Cómo obtener un recuento de la cantidad de documentos en una colección con Cloud Firestore
La consulta de recuento agregado acaba de llegar como vista previa en Firestore.
Anunciado en la Cumbre de Firebase de 2022: https://firebase.blog/posts/2022/10/whats-new-at-Firebase-Sumit-2022
Extracto:
[Vista previa del desarrollador] Función Count(): con la nueva función de recuento en Firstore [sic], ahora puede obtener el recuento de los documentos coincidentes cuando ejecuta una consulta o lee de una colección, sin cargar los documentos reales, lo que le ahorra un montón de tiempo.
Ejemplo de código que mostraron en la cumbre:
Durante la sesión de preguntas y respuestas, alguien preguntó sobre el precio de las consultas agregadas y la respuesta que proporcionó el equipo de Firebase fue que costará 1/1000 del precio de una lectura (redondeado a la lectura más cercana; consulte los comentarios a continuación para obtener más detalles). pero contará todos los registros que formen parte del agregado.
Tenga cuidado al contar el número de documentos en colecciones grandes . Es un poco complejo con la base de datos de Firestore si desea tener un contador precalculado para cada colección.
Código como este no funciona en este caso:
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 razón es que cada activador de Cloud Firestore debe ser idempotente, como dice la documentación de Firestore: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees
Solución
Entonces, para evitar ejecuciones múltiples de su código, debe administrar eventos y transacciones. Esta es mi manera particular de manejar grandes mostradores de cobranza:
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;
});
Casos de uso aquí:
/**
* 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'));
Como puede ver, la clave para evitar ejecuciones múltiples es la propiedad llamada eventId en el objeto de contexto. Si la función ha sido manejada muchas veces para el mismo evento, la identificación del evento será la misma en todos los casos. Lamentablemente, debe tener una colección de "eventos" en su base de datos.