Firestore-Abfrage-Subkollektionen

125

Ich dachte, ich hätte gelesen, dass Sie mit dem neuen Firebase Firestore Untersammlungen abfragen können, aber ich sehe keine Beispiele. Zum Beispiel habe ich mein Firestore-Setup folgendermaßen:

  • Tänze [Sammlung]
    • Tanzname
    • Lieder [Sammlung]
      • Liedtitel

Wie könnte ich "Alle Tänze finden, bei denen songName == 'X'" abfragen?

Nelson.b.austin
quelle
1
Wird dies noch unterstützt, Firestore, Jahr 2020?
Sajanyamaha

Antworten:

148

Update 2019-05-07

Heute haben wir Sammlungsgruppenabfragen veröffentlicht , mit denen Sie Abfragen über Untersammlungen hinweg durchführen können.

So zum Beispiel im Web-SDK:

db.collectionGroup('Songs')
  .where('songName', '==', 'X')
  .get()

Dies würde mit Dokumenten in jeder Sammlung übereinstimmen, in der der letzte Teil des Sammlungspfads "Songs" ist.

Ihre ursprüngliche Frage betraf das Finden von Tänzen, bei denen songName == 'X' ist. Dies ist jedoch immer noch nicht direkt möglich. Für jeden passenden Song können Sie jedoch den übergeordneten Song laden.

Ursprüngliche Antwort

Dies ist eine Funktion, die es noch nicht gibt. Es wird als "Sammlungsgruppenabfrage" bezeichnet und ermöglicht es Ihnen, alle Songs abzufragen, unabhängig davon, welcher Tanz sie enthielt. Dies ist etwas, das wir unterstützen wollen, aber keinen konkreten Zeitplan haben, wann es kommt.

Die alternative Struktur an dieser Stelle besteht darin, Songs zu einer Top-Level-Sammlung zu machen und zu bestimmen, welcher Tanz des Songs Teil einer Eigenschaft des Songs ist.

Gil Gilbert
quelle
147
Es wäre VIEL besser, wenn das Firestore-Entwicklerteam so schnell wie möglich Subkollektionsabfragen implementieren würde. Immerhin sind "leistungsstärkere Abfragen" laut Firestore-Handbuch eines der Hauptverkaufsargumente. Im Moment ist Firestore wie ein Porsche ohne Räder.
Arne Wolframm
21
Sind wir uns einig! Es gibt nur eine begrenzte Anzahl von Stunden am Tag :-).
Gil Gilbert
20
Ich verstehe nicht, wofür zahlen die Leute, wenn die Feuerbasis begrenzt ist? Scheint, als hätte sogar Backendless mehr Funktionen als Firebase. Und warum ist Firebase so beliebt? Scheint, als
wären die
15
Diese Funktion wird dringend benötigt, sonst werden die Leute Alternativen finden, auch wenn wir Fristen einhalten müssen. : P
JD-V
13
Wir brauchen diese Funktion. Zumindest hilft uns der Zeitplan für die Veröffentlichung, darauf vorbereitet zu sein.
Sanjaya Panigraphie
22

UPDATE Now Firestore unterstützt Array-enthält

Diese Dokumente haben

    {danceName: 'Danca name 1', songName: ['Title1','Title2']}
    {danceName: 'Danca name 2', songName: ['Title3']}

mach es so

collection("Dances")
    .where("songName", "array-contains", "Title1")
    .get()...

@ Nelson.b.austin Da Firestore das noch nicht hat, empfehle ich Ihnen eine flache Struktur, was bedeutet:

Dances = {
    danceName: 'Dance name 1',
    songName_Title1: true,
    songName_Title2: true,
    songName_Title3: false
}

Wenn Sie es auf diese Weise haben, können Sie es erledigen:

var songTitle = 'Title1';
var dances = db.collection("Dances");
var query = dances.where("songName_"+songTitle, "==", true);

Ich hoffe das hilft.

norgematos
quelle
2
Wofür ist es songName_Title3: falsenützlich? Wenn ich mich nicht irre, kann es nur verwendet werden, um nach Tänzen zu suchen, die keinen bestimmten Songnamen haben, vorausgesetzt, wir brauchen eine songName_Title3: false, um dances.where("songName_"+songTitle, "==", false); solche Ergebnisse zurückzugeben. Es wäre nicht sinnvoll, wenn jeder Tanz boolesche Flaggen für jedes mögliche Lied hätte Name ...
Epeleg
Dies ist großartig, aber Dokumente sind auf 1 MB beschränkt. Wenn Sie also einem bestimmten Dokument eine lange Liste von Zeichenfolgen oder was auch immer zuordnen müssen, können Sie diesen Ansatz nicht verwenden.
Supertecnoboff
@ Supertecnoboff Das scheint eine schrecklich große und lange Liste von Strings zu sein. Wie performant ist diese "array_contains" -Abfrage und welche performanteren Alternativen gibt es?
Jay Ordway
14

Was ist, wenn Sie Songs als Objekt anstatt als Sammlung speichern? Jeder Tanz als, mit Liedern als Feld: Typ Objekt (keine Sammlung)

{
  danceName: "My Dance",
  songs: {
    "aNameOfASong": true,
    "aNameOfAnotherSong": true,
  }
}

dann könnten Sie mit aNameOfASong nach allen Tänzen fragen:

db.collection('Dances')
  .where('songs.aNameOfASong', '==', true)
  .get()
  .then(function(querySnapshot) {
    querySnapshot.forEach(function(doc) {
      console.log(doc.id, " => ", doc.data());
    });
   })
   .catch(function(error) {
     console.log("Error getting documents: ", error);
    });
Dmartins
quelle
3
Diese Lösung würde funktionieren, ist jedoch nicht skalierbar, wenn die Anzahl der Songs groß ist oder dynamisch wachsen kann. Dies würde die Dokumentgröße erhöhen und die Lese- / Schreibleistung beeinträchtigen. Weitere Informationen hierzu finden Sie in der unten verlinkten Firebase-Dokumentation (siehe den letzten Abschnitt "Einschränkungen" auf der Seite). Firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif
14

UPDATE 2019

Firestore hat Sammelgruppenabfragen veröffentlicht. Siehe Gils Antwort oben oder die offizielle Abfragedokumentation für Sammlungsgruppen


Vorherige Antwort

Wie Gil Gilbert feststellte, scheinen derzeit Abfragen von Sammlungsgruppen in Arbeit zu sein. In der Zwischenzeit ist es wahrscheinlich besser, Sammlungen auf Stammebene zu verwenden und diese Sammlungen einfach mithilfe der Dokument-UIDs zu verknüpfen.

Für diejenigen, die es noch nicht wissen, hat Jeff Delaney einige unglaubliche Anleitungen und Ressourcen für alle, die mit Firebase (und Angular) auf AngularFirebase arbeiten .

Firestore NoSQL Relational Data Modeling - Hier werden die Grundlagen der NoSQL- und Firestore DB-Strukturierung erläutert

Erweiterte Datenmodellierung mit Firestore anhand eines Beispiels - Dies sind erweiterte Techniken, die Sie im Hinterkopf behalten sollten. Eine großartige Lektüre für diejenigen, die ihre Firestore-Fähigkeiten auf die nächste Stufe heben möchten

Matthew Mullin
quelle
7

NEUES UPDATE 8. Juli 2019:

db.collectionGroup('Songs')
  .where('songName', isEqualTo:'X')
  .get()
Nhật Trần
quelle
3

Sie können immer so suchen: -

this.key$ = new BehaviorSubject(null);

return this.key$.switchMap(key =>
  this.angFirestore
    .collection("dances").doc("danceName").collections("songs", ref =>
      ref
        .where("songName", "==", X)
    )
    .snapshotChanges()
    .map(actions => {
      if (actions.toString()) {
        return actions.map(a => {
          const data = a.payload.doc.data() as Dance;
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      } else {
        return false;
      }
    })
);
Ankur Biswas
quelle
3

Abfragebeschränkungen

Cloud Firestore unterstützt die folgenden Abfragetypen nicht:

  1. Abfragen mit Bereichsfiltern in verschiedenen Feldern.

  2. Einzelne Abfragen über mehrere Sammlungen oder Untersammlungen hinweg. Jede Abfrage wird für eine einzelne Sammlung von Dokumenten ausgeführt. Weitere Informationen dazu, wie sich Ihre Datenstruktur auf Ihre Abfragen auswirkt, finden Sie unter Auswählen einer Datenstruktur .

  3. Logische ODER-Abfragen. In diesem Fall sollten Sie für jede ODER-Bedingung eine separate Abfrage erstellen und die Abfrageergebnisse in Ihrer App zusammenführen.

  4. Abfragen mit einer! = -Klausel. In diesem Fall sollten Sie die Abfrage in eine Abfrage mit mehr als und eine Abfrage mit weniger als aufteilen. Obwohl die Abfrageklausel where ("age", "! =", "30") nicht unterstützt wird, können Sie dieselbe Ergebnismenge erhalten, indem Sie zwei Abfragen kombinieren, eine mit der Klausel where ("age", "< "," 30 ") und eine mit der Klausel where (" age ","> ", 30).

ggDeGreat
quelle
2
var songs = []    
db.collection('Dances')
      .where('songs.aNameOfASong', '==', true)
      .get()
      .then(function(querySnapshot) {
        var songLength = querySnapshot.size
        var i=0;
        querySnapshot.forEach(function(doc) {
           songs.push(doc.data())
           i ++;
           if(songLength===i){
                console.log(songs
           }
          console.log(doc.id, " => ", doc.data());
        });
       })
       .catch(function(error) {
         console.log("Error getting documents: ", error);
        });
Alok Prusty
quelle
1

Es könnte besser sein, eine flache Datenstruktur zu verwenden.
In den Dokumenten werden die Vor- und Nachteile verschiedener Datenstrukturen auf dieser Seite angegeben .

Speziell zu den Einschränkungen von Strukturen mit Untersammlungen:

Sie können Subkollektionen nicht einfach löschen oder zusammengesetzte Abfragen über Subkollektionen hinweg durchführen.

Im Gegensatz zu den angeblichen Vorteilen einer flachen Datenstruktur:

Sammlungen auf Stammebene bieten die größte Flexibilität und Skalierbarkeit sowie leistungsstarke Abfragen in jeder Sammlung.

MattCochrane
quelle
1

Ich habe eine Lösung gefunden. Bitte prüfen Sie das.

var museums = Firestore.instance.collectionGroup('Songs').where('songName', isEqualTo: "X");
        museums.getDocuments().then((querySnapshot) {
            setState(() {
              songCounts= querySnapshot.documents.length.toString();
            });
        });

Anschließend können Sie in Ihrem Cloud-Firestore unter console.firebase.google.com die Registerkarten Daten, Regeln, Indizes und Verwendung anzeigen. Schließlich sollten Sie Indizes auf der Registerkarte Indizes festlegen.Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Sammlungs-ID und einen Feldwert ein. Wählen Sie dann die Sammlungsgruppenoption aus. Geniesse es. Vielen Dank

Glücklicher Sohn
quelle
Dies beantwortet die Frage nicht. Die oben erwähnte Abfrage ruft nur alle Songs mit songName = 'X' ab. Dies liefert nicht die Tänze, bei denen songName = 'X' ist.
Sachin Rathod
0

Ich arbeite hier mit Observables und dem AngularFire- Wrapper, aber so habe ich das geschafft.

Es ist irgendwie verrückt, ich lerne immer noch über Observable und habe es möglicherweise übertrieben. Aber es war eine schöne Übung.

Einige Erklärungen (kein RxJS-Experte):

  • songId $ ist ein Observable, das IDs ausgibt
  • dance $ ist ein Observable, das diese ID liest und dann nur den ersten Wert erhält.
  • Anschließend wird die Sammlungsgruppe aller Songs abgefragt, um alle Instanzen davon zu finden.
  • Basierend auf den Instanzen werden die übergeordneten Tänze durchlaufen und ihre IDs abgerufen.
  • Nachdem wir alle Dance-IDs haben, müssen wir sie abfragen, um ihre Daten zu erhalten. Aber ich wollte, dass es gut funktioniert, und anstatt sie einzeln inabzufragen, staple ich sie in Eimern von 10 (der maximale Winkel wird für eine Abfrage benötigt.
  • Wir haben am Ende N Buckets und müssen N Abfragen im Firestore durchführen, um ihre Werte zu erhalten.
  • Sobald wir die Abfragen im Firestore durchgeführt haben, müssen wir die Daten daraus tatsächlich analysieren.
  • und schließlich können wir alle Abfrageergebnisse zusammenführen, um ein einziges Array mit allen darin enthaltenen Tänzen zu erhalten.
type Song = {id: string, name: string};
type Dance = {id: string, name: string, songs: Song[]};

const songId$: Observable<Song> = new Observable();
const dance$ = songId$.pipe(
  take(1), // Only take 1 song name
  switchMap( v =>
    // Query across collectionGroup to get all instances.
    this.db.collectionGroup('songs', ref =>
      ref.where('id', '==', v.id)).get()
  ),
  switchMap( v => {
    // map the Song to the parent Dance, return the Dance ids
    const obs: string[] = [];
    v.docs.forEach(docRef => {
      // We invoke parent twice to go from doc->collection->doc
      obs.push(docRef.ref.parent.parent.id);
    });
    // Because we return an array here this one emit becomes N
    return obs;
  }),
  // Firebase IN support up to 10 values so we partition the data to query the Dances
  bufferCount(10),
  mergeMap( v => { // query every partition in parallel
    return this.db.collection('dances', ref => {
      return ref.where( firebase.firestore.FieldPath.documentId(), 'in', v);
    }).get();
  }),
  switchMap( v => {
    // Almost there now just need to extract the data from the QuerySnapshots
    const obs: Dance[] = [];
    v.docs.forEach(docRef => {
      obs.push({
        ...docRef.data(),
        id: docRef.id
      } as Dance);
    });
    return of(obs);
  }),
  // And finally we reduce the docs fetched into a single array.
  reduce((acc, value) => acc.concat(value), []),
);
const parentDances = await dance$.toPromise();

Ich habe meinen Code kopiert und die Variablennamen in Ihre geändert. Ich bin mir nicht sicher, ob es Fehler gibt, aber es hat gut funktioniert. Lassen Sie mich wissen, wenn Sie Fehler finden oder einen besseren Weg vorschlagen, um sie mit einem nachgebildeten Firestore zu testen.

Eduardo
quelle