Subkoleksi kueri Firestore


125

Saya pikir saya pernah membaca bahwa Anda dapat meminta subkoleksi dengan Firebase Firestore baru, tetapi saya tidak melihat contoh apa pun. Misalnya saya memiliki pengaturan Firestore saya dengan cara berikut:

  • Tarian [koleksi]
    • danceName
    • Lagu [koleksi]
      • nama lagu

Bagaimana saya bisa menanyakan "Temukan semua tarian di mana songName == 'X'"


1
apakah ini belum didukung firestore, tahun 2020?
sajanyamaha

Jawaban:


148

Perbarui 2019-05-07

Hari ini kami merilis kueri grup koleksi , dan ini memungkinkan Anda untuk membuat kueri di seluruh subkoleksi.

Jadi, misalnya di web SDK:

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

Ini akan mencocokkan dokumen dalam koleksi mana pun yang bagian terakhir jalur koleksinya adalah 'Lagu'.

Pertanyaan awal Anda adalah tentang menemukan tarian di mana songName == 'X', dan ini masih tidak mungkin secara langsung, namun, untuk setiap Lagu yang cocok, Anda dapat memuat induknya.

Jawaban asli

Ini adalah fitur yang belum ada. Ini disebut "kueri grup koleksi" dan memungkinkan Anda menanyakan semua lagu terlepas dari tarian mana yang berisi lagu tersebut. Ini adalah sesuatu yang ingin kami dukung tetapi tidak memiliki garis waktu yang konkret kapan itu akan datang.

Struktur alternatif pada tahap ini adalah menjadikan lagu-lagu sebagai koleksi tingkat atas dan menjadikan tarian mana dari lagu tersebut sebagai bagian dari properti lagu.


147
Akan JAUH lebih baik jika tim pengembang Firestore menerapkan kueri subkoleksi secepatnya. Lagi pula, 'kueri yang lebih kuat' adalah salah satu nilai jual utama menurut manual Firestore. Saat ini, Firestore seperti Porsche tanpa roda.
Arne Wolframm

21
Kami setuju! Hanya ada beberapa jam dalam sehari :-).
Gil Gilbert

20
Saya tidak mengerti, untuk apa orang membayar, jika firebase dibatasi? Sepertinya Backendless memiliki lebih banyak fungsi daripada Firebase. Dan mengapa Firebase begitu populer? Tampaknya orang menjadi gila
nzackoya

15
Fitur ini sangat dibutuhkan atau orang lain akan mulai mencari alternatif, bahkan kita memiliki tenggat waktu yang harus dipenuhi. : P
JD-V

13
Kami membutuhkan fitur ini. Paling tidak, jadwal untuk merilis ini akan membantu kami untuk bersiap.
sanjaya panigrahy

22

UPDATE Sekarang Firestore mendukung array-contains

Memiliki dokumen-dokumen ini

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

lakukan dengan cara ini

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

@ Nelson.b.austin Karena firestore belum memiliki itu, saya sarankan Anda untuk memiliki struktur yang datar, artinya:

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

Dengan cara seperti itu, Anda bisa menyelesaikannya:

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

Saya harap ini membantu.


2
untuk apa songName_Title3: falseberguna? jika saya tidak salah, itu hanya dapat digunakan untuk mencari tarian yang tidak memiliki nama lagu tertentu dengan asumsi bahwa kami membutuhkan songName_Title3: falseuntuk dances.where("songName_"+songTitle, "==", false); mengembalikan hasil seperti itu tidak masuk akal jika setiap tarian memiliki bendera boolean untuk setiap lagu yang mungkin nama ...
epeleg

Ini bagus tetapi dokumen dibatasi hingga 1MB, jadi jika Anda perlu mengaitkan daftar panjang string atau apa pun dengan dokumen tertentu, Anda tidak dapat menggunakan pendekatan ini.
Supertecnoboff

@Supertecnoboff Sepertinya itu harus menjadi daftar string yang sangat besar dan panjang. Seberapa berkinerja kueri "array_contains" ini dan apa alternatif berkinerja lainnya?
Jay Ordway

14

Bagaimana jika Anda menyimpan lagu sebagai objek, bukan sebagai koleksi? Setiap menari sebagai, dengan lagu sebagai bidang: ketik Objek (bukan koleksi)

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

maka Anda dapat menanyakan semua tarian dengan 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
Solusi ini akan berfungsi tetapi tidak dapat diskalakan jika jumlah lagu banyak atau dapat bertambah secara dinamis. Ini akan meningkatkan ukuran dokumen dan mempengaruhi kinerja baca / tulis. Lebih lanjut tentang ini dapat ditemukan dalam dokumentasi Firebase yang ditautkan di bawah (lihat bagian terakhir 'Batasan' di halaman) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif

14

UPDATE 2019

Firestore telah merilis Kueri Grup Koleksi. Lihat jawaban Gil di atas atau Dokumentasi Kueri Grup Koleksi resmi


Jawaban Sebelumnya

Seperti yang dinyatakan oleh Gil Gilbert, sepertinya kueri grup koleksi sedang dalam pengerjaan. Sementara itu, mungkin lebih baik menggunakan koleksi tingkat akar dan hanya menautkan antara koleksi ini menggunakan UID dokumen.

Bagi mereka yang belum tahu, Jeff Delaney memiliki beberapa panduan dan sumber daya yang luar biasa untuk siapa pun yang bekerja dengan Firebase (dan Angular) di AngularFirebase .

Pemodelan Data Relasional Firestore NoSQL - Di sini ia menjelaskan dasar-dasar penataan NoSQL dan Firestore DB

Pemodelan Data Tingkat Lanjut Dengan Firestore dengan Contoh - Ini adalah teknik yang lebih maju untuk diingat. Bacaan yang bagus untuk mereka yang ingin meningkatkan keterampilan Firestore mereka ke tingkat berikutnya


7

PEMBARUAN BARU 8 Juli 2019:

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

3

Anda selalu dapat menelusuri seperti ini: -

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

Batasan kueri

Cloud Firestore tidak mendukung jenis kueri berikut:

  1. Kueri dengan filter rentang di berbagai bidang.

  2. Kueri tunggal di beberapa koleksi atau subkoleksi. Setiap kueri berjalan pada satu kumpulan dokumen. Untuk informasi selengkapnya tentang bagaimana struktur data Anda memengaruhi kueri Anda, lihat memilih struktur data .

  3. Kueri OR logis. Dalam kasus ini, Anda harus membuat kueri terpisah untuk setiap kondisi ATAU dan menggabungkan hasil kueri di aplikasi Anda.

  4. Kueri dengan klausa! =. Dalam kasus ini, Anda harus memisahkan kueri menjadi kueri lebih besar dari dan kueri kurang dari. Misalnya, meskipun klausa kueri di mana ("usia", "! =", "30") tidak didukung, Anda bisa mendapatkan hasil yang sama dengan menggabungkan dua kueri, satu dengan klausa di mana ("usia", "< "," 30 ") dan satu dengan klausa where (" 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

Akan lebih baik jika menggunakan struktur data datar.
Dokumen menjelaskan pro dan kontra dari berbagai struktur data di halaman ini .

Khususnya tentang batasan struktur dengan sub-koleksi:

Anda tidak dapat dengan mudah menghapus subkoleksi, atau melakukan kueri gabungan di seluruh subkoleksi.

Dibandingkan dengan keuntungan yang diklaim dari struktur data datar:

Koleksi level root menawarkan fleksibilitas dan skalabilitas paling besar, bersama dengan kueri yang andal dalam setiap koleksi.


1

Saya telah menemukan solusi. Tolong periksa ini.

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

Kemudian Anda dapat melihat tab Data, Rules, Indexes, Usage di cloud firestore Anda dari console.firebase.google.com. Terakhir, Anda harus menyetel indeks di tab indeks.masukkan deskripsi gambar di sini

Isi ID koleksi dan beberapa nilai bidang di sini. Kemudian Pilih opsi grup koleksi. Bersenang senang lah. Terima kasih


Ini tidak menjawab pertanyaan itu. Kueri yang disebutkan di atas hanya mengambil semua Lagu dengan songName = 'X'. Ini tidak akan memberikan tarian di mana songName = 'X'.
sachin rathod

0

Saya bekerja dengan Observables di sini dan bungkus AngularFire tetapi inilah cara saya berhasil melakukannya.

Agak gila, saya masih belajar tentang yang bisa diamati dan saya mungkin melebih-lebihkannya. Tapi itu latihan yang bagus.

Beberapa penjelasan (bukan ahli RxJS):

  • songId $ adalah observable yang akan memancarkan id
  • dance $ adalah observable yang membaca id itu dan kemudian hanya mendapatkan nilai pertama.
  • itu kemudian menanyakan collectionGroup dari semua lagu untuk menemukan semua contohnya.
  • Berdasarkan contoh itu melintasi ke tarian induk dan mendapatkan id mereka.
  • Sekarang kita memiliki semua ID Dance yang kita butuhkan untuk menanyakannya untuk mendapatkan datanya. Tapi saya ingin itu bekerja dengan baik jadi alih-alih menanyakan satu per satu, saya menumpuknya dalam ember 10 (sudut maksimum akan mengambil inkueri.
  • Kami berakhir dengan N keranjang dan perlu melakukan kueri N di firestore untuk mendapatkan nilainya.
  • setelah kami melakukan kueri di firestore, kami masih perlu benar-benar mengurai datanya.
  • dan akhirnya kita bisa menggabungkan semua hasil query untuk mendapatkan array tunggal dengan semua tarian di dalamnya.
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();

Saya menyalin menempelkan kode saya dan mengubah nama variabel menjadi milik Anda, tidak yakin apakah ada kesalahan, tetapi itu berfungsi dengan baik untuk saya. Beri tahu saya jika Anda menemukan kesalahan atau dapat menyarankan cara yang lebih baik untuk mengujinya dengan mungkin beberapa firestore tiruan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.