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
JOINoperatörü yoktur. Hiçbir zaman olmadı. - PartiQL'in
SELECT'i yalnızca tek tabloludur — dilbilgisi tam anlamıylaSELECT … FROM {{table}}[.{{index}}]'tir ve onu iki tabloya doğrultmakValidationException: 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 DESCesnektir 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.pkValidationException: 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 membersana 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
Orderstablosu ve birCustomerstablosu 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ı.