DynamoDB GROUP BY: cómo agregar de otra forma
No hay GROUP BY en DynamoDB. Tampoco hay COUNT, SUM ni AVG —
ni en la API nativa, ni en PartiQL. DynamoDB es un almacén clave-valor /
documental, no un motor analítico, así que la agregación es algo que tú
construyes, no algo que hace el planificador de consultas por ti.
¿Puedes hacer GROUP BY en DynamoDB?
No. DynamoDB no tiene GROUP BY, HAVING ni funciones de agregación como COUNT, SUM y AVG — ni en la API nativa ni en PartiQL, cuyo SELECT solo acepta WHERE y ORDER BY. Agregas precalculando totales a medida que los datos cambian (contadores atómicos o resúmenes con + Lambda) o agrupando del lado de la aplicación después de leer.
- La gramática de
SELECTde PartiQL de DynamoDB esSELECT … FROM … [WHERE …] [ORDER BY …]— y esa es toda la lista. SinGROUP BY, sinHAVING, sin funciones de agregación, sinJOIN(referencia deSELECTde PartiQL de AWS). - Como DynamoDB "no admite de forma nativa operaciones de agregación como
SUMoCOUNTa través de elementos", la propia guía de AWS es precalcular los agregados a medida que los datos cambian y almacenar los resultados como elementos corrientes (AWS: agregación materializada). - La alternativa — leer cada elemento y luego agregar en tu aplicación — funciona, pero pagas por leer toda la tabla en cada consulta.
- Para exploración puntual, el SQL Workbench de DynoTable ejecuta
GROUP BY/COUNT/SUM/AVGdirectamente contra una tabla en vivo — el SQL que el endpoint de PartiQL de DynamoDB rechaza.
Por qué la agregación es difícil en DynamoDB
DynamoDB no tiene motor de agregación en tiempo de escaneo. Query y Scan devuelven elementos;
no los pliegan. Un Scan lee toda la tabla 1 MB a la vez, y la
capacidad que consume se basa en los elementos que lee, no en las filas que conservas — una
FilterExpression se aplica después del escaneo pero antes de que se devuelvan los resultados, así que
estrecha el conjunto de resultados sin bajar la factura (referencia de la API Scan de AWS: un filtro
"no consume unidades de capacidad de lectura adicionales"; la capacidad se basa en el tamaño del
elemento escaneado, no devuelto). No hay un enganche de GROUP BY
del que colgar una suma o un recuento en primer lugar.
PartiQL no cambia esto. PartiQL es un dialecto compatible con SQL sobre el mismo
motor, así que hereda los mismos límites — es una superficie de sintaxis, no un nuevo
modelo de ejecución. La gramática de SELECT documentada
simplemente no tiene un token GROUP BY.
Para la brecha completa entre PartiQL y el SQL real, consulta
PartiQL frente a SQL.
Así que la pregunta no es "cómo escribo un GROUP BY" — es "dónde vive mi
agregado y cuándo se calcula". Hay tres respuestas.
Patrón 1: agregar en la escritura (contadores atómicos)
Si conoces los grupos de antemano — recuento por estado, total por cliente, descargas por mes — mantén un elemento contador y actualízalo en cada escritura.
Usa una ADD para que el incremento sea atómico y seguro ante concurrencia.
ADD funciona con números y conjuntos, y evita la carrera de leer-modificar-escribir, así que
dos escritores incrementando el mismo contador nunca se pisan
(AWS señala que el ADD atómico "evita las condiciones de carrera de leer-modificar-escribir"):
UpdateItem
Key { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression "ADD orderCount :one"
ExpressionAttributeValues { ":one": 1 }
Este es tu SELECT COUNT(*) … GROUP BY status — salvo que el recuento ya está
ahí como un elemento, legible en un GetItem de un solo dígito de milisegundos. La
contrapartida: debes conocer la clave de agrupación en tiempo de escritura, y acoplas la
actualización del contador a la ruta de escritura. Si la aplicación se cae después de la escritura pero
antes de la actualización del contador, los dos se desincronizan — que es exactamente el
modo de fallo que el siguiente patrón desacopla.
Patrón 2: resúmenes con DynamoDB Streams + Lambda
Cuando no quieres lógica de agregación en la ruta de escritura — o la escritura es un
PutItem simple que no puedes envolver fácilmente — muévela aguas abajo. Este es el propio
patrón recomendado de AWS, agregación materializada
(AWS: Uso de GSI para consultas de agregación materializada):
- La aplicación escribe el elemento en bruto (un pedido, una descarga, un evento). Sin lógica de agregación.
- captura la escritura como un registro del stream.
- Una Lambda conectada al stream lee el nuevo elemento, deriva el grupo
(estado, mes, categoría…) y hace
ADDal elemento agregado correspondiente con unUpdateItematómico — que "evita las condiciones de carrera de leer-modificar-escribir" cuando muchas invocaciones tocan el mismo contador. - Consultas el agregado precalculado — a menudo a través de un que
indexa solo los elementos de resumen, así que "top 10 de este mes" es una
QueryconLimit 10.
El truco del GSI disperso: solo los elementos agregados llevan el atributo indexado
(p. ej. Month), así que las filas de eventos en bruto quedan excluidas del índice automáticamente
— "una pequeña fracción del total de elementos de la tabla", lo que mantiene el índice
barato y la lectura rápida.
Esto desacopla la agregación de la ruta de escritura y mantiene las escrituras simples, a costa de la coherencia eventual — AWS señala "un retraso de unos pocos segundos entre que una descarga se registra y que la agregación se actualiza". Para paneles, tablas de clasificación y contadores de tendencia eso está bien.
Aplica la misma advertencia de reintento: una invocación de Lambda reintentada vuelve a ejecutar el ADD, así que
"un reintento incrementaría el recuento más de una vez", dejando un valor aproximado.
Para recuentos exactos, añade idempotencia (p. ej. una expresión de condición con clave en
el id del elemento fuente); de lo contrario, el pequeño margen está bien para análisis y
tablas de clasificación.
Patrón 3: agrupación del lado de la aplicación tras Scan/Query
La opción de fuerza bruta: lee los elementos, agrúpalos en tu código.
groups = {}
resp = table.scan() # or query() for one partition
while True:
for item in resp["Items"]:
key = item["status"]
groups[key] = groups.get(key, 0) + 1
if "LastEvaluatedKey" not in resp:
break
resp = table.scan(ExclusiveStartKey=resp["LastEvaluatedKey"])Esto es correcto y a veces la decisión adecuada — pero sé honesto sobre el costo. Un
Scan lee cada elemento de la tabla, y la capacidad de lectura es la misma
filtres o no. Así que la agrupación del lado de la aplicación sobre un Scan completo significa que pagas
por leer toda la tabla en cada agregación, y la latencia crece con la tabla.
AWS enumera "escanear y contar en tiempo de lectura" como "solo adecuado para conjuntos de datos muy pequeños
donde la latencia no es una preocupación"
(AWS: Por qué precalcular agregaciones).
Acotado a una sola partición mediante Query (p. ej. contar los pedidos de un
cliente), la agrupación del lado de la aplicación es perfectamente razonable — solo estás leyendo una
colección de elementos. Para la brecha de costo completa entre las dos, consulta
Query frente a Scan. En us-east-1 bajo demanda, un
Scan de tabla completa factura 0,5 RCU por 4 KB con consistencia eventual por cada
elemento examinado: una tabla de 1 GB con filas de 1 KB son unas 250.000 RCU
antes de que tu aplicación agrupe nada. Dimensiona un elemento representativo con la
calculadora de tamaño de elemento y después calcula
la tarifa del escaneo en la calculadora de precios.
Para SQL analítico genuinamente ad hoc sobre una tabla de DynamoDB — el desechable
"GROUP BY status, cuéntalos" que ejecutarías una vez— la respuesta de AWS es apuntar un
motor separado a ella: el conector de Amazon Athena para DynamoDB te permite consultar
la tabla con SQL real (GROUP BY, agregados, incluso JOIN a otras fuentes)
mediante un conector Lambda
(AWS: Conector de Amazon Athena para DynamoDB).
Escanea la tabla entre bastidores, así que es una herramienta de informes/BI, no una ruta crítica.
¿Qué patrón uso?
| Necesitas… | Usa |
|---|---|
| Un total de grupo conocido en una ruta de lectura crítica | Patrón 1 — contador atómico (ADD) |
| Agregados sin tocar la ruta de escritura | Patrón 2 — resumen con Streams + Lambda |
| Un recuento acotado a una partición | Patrón 3 — Query y luego agrupar en la aplicación |
| Totales exactos, sin desviación | Patrón 1/2 con guarda de idempotencia |
Un GROUP BY puntual mientras exploras | Workbench de DynoTable (abajo) o Athena |
| BI/informes recurrentes con SQL | Conector de Athena para DynamoDB |
Ejecutar GROUP BY directamente en el SQL Workbench de DynoTable
Los patrones de arriba son cómo sirves agregados en producción. Pero cuando estás explorando una tabla — "¿cuántos pedidos por estado, ahora mismo?" — no quieres aprovisionar una Lambda ni levantar Athena. Quieres escribir la consulta.
Para eso está el SQL Workbench de DynoTable. Ejecuta SQL real —
GROUP BY, COUNT, SUM, AVG, HAVING, incluso JOIN — directamente contra tus
tablas de DynamoDB en vivo, ejecutando la agregación del lado del cliente sobre las filas que
lee. Es el SQL que el endpoint de PartiQL de DynamoDB rechaza:
SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESCEl planteamiento honesto: por debajo, DynoTable lee los elementos como la API lo permite
(Query donde puede, Scan donde debe), los materializa y hace la
agrupación en el Workbench — la misma mecánica de "leer y luego agregar" que el Patrón
3, solo que sin el bucle, y dentro de las reglas de patrones de acceso de DynamoDB. Está
construido para exploración y análisis ad hoc, no para reemplazar un resumen de producción
en una ruta de lectura crítica. Para eso, precalcula (Patrón 1 / 2).
Para el lado del JOIN de la misma cuña — DynoTable ejecuta uniones entre tablas que PartiQL
tampoco puede — consulta DynamoDB JOIN. ¿Comparando clientes
GUI en exactamente esta capacidad? Consulta
la comparación de GUI de DynamoDB.
FAQ
¿Admite PartiQL de DynamoDB GROUP BY?
No. El SELECT de PartiQL de DynamoDB admite solo WHERE y ORDER BY — sin
GROUP BY, HAVING, funciones de agregación ni JOIN. La gramática está
documentada
como SELECT … FROM … [WHERE …] [ORDER BY …].
¿Puedo hacer COUNT(*) sobre toda una tabla de DynamoDB?
No como función de agregación — PartiQL no tiene ninguna. La API te da
Select=COUNT en un Scan/Query, que devuelve un recuento de elementos coincidentes pero
aún lee (y factura) cada elemento que el escaneo toca
(referencia de la API Scan de AWS:
la capacidad se basa en los elementos examinados, no devueltos). Para un total leído con
frecuencia, mantén un elemento contador (Patrón 1).
¿Puedo hacer GROUP BY de la clave de partición?
No en DynamoDB ni en PartiQL. Si "por clave de partición" es un patrón de acceso conocido,
mantén un elemento agregado por clave con un ADD atómico (Patrón 1), o resúmelo
con Streams + Lambda (Patrón 2).
¿Cómo hago SUM o AVG por grupo?
SUM: mantén un total acumulado por grupo y hazle ADD en la escritura. AVG: almacena
tanto la suma como el recuento y divide en tiempo de lectura — no hay promedio nativo.
Para un AVG exploratorio puntual, ejecútalo en el SQL Workbench de DynoTable o mediante el
conector de Athena para DynamoDB.
¿Hay una solución alternativa de partiql group by?
Ninguna del lado de PartiQL. O bien precalcula el agregado (contadores/Streams) y
haz SELECT del elemento de resumen, o ejecuta el GROUP BY en un motor que tenga uno —
el Workbench de DynoTable para ad hoc, Athena para informes recurrentes.
¿Quieres ejecutar GROUP BY contra tus propias tablas sin escribir una Lambda?
Prueba DynoTable y apunta el SQL Workbench a una tabla en vivo.