Recuento de recopilación de Cloud Firestore

Resuelto Guilherme Torres Castro asked hace 54 años • 30 respuestas

¿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?

Guilherme Torres Castro avatar Jan 01 '70 08:01 Guilherme Torres Castro
Aceptado

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.

Matthew Mullin avatar Mar 21 '2018 13:03 Matthew Mullin

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;
Ompel avatar Oct 13 '2017 17:10 Ompel

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

jbb avatar Oct 03 '2017 22:10 jbb

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:

ingrese la descripción de la imagen aquí

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.

Johnny Oshika avatar Oct 18 '2022 16:10 Johnny Oshika

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.

Ferran Verdés avatar Oct 22 '2018 09:10 Ferran Verdés