バックグラウンド
パイプライン クエリは、Firestore の新しいクエリ インターフェースです。これは複雑な式を含む高度なクエリ機能を提供します。また、min, max, substring, regex_match や array_contains_all など、多数の新しい関数のサポートも追加されています。パイプライン クエリでは、インデックスの作成も完全に任意であるため、新しいクエリの作成プロセスが効率化されます。また、パイプライン クエリではクエリの形状に関する多くの制限がなくなっており、大きな in クエリや or クエリでも指定できます。
スタートガイド
クライアント SDK をインストールして初期化するには、スタートガイドの手順をご覧ください。
構文
以降のセクションでは、パイプライン クエリの構文の概要を説明します。
コンセプト
パイプライン クエリでの顕著な違いは、明示的な「ステージ」の順序付けが導入されたことです。これにより、より複雑なクエリを表現できるようになります。ただし、これはステージの順序が暗黙的に決まっていた既存のクエリ インターフェースとは大きく異なります。次のパイプライン クエリの例を考えてみましょう。
Web
const pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the // pipeline would have unintentional results. .limit(10);
Swift
let pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(Field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort([Field("name").ascending()]) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10)
Kotlin
val pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10)
Java
Pipeline pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10);
Python
from google.cloud.firestore_v1.pipeline_expressions import Field pipeline = ( client.pipeline() .collection("cities") .where(Field.of("population").greater_than(100_000)) .sort(Field.of("name").ascending()) .limit(10) )
初期化
パイプライン クエリには、既存の Cloud Firestore クエリから派生した非常に使い慣れた構文があります。まず、次のコードを記述してクエリを初期化します。
Web
const { getFirestore } = require("firebase/firestore"); const { execute } = require("firebase/firestore/pipelines"); const database = getFirestore(app, "enterprise"); const pipeline = database.pipeline();
Swift
let firestore = Firestore.firestore(database: "enterprise") let pipeline = firestore.pipeline()
Kotlin
val firestore = Firebase.firestore("enterprise") val pipeline = firestore.pipeline()
Java
FirebaseFirestore firestore = FirebaseFirestore.getInstance("enterprise"); PipelineSource pipeline = firestore.pipeline();
Python
firestore_client = firestore.client(default_app, "your-new-enterprise-database") pipeline = firestore_client.pipeline()
構造
パイプライン クエリを作成する際に理解しておくべき重要な用語がいくつかあります。それはステージ、式、関数です。

ステージ: パイプラインは 1 つ以上のステージで構成されます。論理的には、これらはクエリの実行に必要な一連の手順(ステージ)を表します。注: 実際には、パフォーマンスを向上させるために、ステージが順序を変えて実行されることがあります。ただし、クエリの意図や正確性は変更されません。
式: ステージは多くの場合、より複雑なクエリを表現できる式を受け入れます。式は、eq("a", 1) のように単一の関数で構成された単純なものである場合があります。また、and(eq("a", 1), eq("b", 2)). のように式をネストして、より複雑な式を表現することもできます。
フィールド参照と定数参照
パイプライン クエリは複雑な式をサポートしています。そのため、値がフィールドを表すのか、定数を表すのかを区別する必要がある場合があります。たとえば次のようになります。
Web
const pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")));
Swift
let pipeline = db.pipeline() .collection("cities") .where(Field("name").equal(Constant("Toronto")))
Kotlin
val pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")))
Java
Pipeline pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")));
Python
from google.cloud.firestore_v1.pipeline_expressions import Field, Constant pipeline = ( client.pipeline() .collection("cities") .where(Field.of("name").equal(Constant.of("Toronto"))) )
ステージ
入力ステージ
入力ステージは、クエリの最初のステージを表します。これはクエリ対象のドキュメントの初期セットを定義します。パイプライン クエリの場合、これは既存のクエリとほぼ同じで、ほとんどのクエリは collection(...) ステージまたは collection_group(...) ステージで始まります。新しい入力ステージは database() と documents(...) の 2 つです。database() はデータベース内のすべてのドキュメントを返すことができます。documents(...) はバッチ読み取りと同じように動作します。
Web
let results; // Return all restaurants in San Francisco results = await execute(db.pipeline().collection("cities/sf/restaurants")); // Return all restaurants results = await execute(db.pipeline().collectionGroup("restaurants")); // Return all documents across all collections in the database (the entire database) results = await execute(db.pipeline().database()); // Batch read of 3 documents results = await execute(db.pipeline().documents([ doc(db, "cities", "SF"), doc(db, "cities", "DC"), doc(db, "cities", "NY") ]));
Swift
var results: Pipeline.Snapshot // Return all restaurants in San Francisco results = try await db.pipeline().collection("cities/sf/restaurants").execute() // Return all restaurants results = try await db.pipeline().collectionGroup("restaurants").execute() // Return all documents across all collections in the database (the entire database) results = try await db.pipeline().database().execute() // Batch read of 3 documents results = try await db.pipeline().documents([ db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ]).execute()
Kotlin
var results: Task<Pipeline.Snapshot> // Return all restaurants in San Francisco results = db.pipeline().collection("cities/sf/restaurants").execute() // Return all restaurants results = db.pipeline().collectionGroup("restaurants").execute() // Return all documents across all collections in the database (the entire database) results = db.pipeline().database().execute() // Batch read of 3 documents results = db.pipeline().documents( db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ).execute()
Java
Task<Pipeline.Snapshot> results; // Return all restaurants in San Francisco results = db.pipeline().collection("cities/sf/restaurants").execute(); // Return all restaurants results = db.pipeline().collectionGroup("restaurants").execute(); // Return all documents across all collections in the database (the entire database) results = db.pipeline().database().execute(); // Batch read of 3 documents results = db.pipeline().documents( db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ).execute();
Python
# Return all restaurants in San Francisco results = client.pipeline().collection("cities/sf/restaurants").execute() # Return all restaurants results = client.pipeline().collection_group("restaurants").execute() # Return all documents across all collections in the database (the entire database) results = client.pipeline().database().execute() # Batch read of 3 documents results = ( client.pipeline() .documents( client.collection("cities").document("SF"), client.collection("cities").document("DC"), client.collection("cities").document("NY"), ) .execute() )
他のすべてのステージと同様に、これらの入力ステージの結果の順序は安定していません。特定の順序が求められる場合は、必ず sort(...) 演算子を追加する必要があります。
Where
where(...) ステージは、前のステージで生成されたドキュメントに対する従来のフィルタ オペレーションとして機能します。これは既存のクエリに存在する「where」構文とほぼ同じです。指定された式が true 以外の値と評価されるドキュメントは、返されるドキュメントから除外されます。
複数の where(...) ステートメントを連結して、and(...) 式として機能させることができます。たとえば、次の 2 つのクエリは論理的に同等であり、同じ意味で使用できます。
Web
let results; results = await execute(db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) ); results = await execute(db.pipeline().collection("books") .where(and(field("rating").equal(5), field("published").lessThan(1900))) );
Swift
var results: Pipeline.Snapshot results = try await db.pipeline().collection("books") .where(Field("rating").equal(5)) .where(Field("published").lessThan(1900)) .execute() results = try await db.pipeline().collection("books") .where(Field("rating").equal(5) && Field("published").lessThan(1900)) .execute()
Kotlin
var results: Task<Pipeline.Snapshot> results = db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) .execute() results = db.pipeline().collection("books") .where(Expression.and(field("rating").equal(5), field("published").lessThan(1900))) .execute()
Java
Task<Pipeline.Snapshot> results; results = db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) .execute(); results = db.pipeline().collection("books") .where(Expression.and( field("rating").equal(5), field("published").lessThan(1900) )) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import And, Field results = ( client.pipeline() .collection("books") .where(Field.of("rating").equal(5)) .where(Field.of("published").less_than(1900)) .execute() ) results = ( client.pipeline() .collection("books") .where(And(Field.of("rating").equal(5), Field.of("published").less_than(1900))) .execute() )
Select / Add Fields / Remove Fields
select(...)、add_fields(...)、remove_fields(...) はすべて、これらを使用して前のステージから返されたフィールドを変更できます。これら 3 つは一般に、プロジェクション スタイルのステージと呼ばれます。
select(...) と add_fields(...) を使用して、式の結果をユーザー指定のフィールド名に指定できます。エラーが発生する式は、null 値になります。select(...) は指定されたフィールド名を持つドキュメントのみを返しますが、add_fields(...) は前のステージのスキーマを拡張します(同じフィールド名の値が上書きされる可能性があります)。
remove_fields(...) を使用して、前のステージから削除するフィールドのセットを指定できます。存在しないフィールド名を指定した場合は、何も行われません。
以下の返されるフィールドを制限するセクションをご覧ください。一般に、このようなステージを使用して結果をクライアントで必要なフィールドのみに制限すると、ほとんどのクエリで費用とレイテンシを削減できます。
Aggregate / Distinct
aggregate(...) ステージでは、入力ドキュメントに対して一連の集計を実行できます。デフォルトでは、すべてのドキュメントがまとめて集計されますが、オプションの grouping 引数を指定すると、入力ドキュメントを異なるバケットに集計できます。
Web
const results = await execute(db.pipeline() .collection("books") .aggregate( field("rating").average().as("avg_rating") ) .distinct(field("genre")) );
Swift
let results = try await db.pipeline() .collection("books") .aggregate([ Field("rating").average().as("avg_rating") ], groups: [ Field("genre") ]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .aggregate( AggregateStage .withAccumulators(AggregateFunction.average("rating").alias("avg_rating")) .withGroups(field("genre")) ) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .aggregate(AggregateStage .withAccumulators( AggregateFunction.average("rating").alias("avg_rating")) .withGroups(field("genre"))) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .aggregate( Field.of("rating").average().as_("avg_rating"), groups=[Field.of("genre")] ) .execute() )
groupings が指定されていない場合、このステージでは 1 つのドキュメントのみが生成されます。指定されている場合は、groupings 値の一意の組み合わせごとにドキュメントが生成されます。
distinct(...) ステージは簡略化された集計演算子で、アキュムレータなしで一意の groupings のみを生成できます。その他の点では、aggregate(...) の場合と同じように動作します。以下に例を示します。
Web
const results = await execute(db.pipeline() .collection("books") .distinct( field("author").toUpper().as("author"), field("genre") ) );
Swift
let results = try await db.pipeline() .collection("books") .distinct([ Field("author").toUpper().as("author"), Field("genre") ]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .distinct( field("author").toUpper().alias("author"), field("genre") ) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .distinct( field("author").toUpper().alias("author"), field("genre") ) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .distinct(Field.of("author").to_upper().as_("author"), "genre") .execute() )
関数
関数は、式や複雑なクエリを作成するための構成要素です。関数の完全なリストと例については、関数リファレンスをご覧ください。では、一般的なクエリの構造を簡単に確認しましょう。

多くのステージでは、1 つ以上の関数を含む式を使用できます。最も一般的な関数の使用は、where(...) ステージと select(...) ステージで見られます。次の 2 種類の主要な関数について理解しておく必要があります。
Web
let results; // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = await execute(db.pipeline().collection("books") .select(field("current").logicalMinimum(field("updated")).as("price_min")) ); // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = await execute(db.pipeline().collection("books") .aggregate(field("price").minimum().as("min_price")) );
Swift
var results: Pipeline.Snapshot // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = try await db.pipeline().collection("books") .select([ Field("current").logicalMinimum(["updated"]).as("price_min") ]) .execute() // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = try await db.pipeline().collection("books") .aggregate([Field("price").minimum().as("min_price")]) .execute()
Kotlin
var results: Task<Pipeline.Snapshot> // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = db.pipeline().collection("books") .select( field("current").logicalMinimum("updated").alias("price_min") ) .execute() // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = db.pipeline().collection("books") .aggregate(AggregateFunction.minimum("price").alias("min_price")) .execute()
Java
Task<Pipeline.Snapshot> results; // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = db.pipeline().collection("books") .select( field("current").logicalMinimum("updated").alias("price_min") ) .execute(); // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = db.pipeline().collection("books") .aggregate(AggregateFunction.minimum("price").alias("min_price")) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field # Type 1: Scalar (for use in non-aggregation stages) # Example: Return the min store price for each book. results = ( client.pipeline() .collection("books") .select( Field.of("current").logical_minimum(Field.of("updated")).as_("price_min") ) .execute() ) # Type 2: Aggregation (for use in aggregate stages) # Example: Return the min price of all books. results = ( client.pipeline() .collection("books") .aggregate(Field.of("price").minimum().as_("min_price")) .execute() )
上限
ほとんどの場合、Enterprise エディションではクエリの形状に制限は課されません。つまり、IN クエリや OR クエリで値が少数に制限されることはありません。代わりに、次の 2 つの主な上限に注意する必要があります。
- 期限: 60 秒(Standard エディションと同じ)。
- メモリ使用量: クエリ実行中に実体化されるデータの量の上限は 128 MiB です。
エラー
クエリの失敗にはさまざまな原因が考えられます。一般的なエラーとその対処方法については、こちらをご覧ください。
| エラーコード | 操作 |
DEADLINE_EXCEEDED
|
実行中のクエリが 60 秒の期限を超えているため、追加の最適化が必要です。ヒントについては、パフォーマンスのセクションをご覧ください。問題の根本原因を特定できない場合は、チームにお問い合わせください。 |
RESOURCE_EXHAUSTED
|
実行しているクエリがメモリ上限を超えているため、追加の最適化が必要です。ヒントについては、パフォーマンスのセクションをご覧ください。問題の根本原因を特定できない場合は、チームにお問い合わせください。 |
INTERNAL
|
サポートについては、チームにお問い合わせください。 |
パフォーマンス
既存のクエリとは異なり、パイプライン クエリではインデックスが常に存在する必要はありません。つまり、クエリのレイテンシが既存のクエリよりも長くなる可能性があります。既存のクエリでは、FAILED_PRECONDITION インデックス不足エラーですぐに失敗します。パイプライン クエリのパフォーマンスを改善するには、次の手順をお試しください。
インデックスを作成する
使用されるインデックス
Query Explain を使用すると、クエリがインデックスによって処理されているか、テーブル スキャンなどの効率の低いオペレーションにフォールバックしているかを特定できます。クエリがインデックスから完全に提供されていない場合は、手順に沿ってインデックスを作成できます。
インデックスの作成
既存のインデックス管理のドキュメントに沿って、インデックスを作成できます。インデックスを作成する前に、Firestore のインデックスに関する一般的なベスト プラクティスを理解しておいてください。クエリでインデックスを活用できるようにするには、ベスト プラクティスに沿って、次の順序でフィールドを含むインデックスを作成します。
- 等式フィルタで使用されるすべてのフィールド(順不同)
- 並べ替えに使用されるすべてのフィールド(同じ順序)。
- 不等式演算子の範囲で使用されるフィールド(クエリ制約の選択性の降順)。
たとえば、次のクエリの場合:
Web
const results = await execute(db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) );
Swift
let results = try await db.pipeline() .collection("books") .where(Field("published").lessThan(1900)) .where(Field("genre").equal("Science Fiction")) .where(Field("rating").greaterThan(4.3)) .sort([Field("published").descending()]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .where(Field.of("published").less_than(1900)) .where(Field.of("genre").equal("Science Fiction")) .where(Field.of("rating").greater_than(4.3)) .sort(Field.of("published").descending()) .execute() )
推奨インデックスは、(genre [...], published DESC, avg_rating DESC). の books に対するコレクション スコープのインデックスです。
インデックス密度
Cloud Firestore は、スパース インデックスと非スパース インデックスをサポートしています。詳細については、インデックス密度をご覧ください。
対象のクエリ + セカンダリ インデックス
返されるすべてのフィールドがセカンダリ インデックスに存在する場合、Firestore はドキュメント全体を取得せずに、インデックスから結果を返すことができます。通常、これによりレイテンシ(と費用)が大幅に改善されます。次のサンプルクエリを使用します。
Web
const results = await execute(db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) );
Swift
let results = try await db.pipeline() .collection("books") .where(Field("category").like("%fantasy%")) .where(Field("title").exists()) .where(Field("author").exists()) .select([Field("title"), Field("author")]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .where(Field.of("category").like("%fantasy%")) .where(Field.of("title").exists()) .where(Field.of("author").exists()) .select("title", "author") .execute() )
データベースに (category [...], title [...], author [...]) の books に対するコレクション スコープのインデックスがすでに存在する場合、メイン ドキュメント自体から何も取得する必要はありません。この場合、インデックスの順序は重要ではなく、[...] を使用してそのことを示されます。
返されるフィールドを制限する
デフォルトでは、Firestore クエリは、従来のシステムの SELECT * と同様に、ドキュメント内のすべてのフィールドを返します。ただし、アプリケーションに必要なフィールドがサブセットのみの場合は、select(...) ステージまたは restrict(...) ステージを使用して、このフィルタリングをサーバーサイドに push できます。これにより、レスポンス サイズが縮小される(ネットワーク下り(外向き)の費用が削減される)と同時に、レイテンシも改善されます。
トラブルシューティング ツール
Query Explain
Query Explain を使用すると、実行指標と使用されたインデックスの詳細を確認できます。
指標
既存の Firestore 指標と完全に統合されている場合は、パイプライン クエリ。
制限事項と既知の問題
特殊なインデックス
パイプライン クエリは、既存の array-contains と vector のインデックスのタイプをまだサポートしていません。Firestore は、このようなクエリを単に拒否するのではなく、他の既存の ascending と descending インデックスの使用を試みます。このため、限定公開プレビューの期間中、このような array_contains 式や find_nearest 式を含むパイプライン クエリは、既存の同等のクエリよりも遅くなることが予想されます。
ページ分け
限定公開プレビュー期間中は、結果セットのページ分けを簡単に行うためのサポートは提供されません。この問題を回避するには、以下に示すように、同等の where(...) ステージと sort(...) ステージを連結します。
Web
// Existing pagination via `startAt()` const q = query(collection(db, "cities"), orderBy("population"), startAt(1000000)); // Private preview workaround using pipelines const pageSize = 2; const pipeline = db.pipeline() .collection("cities") .select("name", "population", "__name__") .sort(field("population").descending(), field("__name__").ascending()); // Page 1 results let snapshot = await execute(pipeline.limit(pageSize)); // End of page marker const lastDoc = snapshot.results[snapshot.results.length - 1]; // Page 2 results snapshot = await execute( pipeline .where( or( and( field("population").equal(lastDoc.get("population")), field("__name__").greaterThan(lastDoc.ref) ), field("population").lessThan(lastDoc.get("population")) ) ) .limit(pageSize) );
Swift
// Existing pagination via `start(at:)` let query = db.collection("cities").order(by: "population").start(at: [1000000]) // Private preview workaround using pipelines let pipeline = db.pipeline() .collection("cities") .where(Field("population").greaterThanOrEqual(1000000)) .sort([Field("population").descending()])
Kotlin
// Existing pagination via `startAt()` val query = db.collection("cities").orderBy("population").startAt(1000000) // Private preview workaround using pipelines val pipeline = db.pipeline() .collection("cities") .where(field("population").greaterThanOrEqual(1000000)) .sort(field("population").descending())
Java
// Existing pagination via `startAt()` Query query = db.collection("cities").orderBy("population").startAt(1000000); // Private preview workaround using pipelines Pipeline pipeline = db.pipeline() .collection("cities") .where(field("population").greaterThanOrEqual(1000000)) .sort(field("population").descending());
Python
from google.cloud.firestore_v1.pipeline_expressions import Field # Existing pagination via `start_at()` query = ( client.collection("cities") .order_by("population") .start_at({"population": 1_000_000}) ) # Private preview workaround using pipelines pipeline = ( client.pipeline() .collection("cities") .where(Field.of("population").greater_than_or_equal(1_000_000)) .sort(Field.of("population").descending()) )
エミュレータのサポート
エミュレータは、パイプライン クエリをまだサポートしていません。
リアルタイムおよびオフライン サポート
パイプライン クエリには、リアルタイム機能とオフライン機能はまだありません。
次のステップ
- 関数とステージのリファレンス ドキュメントを見ていきます。