DynamoDB GROUP BY : comment agréger sans clause GROUP BY
Il n'y a pas de GROUP BY dans DynamoDB. Il n'y a pas non plus de COUNT, SUM ni
AVG — ni dans l'API native, ni dans PartiQL. DynamoDB est un magasin clé-valeur /
document, pas un moteur analytique, donc l'agrégation est quelque chose que tu
construis, pas quelque chose que le planificateur de requêtes fait pour toi.
Peut-on faire un GROUP BY dans DynamoDB ?
Non. DynamoDB n'a ni GROUP BY, ni HAVING, ni fonctions d'agrégation 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é-calculant les totaux à mesure que les données changent (compteurs atomiques ou rollups Streams + Lambda), ou en groupant côté app après la lecture.
- La grammaire
SELECTPartiQL de DynamoDB estSELECT … FROM … [WHERE …] [ORDER BY …]— et c'est toute la liste. Pas deGROUP BY, pas deHAVING, pas de fonctions d'agrégation, pas deJOIN(référenceSELECTPartiQL d'AWS). - Parce que DynamoDB « ne supporte pas nativement les opérations d'agrégation comme
SUMouCOUNTentre items », la propre recommandation d'AWS est de pré-calculer les agrégats à mesure que 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 — fonctionne, mais tu paies pour lire la table entière à chaque requête.
- Pour de l'exploration ponctuelle, le SQL Workbench de DynoTable exécute
GROUP BY/COUNT/SUM/AVGdirectement contre une table en direct — le SQL que l'endpoint PartiQL de DynamoDB rejette.
Pourquoi l'agrégation est difficile dans DynamoDB
DynamoDB n'a pas de moteur d'agrégation au moment du scan. Query et Scan renvoient
des items ; ils ne les replient pas. Un Scan lit la table entière 1 Mo à la fois, et
la capacité qu'il consomme est basée sur les items qu'il lit, pas sur les lignes que tu
gardes — une FilterExpression est appliquée après le scan mais avant que les
résultats reviennent, donc elle rétrécit le jeu de résultats sans réduire la facture
(référence API Scan d'AWS :
un filtre « ne consomme aucune unité de capacité de lecture supplémentaire » ; la
capacité est basée sur la taille d'item scannée, pas renvoyée). Il n'y a aucun crochet
GROUP-BY auquel accrocher une somme ou un compte en premier lieu.
PartiQL ne change pas cela. PartiQL est un dialecte compatible SQL sur le même moteur,
il hérite donc 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 tout simplement pas de jeton GROUP BY. Pour l'écart complet entre PartiQL et le
vrai SQL, voir PartiQL vs SQL.
Donc la question n'est pas « comment écrire un GROUP BY » — c'est « où vit mon agrégat,
et quand est-il calculé ? ». Il y a trois réponses.
Pattern 1 : agréger à l'écriture (compteurs atomiques)
Si tu connais les groupes à l'avance — compte par statut, total par client, téléchargements par mois — garde un item compteur et mets-le à jour à chaque écriture.
Utilise une expression de mise à jour ADD pour que l'incrément soit atomique et sûr en
concurrence. ADD fonctionne sur les nombres et les sets, et il évite la course
read-modify-write, donc deux écrivains incrémentant le même compteur ne s'écrasent jamais
(AWS note que le ADD atomique « évite les conditions de course read-modify-write ») :
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 compte est déjà là sous
forme d'item, lisible en un GetItem de quelques millisecondes. Le compromis : tu dois
connaître la clé de groupement au moment de l'écriture, et tu couples la mise à jour du
compteur au chemin d'écriture. Si l'app plante après l'écriture mais avant la mise à
jour du compteur, les deux dérivent hors synchro — qui est exactement le mode d'échec que
le pattern suivant découple.
Pattern 2 : rollups DynamoDB Streams + Lambda
Quand tu ne veux pas de logique d'agrégation sur le chemin d'écriture — ou que
l'écriture est un simple PutItem que tu ne peux pas facilement encapsuler — déplace-la
en aval. C'est le pattern recommandé par AWS lui-même, l'agrégation matérialisée
(AWS : utiliser les GSI pour des requêtes d'agrégation matérialisée) :
- L'app écrit l'item brut (une commande, un téléchargement, un événement). Aucune logique d'agrégation.
- DynamoDB Streams capture l'écriture comme un enregistrement de stream.
- Un Lambda attaché au stream lit le nouvel item, en dérive le groupe (statut, mois,
catégorie…), et fait
ADDsur l'item agrégat correspondant avec unUpdateItematomique — qui « évite les conditions de course read-modify-write » quand de nombreuses invocations touchent le même compteur. - Tu interroges l'agrégat pré-calculé — souvent via un GSI clairsemé qui n'indexe
que les items de rollup, donc « top 10 ce mois » est un
QueryavecLimit 10.
L'astuce du GSI clairsemé : seuls les items agrégats portent l'attribut indexé (p. ex.
Month), donc les lignes d'événements brutes sont exclues de l'index automatiquement —
« une petite fraction du total des items dans la table », ce qui garde l'index bon marché
et la lecture rapide.
Cela découple l'agrégation du chemin d'écriture et garde les écritures simples, au prix d'une cohérence à terme — AWS note « un délai de quelques secondes entre l'enregistrement d'un téléchargement et la mise à jour de l'agrégation ». Pour les tableaux de bord, classements et compteurs de tendance, c'est acceptable.
La même mise en garde sur les réessais s'applique : une invocation Lambda réessayée
ré-exécute le ADD, donc « un réessai incrémenterait le compte plus d'une fois »,
laissant une valeur approximative. Pour des comptes exacts, ajoute de l'idempotence
(p. ex. une expression de condition basée sur l'id de l'item source) ; sinon la petite
marge convient à l'analytique et aux classements.
Pattern 3 : groupement côté app après Scan/Query
L'option par force brute : lis les items, groupe-les dans ton code.
groups = {}
for item in paginate(table.scan()): # ou query() pour une partition
key = item["status"]
groups[key] = groups.get(key, 0) + 1C'est correct et parfois le bon choix — mais sois honnête sur le coût. Un Scan lit
chaque item de la table, et la capacité de lecture est la même que tu filtres ou non.
Donc un groupement côté app sur un Scan complet signifie que tu paies pour lire la
table entière à chaque agrégation, et la latence croît avec la table. AWS liste
« scanner et compter au moment de la lecture » comme « adapté uniquement à de très
petits jeux de données où la latence n'est pas un souci »
(AWS : pourquoi pré-calculer les agrégations).
Réduit à une seule partition via Query (p. ex. compter les commandes d'un client), le
groupement côté app est parfaitement raisonnable — tu ne lis qu'une seule collection
d'items. Pour l'écart de coût complet entre les deux, voir
Query vs Scan. Pour estimer ce qu'un scan d'agrégation donné
lira avant de l'exécuter, dimensionne un item représentatif avec le
calculateur de taille d'item — la capacité de
lecture s'arrondit au-dessus par 4 Ko,
donc la taille d'item pilote la facture.
Pour du SQL analytique réellement ad hoc sur une table DynamoDB — le jetable
« GROUP BY status, compte-les » que tu lancerais une fois — la réponse d'AWS est de
pointer un moteur séparé dessus : le connecteur Amazon Athena DynamoDB te laisse
interroger la table avec du vrai SQL (GROUP BY, agrégats, et même JOIN vers d'autres
sources) via un connecteur Lambda
(AWS : connecteur Amazon Athena DynamoDB).
Il scanne la table en coulisses, c'est donc un outil de reporting/BI, pas un chemin
chaud.
Quel pattern utiliser ?
| Tu as besoin de… | Utilise |
|---|---|
| Un total de groupe connu sur un chemin de lecture chaud | Pattern 1 — compteur atomique (ADD) |
| Des agrégats sans toucher au chemin d'écriture | Pattern 2 — rollup Streams + Lambda |
| Un compte réduit à une partition | Pattern 3 — Query puis grouper dans l'app |
| Des totaux exacts, sans dérive | Pattern 1/2 avec garde d'idempotence |
Un GROUP BY ponctuel en explorant | Workbench DynoTable (ci-dessous) ou Athena |
| Du BI/reporting récurrent en SQL | Connecteur Athena DynamoDB |
Exécuter GROUP BY directement dans le SQL Workbench de DynoTable
Les patterns ci-dessus sont la façon de servir des agrégats en production. Mais quand tu explores une table — « combien de commandes par statut, maintenant ? » — tu ne veux pas provisionner un Lambda ni mettre Athena debout. Tu veux taper la requête.
C'est à ça que sert le SQL Workbench de DynoTable. Il exécute du vrai SQL —
GROUP BY, COUNT, SUM, AVG, HAVING, et même JOIN — directement contre tes
tables DynamoDB en direct, en 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 DESCLe cadrage honnête : sous le capot, DynoTable lit les items comme l'API le permet
(Query quand il peut, Scan quand il doit), les matérialise, et fait le groupement
dans le Workbench — la même mécanique « lire puis agréger » que le Pattern 3, juste sans
la boucle, et dans les règles de mode d'accès de DynamoDB. Il est conçu pour
l'exploration et l'analyse ad hoc, pas pour remplacer un rollup de production sur un
chemin de lecture chaud. Pour ça, pré-calcule (Pattern 1 / 2).
Pour le côté JOIN du même coin — DynoTable exécute aussi des jointures inter-tables que
PartiQL ne peut pas — voir DynamoDB JOIN. Tu compares les
clients GUI sur exactement cette capacité ? Voir
la comparaison des GUI DynamoDB.
FAQ
DynamoDB PartiQL supporte-t-il GROUP BY ?
Non. Le SELECT PartiQL de DynamoDB supporte WHERE et ORDER BY uniquement — pas de
GROUP BY, HAVING, fonctions d'agrégation ni JOIN. La grammaire est
documentée
comme SELECT … FROM … [WHERE …] [ORDER BY …].
Puis-je faire COUNT(*) sur une table DynamoDB entière ?
Pas comme fonction d'agrégation — PartiQL n'en a aucune. L'API te donne Select=COUNT
sur un Scan/Query, qui renvoie un compte d'items correspondants mais lit (et
facture) quand même chaque item que le scan touche
(référence API Scan d'AWS :
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 faire GROUP BY sur la partition key ?
Pas dans DynamoDB ni PartiQL. Si « par partition key » est un mode d'accès connu,
maintiens un item agrégat par clé avec un ADD atomique (Pattern 1), ou rollup-le avec
Streams + Lambda (Pattern 2).
Comment faire SUM ou AVG par groupe ?
SUM : garde un total courant par groupe et fais-y ADD à l'écriture. AVG : stocke à
la fois la somme et le compte et divise au moment de la lecture — il n'y a pas de moyenne
native. Pour un AVG exploratoire ponctuel, exécute-le dans le SQL Workbench de
DynoTable ou via le connecteur Athena DynamoDB.
Y a-t-il un contournement partiql group by ?
Aucun côté PartiQL. Soit pré-calcule l'agrégat (compteurs/Streams) et fais SELECT sur
l'item de rollup, soit exécute le GROUP BY dans un moteur qui en a un — le Workbench de
DynoTable pour l'ad hoc, Athena pour le reporting récurrent.
Tu veux exécuter GROUP BY contre tes propres tables sans écrire de Lambda ?
Essaie DynoTable et pointe le SQL Workbench vers une table en direct.