Requête basée sur plusieurs clauses where dans 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"
    }
  }  
 }

Je suis un débutant Firebase. Comment puis-je récupérer un résultat à partir des données ci-dessus genre = 'comedy'ETlead = 'Jack Nicholson' ?

Quelles options ai-je?

47d_
la source

Réponses:

239

En utilisant l'API de requête de Firebase , vous pourriez être tenté d'essayer ceci:

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

Mais comme le dit @RobDiMarco de Firebase dans les commentaires:

plusieurs orderBy()appels génèrent une erreur

Donc mon code ci-dessus ne fonctionnera pas .

Je connais trois approches qui fonctionneront.

1. filtrez le plus sur le serveur, faites le reste sur le client

Ce que vous pouvez faire est d'exécuter une orderBy().startAt()./endAt()sur le serveur, de dérouler les données restantes et de les filtrer en code JavaScript sur votre client.

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

2. ajoutez une propriété qui combine les valeurs sur lesquelles vous souhaitez filtrer

Si cela ne suffit pas, vous devriez envisager de modifier / étendre vos données pour permettre votre cas d'utilisation. Par exemple: vous pouvez bourrer genre + lead dans une seule propriété que vous utilisez uniquement pour ce filtre.

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

Vous créez essentiellement votre propre index multi-colonnes de cette façon et pouvez l'interroger avec:

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

David East a écrit une bibliothèque appelée QueryBase qui aide à générer de telles propriétés .

Vous pouvez même faire des requêtes relatives / plage, disons que vous souhaitez autoriser l'interrogation des films par catégorie et par année. Vous utiliseriez cette structure de données:

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

Et puis recherchez des comédies des années 90 avec:

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

Si vous devez filtrer sur plus que l'année, assurez-vous d'ajouter les autres parties de date dans l'ordre décroissant, par exemple "comedy_1997-12-25". De cette façon, l'ordre lexicographique que Firebase fait sur les valeurs de chaîne sera le même que l'ordre chronologique.

Cette combinaison de valeurs dans une propriété peut fonctionner avec plus de deux valeurs, mais vous ne pouvez effectuer un filtre de plage que sur la dernière valeur de la propriété composite.

Une variante très spéciale de ceci est implémentée par la bibliothèque GeoFire pour Firebase . Cette bibliothèque combine la latitude et la longitude d'un emplacement dans un soi-disant Geohash , qui peut ensuite être utilisé pour effectuer des requêtes de portée en temps réel sur Firebase.

3. créer un index personnalisé par programme

Une autre alternative consiste à faire ce que nous avons tous fait avant l'ajout de cette nouvelle API de requête: créer un index dans un nœud différent:

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

Il y a probablement plus d'approches. Par exemple, cette réponse met en évidence un autre index personnalisé en forme d'arbre: https://stackoverflow.com/a/34105063

Frank van Puffelen
la source
3
Lorsque cela sera officiellement publié la semaine prochaine, plusieurs orderBy()appels généreront une erreur, car les clients donnent actuellement des résultats inattendus. Il est possible que cela ait coïncidé dans votre test, mais n'est pas conçu pour fonctionner de manière générique (bien que nous aimerions l'ajouter!).
Rob DiMarco
18
Est-ce toujours d'actualité en 2016 avec Firebase V3? N'y a-t-il pas de meilleures façons de le faire?
Pier
27
Pourquoi nous ne pouvons pas utiliser à la .equalTo('comedy')place de .startAt('comedy').endAt('comedy')?
JCarlosR
41
Nous sommes en 2018, n'y a-t-il pas de solution simple à cela? C'est comme la requête 101 dans la base de données relationnelle. Et compte tenu de tous les commentaires sur la vidéo de David parlant de SQL # 8, je m'attendais à ce que Google le corrige maintenant. Ai-je raté une mise à jour?
Snake
10
@Snake Feb 2019 et le problème n'est toujours pas résolu .... Google est trop lent.
Supertecnoboff
48

J'ai écrit une bibliothèque personnelle qui vous permet de trier par plusieurs valeurs, avec toutes les commandes effectuées sur le serveur.

Rencontrez Querybase!

Querybase prend une référence de base de données Firebase et un tableau de champs que vous souhaitez indexer. Lorsque vous créez de nouveaux enregistrements, il gère automatiquement la génération de clés qui permettent plusieurs requêtes. La mise en garde est qu'elle ne prend en charge que l'équivalence droite (ni inférieure ni supérieure à).

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
la source
Des définitions TypeScript disponibles pour votre bibliothèque?
Levi Fuller
1
C'est écrit en TS! Il suffit donc d'importer :)
David East
7
savez-vous pourquoi ils ont rendu la base de données si restrictive?
Ced
1
Toute option pour faire une version IOS Swift / Android
mding5692
1
David, toute équivalence Android / 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
la source
3
Il n'y a pas de méthode getLead () sur l'objet DataSnapshot.
Parag Kadam
3
Il l'a converti en objet Movie et en supposant qu'il existe un métho getLead ().
Kim G Pham
1
Merci beaucoup. Cela fonctionne comme plus de clauses
where
1

La réponse de Frank est bonne, mais Firestore a array-containsrécemment introduit ce qui facilite les requêtes ET.

Vous pouvez créer un filterschamp pour ajouter vos filtres. Vous pouvez ajouter autant de valeurs que vous le souhaitez. Par exemple, pour filtrer par comédie et Jack Nicholson, vous pouvez ajouter la valeur, comedy_Jack Nicholsonmais si vous le souhaitez également par comédie et 2014, vous pouvez ajouter la valeur comedy_2014sans créer plus de champs.

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

Firebase n'autorise pas les requêtes avec plusieurs conditions. Cependant, j'ai trouvé un moyen de contourner cela:

Nous devons télécharger les données filtrées initiales de la base de données et les stocker dans une liste de tableaux.

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

                    }
                });

Une fois que nous avons obtenu les données filtrées initiales de la base de données, nous devons effectuer un filtrage supplémentaire dans notre 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
la source
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Cela fonctionnera.

jaleel
la source