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
SELECTdo PartiQL do DynamoDB éSELECT … FROM … [WHERE …] [ORDER BY …]— e essa é a lista inteira. SemGROUP BY, semHAVING, sem funções de agregação, semJOIN(referência doSELECTdo PartiQL da AWS). - Como o DynamoDB "não suporta nativamente operações de agregação como
SUMouCOUNTentre 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/AVGdiretamente 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):
- O app escreve o item bruto (um pedido, um download, um evento). Sem lógica de agregação.
- O DynamoDB Streams captura a escrita como um registro de stream.
- Uma Lambda ligada ao stream lê o novo item, deriva o grupo (status, mês, categoria…) e dá
ADDno item de agregação correspondente com umUpdateItematômico — que "evita condições de corrida read-modify-write" quando muitas invocações tocam o mesmo contador. - 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
QuerycomLimit 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) + 1Isso é correto e às vezes a decisão certa — mas seja honesto sobre o custo. Um Scan lê cada
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 item — a 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 quente | Padrão 1 — contador atômico (ADD) |
| Agregações sem tocar no caminho de escrita | Padrão 2 — rollup com Streams + Lambda |
| Uma contagem restrita a uma partição | Padrão 3 — Query e agrupar no app |
| Totais exatos, sem desvio | Padrão 1/2 com proteção de idempotência |
Um GROUP BY pontual enquanto explora | Workbench do DynoTable (abaixo) ou Athena |
| BI/relatório recorrente com SQL | Conector 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 DESCO 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.