Podkolekcje zapytań Firestore

125

Wydawało mi się, że czytałem, że w nowym Firebase Firestore możesz wysyłać zapytania do podkolekcji, ale nie widzę żadnych przykładów. Na przykład mam konfigurację Firestore w następujący sposób:

  • Tańce [kolekcja]
    • danceName
    • Piosenki [kolekcja]
      • Nazwa piosenki

Jak mogę zapytać „Znajdź wszystkie tańce, w których songName == 'X'”

Nelson.b.austin
źródło
1
czy to jest jeszcze obsługiwane Firestore, rok 2020?
sajanyamaha

Odpowiedzi:

148

Aktualizacja 2019-05-07

Dzisiaj udostępniliśmy zapytania o grupy kolekcji , które umożliwiają wykonywanie zapytań dotyczących podkolekcji.

Na przykład w internetowym SDK:

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

Dopasowałoby to dokumenty w każdej kolekcji, w której ostatnią częścią ścieżki do kolekcji jest „Utwory”.

Twoje pierwotne pytanie dotyczyło znalezienia tańców których songName == 'X', a to nadal nie jest bezpośrednio możliwe, jednak dla każdej pasującej piosenki możesz załadować jej rodzica.

Oryginalna odpowiedź

To funkcja, która jeszcze nie istnieje. Nazywa się to „zapytaniem o grupę kolekcji” i umożliwia odpytywanie wszystkich piosenek niezależnie od tego, który taniec je zawiera. Jest to coś, co zamierzamy wspierać, ale nie mamy konkretnego harmonogramu, kiedy to nadejdzie.

Alternatywną strukturą w tym momencie jest uczynienie z piosenek kolekcji na najwyższym poziomie i sprawienie, by taniec był częścią własności piosenki.

Gil Gilbert
źródło
147
Byłoby DUŻO lepiej, gdyby zespół deweloperów Firestore wdrożył zapytania podkolekcji jak najszybciej. W końcu „potężniejsze zapytania” to jeden z głównych punktów sprzedaży według instrukcji Firestore. Obecnie Firestore jest jak Porsche bez kół.
Arne Wolframm
21
Zgadzamy się! Liczba godzin w ciągu dnia jest ograniczona :-).
Gil Gilbert
20
Nie rozumiem, za co płacą ludzie, jeśli baza ogniowa jest ograniczona? Wygląda na to, że nawet Backendless ma większą funkcjonalność niż Firebase. Dlaczego Firebase jest tak popularny? Wygląda na to, że ludzie oszaleli
nzackoya
15
Ta funkcja jest bardzo potrzebna, w przeciwnym razie ludzie zaczną szukać alternatyw, nawet jeśli mamy terminy do dotrzymania. : P
JD-V
13
Potrzebujemy tej funkcji. Przynajmniej harmonogram wydania tego pomoże nam być przygotowanym.
sanjaya panigrahy
22

AKTUALIZACJA Teraz Firestore obsługuje zawartość tablic

Posiadanie tych dokumentów

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

zrób to w ten sposób

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

@ Nelson.b.austin Ponieważ firestore jeszcze tego nie ma, proponuję mieć płaską konstrukcję, co oznacza:

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

Mając to w ten sposób, możesz to zrobić:

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

Mam nadzieję, że to pomoże.

norgematos
źródło
2
do czego się songName_Title3: falseprzydaje? jeśli się nie mylę, można go użyć tylko do wyszukiwania tańców, które nie mają określonej nazwy utworu, przy założeniu, że potrzebujemy, songName_Title3: falseaby dances.where("songName_"+songTitle, "==", false); zwrócić takie wyniki, nie ma sensu, aby każdy taniec miał flagi logiczne dla każdej możliwej piosenki nazwa ...
epeleg
To jest świetne, ale dokumenty są ograniczone do 1 MB, więc jeśli chcesz skojarzyć długą listę ciągów lub cokolwiek z określonym dokumentem, nie możesz użyć tego podejścia.
Supertecnoboff
@Supertecnoboff Wygląda na to, że musiałaby to być okropnie duża i długa lista ciągów. Jak wydajne jest to zapytanie „array_contains” i jakie są bardziej wydajne alternatywy?
Jay Ordway,
14

A co, jeśli przechowujesz utwory jako obiekt, a nie jako zbiór? Każdy taniec jako, z piosenkami jako polem: wpisz Obiekt (nie zbiór)

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

wtedy możesz zapytać o wszystkie tańce za pomocą 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);
    });
dmartins
źródło
3
To rozwiązanie by działało, ale nie jest skalowalne w przypadku, gdy liczba utworów jest duża lub może dynamicznie rosnąć. Zwiększyłoby to rozmiar dokumentu i wpłynęło na wydajność odczytu / zapisu. Więcej na ten temat można znaleźć w dokumentacji Firebase, do której link znajduje się poniżej (patrz ostatnia sekcja „Ograniczenia” na stronie) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif
14

UPDATE 2019

Firestore udostępnił zapytania grupowe dotyczące kolekcji. Zobacz odpowiedź Gila powyżej lub oficjalną dokumentację dotyczącą zapytania grupy kolekcji


Poprzednia odpowiedź

Jak stwierdził Gil Gilbert, wydaje się, że obecnie trwają prace nad zapytaniami grupowymi . W międzyczasie prawdopodobnie lepiej jest używać kolekcji na poziomie głównym i po prostu łączyć te kolekcje za pomocą identyfikatorów UID dokumentu.

Dla tych, którzy jeszcze nie wiedzą, Jeff Delaney ma kilka niesamowitych przewodników i zasobów dla każdego, kto pracuje z Firebase (i Angular) w AngularFirebase .

Relacyjne modelowanie danych Firestore NoSQL - tutaj omawia podstawy strukturyzacji NoSQL i Firestore DB

Zaawansowane modelowanie danych w Firestore na przykładzie - są to bardziej zaawansowane techniki, o których warto pamiętać. Świetna lektura dla tych, którzy chcą przenieść swoje umiejętności w Firestore na wyższy poziom

Matthew Mullin
źródło
7

NOWA AKTUALIZACJA 8 lipca 2019:

db.collectionGroup('Songs')
  .where('songName', isEqualTo:'X')
  .get()
Nhật Trần
źródło
3

Zawsze możesz wyszukiwać w ten sposób: -

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
źródło
3

Ograniczenia zapytań

Cloud Firestore nie obsługuje następujących typów zapytań:

  1. Zapytania z filtrami zakresu w różnych polach.

  2. Pojedyncze zapytania dotyczące wielu kolekcji lub podkolekcji. Każde zapytanie działa na pojedynczej kolekcji dokumentów. Aby uzyskać więcej informacji na temat wpływu struktury danych na zapytania, zobacz Wybieranie struktury danych .

  3. Zapytania logiczne OR. W takim przypadku należy utworzyć oddzielne zapytanie dla każdego warunku LUB i scalić wyniki zapytania w aplikacji.

  4. Zapytania z klauzulą! =. W takim przypadku należy podzielić zapytanie na zapytanie większe niż i mniejsze niż. Na przykład, chociaż klauzula zapytania, gdzie ("wiek", "! =", "30") nie jest obsługiwana, możesz uzyskać ten sam zestaw wyników, łącząc dwa zapytania, jedno z klauzulą ​​gdzie ("wiek", "< "," 30 ") i jedną z klauzulą ​​gdzie (" wiek ","> ", 30).

ggDeGreat
źródło
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
źródło
1

Lepiej byłoby użyć płaskiej struktury danych.
Dokumenty określają zalety i wady różnych struktur danych na tej stronie .

W szczególności o ograniczeniach struktur z podkolekcjami:

Nie można łatwo usunąć kolekcji podrzędnych ani wykonywać zapytań złożonych w ramach kolekcji podrzędnych.

W przeciwieństwie do rzekomych zalet płaskiej struktury danych:

Kolekcje na poziomie katalogu głównego oferują największą elastyczność i skalowalność, a także zaawansowane zapytania w ramach każdej kolekcji.

MattCochrane
źródło
1

Znalazłem rozwiązanie. Proszę to sprawdzić.

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

Następnie możesz zobaczyć dane, reguły, indeksy, karty Użycie w swoim Cloud Firestore na console.firebase.google.com. Na koniec powinieneś ustawić indeksy w zakładce indeksy.wprowadź opis obrazu tutaj

Wpisz tutaj identyfikator kolekcji i jakąś wartość pola. Następnie wybierz opcję grupy kolekcji. Ciesz się tym. Dzięki

Szczęśliwy Syn
źródło
To nie odpowiada na pytanie. Zapytanie wspomniane powyżej pobiera po prostu wszystkie utwory z songName = 'X'. To nie zapewni tańców, w których songName = 'X'.
sachin rathod
0

Pracuję z Observables tutaj i opakowaniem AngularFire, ale oto jak udało mi się to zrobić.

To trochę szalone, wciąż uczę się o obserwowalnych i prawdopodobnie przesadziłem. Ale to było fajne ćwiczenie.

Kilka wyjaśnień (nie ekspert RxJS):

  • songId $ to obserwable, które będą emitować identyfikatory
  • dance $ jest obserwowalnym, który odczytuje ten identyfikator, a następnie pobiera tylko pierwszą wartość.
  • następnie wysyła zapytanie do collectionGroup wszystkich piosenek, aby znaleźć wszystkie jej wystąpienia.
  • Opierając się na instancjach, które przechodzi do tańca rodzica i uzyskać ich identyfikatory.
  • Teraz, gdy mamy już wszystkie identyfikatory Dance, potrzebujemy zapytać ich, aby uzyskać ich dane. Ale chciałem, żeby działał dobrze, więc zamiast odpytywania pojedynczo, umieszczam je w pojemnikach po 10 (maksymalny kąt będzie potrzebny dla inzapytania.
  • Otrzymujemy N zasobników i musimy wykonać N zapytań w Firestore, aby uzyskać ich wartości.
  • kiedy wykonamy zapytania w Firestore, nadal musimy faktycznie przeanalizować dane z tego.
  • i wreszcie możemy połączyć wszystkie wyniki zapytania, aby uzyskać pojedynczą tablicę ze wszystkimi tańcami.
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();

Skopiowałem, wkleiłem mój kod i zmieniłem nazwy zmiennych na twoje, nie jestem pewien, czy są jakieś błędy, ale dla mnie działało dobrze. Daj mi znać, jeśli znajdziesz jakieś błędy lub możesz zasugerować lepszy sposób przetestowania go przy użyciu fałszywego Firestore.

Eduardo
źródło