Consulta baseada em várias cláusulas where no 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"
    }
  }  
 }

Eu sou um novato no Firebase. Como posso recuperar um resultado dos dados acima, onde genre = 'comedy'ANDlead = 'Jack Nicholson' ?

Que opções eu tenho?

47d_
fonte

Respostas:

238

Usando a API de consulta do Firebase , você pode tentar:

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

Mas como @RobDiMarco da Firebase diz nos comentários:

várias orderBy()chamadas gerarão um erro

Portanto, meu código acima não funcionará .

Conheço três abordagens que funcionarão.

1. filtre mais no servidor, faça o resto no cliente

O que você pode fazer é executar um orderBy().startAt()./endAt()no servidor, retirar os dados restantes e filtrá- los no código JavaScript do seu cliente.

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

2. adicione uma propriedade que combine os valores que você deseja filtrar

Se isso não for bom o suficiente, considere modificar / expandir seus dados para permitir seu caso de uso. Por exemplo: você pode agrupar gênero + lead em uma única propriedade que você acabou de usar para esse filtro.

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

Você está basicamente criando seu próprio índice de várias colunas dessa maneira e pode consultá-lo com:

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

David East escreveu uma biblioteca chamada QueryBase que ajuda na geração de tais propriedades .

Você pode até fazer consultas relativas / intervalo, digamos que deseja permitir a consulta de filmes por categoria e ano. Você usaria essa estrutura de dados:

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

E, em seguida, consulte as comédias dos anos 90 com:

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

Se você precisar filtrar mais do que apenas o ano, adicione as outras partes da data em ordem decrescente, por exemplo "comedy_1997-12-25". Dessa forma, a ordem lexicográfica que o Firebase realiza nos valores das strings será a mesma que a ordem cronológica.

Essa combinação de valores em uma propriedade pode funcionar com mais de dois valores, mas você só pode fazer um filtro de intervalo no último valor na propriedade composta.

Uma variante muito especial disso é implementada pela biblioteca GeoFire para Firebase . Essa biblioteca combina a latitude e longitude de um local em um chamado Geohash , que pode ser usado para fazer consultas de intervalo em tempo real no Firebase.

3. crie um índice personalizado programaticamente

Outra alternativa é fazer o que já fizemos antes da adição desta nova API de consulta: criar um índice em um nó diferente:

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

Provavelmente existem mais abordagens. Por exemplo, esta resposta destaca um índice personalizado alternativo em forma de árvore: https://stackoverflow.com/a/34105063

Frank van Puffelen
fonte
3
Quando isso for lançado oficialmente na próxima semana, várias orderBy()chamadas gerarão um erro, porque os clientes atualmente fornecem resultados inesperados. É possível que funcionou coincidentemente em seu teste, mas não foi desenvolvido para funcionar de maneira genérica (embora adoraríamos adicioná-lo!).
Rob DiMarco
18
Isso ainda é relevante em 2016 com o Firebase V3? Não existem maneiras melhores de fazer isso?
Pier
27
Por que não podemos usar em .equalTo('comedy')vez de .startAt('comedy').endAt('comedy')?
perfil completo de JCarlosR
41
É 2018, não existe uma solução simples para isso? É como a consulta 101 no banco de dados relacional. E, considerando todos os comentários no vídeo de David falando sobre o SQL # 8, eu esperava que o Google o corrigisse agora. Perdi alguma atualização?
Snake
10
@Snake Feb 2019 e o problema ainda não foi resolvido ... O Google está muito lento.
Supertecnoboff
48

Eu escrevi uma biblioteca pessoal que permite que você faça pedidos por vários valores, com todos os pedidos feitos no servidor.

Conheça Querybase!

O Querybase utiliza uma Referência do banco de dados do Firebase e uma matriz de campos que você deseja indexar. Quando você cria novos registros, ele manipula automaticamente a geração de chaves que permitem várias consultas. A ressalva é que ele suporta apenas equivalência direta (não menor que ou maior que).

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
fonte
Alguma definição TypeScript disponível para sua biblioteca?
Levi Fuller
1
Está escrito em TS! Então, basta importar :)
David Médio
7
você sabe por que eles tornaram o banco de dados tão restritivo?
Ced
1
Qualquer opção para fazer uma versão do IOS Swift / Android
mding5692 12/12
1
David, alguma equivalência android / Java?
Cobra
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
fonte
3
Não há método getLead () no objeto DataSnapshot.
Parag Kadam
3
Ele o converteu no objeto Movie e assumindo que existe um método getLead ().
Kim G Pham
1
Muito obrigado. Ele está trabalhando como mais onde cláusulas
Nuwan Withanage
1

A resposta de Frank é boa, mas o Firestore foi introduzido array-containsrecentemente, o que facilita as consultas AND.

Você pode criar um filterscampo para adicionar seus filtros. Você pode adicionar quantos valores precisar. Por exemplo, para filtrar por comédia e Jack Nicholson, você pode adicionar o valor, comedy_Jack Nicholsonmas se você também quiser por comédia e 2014, pode adicionar o valor comedy_2014sem criar mais campos.

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

O Firebase não permite consultas com várias condições. No entanto, eu encontrei uma maneira de contornar isso:

Precisamos baixar os dados filtrados iniciais do banco de dados e armazená-los em uma lista de arrays.

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

                    }
                });

Depois de obtermos os dados filtrados iniciais do banco de dados, precisamos fazer mais filtros em nosso 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()));
    }
mayur
fonte
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Isso vai funcionar.

jaleel
fonte