Consulta basada en múltiples cláusulas where en 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"
    }
  }  
 }

Soy un novato de Firebase. ¿Cómo puedo recuperar un resultado de los datos anteriores donde genre = 'comedy'ANDlead = 'Jack Nicholson' ?

¿Que opciones tengo?

47d_
fuente

Respuestas:

238

Usando la API de consulta de Firebase , puede tener la tentación de probar esto:

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

Pero como dice @RobDiMarco de Firebase en los comentarios:

múltiples orderBy()llamadas arrojarán un error

Entonces mi código anterior no funcionará .

Sé de tres enfoques que funcionarán.

1. filtrar más en el servidor, hacer el resto en el cliente

Lo que puede hacer es ejecutar uno orderBy().startAt()./endAt()en el servidor, extraer los datos restantes y filtrarlos en el código JavaScript de su cliente.

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

2. agregue una propiedad que combine los valores que desea filtrar

Si eso no es lo suficientemente bueno, debería considerar modificar / expandir sus datos para permitir su caso de uso. Por ejemplo: podría incluir género + plomo en una sola propiedad que solo usa para este filtro.

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

Básicamente, está creando su propio índice de múltiples columnas de esa manera y puede consultarlo con:

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

David East ha escrito una biblioteca llamada QueryBase que ayuda a generar tales propiedades .

Incluso podría hacer consultas relativas / de rango, supongamos que desea permitir consultas de películas por categoría y año. Usaría esta estructura de datos:

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

Y luego consulta las comedias de los 90 con:

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

Si necesita filtrar más allá del año, asegúrese de agregar las otras partes de fecha en orden descendente, por ejemplo "comedy_1997-12-25". De esta manera, el orden lexicográfico que Firebase hace en los valores de cadena será el mismo que el orden cronológico.

Esta combinación de valores en una propiedad puede funcionar con más de dos valores, pero solo puede hacer un filtro de rango en el último valor de la propiedad compuesta.

Una variante muy especial de esto es implementada por la biblioteca GeoFire para Firebase . Esta biblioteca combina la latitud y la longitud de una ubicación en un llamado Geohash , que luego se puede usar para realizar consultas de rango en tiempo real en Firebase.

3. crear un índice personalizado mediante programación

Otra alternativa es hacer lo que todos hemos hecho antes de agregar esta nueva API de consulta: crear un índice en un nodo 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"

Probablemente hay más enfoques. Por ejemplo, esta respuesta destaca un índice personalizado alternativo en forma de árbol: https://stackoverflow.com/a/34105063

Frank van Puffelen
fuente
3
Cuando esto orderBy()se lance oficialmente la próxima semana, múltiples llamadas arrojarán un error, porque los clientes actualmente dan resultados inesperados. Es posible que por casualidad funcionó en su prueba, pero no está diseñado para funcionar de manera genérica (¡aunque nos encantaría agregarlo!).
Rob DiMarco
18
¿Sigue siendo relevante en 2016 con Firebase V3? ¿No hay mejores maneras de hacer esto?
Pier
27
¿Por qué no podemos usar en .equalTo('comedy')lugar de .startAt('comedy').endAt('comedy')?
JCarlosR
41
Es 2018, ¿no hay una solución simple para esto? Esto es como la consulta 101 en la base de datos relacional. Y dados todos los comentarios en el video de David que habla sobre SQL # 8, hubiera esperado que Google lo arregle ahora. ¿Me perdí alguna actualización?
Serpiente
10
@Snake Feb 2019 y el problema aún no está solucionado ... Google es demasiado lento.
Supertecnoboff
48

He escrito una biblioteca personal que le permite ordenar por múltiples valores, con todos los pedidos realizados en el servidor.

¡Conoce Querybase!

Querybase incluye una referencia de base de datos de Firebase y una matriz de campos en los que desea indexar. Cuando cree nuevos registros, manejará automáticamente la generación de claves que permiten múltiples consultas. La advertencia es que solo admite equivalencia directa (no menor o mayor 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
fuente
¿Alguna definición de TypeScript disponible para su biblioteca?
Levi Fuller
1
Está escrito en TS! Así que solo importa :)
David East
77
¿Sabes por qué hicieron la base de datos tan restrictiva?
Ced
1
Cualquier opción para hacer una versión IOS Swift / Android
mding5692
1
David, ¿alguna equivalencia de Android / Java?
Serpiente
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
fuente
3
No hay un método getLead () en el objeto DataSnapshot.
Parag Kadam
3
Lo convirtió en un objeto Movie y suponiendo que haya un método getLead ().
Kim G Pham
1
Muchas gracias. Está funcionando como más cláusulas where
Nuwan Withanage
1

La respuesta de Frank es buena, pero Firestore presentó array-containsrecientemente eso hace que sea más fácil hacer Y consultas.

Puede crear un filterscampo para agregarle filtros. Puede agregar tantos valores como necesite. Por ejemplo, para filtrar por comedia y Jack Nicholson , puede agregar el valor, comedy_Jack Nicholsonpero si también lo desea por comedia y 2014 , puede agregar el valor comedy_2014sin crear más campos.

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

Firebase no permite realizar consultas con múltiples condiciones. Sin embargo, encontré una forma de evitar esto:

Necesitamos descargar los datos filtrados iniciales de la base de datos y almacenarlos en una lista de matriz.

                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 vez que obtengamos los datos filtrados iniciales de la base de datos, necesitamos hacer más filtros en nuestro backend.

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

Esto funcionará

jaleel
fuente