Orta9 dakikalık okuma

DynamoDB JOIN: Tablolar Nasıl Birleştirilir

DynamoDB'de JOIN yoktur. API'nin bir birleştirme operatörü yoktur, veri modelinin yabancı anahtarları yoktur ve — çoğu insanı şaşırtan kısım — , o SQL tadındaki sorgu katmanı da bir tane eklemez. Bir PartiQL SELECT tam olarak bir tabloyu okur.

İlişkisel bir veritabanından geldiyseniz, bu çarptığınız ilk duvardır. Bu rehber, duvarın neden orada olduğunu, geliştiricilerin bunun yerine yaptığı dört şeyi, gerçekten gerçek bir birleştirmeye ihtiyaç duyduğunuz tek durumu ve bir tanesini nasıl çalıştıracağınızı kapsar.

DynamoDB birleştirme yapabilir mi?

Hayır. DynamoDB tabloları birleştiremez — ne düşük seviyeli API (GetItem / Query / Scan / BatchGetItem) üzerinden, ne üzerinden, ne de yerleşik herhangi bir sorgu planlayıcı üzerinden, çünkü öyle bir şey yoktur. Her okuma bir tabloya ya da onun dizinlerinden birine eşlenir; iki tabloyu eşleşen bir anahtar üzerinde birleştirmek, DynamoDB item'ları döndürdükten sonra uygulamanızda yaptığınız bir şeydir, asla onun içinde değil.

  • DynamoDB'nin JOIN operatörü yoktur. Hiçbir zaman olmadı.
  • PartiQL'in SELECT'i yalnızca tek tabloludur — dilbilgisi tam anlamıyla SELECT … FROM {{table}}[.{{index}}]'tir ve onu iki tabloya doğrultmak ValidationException: Only select from a single table or index is supported. döndürür.
  • AWS'nin önerdiği çözüm, bir birleştirmeye ihtiyaç duymamaktır: ya da ilgili item'lar tek bir istekte getirdiğiniz bir bölümde yaşasın diye single-table tasarımı kullanın.
  • Gerçek tablolar-arası / anlık durum için, birleştirmeyi DynamoDB'nin dışında yaparsınız — uygulamanızda ya da bunu senin için yapan bir araçla.

DynamoDB'nin neden birleştirmesi yok

Bir SQL JOIN, veritabanından birden çok tabloyu okumasını ve onları sorgu zamanında birleştirmesini ister. AWS'nin kendi ilişkisel veriyi modelleme rehberi maliyeti açıklar: şunun gibi bir sorgu

SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products    ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC

esnektir ama "sorgudaki her birleştirme, her tablonun verisi hazırlanıp sonra birleştirilmesi gerektiği için sorgunun çalışma zamanı karmaşıklığını artırır." O iş sınırsızdır — maliyeti sorguya değil, veriye bağlıdır — ki bu tam olarak DynamoDB'nin sahip olmayı reddettiği niteliktir.

Bu yüzden AWS kısıtı tasarıma dahil etti. DynamoDB, onların deyimiyle, "JOIN'leri ortadan kaldırarak (ve verinin denormalizasyonunu teşvik ederek) hem [CPU hem de ağ] kısıtlarını en aza indirmek ve veritabanı mimarisini bir uygulama sorgusunu bir item'a tek bir istekle tamamen yanıtlayacak şekilde optimize etmek için inşa edilmiştir." Bunlar, herhangi bir ölçekte tek haneli milisaniye gecikmesi satın alan niteliklerdir: bir DynamoDB okumasının çalışma zamanı maliyeti, tablo boyutundan bağımsız olarak sabittir. Tasarım gereği, karşı plan yapılacak bir birleştirme motoru ve bir yabancı anahtar kavramı yoktur.

"Ama PartiQL SQL'dir, kesin birleştirir?"

Hayır. PartiQL sana DynamoDB üzerinde SELECT / INSERT / UPDATE / DELETE söz dizimi verir ama SQL değil, SQL-uyumludur. Resmi SELECT dilbilgisi şudur:

SELECT  {{expression}}  [, ...]
FROM    {{table}}[.{{index}}]
[ WHERE {{condition}} ]
[ ORDER BY {{key}} [DESC|ASC], ... ]

FROM, bir tablo alır (isteğe bağlı olarak onun dizinlerinden biri). İkinci bir FROM tablosu, JOIN, alt sorgu ya da CTE yoktur. Her birinin tam olarak nasıl başarısız olduğunu görmek için üçünü de DynamoDB'ye karşı çalıştırdık.

Açık bir JOIN:

SELECT o.pk FROM "Orders" o JOIN "Customers" c ON o.customerId = c.pk
ValidationException: Only select from a single table or index is supported.

FROM içinde iki tablo — aynı ret, yani motor JOIN anahtar sözcüğünü değil, ikinci tabloyu reddediyor:

SELECT * FROM "Orders", "Customers"
ValidationException: Only select from a single table or index is supported.

Bir alt sorgu farklı şekilde başarısız olur; birini hata ayıklıyorsanız bunu bilmek işe yarar. PartiQL çok tablolu kontrole hiç ulaşmaz — IN işlenenini ondan önce reddeder, bu yüzden tablolardan hiç söz etmeyen bir mesaj alırsınız:

SELECT * FROM "Orders" WHERE customerId IN (SELECT pk FROM "Customers")
ValidationException: IN operator must have a left hand argument of type Variable
Reference and right hand argument of type Seq with at least one member

sana bir birleştirme kazandıran hiçbir yazım biçimi yoktur. İlk ikisi ikinci tabloda ölür, üçüncüsü ise daha da erken, işlenende ölür.

PartiQL'in neden SQL gibi göründüğü ama onun gibi davranamadığı hakkında tam gerekçeyi istiyorsanız, bkz. PartiQL ile SQL.

Geliştiricilerin gerçekte kullandığı 4 geçici çözüm

1. Denormalize edin (veriyi kopyalayın)

Aksi hâlde birleştireceğiniz alanları doğrudan item'a depolayın. Bir Order, daha sonra çözeceğiniz bir customerId yerine customerName ve shippingAddress'in bir anlık görüntüsünü taşır. Bir okuma, birleştirme yok.

Yazma zamanı yayılması bedeldir. kaynak değiştiğinde her kopyayı güncellersiniz (genellikle bir işleyicisi aracılığıyla). Okuma karmaşıklığını yazma karmaşıklığıyla takas ediyorsunuz — okuma ağırlıklı bir uygulama için genellikle iyi bir takas.

2. Single-table tasarımı (bölümde ön birleştirme)

İlgili varlıkları, bir birleştirilmiş sonuç olsun diye paylaşılan bir bölüm anahtarı altında tek bir tabloya koyun. Bir müşteri ve tüm siparişleri PK = "CUSTOMER#42"'yi paylaşır; tek bir Query müşteri item'ını artı her sipariş item'ını döndürür — "birleştirme" yazma zamanında zaten gerçekleşti.

Query  PK = "CUSTOMER#42"
→ CUSTOMER#42 / PROFILE      (the customer)
→ CUSTOMER#42 / ORDER#1001   (an order)
→ CUSTOMER#42 / ORDER#1002   (an order)

Bu, bire-çok ilişkilere DynamoDB'nin standart yanıtıdır. Tam bir baştan sona anlatım single-table tasarımında.

3. Uygulama tarafında birleştirme (iki okuma, kodda birleştir)

A tablosundan okuyun, geri aldığınız anahtarları alın, B tablosundan okuyun ve iki sonuç kümesini uygulamanızda birleştirin. Bu ilişkisel birleştirme mantığıdır — sadece veritabanı yerine kodunuzda çalışıyor:

// "Get each order with its customer name" — the manual join.
const {Items: orders} = await ddb.query({TableName: 'Orders' /* … */});

const customers = await Promise.all(
  orders.map((o) => ddb.get({TableName: 'Customers', Key: {id: o.customerId}}))
);

const joined = orders.map((o, i) => ({
  ...o,
  customerName: customers[i].Item?.name
}));

Küçük yayılma için iyidir. Çok sayıda siparişle bir N+1 problemine dönüşür — siparişleri listelemek için bir okuma, sonra sipariş başına bir okuma — ki bu yavaştır ve okuma kapasitesini yakar. BatchGetItem (sıradaki), o ikinci dalgayı tek bir gidiş-dönüşe daraltır.

4. BatchGetItem (tek gidiş-dönüş, birden çok tablo)

BatchGetItem API'nin "aynı anda iki tabloya dokun"maya en yakınıdır: tek bir istek, çağrı başına 100 item ya da 16 MB'a kadar (hangisine önce çarparsa) "bir ya da daha fazla tablodan bir ya da daha fazla item'ın attribute'larını" döndürür. Uygulama tarafı bir birleştirmenin gidiş-dönüşlerini keser — ama bir birleştirme değildir. "İstenen item'ları birincil anahtarla tanımlarsınız"; bir ON koşulu ve ilişkisel eşleştirme yoktur. Anahtarları yine de baştan bilmeniz ve yanıtları kendiniz birleştirmeniz gerekir.

Gerçek bir JOIN kaçınılmaz olduğunda

Dört geçici çözüm, üretim okuma yollarını iyi kapsar. Yıkıldıkları yer, anlık, keşifsel, analitik sorgudur — modellemediğiniz sorgu:

  • Bir Orders tablosu ve bir Customers tablosu arasında "AB'de geçen ay 500 doların üzerinde sipariş veren müşteriler kim?".
  • İki varlık türünü birleştiren tek seferlik bir veri kalitesi kontrolü.
  • Raporlama ve toplamalar (GROUP BY, SUM, COUNT) — ki DynamoDB'nin bunlar için hiç operatörü yoktur.

Bunlar, bir bölüme önceden pişiremeyeceğiniz sorguların ta kendisidir, çünkü tanım gereği onları soracağınızı bilmiyordunuz. İlişkisel iç güdü — bir JOIN yazmak — burada doğru olandır. DynamoDB onu yerel olarak sunamaz ve PartiQL de sunamaz.

Olağan ağır çözüm, S3'e dışa aktarıp Athena ile sorgulamaktır (ya da canlı bir tabloyu JOIN etmek için Athena'nın federe sorgu bağlayıcısını kullanmak) ya da bir veri ambarına aktarmaktır. Bu, ölçekte gerçek analitik için doğrudur ama şimdi, canlı tablonuza karşı yanıtlanmasını istediğiniz bir soru için çok fazla tesisattır.

DynoTable'ın SQL Workbench'i ile gerçek bir JOIN çalıştırma

DynoTable, SQL Workbench'i DynamoDB tablolarınız üzerinde gerçek SQL çalıştıran — JOIN, GROUP BY ve toplama fonksiyonları dahil — bir masaüstü DynamoDB istemcisidir. Item'ları normal DynamoDB API'si üzerinden okur, sonra sorgunun ilişkisel kısımlarını istemcide yürütür. Böylece şunu yazabilirsiniz:

SELECT  c.name, SUM(o.total) AS spend
FROM    Customers c
JOIN    Orders o ON o.customerId = c.id
WHERE   c.region = 'EU'
GROUP BY c.name
HAVING  SUM(o.total) > 500

— ve tanımlı bir ilişkisi olmayan tablolara ve JOIN anahtar sözcüğü olmayan bir sorgu motoruna karşı bir sonuç kümesi alın.

Dürüst uyarı — "DynamoDB'nin erişim deseni kuralları içinde": Workbench yine DynamoDB üzerinden okur, bu yüzden sınırsız bir birleştirme sınırsız bir okumadır. En hızlı sorgular, WHERE yan tümcesinin (ya da birleştirmenin ON attribute'unun) en az bir tarafta bir bölüm anahtarına ya da bir GSI'ye çarptığı sorgulardır, böylece DynamoDB, birleştirme yürütülmeden önce tam bir tablo taraması yerine bir Query çalıştırır. Workbench bu rehberdeki kısıtları ilga etmez — sadece SQL sorusunu sormanıza izin verir, birleştirmeyi elle yazmanız yerine ve alttan ne yaptığını sana söyler.

GUI istemciler arasında, gerçekten doğru olan tek "evet, birleştirebilirsiniz" odur: PartiQL ve AWS'nin kendi NoSQL Workbench'i — ki işlem oluşturucusu tek tablolu işlemler ve PartiQL ifadeleri çalıştırır (JOIN yok, çok tablolu SELECT yok) — ikisi de çoğu diğer GUI istemcisi gibi tek tablo duvarında durur. DynoTable'ın bir DynamoDB GUI olarak nasıl karşılaştırıldığına bakın.

SSS

PartiQL JOIN'i destekler mi? Hayır. PartiQL'in SELECT'i tek bir tabloyu (ya da onun dizinlerinden birini) okur. Çok tablolu bir sorgu ValidationException: Only select from a single table or index is supported. döndürür. API'nin geri kalanıyla aynı duvar.

İki DynamoDB tablosunu tek bir sorguda birleştirebilir misiniz? Yerel olarak hayır. DynamoDB API'sinin, iki tabloyu okuyup onları bir anahtar üzerinde eşleştiren bir ifadesi yoktur. BatchGetItem, birden çok tablodan item'ları tek bir istekte okuyabilir ama bir ON koşulu yoktur — birincil anahtarla adlandırdığınız item'ları döndürür ve eşleştirmeyi sana bırakır. Gerçek bir JOIN … ON … yalnızca DynamoDB'nin dışında olur: uygulamanızda ya da DynoTable'ın SQL Workbench'inde.

Bir tabloyu GSI'siyle birleştirebilir misiniz? Hayır — bir Global Secondary Index birleştireceğiniz ayrı bir tablo değildir; aynı item'ların alternatif bir anahtar görünümüdür. Belirli bir SELECT'te ya tabloyu ya da dizini Query edersiniz, ikisini birlikte birleştirmiş olarak değil. Bir GSI, item'lara farklı bir anahtarla ulaşmanıza izin verir, ki bu genellikle en baştan bir birleştirme ihtiyacını ortadan kaldırır.

İki AWS hesabı arasında (ya da farklı hesaplardaki iki tabloda) birleştirebilir misiniz? Yerel olarak hayır — hesaplar arası bir birleştirme ilkeli yoktur. BatchGetItem, o tablonun kaynak tabanlı politikası çağıranınıza erişim veriyorsa (Mart 2024'ten beri) başka bir hesabın tablosuna ulaşabilir ama yine de bir ON koşulu yoktur, bu yüzden bir birleştirme değil, çok tablolu bir okumadır. Her tarafı okuyup sonuçları uygulamanızda ya da DynoTable'ın Workbench'i gibi bir araçta birleştirirsiniz.

Denormalizasyon gerçekten bir birleştirmeden iyi mi? DynamoDB'nin hedef iş yükü için — öngörülebilir, yüksek hacimli okumalar — evet. Maliyeti yazma zamanına taşırsınız (ve bir miktar veri tekrarını kabul edersiniz), karşılığında düz ölçeklenen tek istekli okumalar alırsınız. Single-table tasarımı rehberi takasları kapsar.


Bu okumalar için anahtarları ve koşulları elle kurmak zahmetlidir — expression builder senin için KeyConditionExpression / FilterExpression söz dizimini üretir ve DynoTable bir geçici çözüm yetmediğinde gerçek SQL'i çalıştırır.

PartiQL ret mesajları 2026-08-11'de DynamoDB Local'a karşı yeniden üretildi (us-east-1, @aws-sdk/client-dynamodb v3.1096.0) ve ValidationException.message alanından kelimesi kelimesine alıntılandı.

Güncellendi