Alt sorgularla birleştirme işlemleri gerçekleştirme

Genel Bakış

Firestore Enterprise sürümü, ilişkili alt sorgular aracılığıyla ilişkisel tarzda birleştirmeleri destekler. Genellikle verilerin normalleştirilmesini veya birden fazla istemci tarafı isteği gerçekleştirilmesini gerektiren birçok NoSQL veritabanının aksine alt sorgular, ilgili koleksiyonlardan veya alt koleksiyonlardan gelen verileri doğrudan sunucuda birleştirmenize ve birleştirmenize olanak tanır.

Alt sorgular, dış sorgu tarafından işlenen her doküman 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 farklı kök koleksiyonlardaki mantıksal olarak bağlantılı verileri birleştirme gibi karmaşık veri alma kalıpları kullanılabilir.

Kavramlar

Bu bölümde, ardışık düzen işlemlerinde birleştirme yapmak için alt sorguları kullanmanın temel kavramları tanıtılmaktadır.

İfade 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 doküman dizisi olarak somutlaştırın.
  • Skaler alt sorgular: Sayı, ortalama veya ilgili bir belgedeki belirli bir alan gibi tek bir değer döndürür.
  • subcollection(...) Alt sorgular: Bire çok üst-alt ilişkisi için basitleştirilmiş birleştirmeler.

Kapsam ve değişkenler

Bir birleştirme yazarken iç içe yerleştirilmiş alt sorgunun genellikle "dış" dokümandaki (üst öğe) alanlara referans vermesi gerekir. Bu kapsamlar arasında köprü oluşturmak 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şkenler daha sonra variable(...) işlevi kullanılarak alt sorguda referans olarak kullanılabilir.

Söz dizimi

Aşağıdaki bölümlerde, birleştirme işlemlerini gerçekleştirmek için kullanılan 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 sonraki iç içe yerleştirilmiş kapsamlarda kullanılmak üzere açıkça adlandırılmış bir değişkene getiren, filtreleme yapılmayan bir aşamadır.

Dizi Alt Sorguları

Dizi alt sorgusu, alt sorgunun tüm sonuç kümesini bir diziye dönüştüren özel bir ifade alt sorgusu türüdür. 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 koleksiyonu somutlaştırırken olduğu gibi, nihai sonuçta tam sonuçlar gerektiğinde kullanışlıdır.

Sorgular, alt sorguda filtreleme, sıralama ve toplama yaparak sorgunun maliyetini azaltmak için getirilmesi ve döndürülmesi gereken veri miktarını da azaltabilir. Alt sorgunun sırasına uyulur. Yani alt sorgudaki bir sort(...) aşaması, nihai dizideki sonuçların sırasını kontrol eder.

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 somutlaştırmadan filtrelemeye izin vermek veya bir alt sorgunun sonucunu elde etmek 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.

Skaler alt sorgu, sonuç başına yalnızca tek bir alan ürettiğinde alan, alt sorgu için üst düzey sonuç olacak şekilde yükseltilir. Bu durum en sık, alt sorgunun şemasının tek bir alan olduğ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 üretebiliyorsa bunlar bir haritaya sarmalanır.

Bir sorguyu skaler ifadeye dönüştürmek için toScalarExpression() SDK sarmalayıcısını kullanın.

subcollection(...) alt sorgu

Aşama olarak sunulan subcollection(...) giriş aşaması, Cloud Firestore hiyerarşik veri modeli üzerinde birleştirme işlemleri yapılmasına olanak tanır. Hiyerarşik bir modelde sorguların genellikle bir dokümanı kendi alt koleksiyonlarındaki verilerle birlikte alması gerekir. Bunu, bir collection_group(...) giriş aşaması ve ardından üst referansta bir filtre kullanarak yapabilirsiniz ancak subcollection(...) çok daha kısa bir söz dizimi sağlar.

Örtük birleştirme koşulu dışında, bu işlem bir dizi alt sorgusuna benzer şekilde çalışır ve iç içe yerleştirilmiş koleksiyon mevcut olmasa bile eşleşen doküman yoksa boş bir sonuç döndürür.

Temel olarak söz dizimsel bir kolaylıktır: Hiyerarşik ilişkiyi çözmek için birleştirme anahtarı olarak dış kapsamdaki dokümanın __name__ değerini otomatik olarak kullanır. Bu nedenle, üst-alt ilişkisiyle bağlantılı koleksiyonlar arasında arama yapmak için tercih edilen yöntemdir.

Örnekler

Örnek veriler

Aşağıdaki komut, sonraki tüm örneklerde kullanılacak bir test verisi kümesi 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 koleksiyondaki 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 restoranlarını getirir ve ilişkili yorumlarını doğrudan yanıta getirip 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 Fazla 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 (Sınırlı Alt Sorgu)

Aşağıdaki sorgu, restaurants koleksiyon grubundaki tüm dokümanları getirir ve her restoran için en yüksek puanlı 2 yorumu getirmek üzere ilişkili bir alt sorgu kullanır.

Bu sayede, yorum dizisinin çok fazla büyümesi ve sorgunun bellek sınırına ulaşması önlenir.

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 koleksiyonları birleştirme

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ı dolaylı 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şulunu İfade Etme

Aşağıdaki sorgu, restaurants koleksiyon grubunu tarar ve kendi restoranlarını inceleyen sahipleri bulmak için reviews koleksiyon grubuyla çok alanlı bir birleştirme işlemi gerçekleştirir.

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 yorumu olmayan 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ştirdiğinizde sunucu, her eşleşen yorum için dış restoran dokümanını kopyalayarak 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 İlişkisiz Alt Sorgu

reviews koleksiyonundaki aşağıdaki sorgu, ortalama puanın üzerindeki yorumları bulmak için kendisi üzerinde ilişkisiz bir alt sorgu kullanarak filtreleme yapar.

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 bellek yönetimi: Çok sayıda dokümanı somutlaştırmak sorgu bellek sınırını (128 MiB) aşabileceğinden toArrayExpression() alt sorgularını kullanırken dikkatli olun. Bu sorunu azaltmak için alt sorguda select(...) kullanarak yalnızca gerekli alanları döndürün ve döndürülen belge sayısını sınırlamak için where(...) filtreleri uygulayın. Alt sorgu tarafından döndürülen belge sayısını sınırlamak için uygunsa limit(...) kullanabilirsiniz.
  • Dizin oluşturma: Bir alt sorgunun where(...) yan tümcesinde kullanılan alanların dizine eklendiğinden emin olun. Performanslı birleştirmeler, tam tablo taramaları yerine dizin aramaları yapabilme özelliğine dayanır.

Sorgularla ilgili diğer en iyi uygulamalar için sorgu optimizasyonuyla ilgili kılavuzumuzu inceleyin.

Sınırlamalar

  • subcollection(...) kapsamı: subcollection(...) giriş aşaması, hiyerarşik ilişkiyi çözmek ve birleştirme işlemini gerçekleştirmek için üst belgenin bağlamını gerektirdiğinden yalnızca alt sorgularda desteklenir.
  • İç İçe Yerleştirme Derinliği: Alt sorgular en fazla 20 katman derinliğinde iç içe yerleştirilebilir.
  • Bellek kullanımı: Gerçekleştirilmiş verilerdeki 128 MiB sınırı, birleştirilmiş tüm dokümanlar dahil olmak üzere sorgunun tamamı için geçerlidir.