सबक्वेरी के साथ JOIN ऑपरेशन करना

बैकग्राउंड

पाइपलाइन ऑपरेशन, Cloud Firestoreके लिए एक नया क्वेरी इंटरफ़ेस है. इस इंटरफ़ेस में, क्वेरी की बेहतर सुविधा मिलती है. इसमें जटिल एक्सप्रेशन शामिल होते हैं. Firestore Enterprise प्लान में, कोरिलेटेड सबक्वेरी की मदद से, रिलेशनल-स्टाइल वाले जॉइन की सुविधा मिलती है. कई NoSQL डेटाबेस के उलट, जिनमें अक्सर डेटा को डीनॉर्मलाइज़ करने या क्लाइंट-साइड से कई अनुरोध करने की ज़रूरत होती है, सबक्वेरी की मदद से, सर्वर पर सीधे तौर पर, मिलते-जुलते कलेक्शन या सबकलेक्शन से डेटा को जोड़ा और इकट्ठा किया जा सकता है.

सबक्वेरी, ऐसे एक्सप्रेशन होते हैं जो बाहरी क्वेरी से प्रोसेस किए गए हर दस्तावेज़ के लिए, नेस्ट की गई पाइपलाइन को एक्ज़ीक्यूट करते हैं. इससे, डेटा को वापस पाने के जटिल पैटर्न बनाए जा सकते हैं. जैसे, किसी दस्तावेज़ के साथ उससे जुड़े सबकलेक्शन के आइटम फ़ेच करना या अलग-अलग रूट कलेक्शन में लॉजिक के हिसाब से लिंक किए गए डेटा को जोड़ना.

कॉन्सेप्ट

इस सेक्शन में, पाइपलाइन ऑपरेशन में जॉइन करने के लिए, सबक्वेरी का इस्तेमाल करने से जुड़े मुख्य कॉन्सेप्ट के बारे में बताया गया है.

एक्सप्रेशन के तौर पर सबक्वेरी

सबक्वेरी, टॉप-लेवल स्टेज नहीं होती. इसके बजाय, यह एक एक्सप्रेशन होती है, जिसका इस्तेमाल किसी भी ऐसी स्टेज में किया जा सकता है जो एक्सप्रेशन स्वीकार करती है. जैसे, select(...), add_fields(...), where(...) या sort(...).

Cloud Firestore तीन तरह की सबक्वेरी के साथ काम करता है:

  • ऐरे सबक्वेरी: सबक्वेरी के पूरे नतीजों के सेट को, दस्तावेज़ों के ऐरे के तौर पर दिखाता है.
  • स्केलर सबक्वेरी: एक वैल्यू का आकलन करती है. जैसे, गिनती, औसत या मिलते-जुलते दस्तावेज़ का कोई खास फ़ील्ड.
  • subcollection(...) सबक्वेरी: एक-से-कई पैरंट-चाइल्ड रिलेशनशिप के लिए, आसान जॉइन.

दायरा और वैरिएबल

जॉइन लिखते समय, नेस्ट की गई सबक्वेरी को अक्सर "बाहरी" दस्तावेज़ (पैरंट) के फ़ील्ड का रेफ़रंस देना होता है. इन दायरों को जोड़ने के लिए, let(...) स्टेज का इस्तेमाल किया जाता है. इसे कुछ एसडीके में define(...) कहा जाता है. इसकी मदद से, पैरंट स्कोप में वैरिएबल तय किए जाते हैं. इसके बाद, सबक्वेरी में variable(...) फ़ंक्शन का इस्तेमाल करके, इनका रेफ़रंस दिया जा सकता है.

सिंटैक्स

इन सेक्शन में, जॉइन करने के सिंटैक्स की खास जानकारी दी गई है.

let(...) स्टेज

let(...) स्टेज को कुछ एसडीके में define(...) कहा जाता है. यह फ़िल्टर करने वाली स्टेज नहीं है. यह साफ़ तौर पर, पैरंट स्कोप से डेटा को नाम वाले वैरिएबल में लाती है, ताकि नेस्ट किए गए बाद के स्कोप में इसका इस्तेमाल किया जा सके.

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

ऐरे सबक्वेरी

ऐरे सबक्वेरी, एक्सप्रेशन सबक्वेरी का एक खास मामला है. यह सबक्वेरी के पूरे नतीजों के सेट को ऐरे में दिखाता है. अगर सबक्वेरी में कोई पंक्ति नहीं दिखती है, तो यह खाली ऐरे के तौर पर दिखती है. यह कभी भी null ऐरे नहीं दिखाती. इस तरह की क्वेरी तब काम की होती हैं, जब आखिरी नतीजे में पूरे नतीजे चाहिए होते हैं. जैसे, नेस्ट किए गए या कोरिलेटेड कलेक्शन को दिखाने के लिए.

क्वेरी, सबक्वेरी में फ़िल्टर, क्रम से लगाना, और एग्रीगेट कर सकती हैं. इससे, फ़ेच और वापस किए जाने वाले डेटा की मात्रा को भी कम किया जा सकता है. इससे क्वेरी की लागत कम करने में मदद मिलती है. सबक्वेरी के क्रम का पालन किया जाता है. इसका मतलब है कि सबक्वेरी में sort(...) स्टेज, आखिरी ऐरे में नतीजों के क्रम को कंट्रोल करती है.

किसी क्वेरी को ऐरे में बदलने के लिए, toArrayExpression() एसडीके रैपर का इस्तेमाल करें.

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"
      ]
    }
    

स्केलर सबक्वेरी

स्केलर सबक्वेरी का इस्तेमाल अक्सर select(...) या where(...) स्टेज में किया जाता है. इससे, क्वेरी के पूरे नतीजे को सीधे तौर पर दिखाए बिना, सबक्वेरी के नतीजे को फ़िल्टर या दिखाया जा सकता है.

ऐसी स्केलर सबक्वेरी जो कोई नतीजा नहीं देती, वह null के तौर पर दिखेगी. वहीं, ऐसी सबक्वेरी जो कई एलिमेंट का आकलन करती है, उससे रनटाइम में गड़बड़ी होगी.

जब कोई स्केलर सबक्वेरी, हर नतीजे के लिए सिर्फ़ एक फ़ील्ड दिखाती है, तो उस फ़ील्ड को सबक्वेरी के टॉप-लेवल नतीजे के तौर पर एलिवेट किया जाता है. आम तौर पर, ऐसा तब होता है, जब सबक्वेरी, select(field("user_name")) या aggregate(countAll().as("total")) के साथ खत्म होती है. इसमें सबक्वेरी का स्कीमा सिर्फ़ एक फ़ील्ड होता है. इसके अलावा, जब कोई सबक्वेरी कई फ़ील्ड दिखा सकती है, तो उन्हें मैप में रैप किया जाता है.

किसी क्वेरी को स्केलर एक्सप्रेशन में बदलने के लिए, toScalarExpression() एसडीके रैपर का इस्तेमाल करें.

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(...) का दायरा: The subcollection(...) इनपुट स्टेज, सिर्फ़ सबक्वेरी में काम करती है, क्योंकि इसे क्रम के हिसाब से बने रिलेशनशिप को हल करने और जॉइन करने के लिए, पैरंट दस्तावेज़ के कॉन्टेक्स्ट की ज़रूरत होती है.
  • नेस्टिंग की गहराई: सबक्वेरी को 20 लेयर तक नेस्ट किया जा सकता है.
  • मेमोरी का इस्तेमाल: दिखाए गए डेटा पर 128 MiB की सीमा, पूरी क्वेरी पर लागू होती है. इसमें, जॉइन किए गए सभी दस्तावेज़ शामिल हैं.