Latar belakang
Operasi Pipeline adalah antarmuka kueri baru untuk Cloud Firestore. Antarmuka ini menyediakan fungsi kueri tingkat lanjut yang mencakup ekspresi kompleks. Edisi Firestore Enterprise mendukung gabungan gaya relasional melalui subkueri berkorelasi. Tidak seperti banyak database NoSQL yang sering kali memerlukan denormalisasi data atau melakukan beberapa permintaan sisi klien, subkueri memungkinkan Anda menggabungkan dan mengagregasi data dari koleksi atau subkoleksi terkait langsung di server.
Subkueri adalah ekspresi yang menjalankan pipeline bertingkat untuk setiap dokumen yang diproses oleh kueri luar. Hal ini memungkinkan pola pengambilan data yang kompleks, seperti mengambil dokumen bersama dengan item subkoleksi terkait atau menggabungkan data yang terhubung secara logis di seluruh koleksi root yang berbeda.
Konsep
Bagian ini memperkenalkan konsep inti di balik penggunaan subkueri untuk melakukan gabungan dalam operasi Pipeline.
Subkueri sebagai ekspresi
Subkueri bukan tahap tingkat atas; melainkan ekspresi yang
dapat digunakan di tahap mana pun yang menerima ekspresi, seperti
select(...),
add_fields(...),
where(...), atau sort(...).
Cloud Firestore mendukung tiga jenis subkueri:
- Subkueri Array: Mewujudkan seluruh kumpulan hasil subkueri sebagai array dokumen.
- Subkueri Skalar: Mengevaluasi ke satu nilai, seperti jumlah, rata-rata, atau kolom tertentu dari dokumen terkait.
- Subkueri
subcollection(...): gabungan yang disederhanakan untuk hubungan induk-turunan satu-ke-banyak.
Cakupan dan variabel
Saat menulis gabungan, subkueri bertingkat sering kali perlu mereferensikan kolom dari dokumen "luar" (induk). Untuk menjembatani cakupan ini, Anda menggunakan tahap
let(...) (disebut sebagai define(...) di beberapa
SDK) untuk menentukan variabel dalam cakupan induk yang kemudian dapat direferensikan dalam
subkueri menggunakan fungsi variable(...).
Sintaksis
Bagian berikut memberikan ringkasan sintaksis untuk melakukan gabungan.
Tahap let(...)
Tahap let(...) (disebut sebagai define(...) di beberapa
SDK) adalah tahap non-pemfilteran yang secara eksplisit mengambil data dari cakupan induk
ke dalam variabel bernama untuk digunakan dalam cakupan bertingkat berikutnya.
Web
async function defineStageData() {
await setDoc(doc(collection(db, "Authors"), "author_123"), {
"id": "author_123",
"name": "Jane Austen"
});
}
Swift
func defineStageData() async throws { try await db.collection("authors").document("author_123").setData([ "id": "author_123", "name": "Jane Austen" ]) }
Kotlin
fun defineStageData() { val author = hashMapOf( "id" to "author_123", "name" to "Jane Austen", ) db.collection("Authors").document("author_123").set(author) }
Java
public void defineStageData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_123"); author.put("name", "Jane Austen"); db.collection("Authors").document("author_123").set(author); }
Subkueri Array
Subkueri Array adalah kasus khusus subkueri ekspresi yang mewujudkan seluruh kumpulan hasil subkueri ke dalam array. Jika subkueri menampilkan nol baris, subkueri akan dievaluasi ke array kosong. Subkueri tidak pernah menampilkan array null. Kueri tersebut berguna jika hasil lengkap diperlukan dalam hasil akhir, seperti saat mewujudkan koleksi bertingkat atau berkorelasi.
Kueri dapat memfilter, mengurutkan, &mengagregasi dalam subkueri untuk juga mengurangi jumlah data yang perlu diambil dan ditampilkan guna membantu mengurangi biaya kueri. Urutan subkueri dipertahankan, yang berarti tahap sort(...) dalam subkueri mengontrol urutan hasil dalam array akhir.
Gunakan wrapper SDK toArrayExpression() untuk mengonversi kueri menjadi array.
Web
async function toArrayExpressionStageData() {
await setDoc(doc(collection(db, "Projects"), "project_1"), {
"id": "project_1",
"name": "Alpha Build"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "System Architecture"
});
await addDoc(collection(db, "Tasks"), {
"project_id": "project_1",
"title": "Database Schema Design"
});
}
Respons
{
id: "project_1",
name: "Alpha Build",
taskTitles: [
"System Architecture", "Database Schema Design"
]
}
Swift
async function toArrayExpressionStageData() { await setDoc(doc(collection(db, "Projects"), "project_1"), { "id": "project_1", "name": "Alpha Build" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "System Architecture" }); await addDoc(collection(db, "Tasks"), { "project_id": "project_1", "title": "Database Schema Design" }); }
Respons
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Kotlin
fun toArrayExpressionData() { val project = hashMapOf( "id" to "project_1", "name" to "Alpha Build", ) db.collection("Projects").document("project_1").set(project) val task1 = hashMapOf( "project_id" to "project_1", "title" to "System Architecture", ) db.collection("Tasks").add(task1) val task2 = hashMapOf( "project_id" to "project_1", "title" to "Database Schema Design", ) db.collection("Tasks").add(task2) }
Respons
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Java
public void toArrayExpressionData() { Map<String, Object> project = new HashMap<>(); project.put("id", "project_1"); project.put("name", "Alpha Build"); db.collection("Projects").document("project_1").set(project); Map<String, Object> task1 = new HashMap<>(); task1.put("project_id", "project_1"); task1.put("title", "System Architecture"); db.collection("Tasks").add(task1); Map<String, Object> task2 = new HashMap<>(); task2.put("project_id", "project_1"); task2.put("title", "Database Schema Design"); db.collection("Tasks").add(task2); }
Respons
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
Subkueri Skalar
Subkueri skalar sering digunakan dalam tahap select(...) atau
where(...) sebagai pemfilteran yang diizinkan atau menghasilkan
hasil subkueri tanpa mewujudkan kueri lengkap secara langsung.
Subkueri skalar yang menghasilkan nol hasil akan dievaluasi ke null itu sendiri, sedangkan subkueri yang dievaluasi ke beberapa elemen akan menghasilkan error runtime.
Jika subkueri skalar hanya menghasilkan satu kolom per hasil, kolom tersebut akan ditingkatkan menjadi hasil tingkat atas untuk subkueri. Hal ini paling sering
terlihat saat subkueri berakhir dengan select(field("user_name")) atau
aggregate(countAll().as("total")) yang skema subkuerinya hanya
satu kolom. Jika tidak, saat subkueri dapat menghasilkan beberapa kolom, kolom tersebut akan digabungkan dalam peta.
Gunakan wrapper SDK toScalarExpression() untuk mengonversi kueri menjadi ekspresi skalar.
Web
async function toScalarExpressionStageData() {
await setDoc(doc(collection(db, "Authors"), "author_202"), {
"id": "author_202",
"name": "Charles Dickens"
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Great Expectations",
"rating": 4.8
});
await addDoc(collection(db, "Books"), {
"author_id": "author_202",
"title": "Oliver Twist",
"rating": 4.5
});
}
Respons
{
"id": "author_202",
"name": "Charles Dickens",
"averageBookRating": 4.65
}
Swift
try await db.collection("authors").document("author_202").setData([ "id": "author_202", "name": "Charles Dickens" ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Great Expectations", "rating": 4.8 ]) try await db.collection("books").document().setData([ "author_id": "author_202", "title": "Oliver Twist", "rating": 4.5 ])
Respons
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Kotlin
fun toScalarExpressionData() { val author = hashMapOf( "id" to "author_202", "name" to "Charles Dickens", ) db.collection("Authors").document("author_202").set(author) val book1 = hashMapOf( "author_id" to "author_202", "title" to "Great Expectations", "rating" to 4.8, ) db.collection("Books").add(book1) val book2 = hashMapOf( "author_id" to "author_202", "title" to "Oliver Twist", "rating" to 4.5, ) db.collection("Books").add(book2) }
Respons
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Java
public void toScalarExpressionData() { Map<String, Object> author = new HashMap<>(); author.put("id", "author_202"); author.put("name", "Charles Dickens"); db.collection("Authors").document("author_202").set(author); Map<String, Object> book1 = new HashMap<>(); book1.put("author_id", "author_202"); book1.put("title", "Great Expectations"); book1.put("rating", 4.8); db.collection("Books").add(book1); Map<String, Object> book2 = new HashMap<>(); book2.put("author_id", "author_202"); book2.put("title", "Oliver Twist"); book2.put("rating", 4.5); db.collection("Books").add(book2); }
Respons
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
Subkueri subcollection(...)
Meskipun ditawarkan sebagai tahap, tahap input
subcollection(...) memungkinkan
melakukan gabungan pada model data hierarkis Cloud Firestore. Dalam model hierarkis, kueri sering kali perlu mengambil dokumen bersama dengan data dari subkoleksinya sendiri. Meskipun Anda dapat mencapainya menggunakan tahap input
collection_group(...) yang diikuti
dengan filter pada referensi induk, subcollection(...) memberikan sintaksis yang jauh lebih
ringkas.
Selain kondisi gabungan implisit, subkueri ini bertindak serupa dengan subkueri array, yang menampilkan hasil kosong jika tidak ada dokumen yang cocok, meskipun koleksi bertingkat tidak ada.
Pada dasarnya, subkueri ini adalah gula sintaksis: subkueri ini secara otomatis menggunakan __name__ dari
dokumen dalam cakupan luar sebagai kunci gabungan untuk menyelesaikan hubungan hierarkis. Hal ini menjadikannya cara yang lebih disukai untuk melakukan pencarian di seluruh koleksi yang ditautkan dalam hubungan induk-turunan.
Praktik terbaik
- Mengelola memori dengan
toArrayExpression(): Berhati-hatilah dengantoArrayExpression()subkueri, karena mewujudkan sejumlah besar dokumen dapat menghabiskan batas memori kueri (128 MiB). Untuk mengatasinya, gunakanselect(...)dalam subkueri untuk hanya menampilkan kolom yang diperlukan dan menerapkanwhere(...)filter untuk membatasi jumlah dokumen yang ditampilkan. Pertimbangkan untuk menggunakanlimit(...)jika sesuai untuk membatasi jumlah dokumen yang ditampilkan oleh subkueri. - Pengindeksan: Pastikan kolom yang digunakan dalam klausa
where(...)dari subkueri diindeks. Gabungan berperforma tinggi bergantung pada kemampuan untuk melakukan pencarian indeks, bukan pemindaian tabel penuh.
Untuk mengetahui praktik terbaik kueri lainnya, lihat panduan kami yang membahas pengoptimalan kueri.
Batasan
subcollection(...)cakupan: Tahap inputsubcollection(...)hanya didukung dalam subkueri, karena memerlukan konteks dokumen induk untuk menyelesaikan hubungan hierarkis dan melakukan gabungan.- Kedalaman Bertingkat: Subkueri dapat dibuat bertingkat hingga kedalaman 20 lapisan.
- Penggunaan Memori: Batas 128 MiB pada data yang diwujudkan berlaku di seluruh kueri, termasuk semua dokumen yang digabungkan.