Conteggio raccolta Cloud Firestore


Risposte:


191

Aggiornamento (aprile 2019) - FieldValue.increment (vedi soluzione di raccolta di grandi dimensioni)


Come per molte domande, la risposta è: dipende .

Dovresti stare molto attento quando gestisci grandi quantità di dati sul front-end. Oltre a rendere lenta la tua parte frontale, Firestore ti addebita anche $ 0,60 per milione di letture che fai.


Piccola raccolta (meno di 100 documenti)

Usare con cura - L'esperienza dell'utente di Frontend potrebbe subire un colpo

Gestire questo sul front-end dovrebbe andare bene finché non si sta facendo troppa logica con questo array restituito.

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

Raccolta media (da 100 a 1000 documenti)

Usare con cura: le invocazioni di lettura del Firestore possono costare molto

La gestione di questo front-end non è fattibile in quanto ha troppe potenzialità per rallentare il sistema degli utenti. Dovremmo gestire questo lato server logico e restituire solo le dimensioni.

Lo svantaggio di questo metodo è che stai ancora invocando le letture del deposito (pari alla dimensione della tua raccolta), che a lungo andare potrebbe finire per costarti più del previsto.

Funzione cloud:

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

Fine frontale:

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

Grande raccolta (oltre 1000 documenti)

La soluzione più scalabile


FieldValue.increment ()

A partire da aprile 2019 Firestore ora consente di incrementare i contatori, in modo completamente atomico e senza leggere i dati prima . Ciò garantisce che abbiamo valori di contatore corretti anche durante l'aggiornamento da più origini contemporaneamente (precedentemente risolto utilizzando le transazioni), riducendo anche il numero di letture del database che eseguiamo.


Ascoltando qualsiasi documento eliminato o creato, possiamo aggiungere o rimuovere da un campo di conteggio presente nel database.

Guarda i documenti del Firestore - Distributed Counters o dai un'occhiata a Data Aggregation di Jeff Delaney. Le sue guide sono davvero fantastiche per chiunque utilizzi AngularFire, ma le sue lezioni dovrebbero ripercuotersi anche su altri framework.

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

Ora sul frontend puoi semplicemente interrogare questo campo numberOfDocs per ottenere le dimensioni della raccolta.


17
Ottima soluzione per grandi collezioni! Vorrei solo aggiungere che gli implementatori dovrebbero racchiudere la lettura e la scrittura in un firestore.runTransaction { ... }blocco. Questo risolve i problemi di concorrenza con l'accesso numberOfDocs.
efemoney,

2
Questi metodi utilizzano un nuovo conteggio del numero di record. Se si utilizza un contatore e si incrementa il contatore utilizzando una transazione, ciò non otterrebbe lo stesso risultato senza il costo aggiuntivo e la necessità di una funzione cloud?
user3836415,

10
La soluzione per grandi collezioni non è idempotente e non funziona su nessuna scala. I trigger di documenti Firestore sono garantiti per essere eseguiti almeno una volta, ma possono essere eseguiti più volte. Quando ciò accade, anche il mantenimento dell'aggiornamento all'interno di una transazione può essere eseguito più di una volta, il che ti darà un numero falso. Quando ho provato questo, ho riscontrato problemi con meno di una dozzina di creazioni di documenti alla volta.
Tym Pollack,

2
Ciao @TymPollack. Ho notato alcuni comportamenti incoerenti utilizzando i trigger cloud. Hai qualche possibilità di collegarmi a un articolo o forum per spiegare il comportamento che hai riscontrato?
Matthew Mullin,

2
@cmprogram stai leggendo l'intera raccolta e i dati quando usi db.collection ('...') ... quindi quando non hai bisogno dei dati allora hai ragione - potresti facilmente richiedere un elenco di ID di raccolta (non dati di documenti di raccolta) e conta come una lettura.
atereshkov,

25

Il modo più semplice per farlo è leggere la dimensione di "querySnapshot".

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

Puoi anche leggere la lunghezza dell'array di documenti all'interno di "querySnapshot".

querySnapshot.docs.length;

Oppure se un "querySnapshot" è vuoto leggendo il valore vuoto, che restituirà un valore booleano.

querySnapshot.empty;

73
Tenere presente che ogni documento "costa" una lettura. Quindi, se conti 100 articoli in una raccolta in questo modo, ti verranno addebitate 100 letture!
Georg,

Corretto, ma non c'è altro modo per riassumere il numero di documenti in una raccolta. E se hai già recuperato la raccolta, leggere l'array "docs" non richiederà altro recupero, quindi non "costerà" più letture.
Ompel,

5
Questo legge tutti i documenti in memoria! Buona fortuna con quello per grandi set di dati ...
Dan Dascalescu,

85
è davvero incredibile che Firebase Firestore non ne abbia db.collection.count(). Pensando di lasciarli solo per questo
Blue Bot

8
Soprattutto per le grandi collezioni, non è giusto addebitarci come se effettivamente avessimo scaricato e utilizzato tutti i documenti. Il conteggio per una tabella (raccolta) è una caratteristica di base. Considerando il loro modello di prezzi e che Firestore è stato lanciato sul mercato nel 2017, è incredibile che Google non fornisca un modo alternativo per ottenere le dimensioni di una collezione. Fino a quando non lo implementano, dovrebbero almeno evitare di addebitarlo.
nibbana,

23

Per quanto ne so non esiste una soluzione integrata per questo ed è possibile solo nel nodo sdk in questo momento. Se hai un

db.collection('someCollection')

Puoi usare

.select([fields])

per definire quale campo si desidera selezionare. Se fai una selezione vuota () otterrai solo una serie di riferimenti a documenti.

esempio:

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

Questa soluzione è solo un'ottimizzazione per il caso peggiore di scaricare tutti i documenti e non si adatta a grandi raccolte!

Dai un'occhiata anche a questo:
come ottenere un conteggio del numero di documenti in una raccolta con Cloud Firestore


Nella mia esperienza, select(['_id'])è più veloce diselect()
JAnton il

13

Fai attenzione a contare il numero di documenti per raccolte di grandi dimensioni . È un po 'complesso con il database di firestore se si desidera avere un contatore precalcolato per ogni raccolta.

Codice come questo non funziona in questo 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;
});

Il motivo è perché ogni trigger di cloud store deve essere idempotente, come dice la documentazione di fireestore: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Soluzione

Pertanto, al fine di prevenire più esecuzioni del codice, è necessario gestire eventi e transazioni. Questo è il mio modo particolare di gestire i contatori di raccolte di grandi dimensioni:

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

Casi d'uso qui:

/**
 * 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'));

Come puoi vedere, la chiave per impedire l'esecuzione multipla è la proprietà chiamata eventId nell'oggetto contesto. Se la funzione è stata gestita più volte per lo stesso evento, l'ID evento sarà lo stesso in tutti i casi. Sfortunatamente, devi avere una raccolta di "eventi" nel tuo database.


2
Stanno definendo questo come se questo comportamento fosse corretto nella versione 1.0. Le funzioni di Amazon AWS presentano lo stesso problema. Qualcosa di così semplice come il conteggio dei campi diventa complesso e costoso.
MarcG,

Provando questo ora poiché sembra una soluzione migliore. Torna indietro ed elimina la tua raccolta di eventi di sempre? Stavo pensando di aggiungere semplicemente un campo data e di eliminare più di un giorno o qualcosa del genere solo per mantenere piccolo il set di dati (possibilmente 1 milione + eventi / giorno). A meno che non ci sia un modo semplice in FS per farlo ... sto usando FS solo da alcuni mesi.
Tym Pollack,

1
Possiamo verificare che context.eventIdsarà sempre lo stesso su più invocazioni dello stesso trigger? Nei miei test sembra essere coerente, ma non riesco a trovare alcuna documentazione "ufficiale" che affermi questo.
Mike McLin,

2
Quindi, dopo averlo usato per un po ', ho scoperto che, mentre questa soluzione funziona esattamente con una sola scrittura, il che è fantastico, se troppi attivano il fuoco da più documenti scritti contemporaneamente e provano ad aggiornare lo stesso conteggio dei documenti, puoi ottenere errori di contesa dal deposito. Li hai incontrati e come ci sei riuscito? (Errore: 10 ABORTATO: troppa contesa su questi documenti. Riprova.)
Tym Pollack

1
@TymPollack guarda i contatori distribuiti che le scritture di documenti sono limitate a circa un aggiornamento al secondo
Jamie

8

Nel 2020 questo non è ancora disponibile in Firebase SDK, tuttavia è disponibile in Firebase Extensions (Beta), tuttavia è piuttosto complesso da configurare e utilizzare ...

Un approccio ragionevole

Helpers ... (creare / eliminare sembra ridondante ma è più economico di 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;
}

Ganci Firestore esportati


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

In azione

Verrà tracciata la raccolta radice dell'edificio e tutte le raccolte secondarie .

inserisci qui la descrizione dell'immagine

Qui sotto il /counters/percorso di root

inserisci qui la descrizione dell'immagine

Ora i conteggi delle raccolte verranno aggiornati automaticamente ed eventualmente! Se hai bisogno di un conteggio, usa semplicemente il percorso di raccolta e aggiungi il prefisso counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));

Questo non è soggetto alla stessa limitazione "1 aggiornamento del documento al secondo"?
Ayyappa,

Sì, ma alla fine è coerente, il che significa che il conteggio delle raccolte alla fine si allinea con il conteggio delle raccolte effettive, è la soluzione più semplice da implementare e in molti casi è accettabile un breve ritardo nel conteggio.
Ben Winding

7

Sono d'accordo con @Matthew, costerà molto se esegui tale query.

[CONSIGLI PER GLI SVILUPPI PRIMA DI INIZIARE I LORO PROGETTI]

Dato che all'inizio abbiamo previsto questa situazione, possiamo effettivamente creare una raccolta, ovvero contatori con un documento, per archiviare tutti i contatori in un campo con tipo number.

Per esempio:

Per ogni operazione CRUD sulla raccolta, aggiorna il documento contatore:

  1. Quando si crea una nuova raccolta / sottocollection: (+1 nel contatore) [1 operazione di scrittura]
  2. Quando si elimina una raccolta / raccolta secondaria: (-1 nel contatore) [1 operazione di scrittura]
  3. Quando aggiorni una raccolta / raccolta secondaria esistente, non eseguire alcuna operazione sul documento contatore: (0)
  4. Quando leggi una raccolta / raccolta secondaria esistente, non eseguire alcuna operazione sul documento contatore: (0)

La prossima volta, quando si desidera ottenere il numero di raccolte, è sufficiente eseguire una query / puntare al campo del documento. [1 operazione di lettura]

Inoltre, è possibile memorizzare il nome delle raccolte in un array, ma questo sarà complicato, la condizione dell'array in firebase è mostrata di seguito:

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

Pertanto, se non si intende eliminare la raccolta, è possibile effettivamente utilizzare l'array per memorizzare l'elenco dei nomi delle raccolte anziché eseguire una query su tutta la raccolta ogni volta.

Spero che sia d'aiuto!


Per una piccola collezione, forse. Ma tieni presente che il limite della dimensione del documento Firestore è ~ 1 MB, che, se gli ID documento in una raccolta sono generati automaticamente (20 byte), sarai in grado di memorizzarne solo ~ 52.425 prima del documento che contiene l'array è troppo grande. Immagino che potresti fare un nuovo documento ogni 50.000 elementi, ma poi mantenere tali array sarebbe del tutto ingestibile. Inoltre, con l'aumentare della dimensione del documento, ci vorrà più tempo per leggere e aggiornare, il che alla fine farà qualsiasi altra operazione su di esso in contesa.
Tym Pollack,

5

No, al momento non esiste un supporto integrato per le query di aggregazione. Tuttavia ci sono alcune cose che potresti fare.

Il primo è documentato qui . È possibile utilizzare le transazioni o le funzioni cloud per mantenere informazioni aggregate:

Questo esempio mostra come utilizzare una funzione per tenere traccia del numero di valutazioni in una raccolta secondaria, nonché della valutazione media.

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 soluzione menzionata da jbb è utile anche se si desidera contare solo raramente i documenti. Assicurati di usare la select()dichiarazione per evitare di scaricare tutti i documenti (è molta larghezza di banda quando hai solo bisogno di un conteggio). select()è disponibile solo negli SDK del server per ora in modo tale soluzione non funzionerà in un'app mobile.


1
Questa soluzione non è idempotente, quindi tutti i trigger che si attivano più di una volta eliminano il numero di valutazioni e la media.
Tym Pollack,

4

Non è disponibile alcuna opzione diretta. Non puoi farlo db.collection("CollectionName").count(). Di seguito sono riportati i due modi in cui è possibile trovare il conteggio del numero di documenti all'interno di una raccolta.

1: - Ottieni tutti i documenti nella raccolta e ottieni le dimensioni (non la soluzione migliore)

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

Usando il codice sopra il tuo documento legge sarà uguale alla dimensione dei documenti all'interno di una raccolta e questo è il motivo per cui si deve evitare di usare la soluzione sopra.

2: - Crea un documento separato con nella tua raccolta che memorizzerà il conteggio del numero di documenti nella raccolta (migliore soluzione)

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

Sopra abbiamo creato un documento con il conteggio dei nomi per memorizzare tutte le informazioni sul conteggio. Puoi aggiornare il documento conteggio nel modo seguente: -

  • Il trigger di creazione di un deposito dati sul conteggio dei documenti
  • Incrementa la proprietà count del documento conteggi quando viene creato un nuovo documento.
  • Ridurre la proprietà count del documento conteggi quando un documento viene eliminato.

wrt price (Document Read = 1) e veloce recupero dei dati la soluzione sopra è buona.


3

Incrementa un contatore 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),
    })
  );

In questo esempio incrementiamo un instanceCountcampo nel progetto ogni volta che un documento viene aggiunto alla instancesraccolta secondaria. Se il campo non esiste ancora, verrà creato e incrementato a 1.

L'incremento è transazionale internamente ma è necessario utilizzare un contatore distribuito se è necessario incrementare più frequentemente di ogni 1 secondo.

È spesso preferibile implementarlo onCreatee onDeletepiuttosto che onWriterichiedere gli onWriteaggiornamenti, il che significa che stai spendendo più soldi per invocazioni di funzioni non necessarie (se aggiorni i documenti nella tua raccolta).


2

Una soluzione alternativa è:

scrivere un contatore in un documento firebase, che si incrementa all'interno di una transazione ogni volta che si crea una nuova voce

Memorizzi il conteggio in un campo della tua nuova voce (es .: posizione: 4).

Quindi si crea un indice su quel campo (posizione DESC).

Puoi eseguire un skip + limit con una query.Where ("position", "<" x) .OrderBy ("position", DESC)

Spero che questo ti aiuti!


1

Ho creato una funzione universale usando tutte queste idee per gestire tutte le contro-situazioni (tranne le query).

L'unica eccezione sarebbe quando fare così tante scritture al secondo, ti rallenta. Un esempio potrebbe essere il Mi piace su un post di tendenza. È eccessivo in un post sul blog, ad esempio, e ti costerà di più. Suggerisco di creare una funzione separata in quel caso usando i frammenti: 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);
        });;
    }
}

Gestisce eventi, incrementi e transazioni. Il bello di questo è che se non sei sicuro dell'accuratezza di un documento (probabilmente mentre sei ancora in beta), puoi eliminare il contatore per farlo aggiungere automaticamente al successivo trigger. Sì, questo costa, quindi non cancellarlo altrimenti.

Stesso tipo di cose per ottenere il conteggio:

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

Inoltre, potresti voler creare un processo cron (funzione pianificata) per rimuovere i vecchi eventi per risparmiare denaro sull'archiviazione del database. È necessario almeno un piano di incendio e potrebbe esserci qualche altra configurazione. Potresti eseguirlo ogni domenica alle 23:00, per esempio. https://firebase.google.com/docs/functions/schedule-functions

Questo non è testato , ma dovrebbe funzionare con alcune modifiche:

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

E infine, non dimenticare di proteggere le raccolte in firestore.rules :

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

Aggiornamento: query

Aggiungendo all'altra mia risposta se si desidera automatizzare anche il conteggio delle query, è possibile utilizzare questo codice modificato nella funzione 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();

Che aggiornerà automaticamente postsCount in userDocument. In questo modo puoi facilmente aggiungere un altro a molti conteggi. Questo ti dà solo idee su come puoi automatizzare le cose. Ti ho anche dato un altro modo per eliminare gli eventi. Devi leggere ogni data per eliminarla, quindi non ti salverà davvero per eliminarli in un secondo momento, rende la funzione più 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;
}

Dovrei anche avvertirti che le funzioni universali verranno eseguite su ogni periodo di chiamata onWrite. Potrebbe essere più economico eseguire la funzione solo su istanze onCreate e onDelete delle raccolte specifiche. Come il database noSQL che stiamo utilizzando, codice e dati ripetuti possono farti risparmiare denaro.


scrivere un articolo su di esso su supporto per un facile accesso.
ahmadalibaloch,


0

Mi ci è voluto un po 'per farlo funzionare in base ad alcune delle risposte sopra, quindi ho pensato di condividerlo per gli altri. Spero sia 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;
});

0

Ho provato molto con approcci diversi. E infine, perfeziono uno dei metodi. Per prima cosa devi creare una raccolta separata e salvare lì tutti gli eventi. In secondo luogo è necessario creare un nuovo lambda per essere attivato dal tempo. Questa lambda conterà gli eventi nella raccolta degli eventi e cancellerà i documenti degli eventi. Dettagli del codice nell'articolo. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca


Includi i dettagli e il codice pertinenti nella risposta stessa , indicando le persone ai tuoi post sul blog non è davvero il punto di StackOverflow.
DBS,

0

Questa query comporterà il conteggio del documento.

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

console.log(count)

1
Non è una buona soluzione perché stai recuperando tutti i documenti dalla raccolta ogni volta. Costerà molto. Un approccio migliore consiste nell'impostare un contatore ogni volta che un nuovo documento viene aggiunto in questa raccolta in modo da poter semplicemente recuperare un documento anziché poche migliaia.
Corentin Houdayer,

0

Questo utilizza il conteggio per creare un ID numerico univoco. Nel mio uso, non diminuirò mai , anche quando documentl'ID è necessario per l'eliminazione.

Su una collectioncreazione che richiede un valore numerico univoco

  1. Designare una raccolta appDatacon un documento, setcon .docIDonly
  2. Impostare uniqueNumericIDAmountsu 0 infirebase firestore console
  3. Utilizzare doc.data().uniqueNumericIDAmount + 1come ID numerico univoco
  4. Aggiorna appDataraccolta 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);
            });
    });

-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });

1
Questa risposta potrebbe usare più contesto nell'esempio di codice.
ShellNinja,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.