DynamoDB GROUP BY: Aggregieren ohne eine GROUP-BY-Klausel
Es gibt kein GROUP BY in DynamoDB. Es gibt auch kein COUNT, SUM oder AVG
— nicht in der nativen API und nicht in PartiQL. DynamoDB ist ein Key-Value-/
Dokument-Store, keine Analytik-Engine, also ist Aggregation etwas, das du
baust, nicht etwas, das der Query-Planer für dich tut.
Kann man in DynamoDB GROUP BY verwenden?
Nein. DynamoDB hat kein GROUP BY, kein HAVING und keine Aggregatfunktionen wie COUNT, SUM und AVG — weder in der nativen API noch in PartiQL, dessen SELECT nur WHERE und ORDER BY akzeptiert. Du aggregierst, indem du Summen vorberechnest, während sich die Daten ändern (atomare Counter oder Streams + Lambda-Rollups), oder indem du nach dem Lesen App-seitig gruppierst.
- DynamoDBs PartiQL-
SELECT-Grammatik istSELECT … FROM … [WHERE …] [ORDER BY …]— und das ist die ganze Liste. KeinGROUP BY, keinHAVING, keine Aggregat- funktionen, keinJOIN(AWS-PartiQL-SELECT-Referenz). - Weil DynamoDB „Aggregationsoperationen wie
SUModerCOUNTüber Items nicht nativ unterstützt", ist AWS' eigene Empfehlung, Aggregate vorzuberechnen, während die Daten sich ändern, und die Ergebnisse als gewöhnliche Items zu speichern (AWS: materialisierte Aggregation). - Die Alternative — jedes Item lesen und dann in deiner App aggregieren — funktioniert, aber du zahlst dafür, die ganze Tabelle bei jeder Query zu lesen.
- Für einmalige Exploration führt DynoTables SQL Workbench
GROUP BY/COUNT/SUM/AVGdirekt gegen eine Live-Tabelle aus — das SQL, das DynamoDBs PartiQL-Endpoint ablehnt.
Warum Aggregation in DynamoDB schwer ist
DynamoDB hat keine Scan-Zeit-Aggregations-Engine. Query und Scan geben Items zurück;
sie falten sie nicht. Ein Scan liest die ganze Tabelle 1 MB nach 1 MB, und die
Kapazität, die er verbraucht, basiert auf den Items, die er liest, nicht den Zeilen, die du behältst — eine
FilterExpression wird nach dem Scan, aber vor der Ergebnis-Rückgabe angewendet, also
verengt sie den Ergebnissatz, ohne die Rechnung zu senken (AWS-Scan-API-Referenz: ein Filter
„verbraucht keine zusätzlichen Read-Capacity-Units"; die Kapazität basiert auf der gescannten Item-
Größe, nicht der zurückgegebenen). Es gibt keinen GROUP-BY-Hook,
an den man überhaupt eine Summe oder einen Count hängen könnte.
PartiQL ändert das nicht. PartiQL ist ein SQL-kompatibler Dialekt über derselben
Engine, also erbt es dieselben Limits — es ist eine Syntax-Oberfläche, kein neues
Ausführungsmodell. Die dokumentierte SELECT-Grammatik
hat schlicht kein GROUP BY-Token.
Für die volle Lücke zwischen PartiQL und echtem SQL siehe
PartiQL vs SQL.
Die Frage ist also nicht „wie schreibe ich ein GROUP BY" — sondern „wo lebt mein
Aggregat, und wann wird es berechnet?" Es gibt drei Antworten.
Muster 1: beim Write aggregieren (atomare Counter)
Wenn du die Gruppen im Voraus kennst — Count pro Status, Summe pro Kunde, Downloads pro Monat — halte ein Counter-Item und aktualisiere es bei jedem Write.
Verwende eine ADD-Update-Expression, damit das Inkrement atomar und nebenläufigkeitssicher ist.
ADD funktioniert auf Zahlen und Sets, und es vermeidet die Read-Modify-Write-Race, sodass
zwei Writer, die denselben Counter inkrementieren, sich nie gegenseitig überschreiben
(AWS weist darauf hin, dass das atomare ADD „Read-Modify-Write-Race-Conditions vermeidet"):
UpdateItem
Key { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression "ADD orderCount :one"
ExpressionAttributeValues { ":one": 1 }
Das ist dein SELECT COUNT(*) … GROUP BY status — nur dass der Count bereits
als Item dasitzt, lesbar in einem einstelligen-Millisekunden-GetItem. Der
Kompromiss: Du musst den Gruppierungs-Schlüssel zur Write-Zeit kennen, und du koppelst das
Counter-Update an den Write-Pfad. Wenn die App nach dem Write, aber
vor dem Counter-Update abstürzt, driften die beiden auseinander — was genau der
Fehlermodus ist, den das nächste Muster entkoppelt.
Muster 2: DynamoDB Streams + Lambda-Rollups
Wenn du keine Aggregationslogik auf dem Write-Pfad willst — oder der Write ein
einfacher PutItem ist, den du nicht leicht umhüllen kannst — verschiebe sie downstream. Das ist AWS' eigenes
empfohlenes Muster, materialisierte Aggregation
(AWS: GSIs für materialisierte Aggregations-Queries nutzen):
- Die App schreibt das rohe Item (eine Order, einen Download, ein Event). Keine Aggregations- logik.
- DynamoDB Streams erfasst den Write als Stream-Record.
- Eine an den Stream angehängte Lambda liest das neue Item, leitet die Gruppe ab
(Status, Monat, Kategorie…) und
ADDet zum passenden Aggregat-Item mit einem atomarenUpdateItem— das „Read-Modify-Write-Race-Conditions vermeidet", wenn viele Invocations denselben Counter berühren. - Du querest das vorberechnete Aggregat — oft durch einen sparse GSI, der
nur die Rollup-Items indiziert, sodass „Top 10 diesen Monat" eine
QuerymitLimit 10ist.
Der Sparse-GSI-Trick: Nur die Aggregat-Items tragen das indizierte Attribut
(z. B. Month), sodass die rohen Event-Zeilen automatisch aus dem Index ausgeschlossen werden
— „ein kleiner Bruchteil der gesamten Items in der Tabelle", was den Index
billig und den Read schnell hält.
Das entkoppelt Aggregation vom Write-Pfad und hält Writes einfach, auf Kosten letztendlicher Konsistenz — AWS weist auf „eine Verzögerung von ein paar Sekunden zwischen einem aufgezeichneten Download und der aktualisierten Aggregation" hin. Für Dashboards, Leaderboards und Trend-Counter ist das in Ordnung.
Dieselbe Retry-Einschränkung gilt: eine erneut versuchte Lambda-Invocation führt das ADD erneut aus, sodass
„ein Retry den Count mehr als einmal inkrementieren würde", was einen näherungsweisen
Wert hinterlässt. Für exakte Counts füge Idempotenz hinzu (z. B. eine Condition-Expression, gekeyt auf
die ID des Quell-Items); andernfalls ist die kleine Marge für Analytik und
Leaderboards in Ordnung.
Muster 3: App-seitige Gruppierung nach Scan/Query
Die Brute-Force-Option: lies die Items, gruppiere sie in deinem Code.
groups = {}
for item in paginate(table.scan()): # oder query() für eine Partition
key = item["status"]
groups[key] = groups.get(key, 0) + 1Das ist korrekt und manchmal die richtige Wahl — aber sei ehrlich über die Kosten. Ein
Scan liest jedes Item in der Tabelle, und die Lesekapazität ist dieselbe,
ob du filterst oder nicht. App-seitige Gruppierung über einen vollen Scan bedeutet also, dass du dafür zahlst,
die gesamte Tabelle bei jeder Aggregation zu lesen, und die Latenz wächst mit der Tabelle.
AWS listet „beim Read scannen und zählen" als „nur für sehr kleine
Datensätze geeignet, bei denen Latenz keine Rolle spielt"
(AWS: Warum Aggregationen vorberechnen).
Auf eine einzelne Partition via Query heruntergebrochen (z. B. die Orders eines
Kunden zählen) ist App-seitige Gruppierung völlig vernünftig — du liest nur eine
Item-Collection. Für die volle Kostenlücke zwischen beiden siehe
Query vs Scan. Um zu schätzen, was ein gegebener Aggregations-
Scan lesen wird, bevor du ihn ausführst, dimensioniere ein repräsentatives Item mit dem
Item-Size-Rechner — Lesekapazität
rundet pro 4 KB auf,
also treibt die Item-Größe die Rechnung.
Für wirklich Ad-hoc-analytisches SQL über eine DynamoDB-Tabelle — das einmalige
„GROUP BY status, zähl sie", das du einmal ausführst — ist AWS' Antwort, eine
separate Engine darauf zu richten: der Amazon Athena DynamoDB Connector lässt dich
die Tabelle mit echtem SQL queryen (GROUP BY, Aggregate, sogar JOINs zu anderen Quellen)
via eines Lambda-Connectors
(AWS: Amazon Athena DynamoDB Connector).
Er scannt die Tabelle hinter den Kulissen, also ist es ein Reporting-/BI-Tool, kein Hot-Path.
Welches Muster verwende ich?
| Du brauchst… | Verwende |
|---|---|
| Eine bekannte Gruppensumme auf einem Hot-Read-Pfad | Muster 1 — atomarer Counter (ADD) |
| Aggregate ohne den Write-Pfad zu berühren | Muster 2 — Streams + Lambda-Rollup |
| Einen Count auf eine Partition beschränkt | Muster 3 — Query dann in App gruppieren |
| Exakte Summen, keine Drift | Muster 1/2 mit Idempotenz-Guard |
Ein einmaliges GROUP BY beim Erkunden | DynoTable Workbench (unten) oder Athena |
| Wiederkehrendes BI/Reporting mit SQL | Athena DynamoDB Connector |
GROUP BY direkt in DynoTables SQL Workbench ausführen
Die Muster oben sind, wie du Aggregate in Produktion bedienst. Aber wenn du eine Tabelle erkundest — „wie viele Orders pro Status, gerade jetzt?" — willst du keine Lambda provisionieren oder Athena aufsetzen. Du willst die Query tippen.
Genau dafür ist DynoTables SQL Workbench da. Sie führt echtes SQL —
GROUP BY, COUNT, SUM, AVG, HAVING, sogar JOIN — direkt gegen deine
Live-DynamoDB-Tabellen aus und führt die Aggregation client-seitig über die Zeilen aus, die sie
liest. Es ist das SQL, das DynamoDBs PartiQL-Endpoint ablehnt:
SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESCDie ehrliche Einordnung: unter der Haube liest DynoTable Items so, wie die API es erlaubt
(Query, wo es kann, Scan, wo es muss), materialisiert sie und macht die
Gruppierung in der Workbench — dieselbe „lesen-dann-aggregieren"-Mechanik wie Muster
3, nur ohne die Schleife, und innerhalb der Zugriffsmuster-Regeln von DynamoDB. Es ist
für Exploration und Ad-hoc-Analyse gebaut, nicht um einen Produktions-
Rollup auf einem Hot-Read-Pfad zu ersetzen. Dafür berechne vor (Muster 1 / 2).
Für die JOIN-Seite desselben Keils — DynoTable führt tabellenübergreifende Joins aus, die PartiQL
auch nicht kann — siehe DynamoDB JOIN. Vergleichst du GUI-
Clients genau bei dieser Fähigkeit? Siehe
den DynamoDB-GUI-Vergleich.
FAQ
Unterstützt DynamoDB PartiQL GROUP BY?
Nein. DynamoDBs PartiQL-SELECT unterstützt nur WHERE und ORDER BY — kein
GROUP BY, HAVING, keine Aggregatfunktionen oder JOIN. Die Grammatik ist
dokumentiert
als SELECT … FROM … [WHERE …] [ORDER BY …].
Kann ich COUNT(*) über eine ganze DynamoDB-Tabelle ausführen?
Nicht als Aggregatfunktion — PartiQL hat keine. Die API gibt dir
Select=COUNT auf einem Scan/Query, das einen Count passender Items zurückgibt, aber
dennoch jedes Item, das der Scan berührt, liest (und berechnet)
(AWS-Scan-API-Referenz:
Kapazität basiert auf untersuchten Items, nicht zurückgegebenen). Für eine häufig gelesene
Summe halte ein Counter-Item (Muster 1).
Kann ich nach dem Partition Key GROUP BY?
Nicht in DynamoDB oder PartiQL. Wenn „pro Partition Key" ein bekanntes Zugriffsmuster ist,
pflege ein Aggregat-Item pro Schlüssel mit einem atomaren ADD (Muster 1), oder rolle es
mit Streams + Lambda auf (Muster 2).
Wie mache ich SUM oder AVG pro Gruppe?
SUM: halte eine laufende Summe pro Gruppe und ADDe beim Write dazu. AVG: speichere
sowohl die Summe als auch den Count und teile zur Read-Zeit — es gibt keinen nativen Durchschnitt.
Für ein einmaliges exploratorisches AVG führe es in DynoTables SQL Workbench oder via dem
Athena DynamoDB Connector aus.
Gibt es einen partiql group by-Workaround?
Keinen PartiQL-seitigen. Entweder berechne das Aggregat vor (Counter/Streams) und
SELECT das Rollup-Item, oder führe das GROUP BY in einer Engine aus, die eines hat —
DynoTables Workbench für Ad-hoc, Athena für wiederkehrendes Reporting.
Willst du GROUP BY gegen deine eigenen Tabellen ausführen, ohne eine Lambda zu schreiben?
Probier DynoTable aus und richte die SQL Workbench auf eine Live-Tabelle.