Présentation des requêtes avec des filtres de plage et d'inégalité sur plusieurs champs

Firestore permet d'utiliser des filtres de plage et d'inégalité sur plusieurs champs dans une même requête. Vous pouvez définir des conditions de plage et d'inégalité sur plusieurs champs, et simplifier le développement de votre application en déléguant l'implémentation de la logique de post-filtrage à Firestore.

Filtres de plage et d'inégalité sur plusieurs champs

La requête suivante utilise des filtres de plage sur la population et la densité pour renvoyer toutes les villes dont la population est supérieure à 1 000 000 d'habitants et la densité de population inférieure à 10 000 habitants par unité de surface.

Version Web 9 modulaire

const q = query(
    collection(db, "cities"),
    where('population', '>', 1000000),
    where('density', '<', 10000),
  );

Swift

let query = db.collection("cities")
  .whereField("population", isGreaterThan: 1000000)
  .whereField("density", isLessThan: 10000)

Objective-C

FIRQuery *query =
 [[[[self.db collectionWithPath:@"cities"]
queryWhereField:@"population" isGreaterThan:@1000000]
   queryWhereField:@"density" isLessThan:@10000];

Java Android

Query query = db.collection("cities")
 .whereGreaterThan("population", 1000000)
 .whereLessThan("density", 10000);

Kotlin+KTX Android

val query = db.collection("cities")
 .whereGreaterThan("population", 1000000)
 .whereLessThan("density", 10000)

Go

   query := client.Collection("cities").
      Where("population", ">", 1000000).
      Where("density", "<", 10000)

Java

db.collection("cities")
  .whereGreaterThan("population", 1000000)
  .whereLessThan("density", 10000);

Node.js

db.collection("cities")
  .where('population', '>', 1000000),
  .where('density', '<', 10000)

Python

from google.cloud import firestore

db = firestore.Client()
query = db.collection("cities")
.where("population", ">", 1000000)
.where("density", "<", 10000)

PHP

$collection = $db->collection('samples/php/cities');
$chainedQuery = $collection
    ->where('population', '>', 1000000)
    ->where('density', '<', 10000);

C#

CollectionReference citiesRef = db.Collection("cities");
Query query = citiesRef
    .WhereGreaterThan("Population", 1000000)
    .WhereLessThan("Density", 10000);
QuerySnapshot querySnapshot = await query.GetSnapshotAsync();
foreach (DocumentSnapshot documentSnapshot in querySnapshot)
{
    var name = documentSnapshot.GetValue<string>("Name");
    var population = documentSnapshot.GetValue<int>("Population");
    var density = documentSnapshot.GetValue<int>("Density");
    Console.WriteLine($"City '{name}' returned by query. Population={population}; Density={density}");
}

Ruby

query = cities_ref.where("population", ">", "1000000")
                  .where("density", "<", 10000)

C++

CollectionReference cities_ref = db->Collection("cities");
Query query = cities_ref.WhereGreaterThan("population", FieldValue::Integer(1000000))
                       .WhereLessThan("density", FieldValue::Integer(10000));

Unity

CollectionReference citiesRef = db.Collection("cities");
Query query = citiesRef.WhereGreaterThan("population", 1000000)
                      .WhereLessThan("density", 10000);

Dart

final citiesRef = FirebaseFirestore.instance.collection('cities')
final query = citiesRef.where("population", isGreaterThan: 1000000)
                  .where("density", isLessThan: 10000);

Considérations relatives à l'indexation

Avant d'exécuter vos requêtes, lisez les informations concernant les requêtes et le modèle de données Firestore.

Dans Firestore, la clause ORDER BY d'une requête détermine les index qui peuvent être utilisés pour répondre à la requête. Par exemple, une requête ORDER BY a ASC, b ASC nécessite un index composite sur les champs a ASC, b ASC.

Pour optimiser les performances et le coût des requêtes Firestore, optimisez l'ordre des champs dans l'index. Pour ce faire, assurez-vous que votre index est ordonné de gauche à droite de sorte que la requête se résume à un ensemble de données qui empêche l'analyse des entrées d'index inutiles.

Supposons que vous souhaitiez effectuer une recherche dans une collection d'employés et trouver les employés aux États-Unis dont le salaire est supérieur à 100 000 $et dont le nombre d'années d'expérience est supérieur à 0. D'après votre compréhension de l'ensemble de données, vous savez que la contrainte de salaire est plus sélective que celle d'expérience. L'index idéal qui réduirait le nombre d'analyses d'index serait (salary [...], experience [...]). Par conséquent, la requête rapide et économique qui ordonnerait salary avant experience se présenterait comme suit :

Java

db.collection("employees")
  .whereGreaterThan("salary", 100000)
  .whereGreaterThan("experience", 0)
  .orderBy("salary")
  .orderBy("experience");

Node.js

db.collection("employees")
  .where("salary", ">", 100000)
  .where("experience", ">", 0)
  .orderBy("salary")
  .orderBy("experience");

Python

db.collection("employees")
  .where("salary", ">", 100000)
  .where("experience", ">", 0)
  .order_by("salary")
  .order_by("experience");

Bonnes pratiques pour optimiser les index

Lorsque vous optimisez des index, tenez compte des bonnes pratiques suivantes.

Trier les champs d'index par égalités, puis par champ d'intervalle ou d'inégalité le plus sélectif

Firestore utilise les champs les plus à gauche d'un index composite pour satisfaire les contraintes d'égalité et la contrainte de plage ou d'inégalité, le cas échéant, sur le premier champ de la requête orderBy(). Ces contraintes peuvent réduire le nombre d'entrées d'index analysées par Firestore. Firestore utilise les champs restants de l'index pour répondre aux autres contraintes de plage ou d'inégalité de la requête. Ces contraintes ne réduisent pas le nombre d'entrées d'index analysées par Firestore, mais filtrent les documents non correspondants afin de réduire le nombre de documents renvoyés aux clients.

Pour en savoir plus sur la création d'index efficaces, consultez Propriétés d'index.

Ordonner les champs par ordre décroissant de sélectivité des contraintes de requête

Pour vous assurer que Firestore sélectionne l'index optimal pour votre requête, spécifiez une clause orderBy() qui ordonne les champs par ordre décroissant de sélectivité des contraintes de requête. Une sélectivité élevée correspond à un sous-ensemble plus petit de documents, tandis qu'une sélectivité faible correspond à un sous-ensemble plus grand de documents. Assurez-vous de sélectionner les champs de plage ou d'inégalité avec une sélectivité plus élevée plus tôt dans l'ordre d'indexation que les champs avec une sélectivité plus faible.

Pour minimiser le nombre de documents que Firestore analyse et renvoie sur le réseau, vous devez toujours ordonner les champs par ordre décroissant de sélectivité des contraintes de requête. Si l'ensemble de résultats n'est pas dans l'ordre requis et qu'il est censé être petit, vous pouvez implémenter une logique côté client pour le réorganiser selon vos attentes.

Par exemple, supposons que vous souhaitiez rechercher dans une collection d'employés ceux qui travaillent aux États-Unis et dont le salaire est supérieur à 100 000 $,et que vous souhaitiez trier les résultats par année d'expérience des employés. Si vous pensez que seul un petit nombre d'employés auront un salaire supérieur à 100 000 $, le moyen le plus efficace d'écrire la requête est le suivant :

Java

db.collection("employees")
  .whereGreaterThan("salary", 100000)
  .orderBy("salary")
  .get()
  .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
          // Order results by `experience`
        }
    });;

Node.js

const querySnapshot = await db.collection('employees')
                              .where("salary", ">", 100000)
                              .orderBy("salary")
                              .get();

// Order results by `experience`

Python

results = db.collection("employees")
            .where("salary", ">", 100000)
            .order_by("salary")
            .stream()

// Order results by `experience`

Bien que l'ajout d'un ordre sur experience à la requête produise le même ensemble de documents et évite de réorganiser les résultats sur les clients, la requête peut lire beaucoup plus d'entrées d'index superflues que la requête précédente. En effet, Firestore préfère toujours un index dont le préfixe des champs d'index correspond à la clause "order by" de la requête. Si experience était ajouté à la clause "order by", Firestore sélectionnerait l'index (experience [...], salary [...]) pour calculer les résultats de la requête. Comme il n'y a pas d'autres contraintes sur experience, Firestore lira toutes les entrées d'index de la collection employees avant d'appliquer le filtre salary pour trouver l'ensemble de résultats final. Cela signifie que les entrées d'index qui ne répondent pas au filtre salary sont toujours lues, ce qui augmente la latence et le coût de la requête.

Tarifs

Les requêtes avec des filtres de plage et d'inégalité sur plusieurs champs sont facturées en fonction des documents et des entrées d'index lus.

Pour en savoir plus, consultez la page Tarifs.

Limites

Outre les limites de requêtes, veuillez noter les limites suivantes avant d'utiliser des requêtes avec des filtres de plage et d'inégalité sur plusieurs champs :

  • Les requêtes avec des filtres de plage ou d'inégalité sur les champs de document et uniquement des contraintes d'égalité sur la clé de document (__name__) ne sont pas acceptées.
  • Firestore limite le nombre de champs de plage ou d'inégalité à 10. Cela permet d'éviter que les requêtes ne deviennent trop coûteuses à exécuter.

Étapes suivantes