Anzahl der Cloud Firestore-Sammlungen

Antworten:

188

Update (April 2019) - FieldValue.increment (siehe große Sammlungslösung)


Wie bei vielen Fragen lautet die Antwort: Es kommt darauf an .

Sie sollten beim Umgang mit großen Datenmengen im Frontend sehr vorsichtig sein. Firestore sorgt nicht nur dafür, dass sich Ihr Front-End träge anfühlt, sondern berechnet Ihnen auch 0,60 US-Dollar pro Million Lesevorgänge .


Kleine Sammlung (weniger als 100 Dokumente)

Vorsichtig verwenden - Die Benutzererfahrung im Frontend kann beeinträchtigt werden

Die Behandlung am Frontend sollte in Ordnung sein, solange Sie mit diesem zurückgegebenen Array nicht zu viel Logik betreiben.

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

Mittlere Sammlung (100 bis 1000 Dokumente)

Vorsichtig verwenden - Firestore-Leseaufrufe können viel kosten

Dies am Frontend zu handhaben ist nicht möglich, da es zu viel Potenzial hat, das Benutzersystem zu verlangsamen. Wir sollten diese Logikserverseite behandeln und nur die Größe zurückgeben.

Der Nachteil dieser Methode ist, dass Sie immer noch Firestore-Lesevorgänge aufrufen (entsprechend der Größe Ihrer Sammlung), was Sie auf lange Sicht mehr kosten kann als erwartet.

Cloud-Funktion:

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

Vorderes Ende:

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

Große Sammlung (1000+ Dokumente)

Skalierbarste Lösung


FieldValue.increment ()

Ab April 2019 ermöglicht Firestore jetzt das Inkrementieren von Zählern vollständig atomar und ohne vorheriges Lesen der Daten . Dies stellt sicher, dass wir auch bei gleichzeitiger Aktualisierung aus mehreren Quellen (zuvor mithilfe von Transaktionen gelöst) korrekte Zählerwerte haben, und reduziert gleichzeitig die Anzahl der von uns durchgeführten Datenbanklesevorgänge.


Durch Abhören von gelöschten oder erstellten Dokumenten können wir ein Zählfeld in der Datenbank ergänzen oder daraus entfernen.

Sehen Sie sich die Firestore-Dokumente an - Verteilte Zähler oder werfen Sie einen Blick auf Data Aggregation von Jeff Delaney. Seine Anleitungen sind wirklich fantastisch für alle, die AngularFire verwenden, aber seine Lektionen sollten sich auch auf andere Frameworks übertragen lassen.

Cloud-Funktion:

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

Jetzt können Sie im Frontend einfach dieses Feld numberOfDocs abfragen, um die Größe der Sammlung zu ermitteln.

Matthew Mullin
quelle
16
Tolle Lösung für große Sammlungen! Ich möchte nur hinzufügen, dass Implementierer das Lesen und Schreiben in einen firestore.runTransaction { ... }Block einschließen sollten. Dies behebt Parallelitätsprobleme beim Zugriff numberOfDocs.
Efemoney
2
Diese Methoden verwenden eine Nachzählung der Anzahl der Datensätze. Wenn Sie einen Zähler verwenden und den Zähler mithilfe einer Transaktion erhöhen, würde dies nicht das gleiche Ergebnis ohne die zusätzlichen Kosten und die Notwendigkeit einer Cloud-Funktion erzielen?
user3836415
8
Die Lösung für große Sammlungen ist nicht idempotent und funktioniert in keinem Maßstab. Firestore-Dokumentauslöser werden garantiert mindestens einmal ausgeführt, können jedoch mehrere Male ausgeführt werden. In diesem Fall kann sogar die Aktualisierung innerhalb einer Transaktion mehrmals ausgeführt werden, wodurch Sie eine falsche Nummer erhalten. Als ich dies versuchte, stieß ich auf Probleme mit weniger als einem Dutzend Dokumenterstellungen gleichzeitig.
Tym Pollack
2
Hallo @TymPollack. Ich habe ein inkonsistentes Verhalten bei der Verwendung von Cloud-Triggern festgestellt. Gibt es eine Möglichkeit, mich mit einem Artikel oder einem Forum zu verknüpfen, um das erlebte Verhalten zu erklären?
Matthew Mullin
2
@cmprogram Sie lesen die gesamte Sammlung und Daten, wenn Sie db.collection ('...') verwenden. Wenn Sie die Daten also nicht benötigen, haben Sie Recht - Sie können leicht eine Liste anfordern Sammlungs-IDs (keine Sammlungsdokumentdaten) und es zählt als ein Lesevorgang.
Atereshkov
24

Am einfachsten ist es, die Größe eines "querySnapshot" zu lesen.

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

Sie können auch die Länge des docs-Arrays in "querySnapshot" lesen.

querySnapshot.docs.length;

Oder wenn ein "querySnapshot" leer ist, lesen Sie den leeren Wert, der einen booleschen Wert zurückgibt.

querySnapshot.empty;
Ompel
quelle
73
Beachten Sie, dass jedes Dokument einen Lesevorgang "kostet". Wenn Sie also auf diese Weise 100 Artikel in einer Sammlung zählen, werden Ihnen 100 Lesevorgänge berechnet!
Georg
Richtig, aber es gibt keine andere Möglichkeit, die Anzahl der Dokumente in einer Sammlung zusammenzufassen. Und wenn Sie die Sammlung bereits abgerufen haben, erfordert das Lesen des Arrays "docs" kein weiteres Abrufen und kostet daher keine weiteren Lesevorgänge.
Ompel
5
Dies liest alle Dokumente im Speicher! Viel Glück damit für große
Datenmengen
83
Das ist wirklich unglaublich, dass Firebase Firestore keine hat db.collection.count(). Ich denke, sie nur dafür fallen zu lassen
Blue Bot
8
Insbesondere bei großen Sammlungen ist es nicht fair, uns eine Gebühr in Rechnung zu stellen, als hätten wir tatsächlich alle Dokumente heruntergeladen und verwendet. Die Anzahl für eine Tabelle (Sammlung) ist eine solche Grundfunktion. Angesichts des Preismodells und der Einführung von Firestore im Jahr 2017 ist es einfach unglaublich, dass Google keine alternative Möglichkeit bietet, die Größe einer Sammlung zu ermitteln. Bis sie es nicht implementieren, sollten sie zumindest vermeiden, dafür Gebühren zu erheben.
Nibbana
23

Soweit ich weiß, gibt es dafür keine eingebaute Lösung und dies ist derzeit nur im Node SDK möglich. Wenn Sie eine haben

db.collection('someCollection')

Sie können verwenden

.select([fields])

um zu definieren, welches Feld Sie auswählen möchten. Wenn Sie ein leeres select () ausführen, erhalten Sie nur eine Reihe von Dokumentreferenzen.

Beispiel:

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

Diese Lösung ist nur eine Optimierung für den schlimmsten Fall des Herunterladens aller Dokumente und lässt sich nicht auf große Sammlungen skalieren!

Schauen Sie sich auch Folgendes an:
So erhalten Sie mit Cloud Firestore die Anzahl der Dokumente in einer Sammlung

jbb
quelle
Nach meiner Erfahrung select(['_id'])ist schneller alsselect()
JAnton
13

Zählen Sie die Anzahl der Dokumente für große Sammlungen sorgfältig . Die Firestore-Datenbank ist etwas komplex, wenn Sie für jede Sammlung einen vorberechneten Zähler haben möchten.

Code wie dieser funktioniert in diesem Fall nicht:

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

Der Grund dafür ist, dass jeder Cloud-Firestore-Trigger idempotent sein muss, wie in der Firestore-Dokumentation angegeben: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Lösung

Um zu verhindern, dass Ihr Code mehrfach ausgeführt wird, müssen Sie Ereignisse und Transaktionen verwalten. Dies ist meine besondere Art, mit großen Sammlungszählern umzugehen:

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

Anwendungsfälle hier:

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

Wie Sie sehen, ist der Schlüssel zum Verhindern der Mehrfachausführung die Eigenschaft eventId im Kontextobjekt . Wenn die Funktion für dasselbe Ereignis mehrmals ausgeführt wurde, ist die Ereignis-ID in allen Fällen dieselbe. Leider müssen Sie "Ereignissammlung" in Ihrer Datenbank haben.

Ferran Verdés
quelle
2
Sie formulieren dies so, als ob dieses Verhalten in der Version 1.0 behoben wäre. Amazon AWS-Funktionen leiden unter demselben Problem. Etwas so Einfaches wie das Zählen von Feldern wird komplex und teuer.
MarcG
Ich werde es jetzt versuchen, da es eine bessere Lösung zu sein scheint. Gehen Sie zurück und löschen Sie Ihre Veranstaltungssammlung jemals? Ich dachte daran, nur ein Datumsfeld hinzuzufügen und älter als einen Tag zu löschen oder etwas, um den Datensatz klein zu halten (möglicherweise 1 Mio. + Ereignisse / Tag). Es sei denn, es gibt in FS einen einfachen Weg, dies zu tun ... ich benutze FS erst seit ein paar Monaten.
Tym Pollack
1
Können wir überprüfen, ob context.eventIddies bei mehreren Aufrufen desselben Triggers immer gleich ist? In meinen Tests scheint es konsistent zu sein, aber ich kann keine "offizielle" Dokumentation finden, die dies besagt.
Mike McLin
2
Nachdem ich dies eine Weile verwendet habe, habe ich festgestellt, dass diese Lösung zwar mit genau einem Schreibvorgang funktioniert. Das ist großartig, wenn zu viele das Auslösen von mehreren Dokumenten gleichzeitig auslösen und versuchen, dasselbe Zählungsdokument zu aktualisieren, können Sie dies Erhalten Sie Konkurrenzfehler vom Firestore. Haben Sie diese kennengelernt und wie sind Sie damit umgegangen? (Fehler: 10 ABGEBROCHEN: Zu viel Streit um diese Dokumente. Bitte versuchen Sie es erneut.)
Tym Pollack
1
@TymPollack Blick auf verteilte Zähler Dokumentschreibvorgänge sind auf ca. ein Update pro Sekunde beschränkt
Jamie
8

Im Jahr 2020 ist dies im Firebase SDK noch nicht verfügbar, es ist jedoch in Firebase Extensions (Beta) verfügbar, es ist jedoch ziemlich komplex einzurichten und zu verwenden ...

Ein vernünftiger Ansatz

Helfer ... (Erstellen / Löschen scheint überflüssig, ist aber billiger als 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;
}

Exportierte Firestore-Hooks


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 Aktion

Die Gebäude Stammsammlung und alle Teilsammlungen werden verfolgt.

Geben Sie hier die Bildbeschreibung ein

Hier unter dem /counters/Wurzelpfad

Geben Sie hier die Bildbeschreibung ein

Jetzt werden die Sammlungszahlen automatisch und eventuell aktualisiert! Wenn Sie eine Zählung benötigen, verwenden Sie einfach den Erfassungspfad und stellen Sie ihm ein Präfix voran counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));
Ben Winding
quelle
Unterliegt dies nicht der gleichen Einschränkung "1 Dokumentaktualisierung pro Sekunde"?
Ayyappa
Ja, aber es ist letztendlich konsistent, was bedeutet, dass die Anzahl der Sammlungen schließlich mit der tatsächlichen Anzahl der Sammlungen übereinstimmt. Dies ist die am einfachsten zu implementierende Lösung, und in vielen Fällen ist eine kurze Verzögerung der Anzahl akzeptabel.
Ben Winding
7

Ich stimme @Matthew zu, es wird viel kosten, wenn Sie eine solche Abfrage durchführen.

[HINWEIS FÜR ENTWICKLER VOR BEGINN IHRER PROJEKTE]

Da wir diese Situation zu Beginn vorausgesehen haben, können wir tatsächlich eine Sammlung erstellen, nämlich Zähler mit einem Dokument, um alle Zähler in einem Feld mit Typ zu speichern number.

Beispielsweise:

Aktualisieren Sie für jede CRUD-Operation in der Sammlung das Zählerdokument:

  1. Wenn Sie eine neue Sammlung / Untersammlung erstellen : (+1 im Zähler) [1 Schreibvorgang]
  2. Wenn Sie eine Sammlung / Untersammlung löschen : (-1 im Zähler) [1 Schreibvorgang]
  3. Wenn Sie eine vorhandene Sammlung / Untersammlung aktualisieren , tun Sie nichts im Zählerdokument: (0)
  4. Wenn Sie eine vorhandene Sammlung / Untersammlung lesen , tun Sie nichts im Zählerdokument: (0)

Wenn Sie das nächste Mal die Anzahl der Sammlungen abrufen möchten, müssen Sie nur das Dokumentfeld abfragen / darauf verweisen. [1 Lesevorgang]

Darüber hinaus können Sie den Sammlungsnamen in einem Array speichern. Dies ist jedoch schwierig. Der Zustand des Arrays in der Firebase wird wie folgt angezeigt:

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

Wenn Sie die Sammlung also nicht löschen möchten, können Sie das Array verwenden, um die Liste der Sammlungsnamen zu speichern, anstatt jedes Mal die gesamte Sammlung abzufragen.

Ich hoffe es hilft!

Angus Tay
quelle
Für eine kleine Sammlung vielleicht. Beachten Sie jedoch, dass die maximale Größe des Firestore-Dokuments ~ 1 MB beträgt. Wenn die Dokument-IDs in einer Sammlung automatisch generiert werden (20 Byte), können Sie nur ~ 52.425 davon speichern, bevor das Dokument das Array enthält ist zu groß. Ich denke, als Problemumgehung könnten Sie alle 50.000 Elemente ein neues Dokument erstellen, aber die Verwaltung dieser Arrays wäre dann völlig unüberschaubar. Wenn die Größe des Dokuments zunimmt, dauert das Lesen und Aktualisieren außerdem länger, wodurch bei anderen Vorgängen möglicherweise eine Zeitüberschreitung auftritt.
Tym Pollack
5

Nein, derzeit gibt es keine integrierte Unterstützung für Aggregationsabfragen. Es gibt jedoch einige Dinge, die Sie tun könnten.

Der erste ist hier dokumentiert . Sie können Transaktionen oder Cloud-Funktionen verwenden, um aggregierte Informationen zu verwalten:

Dieses Beispiel zeigt, wie Sie mithilfe einer Funktion die Anzahl der Bewertungen in einer Untersammlung sowie die durchschnittliche Bewertung verfolgen.

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

Die von jbb erwähnte Lösung ist auch nützlich, wenn Sie Dokumente nur selten zählen möchten. Stellen Sie sicher, dass Sie die select()Anweisung verwenden, um zu vermeiden, dass alle Dokumente heruntergeladen werden (das ist viel Bandbreite, wenn Sie nur eine Zählung benötigen). select()ist derzeit nur in den Server-SDKs verfügbar, sodass die Lösung in einer mobilen App nicht funktioniert.

Sam Stern
quelle
1
Diese Lösung ist nicht idempotent, daher werden alle Auslöser, die mehr als einmal ausgelöst werden, Ihre Anzahl an Bewertungen und Ihren Durchschnitt beeinträchtigen.
Tym Pollack
4

Es ist keine direkte Option verfügbar. Das kannst du nicht db.collection("CollectionName").count(). Im Folgenden finden Sie zwei Möglichkeiten, wie Sie die Anzahl der Dokumente in einer Sammlung ermitteln können.

1: - Holen Sie sich alle Dokumente in der Sammlung und dann die Größe. (Nicht die beste Lösung)

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

Wenn Sie den obigen Code verwenden, entsprechen Ihre gelesenen Dokumente der Größe der Dokumente innerhalb einer Sammlung. Aus diesem Grund muss die Verwendung der obigen Lösung vermieden werden.

2: - Erstellen Sie ein separates Dokument mit in Ihrer Sammlung, in dem die Anzahl der Dokumente in der Sammlung gespeichert wird. (Beste Lösung)

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

Oben haben wir ein Dokument mit Namenszählungen erstellt, um alle Zählinformationen zu speichern. Sie können das Zählungsdokument folgendermaßen aktualisieren: -

  • Erstellen Sie einen Firestore-Trigger für die Dokumentanzahl
  • Erhöhen Sie die count-Eigenschaft des count-Dokuments, wenn ein neues Dokument erstellt wird.
  • Verringern Sie die count-Eigenschaft des count-Dokuments, wenn ein Dokument gelöscht wird.

Für den Preis (Document Read = 1) und den schnellen Datenabruf ist die obige Lösung gut.

Nipun Madan
quelle
3

Inkrementieren Sie einen Zähler mit 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 diesem Beispiel erhöhen wir instanceCountjedes Mal ein Feld im Projekt, wenn ein Dokument zur Untersammlung hinzugefügt wird instances. Wenn das Feld noch nicht vorhanden ist, wird es erstellt und auf 1 erhöht.

Die Inkrementierung erfolgt intern, Sie sollten jedoch einen verteilten Zähler verwenden, wenn Sie häufiger als alle 1 Sekunde inkrementieren müssen.

Es ist oft vorzuziehen , zu implementieren onCreateund onDeletenicht , onWritewie Sie rufen onWritenach Updates , die bedeuten , dass Sie mehr Geld für unnötige Funktionsaufrufe ausgeben (wenn Sie die Dokumentation in Ihrer Sammlung aktualisieren).

Dominic
quelle
2

Eine Problemumgehung ist:

Schreiben Sie einen Zähler in ein Firebase-Dokument, den Sie bei jeder Erstellung eines neuen Eintrags innerhalb einer Transaktion erhöhen

Sie speichern die Zählung in einem Feld Ihres neuen Eintrags (dh: Position: 4).

Anschließend erstellen Sie einen Index für dieses Feld (Position DESC).

Sie können mit einer Abfrage ein Überspringen + Limit durchführen. Wo ("Position", "<" x) .OrderBy ("Position", DESC)

Hoffe das hilft!

Kathan Shah
quelle
1

Mit all diesen Ideen habe ich eine universelle Funktion erstellt, um alle Gegensituationen (außer Abfragen) zu behandeln.

Die einzige Ausnahme wäre, wenn Sie so viele Schreibvorgänge pro Sekunde ausführen, dass Sie dadurch langsamer werden. Ein Beispiel wäre Likes auf einem Trendpost. Es ist zum Beispiel in einem Blog-Beitrag übertrieben und kostet Sie mehr. Ich schlage vor, in diesem Fall eine separate Funktion mit Shards zu erstellen: 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);
        });;
    }
}

Dies behandelt Ereignisse, Inkremente und Transaktionen. Das Schöne daran ist, dass Sie den Zähler löschen können, wenn Sie sich über die Genauigkeit eines Dokuments nicht sicher sind (wahrscheinlich noch in der Beta-Phase), damit es beim nächsten Auslöser automatisch addiert wird. Ja, das kostet, also löschen Sie es nicht anders.

Das Gleiche, um die Zählung zu erhalten:

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

Möglicherweise möchten Sie auch einen Cron-Job (geplante Funktion) erstellen, um alte Ereignisse zu entfernen und Geld beim Datenbankspeicher zu sparen. Sie benötigen mindestens einen Blaze-Plan, und möglicherweise ist eine weitere Konfiguration vorhanden. Sie könnten es zum Beispiel jeden Sonntag um 23 Uhr laufen lassen. https://firebase.google.com/docs/functions/schedule-functions

Dies ist nicht getestet , sollte aber mit ein paar Verbesserungen funktionieren:

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

Und vergessen Sie nicht, die Sammlungen in firestore.rules zu schützen :

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

Update: Abfragen

Wenn Sie meiner anderen Antwort hinzufügen möchten, dass Sie auch die Anzahl der Abfragen automatisieren möchten, können Sie diesen geänderten Code in Ihrer Cloud-Funktion verwenden:

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

Dadurch wird der postsCount im userDocument automatisch aktualisiert . Auf diese Weise können Sie leicht eine andere zu vielen Zählungen hinzufügen. Dies gibt Ihnen nur Ideen, wie Sie Dinge automatisieren können. Ich habe Ihnen auch eine andere Möglichkeit gegeben, die Ereignisse zu löschen. Sie müssen jedes Datum lesen, um es zu löschen, damit Sie es nicht wirklich speichern, um es später zu löschen. Dies verlangsamt nur die Funktion.

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

Ich sollte Sie auch warnen, dass universelle Funktionen in jeder onWrite-Aufrufperiode ausgeführt werden. Es ist möglicherweise billiger, die Funktion nur auf onCreate- und onDelete-Instanzen Ihrer spezifischen Sammlungen auszuführen. Wie bei der von uns verwendeten noSQL-Datenbank können Sie durch wiederholten Code und wiederholte Daten Geld sparen.

Jonathan
quelle
Schreiben Sie einen Artikel darüber auf Medium für den einfachen Zugriff.
ahmadalibaloch
0

Ich habe eine Weile gebraucht, um dies basierend auf einigen der obigen Antworten zum Laufen zu bringen, also dachte ich, ich würde es für andere teilen. Ich hoffe es ist nützlich.

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

Ich habe viel mit verschiedenen Ansätzen versucht. Und schließlich verbessere ich eine der Methoden. Zuerst müssen Sie eine separate Sammlung erstellen und dort alle Ereignisse speichern. Zweitens müssen Sie ein neues Lambda erstellen, das durch die Zeit ausgelöst wird. Dieses Lambda zählt Ereignisse in der Ereignissammlung und löscht Ereignisdokumente. Codedetails im Artikel. https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

Ihor Malanyk
quelle
Bitte fügen Sie die relevanten Details und den Code in die Antwort selbst ein . Das Zeigen von Personen auf Ihre Blog-Beiträge ist nicht wirklich der Punkt von StackOverflow.
DBS
0

Diese Abfrage führt zur Anzahl der Dokumente.

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

console.log(count)
Aravin
quelle
1
Keine gute Lösung, da Sie jedes Mal alle Dokumente aus der Sammlung abrufen. Es wird viel kosten. Ein besserer Ansatz besteht darin, jedes Mal einen Zähler einzurichten, wenn ein neues Dokument zu dieser Sammlung hinzugefügt wird, sodass Sie einfach ein Dokument anstelle einiger Tausender abrufen können.
Corentin Houdayer
-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
Sampath Patro
quelle
1
Diese Antwort könnte mehr Kontext für das Codebeispiel verwenden.
ShellNinja