ข้อมูลเบื้องต้น
การดำเนินการไปป์ไลน์เป็นอินเทอร์เฟซการค้นหาใหม่สำหรับ Cloud Firestore อินเทอร์เฟซนี้มีฟังก์ชันการค้นหาขั้นสูง ซึ่งรวมถึงนิพจน์ที่ซับซ้อน รุ่น Firestore Enterprise รองรับ การรวมสไตล์เชิงสัมพันธ์ผ่านคำสั่งย่อยที่สัมพันธ์กัน Subquery ช่วยให้คุณรวมและรวบรวมข้อมูลจากคอลเล็กชันหรือคอลเล็กชันย่อยที่เกี่ยวข้องได้โดยตรงบนเซิร์ฟเวอร์ ซึ่งแตกต่างจากฐานข้อมูล NoSQL หลายๆ ฐานข้อมูลที่มักจะต้องยกเลิกการทำให้เป็นมาตรฐานของข้อมูลหรือส่งคำขอฝั่งไคลเอ็นต์หลายรายการ
การค้นหาย่อยคือนิพจน์ที่เรียกใช้ไปป์ไลน์ที่ซ้อนกันสำหรับทุกเอกสาร ที่ประมวลผลโดยการค้นหาภายนอก ซึ่งช่วยให้ใช้รูปแบบการดึงข้อมูลที่ซับซ้อนได้ เช่น การดึงข้อมูลเอกสารพร้อมกับรายการในคอลเล็กชันย่อยที่เกี่ยวข้อง หรือ การรวมข้อมูลที่ลิงก์กันอย่างมีตรรกะในคอลเล็กชันรูทที่แตกต่างกัน
แนวคิด
ส่วนนี้จะแนะนําแนวคิดหลักเบื้องหลังการใช้คําสั่งย่อยเพื่อทําการรวมในการดําเนินการไปป์ไลน์
Subquery เป็นนิพจน์
คิวรีย่อยไม่ใช่ขั้นตอนระดับบนสุด แต่เป็นนิพจน์ที่
ใช้ได้ในทุกขั้นตอนที่ยอมรับนิพจน์ เช่น
select(...)
add_fields(...)
where(...) หรือ sort(...)
Cloud Firestore รองรับคิวรีย่อย 3 ประเภท ได้แก่
- การสืบค้นย่อยของอาร์เรย์: สร้างชุดผลลัพธ์ทั้งหมดของการสืบค้นย่อยเป็นอาร์เรย์ของเอกสาร
- การสืบค้นย่อยแบบสเกลาร์: ประเมินเป็นค่าเดียว เช่น จำนวน ค่าเฉลี่ย หรือฟิลด์ที่เฉพาะเจาะจงจากเอกสารที่เกี่ยวข้อง
subcollection(...)คำสั่งย่อย: การรวมที่ง่ายขึ้นสำหรับความสัมพันธ์แบบหนึ่งต่อหลาย ระหว่างรายการหลักกับรายการย่อย
ขอบเขตและตัวแปร
เมื่อเขียนการรวม ข้อมูลย่อยแบบซ้อนมักต้องอ้างอิงฟิลด์
จากเอกสาร "ด้านนอก" (ระดับบน) หากต้องการเชื่อมโยงขอบเขตเหล่านี้ คุณต้องใช้ขั้นตอน let(...) (เรียกว่า define(...) ใน SDK บางรายการ) เพื่อกำหนดตัวแปรในขอบเขตระดับบน ซึ่งจะอ้างอิงได้ในคําค้นหาย่อยโดยใช้ฟังก์ชัน variable(...)
ไวยากรณ์
ส่วนต่อไปนี้จะแสดงภาพรวมของไวยากรณ์สำหรับการรวม
let(...) ขั้นตอน
ขั้นตอนlet(...) (เรียกว่า define(...) ใน SDK บางรายการ) เป็นขั้นตอนที่ไม่ใช่การกรองซึ่งนำข้อมูลจากขอบเขตระดับบนมาไว้ในตัวแปรที่มีชื่ออย่างชัดเจนเพื่อใช้ในขอบเขตที่ซ้อนกันในภายหลัง
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); }
การสืบค้นย่อยของอาร์เรย์
การค้นหาย่อยของอาร์เรย์เป็นกรณีพิเศษของการค้นหาย่อยของนิพจน์ที่ทำให้ชุดผลลัพธ์ทั้งหมดของการค้นหาย่อยกลายเป็นอาร์เรย์ หากคิวรีย่อยแสดงผลแถวเป็น 0 แถว ระบบจะประเมินเป็นอาร์เรย์ว่าง ฟังก์ชันนี้จะไม่แสดงผลอาร์เรย์ null การค้นหาดังกล่าวมีประโยชน์เมื่อต้องการผลลัพธ์ทั้งหมดในผลลัพธ์สุดท้าย เช่น เมื่อสร้างคอลเล็กชันที่ซ้อนกันหรือมีความสัมพันธ์กัน
การค้นหาสามารถกรอง จัดเรียง และรวบรวมในการค้นหาย่อยเพื่อลดปริมาณ
ข้อมูลที่ต้องดึงและส่งคืนเพื่อช่วยลดค่าใช้จ่ายของการค้นหา
ระบบจะพิจารณาลำดับของคำสั่งย่อย ซึ่งหมายความว่าsort(...)สเตจ
ในคำสั่งย่อยจะควบคุมลำดับของผลลัพธ์ในอาร์เรย์สุดท้าย
ใช้ Wrapper ของ toArrayExpression() SDK เพื่อแปลงการค้นหาเป็นอาร์เรย์
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"
});
}
การตอบกลับ
{
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" }); }
การตอบกลับ
{ 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) }
การตอบกลับ
{ 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); }
การตอบกลับ
{ id: "project_1", name: "Alpha Build", taskTitles: [ "System Architecture", "Database Schema Design" ] }
การสืบค้นย่อยแบบสเกลาร์
มักใช้ Subquery แบบสเกลาร์ในขั้นตอน select(...) หรือ
where(...) เพื่ออนุญาตการกรองหรือส่งผลให้
ผลลัพธ์ของ Subquery โดยไม่ต้องสร้างคำค้นหาแบบเต็มโดยตรง
การค้นหาย่อยแบบสเกลาร์ที่ให้ผลลัพธ์เป็น 0 จะประเมินเป็น null เอง
ในขณะที่การค้นหาย่อยที่ประเมินเป็นองค์ประกอบหลายรายการจะทำให้เกิดข้อผิดพลาดรันไทม์
เมื่อคิวรีย่อยแบบสเกลาร์สร้างฟิลด์เดียวต่อผลลัพธ์ ระบบจะยกระดับฟิลด์ดังกล่าวให้เป็นผลลัพธ์ระดับบนสุดสำหรับคิวรีย่อย โดยมักจะเห็นได้เมื่อคำสั่งย่อยลงท้ายด้วย select(field("user_name")) หรือ aggregate(countAll().as("total")) ซึ่งสคีมาของคำสั่งย่อยเป็นเพียงฟิลด์เดียว ไม่เช่นนั้น เมื่อการสืบค้นย่อยสร้างฟิลด์ได้หลายรายการ ระบบจะ
ห่อฟิลด์เหล่านั้นไว้ในแมป
ใช้ Wrapper ของ toScalarExpression() SDK เพื่อแปลงการค้นหาเป็นนิพจน์สเกลาร์
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
});
}
การตอบกลับ
{
"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 ])
การตอบกลับ
{ "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) }
การตอบกลับ
{ "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); }
การตอบกลับ
{ "id": "author_202", "name": "Charles Dickens", "averageBookRating": 4.65 }
subcollection(...) คำสั่งย่อย
แม้จะเสนอเป็นขั้นตอน แต่ขั้นตอนอินพุต subcollection(...) ช่วยให้Cloud Firestore สามารถทำการรวมในโมเดลข้อมูลแบบลำดับชั้นได้ ใน
โมเดลแบบลำดับชั้น คำค้นมักจะต้องดึงข้อมูลเอกสารพร้อมกับข้อมูล
จากคอลเล็กชันย่อยของตัวเอง แม้ว่าคุณจะทำได้โดยใช้ขั้นตอนการป้อนข้อมูล
collection_group(...) ตามด้วยตัวกรองในการอ้างอิงระดับบน subcollection(...) ก็มีไวยากรณ์ที่กระชับกว่ามาก
นอกเหนือจากเงื่อนไขการรวมโดยนัยแล้ว การดำเนินการนี้จะคล้ายกับ คำสั่งย่อยของอาร์เรย์ โดยจะแสดงผลลัพธ์ที่ว่างเปล่าหากไม่มีเอกสารที่ตรงกัน แม้ว่าจะไม่มีคอลเล็กชันที่ซ้อนกันอยู่ก็ตาม
ซึ่งเป็นไวยากรณ์ที่กระชับโดยพื้นฐาน นั่นคือจะใช้__name__ของเอกสารในขอบเขตนอกเป็นคีย์สำหรับการรวมโดยอัตโนมัติเพื่อแก้ปัญหาความสัมพันธ์แบบลำดับชั้น จึงเป็นวิธีที่แนะนำในการค้นหาในคอลเล็กชันที่ลิงก์ในความสัมพันธ์แบบหลักกับย่อย
แนวทางปฏิบัติแนะนำ
- จัดการหน่วยความจำด้วย
toArrayExpression(): ระมัดระวังการใช้toArrayExpression()การค้นหาย่อย เนื่องจากทำให้เกิดการสร้างเอกสารจำนวนมาก ซึ่งอาจทำให้หน่วยความจำของการค้นหาเกินขีดจำกัด (128 MiB) หากต้องการลดปัญหานี้ ให้ใช้select(...)ภายในคําค้นหาย่อยเพื่อแสดงผลเฉพาะฟิลด์ที่จําเป็น และใช้ตัวกรองwhere(...)เพื่อจํากัดจํานวนเอกสารที่แสดงผล ลองใช้limit(...)หากเหมาะสมเพื่อจำกัดจำนวนเอกสารที่คำค้นหาย่อยแสดงผล - การจัดทำดัชนี: ตรวจสอบว่าฟิลด์ที่ใช้ในคําสั่ง
where(...)ของ คําสั่งย่อยได้รับการจัดทําดัชนีแล้ว การรวมที่มีประสิทธิภาพจะขึ้นอยู่กับความสามารถในการดำเนินการ การค้นหาดัชนีแทนการสแกนตารางทั้งหมด
ดูแนวทางปฏิบัติแนะนำเพิ่มเติมเกี่ยวกับคำค้นหาได้ใน คำแนะนำเกี่ยวกับการเพิ่มประสิทธิภาพคำค้นหา
ข้อจำกัด
subcollection(...)ขอบเขต:subcollection(...)ระบบรองรับขั้นตอนการป้อนข้อมูลเฉพาะภายในคำถามย่อย เนื่องจากต้องใช้บริบทของเอกสารหลักเพื่อแก้ไขความสัมพันธ์แบบลำดับชั้นและทำการรวม- ระดับการซ้อน: คุณซ้อนคำสั่งย่อยได้สูงสุด 20 ระดับ
- การใช้หน่วยความจำ: ขีดจำกัด 128 MiB สำหรับข้อมูลที่สร้างขึ้นจะมีผลกับทั้งคำค้นหา รวมถึงเอกสารทั้งหมดที่เข้าร่วม