Zapytanie oparte na wielu klauzulach where w 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"
    }
  }  
 }

Jestem początkującym Firebase. Jak mogę uzyskać wynik z powyższych danych, gdzie genre = 'comedy'ANDlead = 'Jack Nicholson' ?

Jakie mam opcje?

47d_
źródło

Odpowiedzi:

238

Korzystając z interfejsu API zapytań Firebase , możesz ulec pokusie wypróbowania:

// !!! 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()); 
  });

Ale jak mówi @RobDiMarco z Firebase w komentarzach:

wiele orderBy()połączeń spowoduje zgłoszenie błędu

Więc mój powyższy kod nie zadziała .

Znam trzy podejścia, które się sprawdzą.

1. Filtruj najwięcej na serwerze, resztę wykonaj na kliencie

Co można zrobić, to wykonać jeden orderBy().startAt()./endAt()na serwerze, należy pociągnąć w dół pozostałe dane i filtr, który w kodzie JavaScript na kliencie.

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

2. dodaj właściwość, która łączy wartości, według których chcesz filtrować

Jeśli to nie wystarczy, powinieneś rozważyć modyfikację / rozszerzenie danych, aby umożliwić przypadek użycia. Na przykład: możesz umieścić gatunek + ołów w jednej właściwości, której używasz tylko dla tego filtra.

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

Zasadniczo budujesz w ten sposób własny indeks wielokolumnowy i możesz wyszukiwać go za pomocą:

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

David East napisał bibliotekę o nazwie QueryBase, która pomaga w generowaniu takich właściwości .

Możesz nawet wykonywać zapytania dotyczące zakresu / zakresu, powiedzmy, że chcesz zezwolić na wyszukiwanie filmów według kategorii i roku. Użyłbyś tej struktury danych:

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

A potem zapytanie o komedie z lat 90. z:

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

Jeśli chcesz filtrować według więcej niż tylko roku, pamiętaj, aby dodać inne części daty w kolejności malejącej, np "comedy_1997-12-25". W ten sposób uporządkowanie leksykograficzne, które Firebase wykonuje dla wartości ciągu, będzie takie samo, jak uporządkowanie chronologiczne.

To połączenie wartości we właściwości może działać z więcej niż dwiema wartościami, ale można wykonać filtr zakresu tylko na ostatniej wartości we właściwości złożonej.

Bardzo szczególny wariant tego jest realizowany przez bibliotekę GeoFire dla Firebase . Ta biblioteka łączy szerokość i długość geograficzną lokalizacji w tak zwany Geohash , który można następnie wykorzystać do wykonywania zapytań o zasięg w czasie rzeczywistym na Firebase.

3. Utwórz programowo indeks niestandardowy

Jeszcze inną alternatywą jest zrobienie tego, co wszyscy zrobiliśmy przed dodaniem tego nowego interfejsu API zapytań: utwórz indeks w innym węźle:

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

Prawdopodobnie jest więcej podejść. Na przykład ta odpowiedź podkreśla alternatywny indeks niestandardowy w kształcie drzewa: https://stackoverflow.com/a/34105063

Frank van Puffelen
źródło
3
Gdy zostanie to oficjalnie wydane w przyszłym tygodniu, wiele orderBy()połączeń spowoduje zgłoszenie błędu, ponieważ klienci dają obecnie nieoczekiwane wyniki. Możliwe, że przypadkowo zadziałało to w teście, ale nie zostało zbudowane tak, aby działało ogólnie (choć chcielibyśmy to dodać!).
Rob DiMarco,
18
Czy w Firebase V3 jest to nadal aktualne w 2016 roku? Czy nie ma lepszych sposobów na zrobienie tego?
Pier
27
Dlaczego nie możemy użyć .equalTo('comedy')zamiast .startAt('comedy').endAt('comedy')?
JCarlosR
41
Jest 2018, czy nie ma na to prostego rozwiązania? To jest jak zapytanie 101 w relacyjnej bazie danych. Biorąc pod uwagę wszystkie komentarze do filmu Davida mówiące o SQL # 8, spodziewałbym się, że Google to naprawi. Czy przegapiłem aktualizację?
Snake
10
@Snake, luty 2019, a problem nadal nie został rozwiązany .... Google są zbyt wolne.
Supertecnoboff
48

Napisałem osobistą bibliotekę, która pozwala zamawiać według wielu wartości, z całą kolejnością na serwerze.

Poznaj Querybase!

Querybase pobiera odwołanie do bazy danych Firebase i tablicę pól, które chcesz indeksować. Po utworzeniu nowych rekordów automatycznie obsłuży generowanie kluczy, które umożliwiają wielokrotne zapytania. Zastrzeżenie polega na tym, że obsługuje tylko prostą równoważność (nie mniejszą niż lub większą niż).

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
źródło
Jakieś definicje TypeScript dostępne dla Twojej biblioteki?
Levi Fuller,
1
Jest napisane w TS! Więc po prostu zaimportuj :)
David East
7
Czy wiesz, dlaczego wprowadzono tak restrykcyjną bazę danych?
Ced
1
Każda opcja wykonania wersji IOS Swift / Android
mding5692,
1
David, jakaś odpowiednik Androida / Java?
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
źródło
3
Na obiekcie DataSnapshot nie ma metody getLead ().
Parag Kadam
3
Przekształcił go w obiekt Movie i zakładając, że istnieje metho getLead ().
Kim G Pham,
1
Dziękuję bardzo. Działa to tak, jakby więcej klauzul gdzie
Nuwan Withanage
1

Odpowiedź Franka jest dobra, ale array-containsostatnio wprowadzono Firestore, który ułatwia wykonywanie zapytań ORAZ.

Możesz utworzyć filterspole, aby dodać filtry. Możesz dodać tyle wartości, ile potrzebujesz. Na przykład, aby filtrować według komedii i Jacka Nicholsona , możesz dodać wartość, comedy_Jack Nicholsonale jeśli chcesz także według komedii i 2014 , możesz dodać wartość comedy_2014bez tworzenia większej liczby pól.

{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson",
        "year": 2014,
        "filters": [
          "comedy_Jack Nicholson",
          "comedy_2014"
        ]
    }
  }  
}
Więzienie Tiddera
źródło
1

Firebase nie zezwala na zapytania z wieloma warunkami. Znalazłem jednak sposób na to:

Musimy pobrać początkowe przefiltrowane dane z bazy danych i przechowywać je na liście tablic.

                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) {

                    }
                });

Po uzyskaniu początkowych przefiltrowanych danych z bazy danych musimy wykonać dalsze filtrowanie w naszym backendie.

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
źródło
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

To zadziała.

jaleel
źródło