ทำการรวมด้วยการสืบค้นย่อย

ข้อมูลเบื้องต้น

การดำเนินการไปป์ไลน์เป็นอินเทอร์เฟซการค้นหาใหม่สำหรับ 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 สำหรับข้อมูลที่สร้างขึ้นจะมีผลกับทั้งคำค้นหา รวมถึงเอกสารทั้งหมดที่เข้าร่วม