खास जानकारी
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
}
हर ग्रुप के लिए टॉप-एन (सीमा वाली सबक्वेरी)
इस क्वेरी से, 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(...) स्टेज में रखने पर, सर्वर हर मैचिंग समीक्षा के लिए, बाहरी
रेस्टोरेंट के दस्तावेज़ की डुप्लीकेट कॉपी बनाता है. इससे, फ़्लैट, जॉइन किए गए दस्तावेज़ तैयार होते हैं
. ये SQL 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 की सीमा, पूरी क्वेरी पर लागू होती है. इसमें जॉइन किए गए सभी दस्तावेज़ शामिल हैं.