DynamoDB GROUP BY : comment agréger à la place
Il n'y a pas de GROUP BY dans DynamoDB. Il n'y a pas non plus de COUNT, SUM
ou AVG — ni dans l'API native, ni dans PartiQL. DynamoDB est un store
key-value / document, pas un moteur analytics, donc l'agrégation est quelque
chose que tu construis, pas quelque chose que le query planner fait pour
toi.
Peux-tu faire GROUP BY dans DynamoDB ?
Non. DynamoDB n'a ni GROUP BY, ni HAVING, ni de fonctions d'agrégat comme
COUNT, SUM et AVG — ni dans l'API native ni dans PartiQL, dont le
SELECT n'accepte que WHERE et ORDER BY. Tu agrèges en pré-computant les
totaux quand les données changent (compteurs atomiques ou
+ Lambda rollups) ou en groupant côté app après la lecture.
- La grammaire PartiQL
SELECTde DynamoDB estSELECT … FROM … [WHERE …] [ORDER BY …]— et c'est toute la liste. Pas deGROUP BY, pas deHAVING, pas de fonctions d'agrégat, pas deJOIN(AWS PartiQLSELECTreference). - Parce que DynamoDB « doesn't natively support aggregation operations like
SUMorCOUNTacross items », le guidance AWS lui-même est de pré-computer les agrégats quand les données changent et de stocker les résultats comme des items ordinaires (AWS : agrégation matérialisée). - L'alternative — lire chaque item puis agréger dans ton app — marche, mais tu paies pour lire tout le table à chaque query.
- Pour l'exploration one-off, le SQL Workbench de DynoTable lance
GROUP BY/COUNT/SUM/AVGdirectement contre un table live — le SQL que l'endpoint PartiQL de DynamoDB rejette.
Pourquoi l'agrégation est dure dans DynamoDB
DynamoDB n'a pas de moteur d'agrégation scan-time. Query et Scan renvoient
des items ; ils ne les foldent pas. Un Scan lit tout le table 1 MB à la fois,
et la capacité qu'il consomme est basée sur les items qu'il lit, pas les lignes
que tu gardes — un FilterExpression est appliqué après le scan mais avant
que les résultats reviennent, donc il réduit le result set sans baisser la
facture (AWS Scan API reference :
un filter « does not consume any additional read capacity units » ; la capacité
est basée sur la taille d'item scannée, pas renvoyée). Il n'y a pas de hook
GROUP-BY auquel accrocher un sum ou un count pour commencer.
PartiQL ne change pas ça. PartiQL est un dialecte SQL-compatible par-dessus le
même moteur, donc il hérite des mêmes limites — c'est une surface de syntaxe,
pas un nouveau modèle d'exécution. La
grammaire SELECT documentée
n'a simplement pas de token GROUP BY. Pour le gap complet entre PartiQL et le
vrai SQL, vois PartiQL vs SQL.
Demande où vit ton agrégat et quand il est computé. Il y a trois réponses.
Pattern 1 : agréger à l'écriture (compteurs atomiques)
Si tu connais les groupes d'avance — count per status, total per customer, downloads per month — garde un item compteur et mets-le à jour à chaque écriture.
Utilise un ADD pour que
l'incrément soit atomique et concurrency-safe. ADD marche sur numbers et
sets, et évite la race read-modify-write, donc deux writers qui incrémentent le
même compteur ne se clobber jamais
(AWS note que le ADD atomique « avoids read-modify-write race conditions ») :
UpdateItem
Key { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression "ADD orderCount :one"
ExpressionAttributeValues { ":one": 1 }
C'est ton SELECT COUNT(*) … GROUP BY status — sauf que le count est déjà
assis là comme un item, lisible en un GetItem en millisecondes à un chiffre.
Le trade-off : tu dois connaître la grouping key au write time, et tu couples
l'update du compteur au write path. Si l'app crash après l'écriture mais
avant l'update du compteur, les deux dérivent hors sync — exactement le
failure mode que le pattern suivant découple.
Pattern 2 : DynamoDB Streams + Lambda rollups
Quand tu ne veux pas de logique d'agrégation sur le write path — ou que
l'écriture est un plain PutItem que tu ne peux pas facilement wrapper —
déplace-la downstream. C'est le pattern recommandé par AWS lui-même,
materialized aggregation
(AWS: Using GSIs for materialized aggregation queries) :
- L'app écrit l'item brut (un order, un download, un event). Pas de logique d'agrégation.
- capture l'écriture comme un stream record.
- Une Lambda attachée au stream lit le nouvel item, dérive le groupe (status,
month, category…), et
ADDvers l'item agrégat matching avec unUpdateItematomique — qui « avoids read-modify-write race conditions » quand beaucoup d'invocations touchent le même compteur. - Tu queries l'agrégat pré-computé — souvent via un qui indexe seulement les items rollup, donc « top 10 this month »
est un
QueryavecLimit 10.
Seuls les items agrégat portent l'attribut indexé (par ex. Month), donc les
lignes d'event brutes sont exclues de l'index automatiquement — « a small
fraction of the total items in the table », ce qui garde l'index bon marché et
la lecture rapide.
Ça découple l'agrégation du write path et garde les écritures simples, au prix de la cohérence à terme — AWS note « a delay of a few seconds between a download being recorded and the aggregation being updated ». Pour dashboards, leaderboards et compteurs de tendance, ça va.
Une invocation Lambda retried re-lance le ADD, donc « a retry would increment
the count more than once », laissant une valeur approximative. Pour des
counts exacts, ajoute de l'idempotency (par ex. un condition expression keyed
sur l'id de l'item source) ; sinon la petite marge va bien pour analytics et
leaderboards.
Pattern 3 : grouping côté app après Scan/Query
Ou lis les items et groupe-les dans ton code.
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"])C'est correct et parfois le bon appel — mais sois honnête sur le coût. Un
Scan lit chaque item du table, et la capacité de lecture est la même
que tu filtres ou non. Donc le grouping côté app sur un Scan full veut dire
que tu paies pour lire tout le table à chaque agrégation, et la latence
grandit avec le table. AWS liste « scan and count at read time » comme « only
suitable for very small datasets where latency isn't a concern »
(AWS : pourquoi pré-calculer les agrégats).
Scopé à une seule partition via Query (par ex. count les orders d'un
customer), le grouping côté app est parfaitement raisonnable — tu ne lis qu'une
item collection. Pour le gap de coût complet entre les deux, vois
Query vs Scan. En us-east-1 on-demand, un
Scan full-table facture 0.5 RCU par 4 KB eventually-consistent pour
chaque item examiné — un table de 1 GB de lignes de 1 KB fait environ
250 000 RCU avant que ton app groupe quoi que ce soit. Dimensionne un item
représentatif avec le
calculateur de taille d'item, puis
line-rate le scan dans le
calculateur de pricing.
Pour du vrai SQL analytique ad hoc sur un table DynamoDB — le throwaway
« GROUP BY status, count them » que tu lancerais une fois — la réponse AWS
est de pointer un moteur séparé dessus : le Amazon Athena DynamoDB connector
te laisse query le table avec du vrai SQL (GROUP BY, agrégats, même des
JOINs vers d'autres sources) via un connector Lambda
(AWS : le connecteur Amazon Athena pour DynamoDB).
Il scanne le table en coulisses, donc c'est un outil reporting/BI, pas un hot
path.
Quel pattern j'utilise ?
| Tu as besoin de… | Utilise |
|---|---|
| Un total de groupe connu sur un hot read path | Pattern 1 — compteur atomique (ADD) |
| Des agrégats sans toucher le write path | Pattern 2 — Streams + Lambda rollup |
| Un count scopé à une partition | Pattern 3 — Query puis group in app |
| Des totaux exacts, pas de drift | Pattern 1/2 avec guard d'idempotency |
Un GROUP BY one-off pendant l'exploration | DynoTable Workbench (ci-dessous) ou Athena |
| BI/reporting récurrent avec SQL | Athena DynamoDB connector |
Lancer GROUP BY directement dans le SQL Workbench de DynoTable
Les patterns ci-dessus sont comment tu sers des agrégats en production. Mais quand tu explores un table — « combien d'orders per status, right now ? » — tu ne veux pas provisionner une Lambda ou stand up Athena. Tu veux taper la query.
C'est à ça que sert le SQL Workbench de DynoTable. Il lance du vrai SQL —
GROUP BY, COUNT, SUM, AVG, HAVING, même JOIN — directement contre
tes tables DynamoDB live, exécutant l'agrégation côté client sur les lignes
qu'il lit. C'est le SQL que l'endpoint PartiQL de DynamoDB rejette :
SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESCSous le capot DynoTable lit les items comme l'API le permet (Query où elle
peut, Scan où elle doit), les matérialise, et fait le grouping dans le
Workbench — les mêmes mécaniques « read then aggregate » que le Pattern 3,
juste sans la boucle, et dans les règles des modèles d'accès DynamoDB.
C'est bâti pour l'exploration et l'analyse ad hoc, pas pour remplacer un
rollup de production sur un hot read path. Pour ça, pré-compute (Pattern 1 /
2).
Pour le côté JOIN du même wedge — DynoTable lance des joins cross-table que
PartiQL ne peut pas non plus — vois DynamoDB JOIN. Tu
compares des clients GUI exactement sur cette capacité ? Vois
la comparaison GUI DynamoDB.
FAQ
Est-ce que DynamoDB PartiQL support GROUP BY ?
Non. Le SELECT PartiQL de DynamoDB support WHERE et ORDER BY seulement —
pas de GROUP BY, HAVING, fonctions d'agrégat, ou JOIN. La grammaire est
documentée
comme SELECT … FROM … [WHERE …] [ORDER BY …].
Puis-je faire COUNT(*) sur tout un table DynamoDB ?
Pas comme fonction d'agrégat — PartiQL n'en a aucune. L'API te donne
Select=COUNT sur un Scan/Query, qui renvoie un count des items matchés
mais lit (et facture) encore chaque item que le scan touche
(AWS Scan API reference :
la capacité est basée sur les items examinés, pas renvoyés). Pour un total lu
fréquemment, garde un item compteur (Pattern 1).
Puis-je GROUP BY la partition key ?
Pas dans DynamoDB ou PartiQL. Si « per partition key » est un modèle d'accès
connu, maintain un item agrégat par clé avec un ADD atomique (Pattern 1), ou
rollup avec Streams + Lambda (Pattern 2).
Comment faire SUM ou AVG per group ?
SUM : garde un running total per group et ADD dessus à l'écriture. AVG :
stocke à la fois le sum et le count et divise au read time — il n'y a pas de
moyenne native. Pour un AVG exploratoire one-off, lance-le dans le SQL
Workbench de DynoTable ou via le Athena DynamoDB connector.
Y a-t-il un workaround partiql group by ?
Pas de côté PartiQL. Soit tu pré-computes l'agrégat (compteurs/Streams) et
SELECT l'item rollup, soit tu lances le GROUP BY dans un moteur qui en a
un — le Workbench de DynoTable pour l'ad hoc, Athena pour le reporting
récurrent.
Envie de lancer GROUP BY contre tes propres tables sans écrire une Lambda ?
Essaie DynoTable et pointe le SQL Workbench sur un table live.