Présentation d'une requête avec 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 au sein d'une même requête. Vous pouvez désormais appliquer des conditions de plage et d'inégalité à plusieurs champs, et simplifier le développement de votre application en déléguant la mise en œuvre de la logique de post-filtrage à Firestore.

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

La requête suivante renvoie tous les utilisateurs dont l'âge est supérieur à 35 ans et dont la taille est comprise entre 60 et 70 ans, en utilisant des filtres de plage d'âge et de taille.

Version Web 9 modulaire

  const q = query(
      collection(db, "users"),
      where('age', '>', 35),
      where('height', '>', 60),
      where('height', '<', 70)
    );

Swift

 let query = db.collection("users")
   .whereField("age", isGreaterThan: 35)
   .whereField("height", isGreaterThan: 60)
   .whereField("height", isLessThan: 70)

Objective-C

 FIRQuery *query =
  [[[[self.db collectionWithPath:@"users"]
 queryWhereField:@"age" isGreaterThan:@35]
    queryWhereField:@"height" isGreaterThan:@60]
        queryWhereField:@"height" isLessThan:@70];

Java pour Android

 Query query = db.collection("users")
  .whereGreaterThan("age", 35)
  .whereGreaterThan("height", 60)
  .whereLessThan("height", 70);

Kotlin+KTX Android

 val query = db.collection("users")
  .whereGreaterThan("age", 35)
  .whereGreaterThan("height", 60)
  .whereLessThan("height", 70)

Java

  db.collection("users")
    .whereGreaterThan("age", 35)
    .whereGreaterThan("height", 60)
    .whereLessThan("height", 70);

Node.js

db.collection("users")
  .where('age', '>', 35),
  .where('height', '>', 60),
  .where('height', '<', 70)

Remarques sur l'indexation

Avant de commencer à exécuter vos requêtes, assurez-vous d'avoir lu 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 pouvant être utilisés pour la diffuser. Par exemple, une requête ORDER BY a ASC, b ASC nécessite un index composite au niveau des champs a ASC, b ASC.

Pour optimiser les performances et les coûts des requêtes Firestore, vous devez optimiser l'ordre des champs dans l'index. Pour ce faire, assurez-vous que votre index est trié de gauche à droite de sorte que la requête soit distillée vers un ensemble de données, ce qui empêche l'analyse des entrées d'index superflues.

Supposons que vous souhaitiez effectuer une recherche dans une collection d'employés et trouver les employés dont le salaire est supérieur à 100 000 et dont le nombre d'années d'expérience est supérieur à 0. Grâce à votre compréhension de l'ensemble de données, vous savez que la contrainte salariale est plus sélective que la contrainte d'expérience. L'index idéal qui permettrait de réduire le nombre d'analyses d'index est l'index (salary [...], experience [...]). Ainsi, la requête rapide et économique commande salary avant experience et se présente 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.

Classer les champs d'index par égalité, suivis du champ de plage ou d'inégalité le plus sélectif

Firestore utilise les champs les plus à gauche d'un index composite pour respecter 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 satisfaire 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 sans correspondance afin de réduire le nombre de documents renvoyés aux clients.

Pour en savoir plus sur la création d'index efficaces, consultez la définition d'un index parfait.

Classer les champs par ordre décroissant en fonction de la 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é de la contrainte de requête. Une sélectivité élevée correspond à un sous-ensemble plus petit de documents, tandis qu'une sélectivité plus faible correspond à un sous-ensemble plus vaste de documents. Veillez à sélectionner plus tôt dans l'ordre d'index les champs de plage ou d'inégalité avec une sélectivité plus élevée que ceux dont la sélectivité est plus faible.

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

Par exemple, supposons que vous souhaitiez effectuer une recherche dans une collection d'employés pour trouver les employés dont le salaire est supérieur à 100 000 et classer les résultats par année d'expérience de l'employé. Si vous pensez que seul un petit nombre d'employés aura 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 classement sur experience à la requête génère 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 de champs d'index correspond à la clause "order by" de la requête. Si des experience ont été ajoutés à la clause Trier par, Firestore sélectionne 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 lit 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 satisfont pas au filtre salary sont toujours lues, ce qui augmente la latence et le coût de la requête.

Tarification

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

Pour en savoir plus, consultez la page Tarifs.

Limites

Outre les limites applicables aux requêtes, tenez compte des 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 des champs de document et uniquement des contraintes d'égalité sur la clé de document (__name__) ne sont pas acceptées.
  • Firestore limite à 10 le nombre de champs de plage ou d'inégalité. Cela permet d'éviter que les requêtes deviennent trop coûteuses à exécuter.

Étape suivante