Intermediário10 min de leitura

DynamoDB JOIN: Como ingressar em tabelas

Não há JOIN em DynamoDB. O API não possui operador de junção, o modelo de dados não possui chaves estrangeiras e - a parte que surpreende a maioria das pessoas - , o Camada de consulta com sabor SQL, também não adiciona nenhuma. Um PartiQL SELECT lê exatamente uma mesa.

Se você veio de um banco de dados relacional, esta é a primeira barreira que você encontra. Isto guia cobre por que o muro está lá, as quatro coisas que os desenvolvedores fazem, o um caso em que você realmente precisa de uma junção real – e como executá-la.

DynamoDB pode fazer junções?

Não. DynamoDB não pode ingressar em tabelas - não através do API de baixo nível (GetItem / Query / Scan / BatchGetItem), não através, e não através de qualquer planejador de consultas integrado, porque não há nenhum. Cada leitura é mapeada para uma tabela ou um de seus índices; combinar duas tabelas em uma chave correspondente é algo que você faz no seu aplicativo depois DynamoDB retorna os itens, nunca dentro dele.

  • DynamoDB não possui operador JOIN. Nunca aconteceu.
  • O SELECT de PartiQL é apenas tabela única — a gramática é literalmente SELECT… FROM {{table}}[.{{index}}], e apontar para duas tabelas retorna ValidationException: apenas a seleção de uma única tabela ou índice é suportada.
  • A correção que AWS recomenda é não precisar de uma junção:, ou usar design de tabela única para que os itens relacionados fiquem uma partição que você busca em uma única solicitação.
  • Para o caso genuíno de mesa cruzada/ad-hoc, você participa fora DynamoDB — em seu aplicativo ou com uma ferramenta que faça isso por você.

Por que DynamoDB não tem junções

Um SQL JOIN pede ao banco de dados para ler múltiplas tabelas e montá-las na consulta tempo. Próprio de AWS guia para modelagem de dados relacionais explica o custo: uma consulta como

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

é flexível, mas "cada junção na consulta aumenta a complexidade do tempo de execução do consulta porque os dados de cada tabela devem ser preparados e depois montados." Isso o trabalho é ilimitado – seu custo depende dos dados, não da consulta – o que é exatamente a propriedade DynamoDB se recusa a ter.

Então AWS projetou a restrição. DynamoDB é, em suas palavras, "construído para minimizar as restrições [CPU e rede] eliminando JOINs (e incentivando a desnormalização dos dados) e otimizando a arquitetura do banco de dados para responder completamente a uma consulta de aplicativo com uma única solicitação para um item." Esses são as qualidades que compram latência de um dígito em milissegundos em qualquer escala: o o custo de tempo de execução de uma leitura DynamoDB é constante, independentemente do tamanho da tabela. Há nenhum mecanismo de junção e nenhum conceito de chave estrangeira para planejar - por design.

"Mas PartiQL é SQL, certamente ele entra?"

Não. PartiQL fornece a sintaxe SELECT / INSERT / UPDATE / DELETE DynamoDB, mas é SQL-compatível, não SQL. O gramática SELECT oficial é:

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

FROM leva uma tabela (opcionalmente um de seus índices). Não há segundo Tabela FROM, sem JOIN, sem subconsulta, sem CTE. Corremos todos os três contra DynamoDB para ver exatamente como cada um falha.

Um JOIN explícito:

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.

Duas tabelas em FROM — mesma rejeição, então o motor não está recusando o JOIN palavra-chave, está recusando a segunda tabela:

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

Uma subconsulta falha de forma diferente, o que vale a pena saber se você estiver depurando uma. PartiQL não atinge nenhuma verificação de múltiplas tabelas — ele rejeita o operando IN antes disso, você receberá uma mensagem que nunca menciona tabelas:

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

Nenhuma frase lhe dá uma adesão. O primeiro dois morrem na segunda tabela e o terceiro morre ainda mais cedo, no operando.

Se você quiser o raciocínio completo sobre por que PartiQL se parece com SQL, mas não consegue se comportar goste, veja PartiQL vs SQL.

As 4 soluções alternativas que os desenvolvedores realmente usam

1. Desnormalizar (copiar os dados)

Armazene os campos que você associaria diretamente ao item. Uma Ordem carrega um instantâneo de customerName e shippingAddress em vez de um customerId você resolveria mais tarde. Uma leitura, sem adesão.

A distribuição do tempo de gravação é o custo. Quando a fonte muda você atualiza cada cópia (normalmente através de ummanipulador). Você está negociando complexidade de leitura por complexidade de gravação – geralmente uma boa troca para um aplicativo com muita leitura.

2. Design de tabela única (pré-junção na partição)

Coloque entidades relacionadas em uma tabela sob uma chave de partição compartilhada para que

é o resultado unido. Um cliente e todos os seus pedidos compartilham PK = "CLIENTE#42"; um Query retorna o item do cliente mais cada pedido item - a "junção" já aconteceu no momento da gravação.

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

Esta é a resposta canônica DynamoDB para relacionamentos um-para-muitos. Completo passo a passo em design de tabela única.

3. Junção do lado do aplicativo (duas leituras, costura no código)

Leia a tabela A, pegue as chaves que você recebeu, leia a tabela B e mescle as dois conjuntos de resultados em seu aplicativo. É a lógica de junção relacional - apenas rodando no seu código em vez do banco de dados:

// "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
}));

Ótimo para pequenos fan-outs. Com muitos pedidos, torna-se um problema N+1 — uma leitura para listar pedidos, então uma leitura por pedido - o que é lento e queima a capacidade de leitura. BatchGetItem (próximo) reduz a segunda onda em uma viagem de ida e volta.

4. BatchGetItem (uma viagem de ida e volta, múltiplas mesas)

BatchGetItem é o mais próximo que o API chega de "tocar duas mesas ao mesmo tempo": uma request retorna "os atributos de um ou mais itens de um ou mais tabelas", até 100 itens ou 16 MB por chamada, o que ocorrer primeiro. Isso reduz as viagens de ida e volta de uma junção do lado do aplicativo — mas não é uma junção. Você "identificar itens solicitados por chave primária"; não há condição ON e não correspondência relacional. Você ainda precisa conhecer as chaves de antemão e costurar o respostas juntos você mesmo.

Quando um JOIN real é inevitável

As quatro soluções alternativas cobrem bem os caminhos de leitura de produção. Onde eles caem é a consulta ad-hoc, exploratória e analítica — aquela para a qual você não modelou:

  • "Quais clientes na UE fizeram um pedido superior a US$ 500 no mês passado?" em um Tabela Pedidos e uma tabela Clientes.
  • Uma verificação única da qualidade dos dados que une dois tipos de entidades.
  • Relatórios e agregados (GROUP BY, SUM, COUNT) — que DynamoDB não tem operador para nada.

Estas são exatamente as consultas que você não pode pré-preparar em uma partição, porque definição, você não sabia que iria perguntar a eles. O instinto relacional - escreva um JOIN — é o correto aqui. DynamoDB simplesmente não pode atendê-lo nativamente e nem pode PartiQL.

A resposta usual dos pesos pesados é exportar para S3 e consultar com Athena (ou use o conector de consulta federada do Athena para JOIN em uma tabela ativa) ou canalize para um armazém. Isso é correto para análises verdadeiras em escala, mas é uma muito encanamento para uma pergunta que você deseja que seja respondida agora, em sua mesa ao vivo.

Executando um JOIN real com DynoTable's SQL Workbench

DynoTable é um cliente DynamoDB de desktop cujo SQL Workbench é executado SQL real — incluindo JOIN, GROUP BY e funções agregadas — em seu Tabelas DynamoDB. Ele lê os itens através do DynamoDB API normal, então executa as partes relacionais da consulta no cliente. Então você pode escrever:

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
  • e obter um conjunto de resultados, em tabelas que não possuem relacionamento definido e um mecanismo de consulta que não possui palavra-chave JOIN.

A advertência honesta - "dentro das regras de padrão de acesso de DynamoDB": o Workbench ainda lê DynamoDB, então uma junção ilimitada é uma leitura ilimitada. O consultas mais rápidas são aquelas onde a cláusula WHERE (ou o ON da junção atributo) atinge uma chave de partição ou um GSI em pelo menos um lado, então DynamoDB executa um Query em vez de uma tabela completa scan antes da execução da junção. O Workbench não revogar as restrições deste guia - ele apenas permite que você faça a pergunta SQL em vez de você mesmo escrever o ponto à mão e dizer o que está fazendo embaixo.

Entre os clientes GUI, é o único "sim, você pode ingressar" que é realmente verdade: o próprio PartiQL e AWS NãoSQL Workbench — cujo construtor de operações executa operações de tabela única e instruções PartiQL (sem JOIN, sem SELECT de múltiplas mesas) — ambos param na parede de mesa única, assim como a maioria outros clientes GUI. Veja como DynoTable se compara como um DynamoDB GUI.

FAQ

O PartiQL suporta JOIN? Não. O SELECT de PartiQL lê uma única tabela (ou um de seus índices). Um consulta multitabela retorna ValidationException: selecione apenas em uma única tabela ou índice é suportado. Mesma parede que o resto do API.

Você pode unir duas tabelas DynamoDB em uma consulta? Não nativamente. O DynamoDB API não possui nenhuma instrução que leia duas tabelas e corresponda eles em uma chave. BatchGetItem pode ler itens de múltiplas tabelas em uma solicitação, mas não tem condição ON - retorna os itens que você nomeou por chave primária e deixa a correspondência para você. Um verdadeiro JOIN… ON… só acontece fora de DynamoDB: em seu aplicativo ou no SQL Workbench de DynoTable.

Você pode juntar uma mesa ao seu GSI? Não — um Índice Secundário Global não é uma tabela separada que você junte-se a; é uma visão alternativa dos mesmos itens. Você Query qualquer a tabela ou o índice em um determinado SELECT, não ambos unidos. Um GSI permite que você alcance itens por uma chave diferente, o que muitas vezes elimina a necessidade de um junte-se em primeiro lugar.

Você pode ingressar em duas contas AWS (ou duas mesas em contas diferentes)? Não nativamente — não há nenhuma primitiva de junção entre contas. BatchGetItem pode alcançar tabela de outra conta se a política baseada em recursos dessa tabela conceder ao chamador acesso (desde março de 2024), mas ainda não tem condição ON, então é um leitura de várias tabelas, não uma junção. Você leria cada lado e juntaria os resultados em seu aplicativo ou em uma ferramenta como DynoTable's Workbench.

A desnormalização é realmente melhor do que uma junção? Para a carga de trabalho alvo do DynamoDB – leituras previsíveis e de alto volume – sim. Você se move o custo para escrever tempo (e aceitar alguma duplicação de dados) em troca de a solicitação única lê essa escala de maneira plana. O O guia design de tabela única cobre as vantagens e desvantagens.


Construir as chaves e condições para essas leituras manualmente é complicado - o construtor de expressão gera o Sintaxe KeyConditionExpression / FilterExpression para você, e DynoTable executa o SQL real quando uma solução alternativa não funciona.

PartiQL mensagens de rejeição reproduzidas em 11/08/2026 contra DynamoDB Local (us-east-1, @aws-sdk/client-dynamodb v3.1096.0), citado literalmente de ValidationException.message.

Atualizado