Intermediário9 min de leitura

DynamoDB GROUP BY: Como Agregar Sem uma Cláusula GROUP BY

Não há GROUP BY no DynamoDB. Também não há COUNT, SUM ou AVG — nem na API nativa, nem no PartiQL. O DynamoDB é um store chave-valor / documento, não um motor de analytics, então agregação é algo que você constrói, não algo que o planejador de consultas faz por você.

Dá para fazer GROUP BY no DynamoDB?

Não. O DynamoDB não tem GROUP BY, HAVING nem funções de agregação como COUNT, SUM e AVG — nem na API nativa, nem no PartiQL, cujo SELECT aceita apenas WHERE e ORDER BY. Você agrega pré-computando os totais conforme os dados mudam (contadores atômicos ou rollups com Streams + Lambda) ou agrupando no lado do app depois de ler.

  • A gramática do SELECT do PartiQL do DynamoDB é SELECT … FROM … [WHERE …] [ORDER BY …] — e essa é a lista inteira. Sem GROUP BY, sem HAVING, sem funções de agregação, sem JOIN (referência do SELECT do PartiQL da AWS).
  • Como o DynamoDB "não suporta nativamente operações de agregação como SUM ou COUNT entre itens", a própria orientação da AWS é pré-computar as agregações conforme os dados mudam e armazenar os resultados como itens comuns (AWS: agregação materializada).
  • A alternativa — ler cada item e depois agregar no seu app — funciona, mas você paga para ler a tabela inteira a cada consulta.
  • Para exploração pontual, o SQL Workbench do DynoTable roda GROUP BY / COUNT / SUM / AVG diretamente contra uma tabela ao vivo — o SQL que o endpoint PartiQL do DynamoDB rejeita.

Por que agregação é difícil no DynamoDB

O DynamoDB não tem motor de agregação em tempo de scan. Query e Scan retornam itens; eles não os dobram. Um Scan lê a tabela inteira 1 MB por vez, e a capacidade que ele consome é baseada nos itens que lê, não nas linhas que você mantém — uma FilterExpression é aplicada depois do scan, mas antes de os resultados retornarem, então ela estreita o conjunto de resultados sem baixar a conta (referência da API Scan da AWS: um filtro "não consome unidades de capacidade de leitura adicionais"; a capacidade é baseada no tamanho do item escaneado, não no retornado). Não há nem um gancho de GROUP-BY para pendurar uma soma ou contagem em primeiro lugar.

O PartiQL não muda isso. O PartiQL é um dialeto SQL-compatível sobre o mesmo motor, então herda os mesmos limites — é uma superfície de sintaxe, não um novo modelo de execução. A gramática documentada do SELECT simplesmente não tem o token GROUP BY. Para a lacuna completa entre PartiQL e SQL de verdade, veja PartiQL vs SQL.

Então a pergunta não é "como escrevo um GROUP BY" — é "onde mora minha agregação, e quando ela é computada?" Há três respostas.

Padrão 1: agregar na escrita (contadores atômicos)

Se você conhece os grupos de antemão — contagem por status, total por cliente, downloads por mês — mantenha um item contador e atualize-o a cada escrita.

Use uma update expression ADD para que o incremento seja atômico e seguro para concorrência. O ADD funciona em números e sets, e evita a corrida read-modify-write, então dois escritores incrementando o mesmo contador nunca se atropelam (a AWS observa que o ADD atômico "evita condições de corrida read-modify-write"):

UpdateItem
Key                         { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression            "ADD orderCount :one"
ExpressionAttributeValues   { ":one": 1 }

Este é o seu SELECT COUNT(*) … GROUP BY status — só que a contagem já está ali como um item, legível em um GetItem de milissegundos de um dígito. O trade-off: você precisa conhecer a chave de agrupamento no momento da escrita, e você acopla a atualização do contador ao caminho de escrita. Se o app cair depois da escrita mas antes da atualização do contador, os dois saem de sincronia — que é exatamente o modo de falha que o próximo padrão desacopla.

Padrão 2: rollups com DynamoDB Streams + Lambda

Quando você não quer lógica de agregação no caminho de escrita — ou a escrita é um PutItem simples que você não consegue envolver facilmente — mova-a a jusante. Este é o próprio padrão recomendado da AWS, agregação materializada (AWS: usando GSIs para consultas de agregação materializada):

  1. O app escreve o item bruto (um pedido, um download, um evento). Sem lógica de agregação.
  2. O DynamoDB Streams captura a escrita como um registro de stream.
  3. Uma Lambda ligada ao stream lê o novo item, deriva o grupo (status, mês, categoria…) e dá ADD no item de agregação correspondente com um UpdateItem atômico — que "evita condições de corrida read-modify-write" quando muitas invocações tocam o mesmo contador.
  4. Você consulta a agregação pré-computada — muitas vezes por meio de um GSI esparso que indexa apenas os itens de rollup, então "top 10 deste mês" é uma Query com Limit 10.

O truque do GSI esparso: apenas os itens de agregação carregam o atributo indexado (por exemplo, Month), então as linhas de evento bruto são excluídas do índice automaticamente — "uma pequena fração do total de itens na tabela", o que mantém o índice barato e a leitura rápida.

Isso desacopla a agregação do caminho de escrita e mantém as escritas simples, ao custo de consistência eventual — a AWS observa "um atraso de alguns segundos entre um download ser registrado e a agregação ser atualizada." Para dashboards, leaderboards e contadores de tendência isso é tolerável.

A mesma ressalva de retry se aplica: uma invocação de Lambda repetida roda o ADD de novo, então "um retry incrementaria a contagem mais de uma vez", deixando um valor aproximado. Para contagens exatas, adicione idempotência (por exemplo, uma condition expression baseada no id do item de origem); caso contrário, a pequena margem é tolerável para analytics e leaderboards.

Padrão 3: agrupamento no lado do app após Scan/Query

A opção de força bruta: ler os itens, agrupá-los no seu código.

groups = {}
for item in paginate(table.scan()):       # ou query() para uma partição
    key = item["status"]
    groups[key] = groups.get(key, 0) + 1

Isso é correto e às vezes a decisão certa — mas seja honesto sobre o custo. Um Scancada item da tabela, e a capacidade de leitura é a mesma com ou sem filtro. Então o agrupamento no lado do app sobre um Scan completo significa que você paga para ler a tabela inteira a cada agregação, e a latência cresce com a tabela. A AWS lista "escanear e contar em tempo de leitura" como "adequado apenas para conjuntos de dados muito pequenos onde a latência não é uma preocupação" (AWS: por que pré-computar agregações).

Restrito a uma única partição via Query (por exemplo, contar os pedidos de um cliente), o agrupamento no lado do app é perfeitamente razoável — você está lendo só uma coleção de itens. Para a lacuna de custo completa entre os dois, veja Query vs Scan. Para estimar o que um dado scan de agregação vai ler antes de rodá-lo, dimensione um item representativo com a calculadora de tamanho de itema capacidade de leitura arredonda para cima por 4 KB, então o tamanho do item dirige a conta.

Para SQL analítico genuinamente ad-hoc sobre uma tabela do DynamoDB — o descartável "GROUP BY status, conte-os" que você rodaria uma vez — a resposta da AWS é apontar um motor separado para ela: o conector do Amazon Athena para DynamoDB permite consultar a tabela com SQL de verdade (GROUP BY, agregações, até JOINs com outras fontes) via um conector Lambda (AWS: conector do Amazon Athena para DynamoDB). Ele escaneia a tabela nos bastidores, então é uma ferramenta de relatório/BI, não um caminho quente.

Qual padrão eu uso?

Você precisa de…Use
Um total de grupo conhecido num caminho quentePadrão 1 — contador atômico (ADD)
Agregações sem tocar no caminho de escritaPadrão 2 — rollup com Streams + Lambda
Uma contagem restrita a uma partiçãoPadrão 3 — Query e agrupar no app
Totais exatos, sem desvioPadrão 1/2 com proteção de idempotência
Um GROUP BY pontual enquanto exploraWorkbench do DynoTable (abaixo) ou Athena
BI/relatório recorrente com SQLConector do Athena para DynamoDB

Rodando GROUP BY diretamente no SQL Workbench do DynoTable

Os padrões acima são como você serve agregações em produção. Mas quando você está explorando uma tabela — "quantos pedidos por status, agora?" — você não quer provisionar uma Lambda ou montar o Athena. Você quer digitar a consulta.

É para isso que serve o SQL Workbench do DynoTable. Ele roda SQL de verdade — GROUP BY, COUNT, SUM, AVG, HAVING, até JOIN — diretamente contra suas tabelas DynamoDB ao vivo, executando a agregação no lado do cliente sobre as linhas que lê. É o SQL que o endpoint PartiQL do DynamoDB rejeita:

SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESC

O enquadramento honesto: por baixo, o DynoTable lê os itens da forma que a API permite (Query onde pode, Scan onde precisa), materializa-os e faz o agrupamento no Workbench — a mesma mecânica de "ler e depois agregar" do Padrão 3, só que sem o loop, e dentro das regras de padrão de acesso do DynamoDB. Ele foi feito para exploração e análise ad-hoc, não para substituir um rollup de produção num caminho de leitura quente. Para isso, pré-compute (Padrão 1 / 2).

Para o lado do JOIN da mesma cunha — o DynoTable roda joins entre tabelas que o PartiQL também não consegue — veja DynamoDB JOIN. Comparando clientes GUI exatamente nessa capacidade? Veja a comparação de GUIs para DynamoDB.

FAQ

O PartiQL do DynamoDB suporta GROUP BY? Não. O SELECT do PartiQL do DynamoDB suporta apenas WHERE e ORDER BY — sem GROUP BY, HAVING, funções de agregação ou JOIN. A gramática é documentada como SELECT … FROM … [WHERE …] [ORDER BY …].

Posso fazer COUNT(*) sobre uma tabela inteira do DynamoDB? Não como função de agregação — o PartiQL não tem nenhuma. A API te dá Select=COUNT em um Scan/Query, que retorna uma contagem de itens correspondentes mas ainda lê (e cobra) cada item que o scan toca (referência da API Scan da AWS: a capacidade é baseada nos itens examinados, não nos retornados). Para um total lido com frequência, mantenha um item contador (Padrão 1).

Posso fazer GROUP BY na partition key? Não no DynamoDB nem no PartiQL. Se "por partition key" é um padrão de acesso conhecido, mantenha um item de agregação por chave com um ADD atômico (Padrão 1), ou faça o rollup com Streams + Lambda (Padrão 2).

Como faço SUM ou AVG por grupo? SUM: mantenha um total acumulado por grupo e dê ADD nele na escrita. AVG: armazene tanto a soma quanto a contagem e divida no momento da leitura — não há média nativa. Para um AVG exploratório pontual, rode-o no SQL Workbench do DynoTable ou via o conector do Athena para DynamoDB.

Existe uma gambiarra de partiql group by? Nenhuma do lado do PartiQL. Ou pré-compute a agregação (contadores/Streams) e dê SELECT no item de rollup, ou rode o GROUP BY num motor que tenha um — o Workbench do DynoTable para ad-hoc, o Athena para relatório recorrente.


Quer rodar GROUP BY contra suas próprias tabelas sem escrever uma Lambda? Experimente o DynoTable e aponte o SQL Workbench para uma tabela ao vivo.

Atualizado