Sottoraccolte di query Firestore


125

Pensavo di aver letto che puoi interrogare le sottoraccolte con il nuovo Firebase Firestore, ma non vedo alcun esempio. Ad esempio, ho la mia configurazione Firestore nel modo seguente:

  • Danze [collezione]
    • danceName
    • Songs [raccolta]
      • nome della canzone

Come potrei interrogare "Trova tutti i balli in cui songName == 'X'"


1
è supportato ancora Firestore, anno 2020?
sajanyamaha

Risposte:


148

Aggiornamento 2019-05-07

Oggi abbiamo rilasciato le query del gruppo di raccolte e queste consentono di eseguire query su sottoraccolte.

Quindi, ad esempio nel web SDK:

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

Ciò corrisponderebbe a documenti in qualsiasi raccolta in cui l'ultima parte del percorso di raccolta è "Canzoni".

La tua domanda originale riguardava la ricerca di danze in cui songName == 'X', e questo non è ancora possibile direttamente, tuttavia, per ogni canzone che corrisponde puoi caricare il suo genitore.

Risposta originale

Questa è una caratteristica che ancora non esiste. Si chiama "query di gruppo raccolta" e ti consente di interrogare tutte le canzoni indipendentemente dal ballo che le contiene. Questo è qualcosa che intendiamo sostenere ma non abbiamo una tempistica concreta su quando arriverà.

La struttura alternativa a questo punto è rendere le canzoni una raccolta di alto livello e fare in modo che la danza della canzone faccia parte di una proprietà della canzone.


147
Sarebbe MOLTO meglio se il team di sviluppo di Firestore implementasse le query di sottoraccolta al più presto. Dopo tutto, "query più potenti" è uno dei principali punti di forza secondo il manuale di Firestore. In questo momento, Firestore è come una Porsche senza ruote.
Arne Wolframm

21
Siamo d'accordo! C'è solo un numero limitato di ore al giorno :-).
Gil Gilbert

20
Non capisco, per cosa pagano le persone, se la base di fuoco è limitata? Sembra che anche Backendless abbia più funzionalità di Firebase. E perché Firebase è così popolare? Sembra che la gente sia impazzita
nzackoya

15
Questa funzione è altamente necessaria, altrimenti le persone inizieranno a trovare alternative, anche se abbiamo delle scadenze da rispettare. : P
JD-V

13
Abbiamo bisogno di questa caratteristica. In leasing la tempistica per il rilascio di questo ci aiuterà a essere preparati.
Sanjaya Panigrahy

22

AGGIORNA Ora Firestore supporta array-contiene

Avere questi documenti

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

fallo in questo modo

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

@ Nelson.b.austin Dato che firestore non ce l'ha ancora, ti suggerisco di avere una struttura piatta, che significa:

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

Avendolo in questo modo, puoi farlo:

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

Spero che aiuti.


2
a cosa songName_Title3: falseserve? se non sbaglio, può essere utilizzato solo per cercare danze che non hanno un nome specifico di canzone supponendo che ci sia bisogno di un songName_Title3: falseper dances.where("songName_"+songTitle, "==", false); restituire tali risultati non avrebbe senso per ogni ballo avere bandiere booleane per ogni possibile canzone nome ...
epeleg

Questo è ottimo, ma i documenti sono limitati a 1 MB, quindi se devi associare un lungo elenco di stringhe o qualsiasi altra cosa a un documento specifico, non puoi utilizzare questo approccio.
Supertecnoboff

@Supertecnoboff Sembra che dovrebbe essere un elenco di stringhe terribilmente ampio e lungo. Quanto è performante questa query "array_contains" e quali sono le alternative più performanti?
Jay Ordway

14

E se memorizzi le canzoni come oggetto invece che come raccolta? Ogni danza come, con le canzoni come campo: digita Object (non una raccolta)

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

quindi puoi interrogare tutti i balli con aNameOfASong:

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

3
Questa soluzione funzionerebbe ma non è scalabile nel caso in cui il numero di brani sia elevato o possa crescere dinamicamente. Ciò aumenterebbe la dimensione del documento e influirebbe sulle prestazioni di lettura / scrittura. Ulteriori informazioni su questo possono essere trovate nella documentazione di Firebase collegata di seguito (vedere l'ultima sezione "Limitazioni" nella pagina) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif,

14

AGGIORNAMENTO 2019

Firestore ha rilasciato Collection Group Queries. Vedere la risposta di Gil sopra o la documentazione ufficiale delle query del gruppo di raccolta


Risposta precedente

Come affermato da Gil Gilbert, sembra che le query del gruppo di raccolta siano attualmente in lavorazione. Nel frattempo è probabilmente meglio usare le raccolte a livello di root e collegarle semplicemente tra loro utilizzando l'UID del documento.

Per coloro che non lo sanno già, Jeff Delaney ha alcune guide e risorse incredibili per chiunque lavori con Firebase (e Angular) su AngularFirebase .

Firestore NoSQL Relational Data Modeling - Qui analizza le basi della struttura NoSQL e Firestore DB

Modellazione avanzata dei dati con Firestore by Example - Queste sono tecniche più avanzate da tenere in mente. Un'ottima lettura per coloro che desiderano portare le proprie abilità in Firestore a un livello superiore


7

NUOVO AGGIORNAMENTO 8 luglio 2019:

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

3

Puoi sempre cercare in questo modo: -

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

3

Limitazioni delle query

Cloud Firestore non supporta i seguenti tipi di query:

  1. Interrogazioni con filtri di intervallo su diversi campi.

  2. Singole query su più raccolte o sottoraccolte. Ogni query viene eseguita su una singola raccolta di documenti. Per ulteriori informazioni su come la struttura dei dati influisce sulle query, vedere Scegliere una struttura dati .

  3. Interrogazioni OR logiche. In questo caso, dovresti creare una query separata per ogni condizione OR e unire i risultati della query nella tua app.

  4. Query con una clausola! =. In questo caso, è necessario suddividere la query in una query maggiore di e una query minore di. Ad esempio, sebbene la clausola query dove ("age", "! =", "30") non sia supportata, puoi ottenere lo stesso set di risultati combinando due query, una con la clausola where ("age", "< "," 30 ") e uno con la clausola dove (" age ","> ", 30).


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

1

Potrebbe essere meglio utilizzare una struttura dati piatta.
I documenti specificano i pro ei contro delle diverse strutture di dati in questa pagina .

In particolare sui limiti delle strutture con sotto-raccolte:

Non è possibile eliminare facilmente sottoraccolte o eseguire query composte tra sottoraccolte.

In contrasto con i presunti vantaggi di una struttura dati piatta:

Le raccolte a livello di radice offrono la massima flessibilità e scalabilità, insieme a potenti query all'interno di ciascuna raccolta.


1

Ho trovato una soluzione. Per favore controlla questo.

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

E poi puoi vedere dati, regole, indici, schede di utilizzo nel tuo cloud firestore da console.firebase.google.com. Infine, dovresti impostare gli indici nella scheda degli indici.inserisci qui la descrizione dell'immagine

Inserisci qui l'ID raccolta e un valore di campo. Quindi seleziona l'opzione del gruppo di raccolta. Divertirsi. Grazie


Questo non risponde alla domanda. La query sopra menzionata recupera solo tutte le canzoni con songName = 'X'. Questo non fornirà le danze in cui songName = 'X'.
Sachin Rathod

0

Sto lavorando con Observables qui e AngularFire wrapper ma ecco come sono riuscito a farlo.

È un po 'folle, sto ancora imparando a conoscere gli osservabili e forse ho esagerato. Ma è stato un bell'esercizio.

Qualche spiegazione (non un esperto RxJS):

  • songId $ è un osservabile che emetterà ID
  • dance $ è un osservabile che legge quell'id e quindi ottiene solo il primo valore.
  • quindi interroga il collectionGroup di tutti i brani per trovarne tutte le istanze.
  • In base alle istanze che attraversa fino al genitore Dances e ottiene i loro ID.
  • Ora che abbiamo tutti gli ID Dance, dobbiamo interrogarli per ottenere i loro dati. Ma volevo che funzionasse bene, quindi invece di interrogarli uno per uno li ho raggruppati in bucket da 10 (l'angolazione massima richiederà per una inquery.
  • Finiamo con N bucket e dobbiamo eseguire N query su Firestore per ottenere i loro valori.
  • una volta che abbiamo eseguito le query su Firestore, dobbiamo ancora analizzare effettivamente i dati da quello.
  • e finalmente possiamo unire tutti i risultati della query per ottenere un singolo array con tutte le danze in esso.
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();

Ho copiato il mio codice e ho cambiato i nomi delle variabili con i tuoi, non sono sicuro che ci siano errori, ma ha funzionato bene per me. Fammi sapere se trovi errori o puoi suggerire un modo migliore per testarlo con forse qualche finto firestore.

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.