Intermedio10 min de lectura

DynamoDB GROUP BY: cómo agregar sin una cláusula GROUP BY

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 / documento, no un motor analítico, así que la agregación es algo que construyes, no algo que hace el planificador de consultas por ti.

¿Se puede 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 los totales a medida que los datos cambian (contadores atómicos o rollups con Streams + Lambda) o agrupando del lado de la aplicación tras la lectura.

  • La gramática de SELECT de PartiQL de DynamoDB es SELECT … FROM … [WHERE …] [ORDER BY …] — y esa es la lista completa. Sin GROUP BY, sin HAVING, sin funciones de agregación, sin JOIN (referencia de SELECT de PartiQL de AWS).
  • Como DynamoDB «no soporta de forma nativa operaciones de agregación como SUM o COUNT entre items», la propia recomendación de AWS es precalcular los agregados a medida que los datos cambian y almacenar los resultados como items ordinarios (AWS: agregación materializada).
  • La alternativa — leer cada item 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 / AVG directamente contra una tabla en vivo — el SQL que el endpoint 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 items; no los pliegan. Un Scan lee la tabla entera 1 MB a la vez, y la capacidad que consume se basa en los items que lee, no en las filas que conservas — un FilterExpression se aplica después del scan pero antes de que vuelvan los resultados, así que estrecha el conjunto de resultados sin bajar la factura (referencia de la API de Scan de AWS: un filtro «no consume ninguna unidad de capacidad de lectura adicional»; la capacidad se basa en el tamaño del item escaneado, no devuelto). No hay un gancho 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 SQL real, mira 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 item contador y actualízalo en cada escritura.

Usa una expresión de actualización ADD para que el incremento sea atómico y seguro frente a concurrencia. ADD funciona sobre números y conjuntos, y evita la carrera de lectura-modificación-escritura, 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 lectura-modificación-escritura»):

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

Este es tu SELECT COUNT(*) … GROUP BY status — excepto que el recuento ya está ahí sentado como un item, leíble en un GetItem de un dígito de milisegundo. El compromiso: 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 falla 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: rollups con DynamoDB Streams + Lambda

Cuando no quieres lógica de agregación en la ruta de escritura — o la escritura es un PutItem plano 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):

  1. La aplicación escribe el item en crudo (un pedido, una descarga, un evento). Sin lógica de agregación.
  2. DynamoDB Streams captura la escritura como un registro de stream.
  3. Una Lambda conectada al stream lee el item nuevo, deriva el grupo (estado, mes, categoría…), y hace ADD al item de agregado coincidente con un UpdateItem atómico — que «evita las condiciones de carrera de lectura-modificación-escritura» cuando muchas invocaciones tocan el mismo contador.
  4. Consultas el agregado precalculado — a menudo a través de un GSI disperso que indexa solo los items de rollup, así que «los 10 primeros de este mes» es un Query con Limit 10.

El truco del GSI disperso: solo los items de agregado llevan el atributo indexado (p. ej. Month), así que las filas de evento en crudo se excluyen del índice automáticamente — «una pequeña fracción del total de items en 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 consistencia eventual — AWS señala «un retraso de unos segundos entre que se registra una descarga y se actualiza la agregación». Para paneles, clasificaciones y contadores de tendencia eso está bien.

Aplica el mismo aviso 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 clavada en el id del item fuente); de lo contrario, el pequeño margen está bien para análisis y clasificaciones.

Patrón 3: agrupación del lado de la aplicación tras Scan/Query

La opción de fuerza bruta: lee los items, agrúpalos en tu código.

groups = {}
for item in paginate(table.scan()):       # o query() para una partición
    key = item["status"]
    groups[key] = groups.get(key, 0) + 1

Esto es correcto y a veces la decisión correcta — pero sé honesto sobre el coste. Un Scan lee cada item 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 lista «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).

Reducido a una sola partición vía 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 items. Para la brecha completa de coste entre las dos, mira Query frente a Scan. Para estimar lo que un scan de agregación dado leerá antes de ejecutarlo, dimensiona un item representativo con la calculadora de tamaño de itemla capacidad de lectura redondea hacia arriba por cada 4 KB, así que el tamaño del item determina la factura.

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 él: el conector DynamoDB de Amazon Athena te deja consultar la tabla con SQL real (GROUP BY, agregados, incluso JOINs a otras fuentes) vía un conector Lambda (AWS: Conector DynamoDB de Amazon Athena). Escanea la tabla por detrás, así que es una herramienta de informes/BI, no una ruta caliente.

¿Qué patrón uso?

Necesitas…Usa
Un total de grupo conocido en una ruta de lectura calientePatrón 1 — contador atómico (ADD)
Agregados sin tocar la ruta de escrituraPatrón 2 — rollup con Streams + Lambda
Un recuento reducido a una particiónPatrón 3 — Query luego agrupar en la app
Totales exactos, sin derivaPatrón 1/2 con guarda de idempotencia
Un GROUP BY puntual mientras explorasWorkbench de DynoTable (abajo) o Athena
BI/informes recurrentes con SQLConector DynamoDB de Athena

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 montar Athena. Quieres escribir la consulta.

Para eso es 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 en el lado del cliente sobre las filas que lee. Es el SQL que el endpoint 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 DESC

El encuadre honesto: por debajo DynoTable lee items de la forma en que 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 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 rollup de producción en una ruta de lectura caliente. Para eso, precalcula (Patrón 1 / 2).

Para el lado del JOIN de la misma cuña — DynoTable ejecuta joins entre tablas que PartiQL tampoco puede — mira DynamoDB JOIN. ¿Comparando clientes con GUI exactamente sobre esta capacidad? Mira la comparativa de GUI para DynamoDB.

Preguntas frecuentes

¿Soporta PartiQL de DynamoDB GROUP BY? No. El SELECT de PartiQL de DynamoDB soporta 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 una tabla de DynamoDB entera? 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 items coincidentes pero aún lee (y factura) cada item que el scan toca (referencia de la API de Scan de AWS: la capacidad se basa en los items examinados, no devueltos). Para un total leído frecuentemente, mantén un item contador (Patrón 1).

¿Puedo hacer GROUP BY por 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 item de agregado por clave con un ADD atómico (Patrón 1), o haz el rollup con Streams + Lambda (Patrón 2).

¿Cómo hago SUM o AVG por grupo? SUM: mantén un total acumulado por grupo y haz ADD en la escritura. AVG: almacena tanto la suma como el recuento y divide en tiempo de lectura — no hay media nativa. Para un AVG exploratorio puntual, ejecútalo en el SQL Workbench de DynoTable o vía el conector DynamoDB de Athena.

¿Hay una solución alternativa de partiql group by? Ninguna del lado de PartiQL. O precalcula el agregado (contadores/Streams) y haz SELECT del item de rollup, 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.

Actualizado