複数のフィールドに対する範囲フィルタと不等式フィルタを使用したクエリの概要

Firestore では、1 つのクエリで複数のフィールドに対する範囲フィルタと不等式フィルタの使用をサポートします。複数のフィールドに対する範囲条件と不等式条件を含むことができるようになり、フィルタリング後のロジックの実装を Firestore に委任することで、アプリケーション開発を簡素化できるようになりました。

複数のフィールドに対する範囲フィルタと不等式フィルタ

次のクエリは、年齢と身長に対する範囲フィルタを使用して、年齢が 35 歳以上で、身長が 60 ~ 70 であるすべてのユーザーを返します。

Web version 9 のモジュラー

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

検討事項のインデックス登録

クエリの実行を開始する前に、必ずクエリと Firestore データモデルについて読んでおいてください。

Firestore では、クエリの ORDER BY 句により、クエリの処理に使用できるインデックスを決定します。たとえば、ORDER BY a ASC, b ASC クエリでは、a ASC, b ASC フィールドに対する複合インデックスが必要です。

Firestore クエリのパフォーマンスと費用を最適化するには、インデックス内のフィールドの順序を最適化する必要があります。これを行うには、不要なインデックス エントリのスキャンを防ぐデータセットにクエリが抽出するようにインデックスを左から右に並べる必要があります。

従業員のコレクションを検索し、給与が 100,000 を超え、経験年数が 0 を超える従業員を見つけるとします。データセットに関する知識に基づき、給与の制約は経験の制約よりも選択的であることがわかります。インデックス スキャンの数を減らす理想的なインデックスは、(salary [...], experience [...]) です。したがって、高速で費用対効果の高いクエリは、experience の前に salary を順序付けすると、次のようになります。

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");

インデックスの最適化のベストプラクティス

インデックスを最適化するときは、次のベスト プラクティスに注意してください。

等式ごとにインデックス フィールドを並べ、最も選択的な範囲または不等式フィールドが続く

Firestore は、複合インデックスの左端のフィールドを使用して、orderBy() クエリの最初のフィールドに対する等式の制約および範囲または不等式の制約(存在する場合)を満たします。これらの制約により、Firestore がスキャンするインデックス エントリの数を減らすことができます。Firestore は、インデックスの残りのフィールドを使用して、クエリの他の範囲または不等式の制約を満たします。これらの制約によって、Firestore がスキャンするインデックス エントリの数は減りませんが、一致しないドキュメントは除外されるため、クライアントに返されるドキュメント数は少なくなります。

効率的なインデックスの作成の詳細については、完全なインデックスの定義をご覧ください。

クエリ制約の選択性の降順でフィールドを並べ替える

Firestore がクエリに最適なインデックスを選択するようにするには、クエリ制約の選択性の降順でフィールドを並べ替える orderBy() 句を指定します。選択性が高いほど、一致するドキュメントのサブセットは小さくなりますが、選択性が低いほど、一致するドキュメントのサブセットは大きくなります。選択性が低いフィールドよりも、インデックスの順序の早い段階で高い選択性を持つ範囲または不等式フィールドを選択するようにしてください。

Firestore がネットワーク経由でスキャンして返すドキュメントの数を最小限に抑えるには、常にクエリ制約の選択性の降順でフィールドを並べ替える必要があります。結果セットが必要な順序ではなく、結果セットのサイズが小さいと予想される場合、クライアントサイドのロジックを実装して、順序の想定に沿って順序を並べ替えることができます。

たとえば、従業員のコレクションを検索して、給与が 100,000 を超える従業員を見つけて、結果を従業員の経験年数で並べ替えるとします。少数の従業員のみ給与が 100,000 を超えることが予想される場合は、クエリを作成する最も効率的な方法は次のとおりです。

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`

experience に対する順序付けをクエリに追加すると、同じドキュメントのセットが生成され、クライアントでの結果の順序の並び替えは不要ですが、クエリは前のクエリよりも多くの不要なインデックス エントリを読み取る場合があります。これは、Firestore が常に、インデックス フィールドの接頭辞がクエリの ORDER BY 句と一致するインデックスを優先するためです。experience が ORDER BY 句に追加された場合、Firestore はクエリ結果を計算するために (experience [...], salary [...]) インデックスを選択します。experience には他の制約がないため、Firestore は、salary フィルタを適用する前に employees コレクションのすべてのインデックス エントリを読み取り、最終結果セットを見つけます。つまり、salary フィルタを満たさないインデックス エントリがまだ読み取られるため、クエリのレイテンシとコストが増加します。

料金

複数のフィールドに対して範囲フィルタと不等式フィルタを使用したクエリは、ドキュメントの読み取り数とインデックス エントリの読み取り数に基づいて課金されます。

詳細については、料金ページをご覧ください。

制限事項

クエリの制限とは別に、複数のフィールドに対する範囲フィルタと不等式フィルタでクエリを使用する前に、次の制限事項に注意してください。

  • ドキュメント フィールドに対する範囲フィルタまたは不等式フィルタがあり、ドキュメント キー (__name__) に対する等式制約のみがあるクエリは、サポートされていません。
  • Firestore では、範囲フィールドまたは不等式フィールドの数が 10 に制限されています。これは、クエリの実行に要するコストが高くならないようにするためです。

次のステップ