खास जानकारी
Firestore Enterprise वर्शन, कोरिलेटेड सबक्वेरी की मदद से रिलेशनल-स्टाइल जॉइन के साथ काम करता है. कई NoSQL डेटाबेस में, डेटा को डीनॉर्मलाइज़ करने या क्लाइंट-साइड पर कई अनुरोध करने की ज़रूरत होती है. हालांकि, सबक्वेरी की मदद से, मिलते-जुलते कलेक्शन या सब-कलेक्शन के डेटा को सीधे सर्वर पर जोड़ा और एग्रीगेट किया जा सकता है.
सबक्वेरी ऐसे एक्सप्रेशन होते हैं जो बाहरी क्वेरी से प्रोसेस किए गए हर दस्तावेज़ के लिए, नेस्ट की गई पाइपलाइन को एक्ज़ीक्यूट करते हैं. इससे डेटा को वापस पाने के जटिल पैटर्न चालू हो जाते हैं. जैसे, किसी दस्तावेज़ को उसके सब-कलेक्शन के आइटम के साथ फ़ेच करना या अलग-अलग रूट कलेक्शन में मौजूद, लॉजिकल तौर पर लिंक किए गए डेटा को जोड़ना.
कॉन्सेप्ट
इस सेक्शन में, पाइपलाइन ऑपरेशन में जॉइन करने के लिए सबक्वेरी इस्तेमाल करने के मुख्य कॉन्सेप्ट के बारे में बताया गया है.
एक्सप्रेशन के तौर पर सबक्वेरी
सबक्वेरी, टॉप-लेवल स्टेज नहीं है. इसके बजाय, यह एक एक्सप्रेशन है, जिसका इस्तेमाल एक्सप्रेशन स्वीकार करने वाले किसी भी स्टेज में किया जा सकता है. जैसे, select(...), add_fields(...), where(...) या sort(...).
Cloud Firestore में तीन तरह की सबक्वेरी इस्तेमाल की जा सकती हैं:
- अरे सबक्वेरी: सबक्वेरी के पूरे नतीजे को दस्तावेज़ों के अरे के तौर पर सेव करें.
- स्केलर सबक्वेरी: ये क्वेरी, किसी एक वैल्यू का आकलन करती हैं. जैसे, किसी संबंधित दस्तावेज़ से गिनती, औसत या कोई खास फ़ील्ड.
subcollection(...)सबक्वेरी: वन-टू-मेनी पैरंट-चाइल्ड रिलेशन के लिए, आसान जॉइन.
दायरा और वैरिएबल
जॉइन लिखते समय, नेस्ट की गई सबक्वेरी को अक्सर "बाहरी" दस्तावेज़ (पैरंट) के फ़ील्ड का रेफ़रंस देना पड़ता है. इन स्कोप को जोड़ने के लिए, let(...) स्टेज (कुछ SDK टूल में इसे define(...) कहा जाता है) का इस्तेमाल करें. इससे पैरंट स्कोप में वैरिएबल तय किए जा सकते हैं. इसके बाद, variable(...) फ़ंक्शन का इस्तेमाल करके, सबक्वेरी में उन्हें रेफ़रंस के तौर पर इस्तेमाल किया जा सकता है.
सिंटैक्स
यहां दिए गए सेक्शन में, जॉइन करने के लिए सिंटैक्स की खास जानकारी दी गई है.
let(...) स्टेज
let(...) स्टेज (कुछ SDK टूल में इसे define(...) कहा जाता है) एक ऐसी स्टेज है जिसमें फ़िल्टर नहीं किया जाता. यह साफ़ तौर पर पैरंट स्कोप से डेटा को नाम वाले वैरिएबल में लाता है, ताकि बाद के नेस्ट किए गए स्कोप में इसका इस्तेमाल किया जा सके.
कलेक्शन की सबक्वेरी
अरे सबक्वेरी, एक्सप्रेशन सबक्वेरी का एक खास केस है. यह सबक्वेरी के पूरे नतीजे को एक अरे में बदल देती है. अगर सबक्वेरी से कोई लाइन नहीं मिलती है, तो यह खाली कलेक्शन के तौर पर दिखता है. यह कभी भी null अरे नहीं दिखाता. इस तरह की क्वेरी तब काम आती हैं, जब फ़ाइनल नतीजे में पूरे नतीजे चाहिए हों. जैसे, नेस्ट किए गए या मिलते-जुलते कलेक्शन को तैयार करते समय.
क्वेरी, सबक्वेरी में फ़िल्टर, क्रम से लगाने, और एग्रीगेट करने की सुविधा देती हैं. इससे फ़ेच और रिटर्न किए जाने वाले डेटा की मात्रा कम हो जाती है. साथ ही, क्वेरी की लागत कम करने में मदद मिलती है. सबक्वेरी के क्रम का पालन किया जाता है. इसका मतलब है कि सबक्वेरी में sort(...) स्टेज, फ़ाइनल कलेक्शन में नतीजों के क्रम को कंट्रोल करता है.
किसी क्वेरी को कलेक्शन में बदलने के लिए, toArrayExpression() SDK रैपर का इस्तेमाल करें.
स्केलर सबक्वेरी
स्केलर सबक्वेरी का इस्तेमाल अक्सर select(...) या where(...) स्टेज में किया जाता है. इससे पूरी क्वेरी को सीधे तौर पर लागू किए बिना, सबक्वेरी के नतीजे को फ़िल्टर किया जा सकता है या उसका नतीजा निकाला जा सकता है.
एक स्केलर सबक्वेरी जो शून्य नतीजे देती है, उसका आकलन null के तौर पर किया जाएगा. वहीं, एक सबक्वेरी जो कई एलिमेंट का आकलन करती है, उसके नतीजे के तौर पर रनटाइम से जुड़ी गड़बड़ी होगी.
जब कोई स्केलर सबक्वेरी, हर नतीजे के लिए सिर्फ़ एक फ़ील्ड जनरेट करती है, तो उस फ़ील्ड को सबक्वेरी के लिए टॉप-लेवल नतीजे के तौर पर एलिवेट किया जाता है. आम तौर पर, ऐसा तब होता है, जब सबक्वेरी select(field("user_name")) या aggregate(countAll().as("total")) पर खत्म होती है. इसमें सबक्वेरी का स्कीमा सिर्फ़ एक फ़ील्ड होता है. अगर कोई सबक्वेरी कई फ़ील्ड जनरेट कर सकती है, तो उन्हें मैप में रैप किया जाता है.
किसी क्वेरी को स्केलर एक्सप्रेशन में बदलने के लिए, toScalarExpression() SDK रैपर का इस्तेमाल करें.
subcollection(...) सबक्वेरी
subcollection(...) इनपुट स्टेज, एक स्टेज के तौर पर उपलब्ध होता है. इसकी मदद से, Cloud Firestore के हैरारकी वाले डेटा मॉडल पर जॉइन किए जा सकते हैं. सबसे ज़रूरी कॉन्टेंट को पहले दिखाने वाले मॉडल में, क्वेरी को अक्सर किसी दस्तावेज़ के साथ-साथ उसके सब-कलेक्शन से डेटा को वापस लाना पड़ता है. हालांकि, collection_group(...) इनपुट स्टेज और पैरंट रेफ़रंस पर फ़िल्टर का इस्तेमाल करके भी ऐसा किया जा सकता है, लेकिन subcollection(...) ज़्यादा सटीक सिंटैक्स उपलब्ध कराता है.
इंप्लिसिट जॉइन कंडीशन के अलावा, यह अरे सबक्वेरी की तरह काम करता है. अगर कोई दस्तावेज़ मैच नहीं होता है, तो यह खाली नतीजा दिखाता है. भले ही, नेस्ट किया गया कलेक्शन मौजूद न हो.
यह मुख्य रूप से सिंटैक्टिक शुगर है: यह हैरारकी वाले संबंध को हल करने के लिए, बाहरी स्कोप में मौजूद दस्तावेज़ के __name__ को जॉइन की के तौर पर अपने-आप इस्तेमाल करता है. इस वजह से, पैरंट-चाइल्ड रिलेशनशिप में लिंक किए गए कलेक्शन में लुकअप करने के लिए, यह सबसे सही तरीका है.
उदाहरण
डेटा का उदाहरण
यहां दिए गए उदाहरणों में इस्तेमाल करने के लिए, टेस्ट डेटा का एक सेट लोड किया गया है.
Node.js
// Load set of cities.
const cities = collection(db, "cities");
await setDoc(doc(cities, "SF"), {
name: "San Francisco",
state: "CA",
country: "USA",
});
await setDoc(doc(cities, "LA"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
await setDoc(doc(cities, "DC"), {
name: "Washington, D.C.",
state: null,
country: "USA"
});
await setDoc(doc(cities, "TOK"), {
name: "Tokyo",
state: null,
country: "Japan"
});
// Load restaurants in various cities.
const sfRestaurants = collection(db, "cities", "SF", "restaurants");
const laRestaurants = collection(db, "cities", "LA", "restaurants");
const dcRestaurants = collection(db, "cities", "DC", "restaurants");
const rest1 = await addDoc(sfRestaurants, {
name: "Golden Gate Pizza",
type: "pizza",
owner_id: "Mario Rossi"
});
const rest2 = await addDoc(sfRestaurants, {
name: "Bay Area Burger",
type: "burger",
owner_id: "Sarah Jenkins"
});
const rest3 = await addDoc(sfRestaurants, {
name: "Sunset Taco",
type: "mexican",
owner_id: "Edward"
});
const rest4 = await addDoc(laRestaurants, {
name: "Hollywood Sushi",
type: "sushi",
owner_id: "Ken Kenji"
});
const rest5 = await addDoc(laRestaurants, {
name: "Venice Pizza",
type: "pizza",
owner_id: "Luigi Romano"
});
const rest6 = await addDoc(dcRestaurants, {
name: "Capitol Tacos",
type: "mexican",
owner_id: "Maria Garcia"
});
const rest7 = await addDoc(dcRestaurants, {
name: "Georgetown Coffee",
type: "cafe",
owner_id: "David Kim"
});
// Load collection of reviews.
const reviews = collection(db, "reviews");
await addDoc(reviews, { restaurant: rest1, rating: 5, reviewer_id "Alice" });
await addDoc(reviews, { restaurant: rest1, rating: 4, reviewer_id "Bob" });
await addDoc(reviews, { restaurant: rest2, rating: 4, reviewer_id "Charlie" });
await addDoc(reviews, { restaurant: rest3, rating: 5, reviewer_id "Diana" });
await addDoc(reviews, { restaurant: rest3, rating: 4, reviewer_id "Edward" });
await addDoc(reviews, { restaurant: rest3, rating: 4, reviewer_id "Fiona" });
// rest4 has 0 reviews
await addDoc(reviews, { restaurant: rest5, rating: 3, reviewer_id "George" });
await addDoc(reviews, { restaurant: rest6, rating: 5, reviewer_id "Hannah" });
await addDoc(reviews, { restaurant: rest6, rating: 4, reviewer_id "Ian" });
await addDoc(reviews, { restaurant: rest7, rating: 5, reviewer_id "Julia" });
किसी दूसरे कलेक्शन में दस्तावेज़ खोजना
reviews कलेक्शन ग्रुप पर मौजूद यह क्वेरी, प्राथमिक कुंजी के रेफ़रंस का इस्तेमाल करके, restaurant कलेक्शन ग्रुप में लुकअप करती है.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("reviews")
.define(field("restaurant").as("restaurant_name"))
.addFields(db.pipeline()
.collectionGroup("restaurant")
.where(field("__name__").equal(variable("restaurant_name")))
.select("name", "type")
.toScalarExpression()
.as("restaurant")));
जवाब
{
rating: 5,
reviewer_id "Alice",
restaurant: { name: "Golden Gate Pizza", type: "pizza" }
},
{
rating: 4,
reviewer_id "Bob",
restaurant: { name: "Golden Gate Pizza", type: "pizza" }
},
{
rating: 4,
reviewer_id "Charlie",
restaurant: { name: "Bay Area Burger", type: "burger" }
},
{
rating: 5,
reviewer_id "Diana",
restaurant: { name: "Sunset Taco", type: "mexican" }
},
{
rating: 4,
reviewer_id "Edward",
restaurant: { name: "Sunset Taco", type: "mexican" }
},
{
rating: 4,
reviewer_id "Fiona",
restaurant: { name: "Sunset Taco", type: "mexican" }
},
{
rating: 3,
reviewer_id "George",
restaurant: { name: "Venice Pizza", type: "pizza" }
},
{
rating: 5,
reviewer_id "Hannah",
restaurant: { name: "Capitol Tacos", type: "mexican" }
},
{
rating: 4,
reviewer_id "Ian",
restaurant: { name: "Capitol Tacos", type: "mexican" }
},
{
rating: 5,
reviewer_id "Julia",
restaurant: { name: "Georgetown Coffee", type: "cafe" }
}
एक से ज़्यादा कलेक्शन को मर्ज करना
यहां दी गई क्वेरी, restaurants कलेक्शन ग्रुप से पिज़्ज़ा की सभी जगहों की जानकारी फ़ेच करती है. साथ ही, उनसे जुड़ी समीक्षाओं को सीधे जवाब में फ़ेच और एम्बेड करने के लिए, कलेक्शन की सबक्वेरी का इस्तेमाल करती है.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.where(field("type").equal("pizza"))
.define(field("__name__").as("restaurant_name"))
.select(
field("name"),
db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("restaurant_name")))
.select("rating", "reviewer_id")
.toArrayExpression()
.as("reviews")));
जवाब
{
name: "Golden Gate Pizza",
reviews: [
{ rating: 5, reviewer_id "Alice" },
{ rating: 4, reviewer_id "Bob" }
]
},
{
name: "Venice Pizza",
type: "pizza",
owner_id: "Luigi Romano",
reviews: [
{ rating: 3, reviewer_id "George" }
]
}
एक से ज़्यादा कलेक्शन में एग्रीगेट करना
restaurants कलेक्शन ग्रुप पर की गई यह क्वेरी, कोरिलेटेड सबक्वेरी का इस्तेमाल करती है. इससे reviews कलेक्शन ग्रुप में मौजूद हर रेस्टोरेंट की औसत रेटिंग मिलती है.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.where(field("type").equal("pizza"))
.define(field("__name__").as("restaurant_name"))
.select(
field("name"),
db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("restaurant_name")))
.aggregate(average("rating").as("avg_rating"))
.toScalarExpression()
.as("avg_rating")));
जवाब
{
name: "Golden Gate Pizza",
avg_rating: 4.5
},
{
name: "Venice Pizza",
avg_rating: 3.0
}
हर ग्रुप के लिए टॉप-N (सीमा के साथ सबक्वेरी)
नीचे दी गई क्वेरी, restaurants कलेक्शन ग्रुप से सभी दस्तावेज़ फ़ेच करती है. साथ ही, हर रेस्टोरेंट के लिए सबसे ज़्यादा रेटिंग वाली दो समीक्षाएं फ़ेच करने के लिए, कोरिलेटेड सबक्वेरी का इस्तेमाल करती है.
इससे यह पक्का होता है कि समीक्षाओं का कलेक्शन बहुत बड़ा न हो जाए और क्वेरी की मेमोरी की सीमा को पार न करे.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.define(field("__name__").as("restaurant_name"))
.select(
field("name"),
db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("restaurant_name")))
.sort(field("rating").descending())
.limit(2)
.select("rating", "reviewer_id")
.toArrayExpression()
.as("top_reviews")));
जवाब
{
name: "Golden Gate Pizza",
top_reviews: [
{ rating: 5, reviewer_id "Alice" },
{ rating: 4, reviewer_id "Bob" }
]
},
{
name: "Bay Area Burger",
top_reviews: [
{ rating: 4, reviewer_id "Charlie" }
]
},
{
name: "Sunset Taco",
top_reviews: [
{ rating: 5, reviewer_id "Diana" },
{ rating: 4, reviewer_id "Edward" }
]
},
{
name: "Hollywood Sushi",
top_reviews: []
},
{
name: "Venice Pizza",
top_reviews: [
{ rating: 3, reviewer_id "George" }
]
},
{
name: "Capitol Tacos",
top_reviews: [
{ rating: 5, reviewer_id "Hannah" },
{ rating: 4, reviewer_id "Ian" }
]
},
{
name: "Georgetown Coffee",
top_reviews: [
{ rating: 5, reviewer_id "Julia" }
]
}
सब-कलेक्शन में शामिल होना
नीचे दी गई क्वेरी, cities कलेक्शन को स्कैन करती है. साथ ही, नेस्ट किए गए कलेक्शन के दस्तावेज़ों को जोड़ने के लिए, subcollection(...) स्टेज का इस्तेमाल करती है, ताकि हर शहर में रेस्टोरेंट की संख्या का पता लगाया जा सके.
Node.js
let results = await execute(db.pipeline()
.collection("cities")
.addFields(subcollection("restaurants")
.toArrayExpression()
.length()
.as("restaurant_count")));
जवाब
{
__name__: cities/SF,
name: "San Francisco",
state: "CA",
country: "USA",
restaurant_count: 3
},
{
__name__: cities/LA,
name: "Los Angeles",
state: "CA",
country: "USA",
restaurant_count: 2
},
{
__name__: cities/DC,
name: "Washington, D.C.",
state: null,
country: "USA",
restaurant_count: 2
},
{
__name__: cities/TOK,
name: "Tokyo",
state: null,
country: "Japan",
restaurant_count: 0
}
एक से ज़्यादा जॉइन कंडीशन को एक्सप्रेस करना
नीचे दी गई क्वेरी, restaurants कलेक्शन ग्रुप को स्कैन करती है और reviews कलेक्शन ग्रुप के साथ मल्टी-फ़ील्ड जॉइन करती है. इससे उन मालिकों का पता चलता है जो अपने रेस्टोरेंट की समीक्षा कर रहे हैं.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.define(field("owner_id"), field("__name__"))
.where(db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("__name__")))
.where(field("author").equal(variable("owner_id")))
.aggregate(count().as("c"))
.toScalarExpression()
.greaterThan(0)));
जवाब
{
__name__: cities/SF/restaurants/X9An0HIlx29A9GPuRthS,
name: "Sunset Taco",
type: "mexican",
owner_id: "Edward"
}
एंटी-जॉइन (NOT EXISTS)
नीचे दी गई क्वेरी, restaurants कलेक्शन ग्रुप को स्कैन करती है और उन सभी रेस्टोरेंट को ढूंढती है जिनकी अब तक कोई समीक्षा नहीं की गई है.
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.define(field("__name__").as("restaurant_name"))
.where(db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("restaurant_name")))
.aggregate(count().as("review_count"))
.toScalarExpression()
.equal(0)));
जवाब
{
__name__: "cities/LA/restaurants/X9An0HIlx29A9GPuRthS",
name: "Hollywood Sushi",
type: "sushi",
owner_id: "Ken Kenji"
}
सबक्वेरी को जॉइन के तौर पर इस्तेमाल करना
नीचे दी गई क्वेरी, पिज़्ज़ा की हर दुकान और उसकी समीक्षाओं के बीच के संबंध को फ़्लैट करती है. सबक्वेरी को unnest(...) स्टेज में रखकर, सर्वर हर मैच होने वाली समीक्षा के लिए, रेस्टोरेंट के बाहरी दस्तावेज़ की डुप्लीकेट कॉपी बनाता है. इससे फ़्लैट और जुड़े हुए दस्तावेज़ बनते हैं (एसक्यूएल INNER JOIN की तरह).
Node.js
let results = await execute(db.pipeline()
.collectionGroup("restaurants")
.where(field("type").equal("pizza"))
.define(field("__name__").as("restaurant_name"))
.unnest(
db.pipeline()
.collectionGroup("reviews")
.where(field("restaurant").equal(variable("restaurant_name")))
.select("rating", "reviewer_id")
.toArrayExpression()
.as("review")));
जवाब
{
__name__: "cities/SF/restaurants/xU4pu8nFpnJDPZOwcSPP",
name: "Golden Gate Pizza",
type: "pizza",
owner_id: "Mario Rossi"
review: { rating: 5, reviewer_id "Alice" }
},
{
__name__: "cities/SF/restaurants/xU4pu8nFpnJDPZOwcSPP",
name: "Golden Gate Pizza",
type: "pizza",
owner_id: "Mario Rossi",
review: { rating: 4, reviewer_id "Bob" }
},
{
__name__: "cities/LA/restaurants/6CYntvNgbYzgaW652Gq1",
name: "Venice Pizza",
type: "pizza",
owner_id: "Luigi Romano",
review: { rating: 3, reviewer_id "George" }
}
फ़िल्टर के तौर पर अनकोरिलेटेड सबक्वेरी
reviews कलेक्शन पर मौजूद यह क्वेरी, खुद पर मौजूद एक अनकोरिलेटेड सबक्वेरी का इस्तेमाल करके फ़िल्टर करती है. इससे, औसत रेटिंग से ज़्यादा रेटिंग वाली समीक्षाएं ढूंढी जाती हैं.
Node.js
let results = await execute(db.pipeline()
.collection("reviews")
// Average review rating is 4.3
.where(field("rating").greaterThan(db.pipeline()
.collection("reviews")
.aggregate(average("rating").as("avg"))
.toScalarExpression())))
.select("rating", "reviewer_id");
जवाब
{
rating: 5,
reviewer_id "Alice"
},
{
rating: 5,
reviewer_id "Diana"
},
{
rating: 5,
reviewer_id "Hannah"
},
{
rating: 5,
reviewer_id "Julia"
}
सबसे सही तरीके
toArrayExpression()की मदद से मेमोरी मैनेज करना:toArrayExpression()सबक्वेरी का इस्तेमाल करते समय सावधानी बरतें. ऐसा इसलिए, क्योंकि बड़ी संख्या में दस्तावेज़ों को मैटीरियलाइज़ करने से, क्वेरी की मेमोरी की सीमा (128 MiB) खत्म हो सकती है. इस समस्या को कम करने के लिए, सबक्वेरी मेंselect(...)का इस्तेमाल करें, ताकि सिर्फ़ ज़रूरी फ़ील्ड दिखें. साथ ही,where(...)फ़िल्टर लागू करें, ताकि क्वेरी के नतीजे में कम दस्तावेज़ दिखें. अगर ज़रूरी हो, तोlimit(...)का इस्तेमाल करें. इससे सबक्वेरी से मिलने वाले दस्तावेज़ों की संख्या को सीमित किया जा सकता है.- इंडेक्स करना: पक्का करें कि सबक्वेरी के
where(...)क्लॉज़ में इस्तेमाल किए गए फ़ील्ड इंडेक्स किए गए हों. परफ़ॉर्मेंस वाले जॉइन, पूरी टेबल को स्कैन करने के बजाय इंडेक्स को खोजने की क्षमता पर निर्भर करते हैं.
क्वेरी के सबसे सही तरीकों के बारे में ज़्यादा जानने के लिए, क्वेरी ऑप्टिमाइज़ेशन से जुड़ी हमारी गाइड देखें.
सीमाएं
subcollection(...)स्कोप:subcollection(...)इनपुट स्टेज सिर्फ़ सबक्वेरी में काम करता है. ऐसा इसलिए, क्योंकि हैरारकी वाले संबंध को हल करने और जॉइन करने के लिए, पैरंट दस्तावेज़ के कॉन्टेक्स्ट की ज़रूरत होती है.- नेस्टिंग डेप्थ: सबक्वेरी को ज़्यादा से ज़्यादा 20 लेयर तक नेस्ट किया जा सकता है.
- मेमोरी का इस्तेमाल: मटीरियलाइज़्ड डेटा के लिए 128 MiB की सीमा, पूरी क्वेरी पर लागू होती है. इसमें शामिल किए गए सभी दस्तावेज़ भी शामिल हैं.