Abfrage basierend auf mehreren where-Klauseln in 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"
    }
  }  
 }

Ich bin ein Firebase-Neuling. Wie kann ich ein Ergebnis aus den obigen Daten abrufen, wo genre = 'comedy'UNDlead = 'Jack Nicholson' ?

Welche Möglichkeiten habe ich?

47d_
quelle

Antworten:

238

Wenn Sie die Abfrage-API von Firebase verwenden , könnten Sie versucht sein, Folgendes zu versuchen:

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

Aber wie @RobDiMarco von Firebase in den Kommentaren sagt:

Bei mehreren orderBy()Anrufen wird ein Fehler ausgegeben

Also meine obigen Code wird nicht funktionieren .

Ich kenne drei Ansätze, die funktionieren werden.

1. Filtern Sie am meisten auf dem Server, erledigen Sie den Rest auf dem Client

Sie können eine orderBy().startAt()./endAt()auf dem Server ausführen , die verbleibenden Daten abrufen und diese in JavaScript-Code auf Ihrem Client filtern.

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

2. Fügen Sie eine Eigenschaft hinzu, die die Werte kombiniert, nach denen Sie filtern möchten

Wenn dies nicht gut genug ist, sollten Sie in Betracht ziehen, Ihre Daten zu ändern / zu erweitern, um Ihren Anwendungsfall zu ermöglichen. Zum Beispiel: Sie könnten Genre + Blei in eine einzelne Eigenschaft einfügen, die Sie nur für diesen Filter verwenden.

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

Auf diese Weise erstellen Sie im Wesentlichen Ihren eigenen mehrspaltigen Index und können ihn abfragen mit:

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

David East hat eine Bibliothek namens QueryBase geschrieben, die beim Generieren solcher Eigenschaften hilft .

Sie können sogar Relativ- / Bereichsabfragen durchführen. Angenommen, Sie möchten das Abfragen von Filmen nach Kategorie und Jahr zulassen. Sie würden diese Datenstruktur verwenden:

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

Und dann nach Komödien der 90er abfragen mit:

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

Wenn Sie mehr als nur das Jahr filtern müssen, stellen Sie sicher, dass Sie die anderen Datumsteile in absteigender Reihenfolge hinzufügen, z "comedy_1997-12-25". Auf diese Weise entspricht die lexikografische Reihenfolge, die Firebase für Zeichenfolgenwerte ausführt, der chronologischen Reihenfolge.

Diese Kombination von Werten in einer Eigenschaft kann mit mehr als zwei Werten arbeiten, Sie können jedoch nur einen Bereichsfilter für den letzten Wert in der zusammengesetzten Eigenschaft ausführen.

Eine ganz besondere Variante davon implementiert die GeoFire-Bibliothek für Firebase . Diese Bibliothek kombiniert den Breiten- und Längengrad eines Ortes zu einem sogenannten Geohash , mit dem dann Echtzeit-Bereichsabfragen in Firebase durchgeführt werden können.

3. Erstellen Sie programmgesteuert einen benutzerdefinierten Index

Eine weitere Alternative besteht darin, das zu tun, was wir alle getan haben, bevor diese neue Abfrage-API hinzugefügt wurde: Erstellen Sie einen Index in einem anderen Knoten:

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

Es gibt wahrscheinlich mehr Ansätze. In dieser Antwort wird beispielsweise ein alternativer baumförmiger benutzerdefinierter Index hervorgehoben: https://stackoverflow.com/a/34105063

Frank van Puffelen
quelle
3
Wenn dies nächste Woche offiziell veröffentlicht wird, wird bei mehreren orderBy()Anrufen ein Fehler ausgegeben, da die Clients derzeit unerwartete Ergebnisse liefern. Es ist möglich, dass es in Ihrem Test zufällig funktioniert hat, aber nicht dafür ausgelegt ist, dies generisch zu funktionieren (obwohl wir es gerne hinzufügen würden!).
Rob DiMarco
18
Ist dies 2016 mit Firebase V3 noch relevant? Gibt es dafür keine besseren Möglichkeiten?
Pier
27
Warum können wir nicht .equalTo('comedy')statt verwenden .startAt('comedy').endAt('comedy')?
JCarlosR
41
Es ist 2018, gibt es dafür keine einfache Lösung? Dies ist wie die Abfrage 101 in einer relationalen Datenbank. Und angesichts all der Kommentare zu Davids Video über SQL # 8 hätte ich erwartet, dass Google es jetzt behebt. Habe ich ein Update verpasst?
Schlange
10
@Snake Feb 2019 und das Problem ist immer noch nicht behoben .... Google sind zu langsam.
Supertecnoboff
48

Ich habe eine persönliche Bibliothek geschrieben, in der Sie nach mehreren Werten bestellen können, wobei die gesamte Bestellung auf dem Server erfolgt.

Lernen Sie Querybase kennen!

Querybase enthält eine Firebase-Datenbankreferenz und ein Array von Feldern, auf die Sie indizieren möchten. Wenn Sie neue Datensätze erstellen, werden automatisch Schlüssel generiert, die mehrere Abfragen ermöglichen. Die Einschränkung ist, dass es nur eine gerade Äquivalenz unterstützt (nicht kleiner oder größer als).

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
quelle
Gibt es TypeScript-Definitionen für Ihre Bibliothek?
Levi Fuller
1
Es ist in TS geschrieben! Also einfach importieren :)
David East
7
Wissen Sie, warum sie die Datenbank so restriktiv gemacht haben?
Ced
1
Jede Option, um eine IOS Swift / Android-Version zu machen
mding5692
1
David, gibt es eine Android / Java-Entsprechung dazu?
Schlange
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
quelle
3
Es gibt keine getLead () -Methode für das DataSnapshot-Objekt.
Parag Kadam
3
Er konvertierte es in ein Movie-Objekt und nahm an, dass es eine getLead () -Metho gibt.
Kim G Pham
1
Vielen Dank. Es funktioniert wie mehr wo Klauseln
Nuwan Withanage
1

Franks Antwort ist gut, aber Firestore wurde array-containskürzlich eingeführt , was es einfacher macht, UND-Abfragen durchzuführen.

Sie können ein filtersFeld erstellen , um Ihre Filter hinzuzufügen. Sie können beliebig viele Werte hinzufügen. Wenn Sie beispielsweise nach Comedy und Jack Nicholson filtern comedy_Jack Nicholsonmöchten, können Sie den Wert hinzufügen. Wenn Sie jedoch auch nach Comedy und 2014 filtern möchten, können Sie den Wert hinzufügen, comedy_2014ohne weitere Felder zu erstellen.

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

Firebase erlaubt keine Abfrage unter mehreren Bedingungen. Ich habe jedoch einen Weg gefunden, dies zu umgehen:

Wir müssen die anfänglich gefilterten Daten aus der Datenbank herunterladen und in einer Array-Liste speichern.

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

                    }
                });

Sobald wir die anfänglich gefilterten Daten aus der Datenbank erhalten haben, müssen wir in unserem Backend weitere Filter durchführen.

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
quelle
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Das wird funktionieren.

Jaleel
quelle