Query basata su più clausole where 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"
    }
  }  
 }

Sono un novizio di Firebase. Come posso recuperare un risultato dai dati sopra dove genre = 'comedy'ANDlead = 'Jack Nicholson' ?

Quali opzioni ho?

Risposte:


238

Utilizzando l'API Query di Firebase , potresti essere tentato di provare questo:

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

Ma come dice @RobDiMarco di Firebase nei commenti:

più orderBy()chiamate genereranno un errore

Quindi il mio codice sopra non funzionerà .

Conosco tre approcci che funzioneranno.

1. filtrare la maggior parte sul server, fare il resto sul client

Quello che puoi fare è eseguirne uno orderBy().startAt()./endAt()sul server, estrarre i dati rimanenti e filtrarli nel codice JavaScript sul tuo client.

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

2. aggiungere una proprietà che combina i valori su cui si desidera filtrare

Se ciò non è abbastanza buono, dovresti considerare di modificare / espandere i tuoi dati per consentire il tuo caso d'uso. Ad esempio: puoi inserire genere + lead in una singola proprietà che usi solo per questo filtro.

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

In sostanza stai costruendo il tuo indice multi-colonna in quel modo e puoi interrogarlo con:

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

David East ha scritto una libreria chiamata QueryBase che aiuta a generare tali proprietà .

Potresti anche fare query relative / intervallo, supponiamo che tu voglia consentire l'interrogazione di film per categoria e anno. Utilizzeresti questa struttura di dati:

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

E poi cerca commedie degli anni '90 con:

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

Se è necessario filtrare più di un solo anno, assicurarsi di aggiungere le altre parti della data in ordine decrescente, ad es "comedy_1997-12-25". In questo modo l'ordinamento lessicografico che Firebase esegue sui valori di stringa sarà lo stesso dell'ordinamento cronologico.

Questa combinazione di valori in una proprietà può funzionare con più di due valori, ma è possibile eseguire solo un filtro intervallo sull'ultimo valore nella proprietà composita.

Una variante molto speciale di questo è implementata dalla libreria GeoFire per Firebase . Questa libreria combina la latitudine e la longitudine di un luogo in un cosiddetto Geohash , che può quindi essere utilizzato per eseguire query di portata in tempo reale su Firebase.

3. creare un indice personalizzato a livello di codice

Un'altra alternativa è fare ciò che abbiamo fatto tutti prima dell'aggiunta di questa nuova API Query: creare un indice in un nodo diverso:

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

Probabilmente ci sono più approcci. Ad esempio, questa risposta evidenzia un indice personalizzato alternativo a forma di albero: https://stackoverflow.com/a/34105063


3
Quando questo verrà rilasciato ufficialmente la prossima settimana, più orderBy()chiamate genereranno un errore, poiché i client attualmente forniscono risultati imprevisti. È possibile che abbia funzionato casualmente nel tuo test, ma non è stato progettato per funzionare in modo generico (anche se ci piacerebbe aggiungerlo!).
Rob DiMarco,

18
È ancora rilevante nel 2016 con Firebase V3? Non ci sono modi migliori per farlo?
Pier

27
Perché non possiamo usare .equalTo('comedy')invece di .startAt('comedy').endAt('comedy')?
JCarlosR

41
È il 2018, non esiste una soluzione semplice per questo? È come la query 101 nel database relazionale. E dato tutti i commenti sul video di David che parla di SQL n. 8, mi sarei aspettato che Google lo risolvesse ormai. Ho perso qualche aggiornamento?
Snake,

10
@Snake Febbraio 2019 e il problema non è stato ancora risolto .... Google è troppo lento.
Supertecnoboff,

48

Ho scritto una libreria personale che ti consente di ordinare in base a più valori, con tutti gli ordini effettuati sul server.

Scopri Querybase!

Querybase contiene un riferimento al database Firebase e una matrice di campi su cui si desidera indicizzare. Quando crei nuovi record, gestirà automaticamente la generazione di chiavi che consentono query multiple. L'avvertenza è che supporta solo l'equivalenza diretta (non inferiore o maggiore di).

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

Sono disponibili definizioni TypeScript per la tua libreria?
Levi Fuller,

1
È scritto in TS! Quindi basta importare :)
David East,

7
sai perché hanno reso il database così restrittivo?
Ced

1
Qualsiasi opzione per eseguire una versione IOS Swift / Android
mding5692,

1
David, qualche equivalenza Android / Java ad esso?
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) {

    }
});

3
Non esiste alcun metodo getLead () sull'oggetto DataSnapshot.
Parag Kadam,

3
Lo ha convertito in oggetto Movie e supponendo che esista un metho getLead ().
Kim G Pham,

1
Grazie mille. Funziona come altro dove le clausole
Nuwan Withanage,

1

La risposta di Frank è buona, ma Firestore ha introdotto di array-containsrecente ciò che semplifica l'esecuzione e le query.

È possibile creare un filterscampo per aggiungere filtri. Puoi aggiungere tutti i valori di cui hai bisogno. Ad esempio per filtrare per commedia e Jack Nicholson è possibile aggiungere il valore, comedy_Jack Nicholsonma se lo si desidera anche per commedia e 2014 è possibile aggiungere il valore comedy_2014senza creare più campi.

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

1

Firebase non consente di eseguire query con più condizioni. Tuttavia, ho trovato un modo per aggirare questo:

Dobbiamo scaricare i dati filtrati iniziali dal database e archiviarli in un elenco di 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) {

                    }
                });

Una volta ottenuti i dati filtrati iniziali dal database, è necessario eseguire ulteriori filtri nel nostro back-end.

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

-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Questo funzionerà.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.