Genel Bakış
Firestore Enterprise sürümü, ilişkili alt sorgular aracılığıyla ilişkisel tarzda birleştirmeleri destekler. Genellikle verilerin normalleştirilmemesini veya birden fazla istemci taraflı isteğin yapılmasını gerektiren birçok NoSQL veritabanının aksine, alt sorgular ilgili koleksiyonlardaki veya alt koleksiyonlardaki verileri doğrudan sunucuda birleştirmenize ve toplamanıza olanak tanır.
Alt sorgular, dış sorgu tarafından işlenen her belge için iç içe yerleştirilmiş bir ardışık düzeni yürüten ifadelerdir. Bu sayede, bir dokümanı ilgili alt koleksiyon öğeleriyle birlikte getirme veya mantıksal olarak bağlı verileri farklı kök koleksiyonlarda birleştirme gibi karmaşık veri alma kalıpları etkinleştirilir.
Kavramlar
Bu bölümde, alt sorguları kullanarak işlem hattı işlemlerinde birleştirme gerçekleştirmenin temel kavramları tanıtılmaktadır.
İfadeler olarak alt sorgular
Alt sorgu, üst düzey bir aşama değildir. Bunun yerine, select(...), add_fields(...), where(...) veya sort(...) gibi ifadeleri kabul eden herhangi bir aşamada kullanılabilen bir ifadedir.
Cloud Firestore üç tür alt sorguyu destekler:
- Dizi Alt Sorguları: Alt sorgunun tüm sonuç kümesini belge dizisi olarak gerçekleştirin.
- Tek Değerli Alt Sorgular: Bir sayım, ortalama veya ilgili bir dokümandaki belirli bir alan gibi tek bir değerle değerlendirilir.
subcollection(...)Alt sorgular: Bire çok üst-alt ilişkisi için birleştirme işlemleri basitleştirildi.
Kapsam ve değişkenler
Birleştirme işlemi yazarken iç içe yerleştirilmiş alt sorgunun genellikle "dış" dokümandaki (üst) alanlara referans vermesi gerekir. Bu kapsamları birleştirmek için let(...) aşamasını (bazı SDK'larda define(...) olarak adlandırılır) kullanarak üst kapsamda değişkenler tanımlarsınız. Bu değişkenlere daha sonra variable(...) işlevi kullanılarak alt sorguda referans verilebilir.
Söz dizimi
Aşağıdaki bölümlerde, birleştirme işlemleri için söz dizimine genel bir bakış sunulmaktadır.
let(...) aşaması
let(...) aşaması (bazı SDK'larda define(...) olarak adlandırılır), üst kapsamdaki verileri açıkça getirip sonraki iç içe yerleştirilmiş kapsamlarda kullanılmak üzere adlandırılmış bir değişkene yerleştiren, filtreleme yapmayan bir aşamadır.
Dizi Alt Sorguları
Dizi alt sorgusu, alt sorgunun sonuç kümesinin tamamını bir dizide somutlaştıran özel bir ifade alt sorgusudur. Alt sorgu sıfır satır döndürürse boş bir dizi olarak değerlendirilir. Hiçbir zaman null dizisi döndürmez. Bu tür sorgular, iç içe yerleştirilmiş veya ilişkili bir koleksiyon oluşturma gibi durumlarda nihai sonuçta tüm sonuçların gerekli olduğu zamanlarda kullanışlıdır.
Sorgular, getirilmesi ve döndürülmesi gereken veri miktarını azaltmak için alt sorguda filtreleme, sıralama ve toplama işlemleri yapabilir. Bu da sorgunun maliyetini düşürmeye yardımcı olur. Alt sorgunun sırası dikkate alınır. Bu nedenle, alt sorgudaki sort(...) aşaması, nihai dizideki sonuçların sırasını kontrol eder.
Bir sorguyu diziye dönüştürmek için toArrayExpression() SDK sarmalayıcısını kullanın.
Skaler Alt Sorgular
Skaler alt sorgular, genellikle select(...) veya where(...) aşamasında, tam sorguyu doğrudan oluşturmadan filtrelemeye veya alt sorgunun sonucunu elde etmeye olanak tanımak için kullanılır.
Sıfır sonuç üreten bir skaler alt sorgu null olarak değerlendirilirken birden fazla öğe olarak değerlendirilen bir alt sorgu, çalışma zamanı hatasına neden olur.
Bir skaler alt sorgu sonuç başına yalnızca tek bir alan ürettiğinde alan, alt sorgunun üst düzey sonucu olacak şekilde yükseltilir. Bu durum en sık, alt sorgunun şeması tek bir alandan oluştuğu ve alt sorgunun select(field("user_name")) veya aggregate(countAll().as("total")) ile bittiği durumlarda görülür. Aksi takdirde, bir alt sorgu birden fazla alan üretebildiğinde bu alanlar bir haritaya sarılır.
Bir sorguyu skaler ifadeye dönüştürmek için toScalarExpression() SDK sarmalayıcısını kullanın.
subcollection(...) Alt sorgular
Bir aşama olarak sunulan subcollection(...) giriş aşaması, Cloud Firestore'nin hiyerarşik veri modelinde birleştirmeler yapılmasına olanak tanır. Hiyerarşik modelde sorguların genellikle bir dokümanı kendi alt koleksiyonlarındaki verilerle birlikte alması gerekir. Bu işlemi, üst referans üzerinde bir filtreyle devam eden bir collection_group(...) giriş aşaması kullanarak gerçekleştirebilirsiniz ancak subcollection(...) çok daha kısa bir söz dizimi sağlar.
Bu işlev, örtülü birleştirme koşulu dışında bir dizi alt sorgusuna benzer şekilde çalışır. İç içe yerleştirilmiş koleksiyon mevcut olmasa bile hiçbir belge eşleşmezse boş bir sonuç döndürür.
Temelde söz dizimi kolaylığı sağlar: Hiyerarşik ilişkiyi çözmek için dış kapsamdaki dokümanın __name__ özelliğini otomatik olarak birleştirme anahtarı olarak kullanır. Bu nedenle, üst-alt ilişkisiyle bağlı koleksiyonlarda arama yapmak için tercih edilen yöntemdir.
Örnekler
Örnek veriler
Aşağıdaki kod, sonraki tüm örneklerde kullanılacak bir dizi test verisi yükler.
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" });
Başka bir koleksiyonda doküman arama
reviews koleksiyon grubundaki aşağıdaki sorgu, birincil anahtar referansı kullanarak restaurant koleksiyon grubunda arama yapar.
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")));
Yanıt
{
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" }
}
Birden Fazla Koleksiyonu Birleştirme
Aşağıdaki sorgu, restaurants koleksiyon grubundaki tüm pizza yerlerini getirir ve ilişkili yorumlarını doğrudan yanıta getirmek ve yerleştirmek için bir dizi alt sorgusu kullanır.
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")));
Yanıt
{
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" }
]
}
Birden Çok Koleksiyonda Toplama
restaurants koleksiyon grubundaki aşağıdaki sorgu, reviews koleksiyon grubundaki her restoranın ortalama puanını almak için ilişkili bir alt sorgu kullanır.
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")));
Yanıt
{
name: "Golden Gate Pizza",
avg_rating: 4.5
},
{
name: "Venice Pizza",
avg_rating: 3.0
}
Grup Başına En İyi N (Limitli Alt Sorgu)
Aşağıdaki sorgu, restaurants koleksiyon grubundaki tüm dokümanları getirir ve her restoran için en yüksek puanlı ilk 2 yorumu getirmek üzere ilişkili bir alt sorgu kullanır.
Bu sayede, yorum dizisinin çok büyümemesi ve sorgunun bellek sınırına ulaşmaması sağlanır.
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")));
Yanıt
{
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" }
]
}
Alt Koleksiyonlara Katılma
Aşağıdaki sorgu, cities koleksiyonunu tarar ve şehir başına restoran sayısını bulmak için iç içe yerleştirilmiş bir koleksiyondaki dokümanları örtülü olarak birleştirmek üzere subcollection(...) aşamasını kullanır.
Node.js
let results = await execute(db.pipeline()
.collection("cities")
.addFields(subcollection("restaurants")
.toArrayExpression()
.length()
.as("restaurant_count")));
Yanıt
{
__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
}
Birden Fazla Birleştirme Koşulu İfade Etme
Aşağıdaki sorgu, restaurants koleksiyon grubunu tarar ve kendi restoranlarını inceleyen sahipleri bulmak için restaurants koleksiyon grubuyla çok alanlı birleştirme işlemi gerçekleştirir.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)));
Yanıt
{
__name__: cities/SF/restaurants/X9An0HIlx29A9GPuRthS,
name: "Sunset Taco",
type: "mexican",
owner_id: "Edward"
}
Anti-Join (NOT EXISTS)
Aşağıdaki sorgu, restaurants koleksiyon grubunu tarar ve henüz yorum almamış tüm restoranları bulur.
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)));
Yanıt
{
__name__: "cities/LA/restaurants/X9An0HIlx29A9GPuRthS",
name: "Hollywood Sushi",
type: "sushi",
owner_id: "Ken Kenji"
}
Birleştirme olarak alt sorgu
Aşağıdaki sorgu, her pizza restoranı ile yorumları arasındaki ilişkiyi düzleştirir. Alt sorguyu bir unnest(...) aşamasına yerleştirerek sunucu, her eşleşen yorum için dıştaki restoran dokümanını kopyalar ve düz, birleştirilmiş dokümanlar (SQL INNER JOIN'ye benzer) oluşturur.
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")));
Yanıt
{
__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" }
}
Filtre olarak ilişkisiz alt sorgu
reviews koleksiyonundaki aşağıdaki sorgu, ortalama puandan daha yüksek puanlı yorumları bulmak için kendisi üzerinde ilişkisiz bir alt sorgu kullanarak filtreleme işlemi gerçekleştirir.
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");
Yanıt
{
rating: 5,
reviewer_id "Alice"
},
{
rating: 5,
reviewer_id "Diana"
},
{
rating: 5,
reviewer_id "Hannah"
},
{
rating: 5,
reviewer_id "Julia"
}
En iyi uygulamalar
toArrayExpression()ile belleği yönetme: Çok sayıda dokümanın oluşturulması sorgu belleği sınırını (128 MiB) aşabileceğindentoArrayExpression()alt sorgularını kullanırken dikkatli olun. Bunu azaltmak için alt sorgudaselect(...)kullanarak yalnızca gerekli alanları döndürün ve döndürülen doküman sayısını sınırlamak içinwhere(...)filtreleri uygulayın. Alt sorgu tarafından döndürülen belge sayısını sınırlamak için uygunsalimit(...)işlevini kullanabilirsiniz.- Dizin oluşturma: Bir alt sorgunun
where(...)ifadesinde kullanılan alanların dizine eklendiğinden emin olun. Yüksek performanslı birleştirmeler, tam tablo taramaları yerine dizin aramaları yapabilmeye dayanır.
Sorgularla ilgili daha fazla en iyi uygulama için sorgu optimizasyonunu ele alan rehberimize göz atın.
Sınırlamalar
subcollection(...)kapsamı:subcollection(...)giriş aşaması yalnızca alt sorgularda desteklenir. Bunun nedeni, hiyerarşik ilişkiyi çözmek ve birleştirme işlemini gerçekleştirmek için üst dokümanın bağlamını gerektirmesidir.- İç İçe Yerleştirme Derinliği: Alt sorgular en fazla 20 kat iç içe yerleştirilebilir.
- Bellek Kullanımı: Gerçekleştirilmiş veriler için 128 MiB sınırı, birleştirilmiş tüm belgeler dahil olmak üzere sorgunun tamamı için geçerlidir.