Google Firestore - Wie erhalte ich Dokumente mit mehreren IDs auf einer Rundreise?


98

Ich frage mich, ob es möglich ist, mehrere Dokumente anhand der ID-Liste in einer Hin- und Rückfahrt (Netzwerkanruf) zum Firestore abzurufen.


4
Sie scheinen davon auszugehen, dass die Roundtrips Leistungsprobleme in Ihrer App verursachen. Das würde ich nicht annehmen. Firebase hat in solchen Fällen eine lange Erfolgsgeschichte, da es die Anforderungen weiterleitet . Obwohl ich nicht überprüft habe, wie sich Firestore in diesem Szenario verhält, würde ich gerne einen Beweis für ein Leistungsproblem sehen, bevor ich davon ausgehe, dass es existiert.
Frank van Puffelen

1
Sagen wir , ich brauche Dokumente a, b, cetwas zu tun. Ich fordere alle drei parallel in getrennten Anfragen an. adauert 100 ms, bdauert 150 ms und cdauert 3000 ms. Daher muss ich auf 3000 ms warten, um die Aufgabe zu erledigen. Es wird maxvon ihnen sein. Es wird riskanter, wenn die Anzahl der abzurufenden Dokumente groß ist. Abhängig vom Netzwerkstatus denke ich, dass dies zu einem Problem werden kann.
Joon

1
Würde es nicht genauso lange SELECT * FROM docs WHERE id IN (a,b,c)dauern , sie alle als Single zu senden ? Ich sehe keinen Unterschied, da die Verbindung einmal hergestellt wird und der Rest darüber geleitet wird. Die Zeit (nach dem ersten Verbindungsaufbau) ist die Ladezeit aller Dokumente + 1 Hin- und Rückfahrt, für beide Ansätze gleich. Wenn es sich für Sie anders verhält, können Sie ein Beispiel teilen (wie in meiner verknüpften Frage)?
Frank van Puffelen

Ich glaube ich habe dich verloren. Wenn Sie sagen, dass es sich um eine Pipeline handelt, bedeutet dies, dass Firestore in einem Roundtrip zur Datenbank automatisch Anfragen gruppiert und an ihren Server sendet?
Joon

Zu Ihrer Information, was ich unter einer Rundreise verstehe, ist ein Netzwerkanruf vom Client an die Datenbank. Ich frage, ob mehrere Abfragen von Firestore automatisch als eine Roundtrip gruppiert werden oder ob mehrere Abfragen als mehrere Roundtrips parallel ausgeführt werden.
Joon

Antworten:


86

Wenn Sie sich innerhalb des Knotens befinden:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L701

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

Dies ist speziell für das Server-SDK

UPDATE: "Cloud Firestore [clientseitiges SDK] unterstützt jetzt IN-Abfragen!"

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])


28
Für alle, die diese Methode mit einem dynamisch generierten Array von Dokumentreferenzen aufrufen möchten, können Sie dies folgendermaßen tun: firestore.getAll (... arrayOfReferences) .then ()
Horea

1
Es tut mir leid @KamanaKisinga ... Ich habe seit fast einem Jahr keine Firebase-Sachen mehr gemacht und kann zu diesem Zeitpunkt nicht wirklich helfen (hey schau, ich habe diese Antwort heute tatsächlich vor einem Jahr gepostet!)
Nick Franceschina

2
Die clientseitigen SDKs bieten jetzt auch diese Funktionalität. Siehe Jeodonaras Antwort für ein Beispiel: stackoverflow.com/a/58780369
Frank van Puffelen

3
Warnung: Der In-Filter ist derzeit auf 10 Elemente begrenzt. Sie werden also wahrscheinlich herausfinden, dass es nutzlos ist, wenn Sie kurz vor der Produktion stehen.
Martin Cremer

6
Eigentlich müssen Sie verwenden firebase.firestore.FieldPath.documentId()und nicht'id'
Maddocks

20

Sie haben gerade diese Funktionalität angekündigt, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Jetzt können Sie Abfragen wie verwenden, aber beachten Sie, dass die Eingabegröße nicht größer als 10 sein darf.

userCollection.where('uid', 'in', ["1231","222","2131"])


Es gibt eher eine whereIn-Abfrage als wo. Und ich weiß nicht, wie ich eine Abfrage für mehrere Dokumente aus einer Liste von IDs von Dokumenten entwerfen soll, die zu einer bestimmten Sammlung gehören. Bitte helfen Sie.
Kompilierungsfehler Ende

15
@Compileerrorend könnten Sie dies versuchen? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
Jeadonara

Vielen Dank, vor allem für diefirebase.firestore.FieldPath.documentId()
Cherniv

10

Nein, derzeit gibt es keine Möglichkeit, mehrere Leseanforderungen mit dem Cloud Firestore SDK zu stapeln, und daher keine Möglichkeit, sicherzustellen, dass Sie alle Daten gleichzeitig lesen können.

Wie Frank van Puffelen in den obigen Kommentaren gesagt hat, bedeutet dies jedoch nicht, dass das Abrufen von 3 Dokumenten dreimal so langsam ist wie das Abrufen eines Dokuments. Es ist am besten, eigene Messungen durchzuführen, bevor Sie hier zu einer Schlussfolgerung gelangen.


1
Die Sache ist, dass ich theoretische Grenzen für die Leistung von Firestore kennen möchte, bevor ich zu Firestore migriere. Ich möchte nicht migrieren und dann feststellen, dass es für meinen Anwendungsfall nicht gut genug ist.
Joon

2
Hallo, hier gibt es auch eine Überlegung von cose. Angenommen, ich habe eine Liste aller IDs meines Freundes gespeichert und die Nummer ist 500. Ich kann die Liste in 1 Lesekosten erhalten, aber um ihren Namen und ihre photoURL anzuzeigen, kostet es mich 500 Lesevorgänge.
Tapas Mukherjee

1
Wenn Sie versuchen, 500 Dokumente zu lesen, sind 500 Lesevorgänge erforderlich. Wenn Sie die benötigten Informationen aus allen 500 Dokumenten in einem einzigen zusätzlichen Dokument zusammenfassen, ist nur ein Lesevorgang erforderlich. Diese Art der Datenverdoppelung ist in den meisten NoSQL-Datenbanken, einschließlich Cloud Firestore, ganz normal.
Frank van Puffelen

1
@FrankvanPuffelen In mongoDb können Sie beispielsweise ObjectId wie folgt verwenden: stackoverflow.com/a/32264630/648851 .
Sitian Liu

1
Wie @FrankvanPuffelen sagte, ist die Duplizierung von Daten in der NoSQL-Datenbank ziemlich häufig. Hier müssen Sie sich fragen, wie oft diese Daten gelesen werden müssen und wie aktuell sie sein müssen. Wenn Sie 500 Benutzerinformationen speichern, z. B. deren Name + Foto + ID, können Sie diese in einem Lesevorgang abrufen. Wenn Sie sie jedoch auf dem neuesten Stand benötigen, müssen Sie wahrscheinlich eine Cloud-Funktion verwenden, um diese Referenzen jedes Mal zu aktualisieren, wenn ein Benutzer seinen Namen / sein Foto aktualisiert. Daher wird eine Cloud-Funktion ausgeführt und einige Schreibvorgänge ausgeführt. Es gibt keine "richtige" / "bessere" Implementierung, es hängt nur von Ihrem Anwendungsfall ab.
Schankam

9

In der Praxis würden Sie firestore.getAll so verwenden

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

oder mit Versprechenssyntax

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}

2
Dies sollte wirklich die ausgewählte Antwort sein, da Sie damit mehr als 10 IDs verwenden können
sshah98

9

Sie könnten eine Funktion wie diese verwenden:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

Es kann mit einer einzigen ID aufgerufen werden:

getById('collection', 'some_id')

oder ein Array von IDs:

getById('collection', ['some_id', 'some_other_id'])

5

Der beste Weg, dies zu tun, ist sicherlich die Implementierung der eigentlichen Abfrage von Firestore in einer Cloud-Funktion. Es würde dann nur einen einzigen Hin- und Rückruf vom Kunden zu Firebase geben, was genau das zu sein scheint, wonach Sie fragen.

Sie möchten wirklich Ihre gesamte Datenzugriffslogik wie diese Serverseite beibehalten.

Intern wird es wahrscheinlich die gleiche Anzahl von Anrufen bei Firebase selbst geben, aber alle werden über die superschnellen Verbindungen von Google und nicht über das externe Netzwerk erfolgen. In Kombination mit dem von Frank van Puffelen erläuterten Pipelining sollten Sie eine hervorragende Leistung erzielen dieser Ansatz.


3
Das Speichern der Implementierung in einer Cloud-Funktion ist in einigen Fällen, in denen Sie über eine komplexe Logik verfügen, die richtige Entscheidung, wahrscheinlich jedoch nicht in dem Fall, in dem Sie nur eine Liste mit mehreren IDs zusammenführen möchten. Was Sie verlieren, ist das clientseitige Caching und die standardisierte Rückgabeformatierung bei regulären Anrufen. Dies verursachte mehr Leistungsprobleme als es in einigen Fällen in meinen Apps löste, als ich den Ansatz verwendete.
Jeremiah

3

Wenn Sie Flattern verwenden, können Sie Folgendes tun:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

Dies gibt eine Zukunft zurück, List<DocumentSnapshot>die Sie wiederholen können, wenn Sie sich fit fühlen.


2

So würden Sie in Kotlin mit dem Android SDK so etwas machen.
Möglicherweise nicht unbedingt in einer Hin- und Rückfahrt, aber es gruppiert das Ergebnis effektiv und vermeidet viele verschachtelte Rückrufe.

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

Beachten Sie, dass das Abrufen bestimmter Dokumente viel besser ist als das Abrufen aller Dokumente und das Filtern des Ergebnisses. Dies liegt daran, dass Firestore Ihnen die Abfrageergebnismenge in Rechnung stellt.


1
Funktioniert gut, genau das, wonach ich gesucht habe!
Georgi

0

Dies scheint im Firestore derzeit nicht möglich zu sein. Ich verstehe nicht, warum Alexanders Antwort akzeptiert wird. Die von ihm vorgeschlagene Lösung gibt nur alle Dokumente in der Sammlung "Benutzer" zurück.

Je nachdem, was Sie tun müssen, sollten Sie die relevanten Daten, die Sie anzeigen müssen, duplizieren und nur bei Bedarf ein vollständiges Dokument anfordern.


0

Das Beste, was Sie tun können, ist, es nicht zu verwenden, Promise.allda Ihr Client darauf warten muss.all die Lesevorgänge bevor Sie fortfahren.

Iterieren Sie die Lesevorgänge und lassen Sie sie unabhängig voneinander auflösen. Auf der Clientseite läuft dies wahrscheinlich darauf hinaus, dass die Benutzeroberfläche mehrere Progress Loader-Images unabhängig voneinander in Werte auflöst. Dies ist jedoch besser als das Einfrieren des gesamten Clients bis.all die Lesevorgänge aufgelöst sind.

Speichern Sie daher alle synchronen Ergebnisse sofort in der Ansicht und lassen Sie die asynchronen Ergebnisse einzeln beim Auflösen eingehen. Dies mag wie eine geringfügige Unterscheidung erscheinen, aber wenn Ihr Client über eine schlechte Internetverbindung verfügt (wie ich sie derzeit in diesem Café habe), führt das Einfrieren der gesamten Client-Erfahrung für einige Sekunden wahrscheinlich zu einer "Diese App ist zum Kotzen" -Erfahrung.


2
Es ist asynchron, es gibt viele Anwendungsfälle für die Verwendung Promise.all... es muss nicht unbedingt irgendetwas "einfrieren" - Sie müssen möglicherweise auf alle Daten warten, bevor Sie etwas Sinnvolles tun können
Ryan Taylor

Es gibt mehrere Anwendungsfälle, in denen Sie alle Ihre Daten laden müssen. Daher kann Promise.all das Warten (wie bei einem Spinner mit einer entsprechenden Nachricht, ohne dass Sie eine Benutzeroberfläche wie Sie sagen einfrieren müssen) vollständig benötigen. Es kommt nur wirklich darauf an, welche Art von Produkten Sie hier bauen. Diese Art von Kommentaren ist meiner Meinung nach sehr irrelevant und es sollten keine "besten" Wörter darin sein. Es hängt wirklich von den verschiedenen Anwendungsfällen ab, denen Sie begegnen können, und davon, was Ihre App für den Benutzer tut.
Schankam

0

Ich hoffe das hilft dir, es funktioniert bei mir.

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.