Kueri berdasarkan beberapa klausa tempat di Firebase

244
{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson"
    },
    "movie2": {
        "genre": "Horror",
        "name": "The Shining",
        "lead": "Jack Nicholson"
    },
    "movie3": {
        "genre": "comedy",
        "name": "The Mask",
        "lead": "Jim Carrey"
    }
  }  
 }

Saya seorang pemula Firebase. Bagaimana saya bisa mengambil hasil dari data di atas di mana genre = 'comedy'DANlead = 'Jack Nicholson' ?

Opsi apa yang saya miliki?

47d_
sumber

Jawaban:

238

Menggunakan API Kueri Firebase , Anda mungkin tergoda untuk mencoba ini:

// !!! THIS WILL NOT WORK !!!
ref
  .orderBy('genre')
  .startAt('comedy').endAt('comedy')
  .orderBy('lead')                  // !!! THIS LINE WILL RAISE AN ERROR !!!
  .startAt('Jack Nicholson').endAt('Jack Nicholson')
  .on('value', function(snapshot) { 
      console.log(snapshot.val()); 
  });

Tetapi seperti yang dikatakan @RobDiMarco dari Firebase dalam komentar:

beberapa orderBy()panggilan akan menimbulkan kesalahan

Jadi kode saya di atas tidak akan berfungsi .

Saya tahu tiga pendekatan yang akan berhasil.

1. filter paling banyak di server, lakukan sisanya pada klien

Yang dapat Anda lakukan adalah menjalankan satu orderBy().startAt()./endAt()di server, tarik ke bawah data yang tersisa dan filter itu dalam kode JavaScript pada klien Anda.

ref
  .orderBy('genre')
  .equalTo('comedy')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      if (movie.lead == 'Jack Nicholson') {
          console.log(movie);
      }
  });

2. tambahkan properti yang menggabungkan nilai yang ingin Anda filter

Jika itu tidak cukup baik, Anda harus mempertimbangkan memodifikasi / memperluas data Anda untuk memungkinkan kasus penggunaan Anda. Misalnya: Anda bisa memasukkan genre + lead ke satu properti yang baru saja Anda gunakan untuk filter ini.

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_lead": "comedy_Jack Nicholson"
},...

Anda pada dasarnya membangun indeks multi-kolom Anda sendiri dengan cara itu dan dapat menanyakannya dengan:

ref
  .orderBy('genre_lead')
  .equalTo('comedy_Jack Nicholson')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

David East telah menulis perpustakaan bernama QueryBase yang membantu menghasilkan properti seperti itu .

Anda bahkan dapat melakukan kueri / rentang relatif, katakanlah Anda ingin mengizinkan kueri film berdasarkan kategori dan tahun. Anda akan menggunakan struktur data ini:

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_year": "comedy_1997"
},...

Dan kemudian mencari komedi tahun 90-an dengan:

ref
  .orderBy('genre_year')
  .startAt('comedy_1990')
  .endAt('comedy_2000')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

Jika Anda perlu memfilter lebih dari sekedar tahun, pastikan untuk menambahkan bagian tanggal lainnya dalam urutan menurun, misalnya "comedy_1997-12-25". Dengan cara ini urutan leksikografis yang dilakukan Firebase pada nilai string akan sama dengan urutan kronologisnya.

Menggabungkan nilai-nilai dalam properti dapat bekerja dengan lebih dari dua nilai, tetapi Anda hanya bisa melakukan filter rentang pada nilai terakhir di properti komposit.

Varian yang sangat istimewa ini diterapkan oleh perpustakaan GeoFire untuk Firebase . Perpustakaan ini menggabungkan garis lintang dan garis bujur dari suatu lokasi ke dalam apa yang disebut Geohash , yang kemudian dapat digunakan untuk melakukan kueri rentang waktu nyata di Firebase.

3. buat indeks kustom secara terprogram

Namun alternatif lain adalah melakukan apa yang kita semua lakukan sebelum API Kueri baru ini ditambahkan: buat indeks di simpul yang berbeda:

  "movies"
      // the same structure you have today
  "by_genre"
      "comedy"
          "by_lead"
              "Jack Nicholson"
                  "movie1"
              "Jim Carrey"
                  "movie3"
      "Horror"
          "by_lead"
              "Jack Nicholson"
                  "movie2"

Mungkin ada lebih banyak pendekatan. Misalnya, jawaban ini menyoroti indeks kustom berbentuk pohon alternatif: https://stackoverflow.com/a/34105063

Frank van Puffelen
sumber
3
Ketika ini secara resmi dirilis minggu depan, beberapa orderBy()panggilan akan menimbulkan kesalahan, karena klien saat ini memberikan hasil yang tidak terduga. Mungkin saja tes ini bekerja secara kebetulan, tetapi tidak dibuat untuk bekerja secara umum (meskipun kami ingin menambahkannya!).
Rob DiMarco
18
Apakah ini masih relevan pada 2016 dengan Firebase V3? Apakah tidak ada cara yang lebih baik untuk melakukan ini?
Dermaga
27
Kenapa kita tidak bisa menggunakan .equalTo('comedy')bukan .startAt('comedy').endAt('comedy')?
JCarlosR
41
Ini tahun 2018, apakah tidak ada solusi sederhana untuk ini? Ini seperti kueri 101 dalam basis data relasional. Dan mengingat semua komentar di video David berbicara tentang SQL # 8, saya sudah mengharapkan Google untuk memperbaikinya sekarang. Apakah saya ketinggalan pembaruan?
Snake
10
@ Buat Februari 2019 dan masalahnya masih belum diperbaiki .... Google terlalu lambat.
Supertecnoboff
48

Saya telah menulis perpustakaan pribadi yang memungkinkan Anda memesan dengan beberapa nilai, dengan semua pemesanan dilakukan di server.

Temui Querybase!

Querybase menggunakan Referensi Firebase Database dan berbagai bidang yang ingin Anda indeks. Saat Anda membuat catatan baru, itu akan secara otomatis menangani pembuatan kunci yang memungkinkan untuk beberapa permintaan. Peringatannya adalah ia hanya mendukung kesetaraan lurus (tidak kurang dari atau lebih besar dari).

const databaseRef = firebase.database().ref().child('people');
const querybaseRef = querybase.ref(databaseRef, ['name', 'age', 'location']);

// Automatically handles composite keys
querybaseRef.push({ 
  name: 'David',
  age: 27,
  location: 'SF'
});

// Find records by multiple fields
// returns a Firebase Database ref
const queriedDbRef = querybaseRef
 .where({
   name: 'David',
   age: 27
 });

// Listen for realtime updates
queriedDbRef.on('value', snap => console.log(snap));
David East
sumber
Adakah definisi TypeScript yang tersedia untuk perpustakaan Anda?
Levi Fuller
1
Itu ditulis dalam TS! Jadi impor saja :)
David East
7
apakah Anda tahu mengapa mereka membuat database sangat ketat?
Ced
1
Opsi apa pun untuk melakukan versi iOS Swift / Android
mding5692
1
David, ada kesetaraan android / Java untuk itu?
Snake
3
var ref = new Firebase('https://your.firebaseio.com/');

Query query = ref.orderByChild('genre').equalTo('comedy');
query.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot movieSnapshot : dataSnapshot.getChildren()) {
            Movie movie = dataSnapshot.getValue(Movie.class);
            if (movie.getLead().equals('Jack Nicholson')) {
                console.log(movieSnapshot.getKey());
            }
        }
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {

    }
});
Andrew Lam
sumber
3
Tidak ada metode getLead () pada objek DataSnapshot.
Parag Kadam
3
Dia mengonversinya menjadi objek Film dan dengan asumsi ada getLead () metho.
Kim G Pham
1
Terima kasih banyak. Ini bekerja seperti lebih banyak klausa mana
Nuwan Withanage
1

Jawaban Frank bagus tetapi Firestore array-containsbaru-baru ini memperkenalkannya yang membuatnya lebih mudah dilakukan DAN pertanyaan.

Anda dapat membuat filtersbidang untuk menambahkan Anda filter. Anda dapat menambahkan nilai sebanyak yang Anda butuhkan. Misalnya untuk memfilter menurut komedi dan Jack Nicholson Anda dapat menambahkan nilai comedy_Jack Nicholsontetapi jika Anda juga ingin komedi dan 2014 Anda dapat menambahkan nilai comedy_2014tanpa membuat lebih banyak bidang.

{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson",
        "year": 2014,
        "filters": [
          "comedy_Jack Nicholson",
          "comedy_2014"
        ]
    }
  }  
}
Penjara Tidder
sumber
1

Firebase tidak mengizinkan kueri dengan berbagai kondisi. Namun, saya memang menemukan jalan keluar untuk ini:

Kita perlu mengunduh data yang difilter awal dari database dan menyimpannya dalam daftar array.

                Query query = databaseReference.orderByChild("genre").equalTo("comedy");
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                        ArrayList<Movie> movies = new ArrayList<>();
                        for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
                            String lead = dataSnapshot1.child("lead").getValue(String.class);
                            String genre = dataSnapshot1.child("genre").getValue(String.class);

                            movie = new Movie(lead, genre);

                            movies.add(movie);

                        }

                        filterResults(movies, "Jack Nicholson");

                        }

                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError databaseError) {

                    }
                });

Setelah kami mendapatkan data yang difilter awal dari database, kami perlu melakukan filter lebih lanjut di backend kami.

public void filterResults(final List<Movie> list,  final String genre) {
        List<Movie> movies = new ArrayList<>();
        movies = list.stream().filter(o -> o.getLead().equals(genre)).collect(Collectors.toList());
        System.out.println(movies);

        employees.forEach(movie -> System.out.println(movie.getFirstName()));
    }
mayur
sumber
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Ini akan bekerja

jaleel
sumber