Recuento de colecciones de Cloud Firestore

Respuestas:

189

Actualización (abril de 2019) - FieldValue.increment (ver solución de colección grande)


Como 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 front-end se sienta lento, Firestore también le cobra $ 0.60 por millón de lecturas que realiza.


Colección pequeña (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 que 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 de los usuarios. 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á invocando lecturas de almacén de incendios (igual al tamaño de su colección), que a la larga puede terminar costándole más de lo esperado.

Función de la 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


FieldValue.increment ()

A partir de abril de 2019, Firestore ahora permite incrementar los contadores, de forma completamente atómica, y sin leer los datos antes . Esto garantiza que tengamos los valores de contador correctos incluso cuando se actualizan desde múltiples fuentes simultáneamente (previamente resueltas mediante transacciones), al tiempo que se reduce la cantidad de lecturas de bases de datos que realizamos.


Al escuchar cualquier documento eliminado o creado, podemos agregar o eliminar de un campo de recuento que se encuentra en la base de datos.

Vea los documentos de la tienda de incendios: contadores distribuidos o eche un vistazo a la agregación de datos por Jeff Delaney. Sus guías son realmente fantásticas para cualquiera que use AngularFire, pero sus lecciones también deberían trasladarse a otros marcos.

Función de la 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
fuente
17
¡Gran solución para grandes colecciones! Solo me gustaría agregar que los implementadores deben envolver la lectura y la escritura en un firestore.runTransaction { ... }bloque. Esto soluciona problemas de concurrencia con el acceso numberOfDocs.
efemoney
2
Estos métodos utilizan un recuento del número de registros. Si usa un contador e incrementa el contador usando una transacción, ¿eso no lograría el mismo resultado sin el costo adicional y la necesidad de una función en la nube?
user3836415
10
La solución para grandes colecciones no es idempotente y no funciona a ninguna escala. Se garantiza que los disparadores de documentos de Firestore se ejecutarán al menos una vez, pero pueden ejecutarse varias veces. Cuando esto sucede, incluso mantener la actualización dentro de una transacción puede ejecutarse más de una vez, lo que le dará un número falso. Cuando probé esto, me encontré con problemas con menos de una docena de creaciones de documentos a la vez.
Tym Pollack
2
Hola @TymPollack. He notado un comportamiento inconsistente al usar disparadores de nube. ¿Alguna posibilidad de que me vincules a un artículo o foro para explicar el comportamiento que has experimentado?
Matthew Mullin el
2
@cmprogram está leyendo toda la colección y los datos cuando usa db.collection ('...') ... así que cuando no necesita los datos, tiene razón: puede solicitar fácilmente una lista de ID de recopilación (no datos de documentos de recopilación) y cuenta como una lectura.
atereshkov
24

La forma más sencilla de hacerlo es leer el tamaño de una "consulta instantánea".

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 un "querySnapshot" está vacío leyendo el valor vacío, que devolverá un valor booleano.

querySnapshot.empty;
Ompel
fuente
73
Tenga en cuenta que cada documento "cuesta" una lectura. Entonces, si cuenta 100 artículos en una colección de esta manera, ¡se le cobrará por 100 lecturas!
Georg
Correcto, pero no hay otra forma de resumir la cantidad de documentos en una colección. Y si ya buscó la colección, leer la matriz "docs" no requerirá más búsqueda, por lo tanto, no "costará" más lecturas.
Ompel
55
¡Esto lee todos los documentos en la memoria! Buena suerte con eso para grandes conjuntos de datos ...
Dan Dascalescu
85
Esto es realmente increíble que Firebase Firestore no tenga db.collection.count(). Pensando en dejarlos solo por esto
Blue Bot
8
Especialmente para colecciones grandes, no es justo cobrarnos como si realmente hubiéramos descargado y usado todos los documentos. Contar para una tabla (colección) es una característica tan básica. Teniendo en cuenta su modelo de precios y que Firestore se lanzó en 2017, es increíble que Google no proporcione una forma alternativa de obtener el tamaño de una colección. Hasta que no lo implementen, al menos deberían evitar cobrarlo.
nibbana
23

Hasta donde sé, no hay una solución integrada para esto y solo es posible en el nodo sdk en este momento. Si tienes un

db.collection('someCollection')

puedes usar

.select([fields])

para definir qué campo desea seleccionar. Si hace una selección vacía (), solo obtendrá una matriz 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 descarga de todos los documentos y no se escala en grandes colecciones!

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
fuente
En mi experiencia, select(['_id'])es más rápido queselect()
JAnton
13

Tenga cuidado al contar el número de documentos para grandes colecciones . Es un poco complejo con la base de datos de firestore si desea tener un contador precalculado para cada colección.

Un 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 porque cada desencadenante del almacén de incendios en la nube debe ser idempotente, como dice la documentación del almacén de incendios: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Solución

Por lo tanto, para evitar múltiples ejecuciones de su código, debe administrar eventos y transacciones. Esta es mi forma particular de manejar grandes contadores de colección:

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

Use los casos 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 la ejecución múltiple es la propiedad llamada eventId en el objeto de contexto. Si la función se ha manejado muchas veces para el mismo evento, la identificación del evento será la misma en todos los casos. Desafortunadamente, debe tener una colección de "eventos" en su base de datos.

Ferran Verdés
fuente
2
Están redactando esto como si este comportamiento se solucionara en la versión 1.0. Las funciones de Amazon AWS tienen el mismo problema. Algo tan simple como contar campos se vuelve complejo y costoso.
MarcG
Voy a probar esto ahora, ya que parece una mejor solución. ¿Vuelves y purgas tu colección de eventos alguna vez? Estaba pensando en agregar un campo de fecha y purgar más de un día o algo para mantener el conjunto de datos pequeño (posiblemente 1mil + eventos / día). A menos que haya una manera fácil en FS de hacer eso ... solo he estado usando FS unos meses.
Tym Pollack
1
¿Podemos verificar que context.eventIdsiempre será igual en múltiples invocaciones del mismo activador? En mis pruebas parece ser consistente, pero no puedo encontrar ninguna documentación "oficial" que indique esto.
Mike McLin
2
Entonces, después de usar esto por un tiempo, descubrí que, si bien esta solución funciona con exactamente una escritura, lo cual es genial, si se activan demasiados disparadores de documentos múltiples que se escriben a la vez e intentan actualizar el mismo documento de conteo, puede Obtener errores de contención de Firestore. ¿Te has encontrado con ellos y cómo lo superaste? (Error: 10 ABORTADO: Demasiada contienda en estos documentos. Por favor, inténtelo de nuevo.)
Tym Pollack
1
La mirada de @TymPollack a los contadores distribuidos de documentos se limita a aproximadamente una actualización por segundo
Jamie
8

En 2020, esto todavía no está disponible en Firebase SDK; sin embargo, está disponible en Firebase Extensions (Beta); sin embargo, es bastante complejo de configurar y usar ...

Un enfoque razonable

Ayudantes ... (crear / eliminar parece redundante pero es más barato 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;
}

Ganchos exportados de Firestore


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 acción

Se rastreará la colección raíz del edificio y todas las subcolecciones .

ingrese la descripción de la imagen aquí

Aquí debajo de la /counters/ruta raíz

ingrese la descripción de la imagen aquí

¡Ahora los recuentos de colecciones se actualizarán automáticamente y eventualmente! Si necesita un recuento, simplemente use la ruta de recopilación y agregue el prefijo counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));
Ben Winding
fuente
¿No está sujeto a la misma limitación "1 actualización de documentos por segundo"?
Ayyappa
Sí, pero eventualmente es consistente, lo que significa que el recuento de recolección finalmente se alineará con el recuento de recolección real, es la solución más fácil de implementar y, en muchos casos, es aceptable un breve retraso en el recuento.
Ben Winding
7

Estoy de acuerdo con @Matthew, costará mucho si realiza dicha consulta.

[CONSEJO PARA DESARROLLADORES ANTES DE COMENZAR SUS PROYECTOS]

Como hemos previsto esta situación al principio, podemos hacer una colección, a saber, contadores con un documento para almacenar todos los contadores en un campo con tipo number.

Por ejemplo:

Para cada operación CRUD en la colección, actualice el documento de contador:

  1. Cuando crea una nueva colección / subcolección: (+1 en el contador) [1 operación de escritura]
  2. Cuando elimina una colección / subcolección: (-1 en el contador) [1 operación de escritura]
  3. Cuando actualice una colección / subcolección existente, no haga nada en el documento del contador: (0)
  4. Cuando lea una colección / subcolección existente, no haga nada en el documento del contador: (0)

La próxima vez, cuando desee obtener el número de recopilación, solo necesita consultar / señalar el campo del documento. [1 operación de lectura]

Además, puede almacenar el nombre de las colecciones en una matriz, pero esto será complicado, la condición de la matriz en firebase se muestra a continuación:

// 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'}

Entonces, si no va a eliminar la colección, en realidad puede usar la matriz para almacenar la lista de nombres de colecciones en lugar de consultar toda la colección cada vez.

¡Espero eso ayude!

Angus Tay
fuente
Para una pequeña colección, tal vez. Pero tenga en cuenta que el límite de tamaño de documento de Firestore es de ~ 1 MB, que, si los ID de documento en una colección se generan automáticamente (20 bytes), entonces solo podrá almacenar ~ 52,425 de ellos antes del documento que contiene la matriz es demasiado grande. Supongo que como solución alternativa podría crear un nuevo documento cada 50,000 elementos, pero luego mantener esos arreglos sería completamente inmanejable. Además, a medida que crece el tamaño del documento, tomará más tiempo leerlo y actualizarlo, lo que eventualmente hará que cualquier otra operación se agote.
Tym Pollack
5

No, en este momento no hay soporte incorporado para consultas de agregación. Sin embargo, hay algunas cosas que podrías hacer.

El primero está documentado aquí . Puede usar transacciones o funciones en la nube para mantener información agregada:

Este ejemplo muestra cómo usar una función para realizar un seguimiento del número de calificaciones en una subcolección, así como la calificación promedio.

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 solución que mencionó jbb también es útil si solo desea contar documentos con poca frecuencia. Asegúrese de usar la select()declaración para evitar descargar todos los documentos (eso es mucho ancho de banda cuando solo necesita un recuento). select()por ahora solo está disponible en los SDK del servidor, por lo que la solución no funcionará en una aplicación móvil.

Sam Stern
fuente
1
Esta solución no es idempotente, por lo que cualquier disparador que se dispare más de una vez anulará su número de calificaciones y promedio.
Tym Pollack
4

No hay una opción directa disponible. Usted cant't haces db.collection("CollectionName").count(). A continuación se muestran las dos formas en que puede encontrar el recuento de la cantidad de documentos dentro de una colección.

1: - Obtenga todos los documentos de la colección y luego obtenga su tamaño. (No es la mejor solución)

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

Al usar el código anterior, las lecturas de sus documentos serán iguales al tamaño de los documentos dentro de una colección y esa es la razón por la cual uno debe evitar el uso de la solución anterior.

2: - Cree un documento separado con en su colección que almacenará la cantidad de documentos en la colección. (La mejor solución)

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

Arriba creamos un documento con recuentos de nombres para almacenar toda la información de recuento. Puede actualizar el documento de recuento de la siguiente manera: -

  • Crear disparadores de almacén de incendios en los recuentos de documentos
  • Incremente la propiedad de conteo del documento de conteo cuando se crea un nuevo documento.
  • Disminuya la propiedad de conteo del documento de conteo cuando se elimina un documento.

wrt price (Document Read = 1) y recuperación rápida de datos, la solución anterior es buena.

Nipun Madan
fuente
3

Incremente un contador usando 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),
    })
  );

En este ejemplo, incrementamos un instanceCountcampo en el proyecto cada vez que se agrega un documento a la instancessubcolección. Si el campo aún no existe, se creará e incrementará a 1.

El incremento es transaccional internamente, pero debe usar un contador distribuido si necesita incrementar con más frecuencia que cada 1 segundo.

A menudo es preferible implementarlo onCreatey en onDeletelugar de hacerlo onWrite, solicitará onWriteactualizaciones, lo que significa que está gastando más dinero en invocaciones de funciones innecesarias (si actualiza los documentos de su colección).

Dominic
fuente
2

Una solución alternativa es:

escribe un contador en un documento de Firebase, que incrementa dentro de una transacción cada vez que crea una nueva entrada

Almacena el recuento en un campo de su nueva entrada (es decir: posición: 4).

Luego crea un índice en ese campo (posición DESC).

Puede hacer un salto + límite con una consulta. Donde ("posición", "<" x) .OrderBy ("posición", DESC)

¡Espero que esto ayude!

Kathan Shah
fuente
1

Creé una función universal usando todas estas ideas para manejar todas las situaciones de contador (excepto las consultas).

La única excepción sería cuando hacer tantas escrituras por segundo, te ralentiza. Un ejemplo sería Me gusta en una publicación de tendencias. Es excesivo en una publicación de blog, por ejemplo, y le costará más. Sugiero crear una función separada en ese caso usando fragmentos: 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);
        });;
    }
}

Esto maneja eventos, incrementos y transacciones. Lo bueno de esto es que si no está seguro de la precisión de un documento (probablemente mientras esté en versión beta), puede eliminar el contador para que se agregue automáticamente en el siguiente activador. Sí, esto cuesta, así que no lo elimine de otra manera.

El mismo tipo de cosas para obtener el recuento:

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

Además, puede crear un trabajo cron (función programada) para eliminar eventos antiguos y ahorrar dinero en el almacenamiento de la base de datos. Necesita al menos un plan de incendio, y puede haber alguna configuración más. Puede ejecutarlo todos los domingos a las 11 p.m., por ejemplo. https://firebase.google.com/docs/functions/schedule-functions

Esto no se ha probado , pero debería funcionar con algunos ajustes:

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

Y por último, no olvide proteger las colecciones en firestore.rules :

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

Actualización: consultas

Agregando a mi otra respuesta si también desea automatizar el conteo de consultas, puede usar este código modificado en su función en la nube:

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

Lo que actualizará automáticamente el postsCount en el userDocument. Podría agregar fácilmente otro a muchos recuentos de esta manera. Esto solo te da ideas de cómo puedes automatizar las cosas. También te di otra forma de eliminar los eventos. Debe leer cada fecha para eliminarla, por lo que realmente no le ahorrará eliminarlas más tarde, solo hace que la función sea más lenta.

/**
 * 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;
}

También debo advertirle que las funciones universales se ejecutarán en cada período de llamadas onWrite. Puede ser más barato ejecutar solo la función en instancias onCreate y onDelete de sus colecciones específicas. Al igual que la base de datos noSQL que estamos usando, el código y los datos repetidos pueden ahorrarle dinero.

Jonathan
fuente
escriba un artículo sobre esto en medio para facilitar el acceso.
ahmadalibaloch
0

Me tomó un tiempo hacer que esto funcionara en base a algunas de las respuestas anteriores, así que pensé en compartirlo para que otros lo usen. Espero que sea útil.

'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
fuente
0

He intentado mucho con diferentes enfoques. Y finalmente, mejoro uno de los métodos. Primero debe crear una colección separada y guardar allí todos los eventos. En segundo lugar, debe crear una nueva lambda que se activará con el tiempo. Esta lambda contará eventos en la colección de eventos y borrará documentos de eventos. Detalles del código en el artículo. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

Ihor Malaniuk
fuente
Incluya los detalles y el código relevantes en la respuesta en sí , señalar a las personas en las publicaciones de su blog no es realmente el objetivo de StackOverflow.
DBS
0

Esta consulta dará como resultado el recuento de documentos.

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

console.log(count)
Aravin
fuente
1
No es una buena solución, ya que está buscando todos los documentos de la colección cada vez. Costará mucho. Un mejor enfoque es configurar un contador cada vez que se agrega un nuevo documento a esta colección para que pueda buscar un documento en lugar de unos pocos miles.
Corentin Houdayer
0

Esto usa el conteo para crear una identificación numérica única. En mi uso, no volveré a disminuir nunca , incluso cuando documentse elimine la identificación necesaria.

Sobre una collectioncreación que necesita un valor numérico único

  1. Designar una colección appDatacon un documento, setcon .docidentificaciónonly
  2. Conjunto uniqueNumericIDAmount a 0 en elfirebase firestore console
  3. Usar doc.data().uniqueNumericIDAmount + 1como la identificación numérica única
  4. Actualizar appDatacolección uniqueNumericIDAmountconfirebase.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
fuente
-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
Sampath Patro
fuente
1
Esta respuesta podría usar más contexto en cuanto al código de ejemplo.
ShellNinja el