JOIN di DynamoDB: cara menggabungkan tabel
Tidak ada JOIN di DynamoDB. API tidak punya operator join, model data tidak
punya foreign key, dan — bagian yang paling mengejutkan kebanyakan orang —
, lapisan query rasa SQL, juga tidak menambahkannya.
SELECT PartiQL membaca tepat satu tabel.
Kalau Anda datang dari database relasional, ini adalah tembok pertama yang Anda tabrak. Panduan ini membahas mengapa tembok itu ada, empat hal yang dilakukan developer sebagai gantinya, satu-satunya kasus di mana Anda benar-benar butuh join sungguhan — dan cara menjalankannya.
Bisakah DynamoDB melakukan join?
Tidak. DynamoDB tidak bisa menggabungkan tabel — tidak lewat API tingkat rendah
(GetItem / Query / Scan / BatchGetItem), tidak lewat ,
dan tidak lewat query planner bawaan, karena tidak ada. Setiap baca memetakan ke
satu tabel atau salah satu indeksnya; menggabungkan dua tabel pada key yang cocok
adalah sesuatu yang Anda lakukan di app setelah DynamoDB mengembalikan item,
bukan di dalamnya.
- DynamoDB tidak punya operator
JOINsama sekali. Tidak pernah. SELECTPartiQL hanya satu tabel — grammar-nya secara harfiahSELECT … FROM {{table}}[.{{index}}], dan mengarahkannya ke dua tabel mengembalikanValidationException: Only select from a single table or index is supported.- Perbaikan yang direkomendasikan AWS adalah tidak perlu join: , atau pakai single-table design agar item terkait hidup di satu partisi yang Anda ambil dalam satu request.
- Untuk kasus cross-table / ad-hoc yang sungguhan, Anda join di luar DynamoDB — di app, atau dengan tool yang melakukannya untuk Anda.
Mengapa DynamoDB tidak punya join
JOIN SQL meminta database membaca banyak tabel dan merakitnya saat query. Panduan
AWS tentang modeling data relasional
merinci biayanya: query seperti
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 DESCfleksibel, tetapi «setiap join di query meningkatkan kompleksitas runtime karena data dari setiap tabel harus di-stage lalu dirakit». Pekerjaan itu tanpa batas — biayanya bergantung pada data, bukan query — tepat properti yang DynamoDB menolak miliki.
Jadi AWS merancang constraint-nya di muka. DynamoDB, dalam kata mereka sendiri,
«dibangun untuk meminimalkan kedua constraint [CPU dan jaringan] itu dengan
menghilangkan JOIN (dan mendorong denormalisasi data) serta mengoptimalkan
arsitektur agar menjawab query aplikasi sepenuhnya dengan satu request ke sebuah
item». Itulah kualitas yang membeli latensi satu digit milidetik pada skala apa
pun: biaya runtime sebuah baca DynamoDB konstan berapa pun ukuran tabel. Tidak
ada join engine dan tidak ada konsep foreign key yang bisa direncanakan —
by design.
«Tapi PartiQL itu SQL, pasti bisa join?»
Tidak. PartiQL memberi Anda sintaks SELECT / INSERT / UPDATE / DELETE
di atas DynamoDB, tetapi itu compatible dengan SQL, bukan SQL. Grammar resmi
SELECT:
SELECT {{expression}} [, ...]
FROM {{table}}[.{{index}}]
[ WHERE {{condition}} ]
[ ORDER BY {{key}} [DESC|ASC], ... ]FROM mengambil satu tabel (opsional salah satu indeksnya). Tidak ada
tabel FROM kedua, tidak ada JOIN, tidak ada subquery, tidak ada CTE. Kami
menjalankan ketiga formulasi terhadap DynamoDB untuk melihat tepat bagaimana
masing-masing gagal.
JOIN eksplisit:
SELECT o.pk FROM "Orders" o JOIN "Customers" c ON o.customerId = c.pkValidationException: Only select from a single table or index is supported.Dua tabel di FROM — penolakan yang sama, jadi engine tidak menolak keyword
JOIN, ia menolak tabel kedua:
SELECT * FROM "Orders", "Customers"ValidationException: Only select from a single table or index is supported.Subquery gagal dengan cara berbeda, yang berguna saat debugging. PartiQL
bahkan tidak sampai ke cek multi-tabel — ia menolak operand IN lebih dulu,
jadi Anda mendapat pesan yang tidak pernah menyebut tabel:
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 memberKonsekuensi praktis: tidak ada formulasi yang memberi Anda join. Dua yang pertama mati di tabel kedua, dan yang ketiga mati lebih awal, di operand.
Kalau Anda ingin seluruh alasan mengapa PartiQL terlihat seperti SQL tetapi tidak bisa berperilaku seperti itu, lihat PartiQL vs SQL.
4 workaround yang benar-benar dipakai developer
1. Denormalisasi (salin data)
Simpan field yang biasanya Anda join langsung di item. Sebuah Order membawa
snapshot customerName dan shippingAddress alih-alih customerId yang
nanti Anda resolve. Satu baca, tanpa join.
Biayanya fan-out saat tulis: ketika sumber berubah, Anda memperbarui setiap salinan (biasanya lewat handler ). Anda menukar kompleksitas baca dengan kompleksitas tulis — biasanya trade yang bagus untuk app yang read-heavy.
2. Single-table design (pre-join di partisi)
Letakkan entity terkait di satu tabel di bawah partition key bersama agar
sebuah adalah hasil yang sudah di-join.
Customer dan semua order-nya berbagi PK = "CUSTOMER#42"; satu Query
mengembalikan item customer plus setiap item order — «join»-nya sudah terjadi
saat tulis.
Query PK = "CUSTOMER#42"
→ CUSTOMER#42 / PROFILE (the customer)
→ CUSTOMER#42 / ORDER#1001 (an order)
→ CUSTOMER#42 / ORDER#1002 (an order)
Ini jawaban kanonik DynamoDB untuk relasi one-to-many. Penjelasan lengkap di single-table design.
3. Join di sisi aplikasi (dua baca, jahit di kode)
Baca dari tabel A, ambil key yang Anda bawa, baca dari tabel B, dan gabungkan kedua result set di aplikasi. Itu logika join relasional — hanya saja berjalan di kode Anda, bukan di database:
// "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
}));Baik untuk fan-out kecil. Dengan banyak order, ini menjadi masalah N+1 —
satu baca untuk daftar order, lalu satu baca per order — lambat dan
menghabiskan read capacity. BatchGetItem (di bawah) meruntuhkan gelombang
kedua itu jadi satu round-trip.
4. BatchGetItem (satu round-trip, banyak tabel)
BatchGetItem
adalah yang paling dekat API dengan «menyentuh dua tabel sekaligus»: satu
request mengembalikan «atribut dari satu atau lebih item dari satu atau lebih
tabel», hingga 100 item atau 16 MB per panggilan, mana yang lebih dulu.
Memotong round-trip join di app — tetapi bukan join. «Anda mengidentifikasi
item yang diminta lewat primary key»; tidak ada kondisi ON atau matching
relasional. Anda tetap harus tahu key-nya di muka dan menjahit respons sendiri.
Kapan JOIN sungguhan tak terhindarkan
Empat workaround di atas menutup path baca produksi dengan baik. Di mana mereka jatuh adalah query ad-hoc, eksplorasi, analitik — yang tidak Anda modelkan:
- «Customer EU mana yang membuat order lebih dari $500 bulan lalu?» lintas
tabel
OrdersdanCustomers. - Cek kualitas data one-off yang menggabungkan dua tipe entity.
- Reporting dan agregat (
GROUP BY,SUM,COUNT) — yang DynamoDB tidak punya operatornya sama sekali.
Itulah tepat query yang tidak bisa Anda bake ke dalam partisi, karena menurut
definisi Anda tidak tahu akan membuatnya. Insting relasional — menulis JOIN —
benar di sini. DynamoDB sekadar tidak bisa menyajikannya secara native, dan
PartiQL juga tidak.
Jawaban berat yang biasa adalah
export ke S3 dan query dengan Athena
(atau konektor federated query Athena untuk JOIN ke tabel live), atau
menyalurkan ke warehouse. Benar untuk analytics skala besar sungguhan, tetapi
banyak plumbing untuk pertanyaan yang Anda ingin jawab sekarang, terhadap
tabel live Anda.
Menjalankan JOIN sungguhan dengan SQL Workbench DynoTable
DynoTable adalah client DynamoDB desktop yang SQL Workbench-nya
menjalankan SQL sungguhan — termasuk JOIN, GROUP BY, dan fungsi agregasi —
terhadap tabel DynamoDB Anda. Ia membaca item lewat API DynamoDB biasa, lalu
menjalankan bagian relasional query di klien. Jadi Anda bisa menulis:
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— dan mendapat result set, terhadap tabel yang tidak punya relasi terdefinisi
dan query engine yang tidak punya keyword JOIN.
Caveat jujur — «di dalam aturan access pattern DynamoDB»: Workbench tetap
membaca lewat DynamoDB, jadi join tanpa batas adalah baca tanpa batas. Query
tercepat adalah yang klausul WHERE-nya (atau atribut ON join) mengenai
partition key atau GSI di setidaknya satu sisi,
agar DynamoDB menjalankan Query alih-alih
Scan full-table sebelum join berjalan.
Workbench tidak menghapus constraint panduan ini — ia hanya membiarkan Anda
mengajukan pertanyaan dalam SQL alih-alih menulis jahitan manual, dan
memberitahu apa yang dilakukannya di bawahnya.
Di antara client GUI, ini satu-satunya «ya, Anda bisa join» yang benar-benar benar: PartiQL dan NoSQL Workbench AWS sendiri — yang operation builder-nya menjalankan operasi satu-tabel dan statement PartiQL (tanpa JOIN, tanpa SELECT multi-tabel) — keduanya berhenti di tembok satu-tabel, seperti kebanyakan client GUI lain. Lihat bagaimana DynoTable dibandingkan sebagai GUI DynamoDB.
FAQ
Apakah PartiQL mendukung JOIN?
Tidak. SELECT PartiQL membaca satu tabel (atau salah satu indeksnya). Query
multi-tabel mengembalikan ValidationException: Only select from a single table or index is supported. Tembok yang sama dengan sisa API.
Bisakah Anda menggabungkan dua tabel DynamoDB dalam satu query?
Tidak secara native. API DynamoDB tidak punya statement yang membaca dua tabel
dan mencocokkannya pada sebuah key. BatchGetItem bisa membaca item dari
beberapa tabel dalam satu request, tetapi tidak punya kondisi ON — ia
mengembalikan item yang Anda sebut lewat primary key dan menyerahkan matching
kepada Anda. JOIN … ON … sungguhan hanya terjadi di luar DynamoDB: di app,
atau di SQL Workbench DynoTable.
Bisakah Anda menggabungkan tabel dengan GSI-nya?
Tidak — global secondary index bukan tabel terpisah
yang Anda join; itu tampilan by-key alternatif dari item yang sama. Anda
Query atau tabel atau indeks dalam satu SELECT tertentu, bukan keduanya
yang di-join. GSI membiarkan Anda mencapai item lewat key berbeda, yang sering
menghilangkan kebutuhan join sejak awal.
Bisakah Anda join lintas dua akun AWS (atau dua tabel di akun berbeda)?
Tidak secara native — tidak ada primitif join lintas-akun. BatchGetItem bisa
mencapai tabel akun lain jika resource-based policy tabel itu memberi akses ke
caller Anda (sejak Maret 2024), tetapi tetap tanpa kondisi ON, jadi itu baca
multi-tabel, bukan join. Anda akan membaca tiap sisi dan menggabungkan hasil di
aplikasi atau tool seperti Workbench DynoTable.
Apakah denormalisasi benar-benar lebih baik daripada join? Untuk workload target DynamoDB — baca prediktabel volume tinggi — ya. Anda memindahkan biaya ke saat tulis (dan menerima sedikit duplikasi data) demi baca satu-request yang skalanya datar. Panduan single-table design menutupi trade-off-nya.
Membangun key dan kondisi untuk baca-baca ini secara manual membosankan —
Expression Builder menghasilkan sintaks
KeyConditionExpression / FilterExpression untuk Anda, dan
DynoTable menjalankan SQL sungguhan ketika workaround tidak cukup.
Pesan penolakan PartiQL direproduksi 11 Agustus 2026 terhadap DynamoDB Local (us-east-1,
@aws-sdk/client-dynamodb v3.1096.0), dikutip kata demi kata dari
ValidationException.message.