Zusammenfassung

Aggregatfunktionen fassen mehrere Tupel zu einem einzigen Ergebniswert zusammen (COUNT, SUM, AVG, MIN, MAX) und werden meist mit GROUP BY kombiniert, um Berechnungen pro Gruppe durchzuführen. HAVING filtert Gruppen nach der Aggregation, während WHERE einzelne Zeilen vor der Gruppierung filtert. Zentraler Merkpunkt: Die meisten DBMS ignorieren NULL-Werte in Aggregatfunktionen, wobei COUNT(*) alle Zeilen zählt und COUNT(spalte) nur Zeilen mit NOT NULL-Wert.

Kernkonzepte

COUNT(*) vs COUNT(spalte) vs COUNT(DISTINCT spalte)

COUNT(*) zählt alle Tupel einer Gruppe inklusive Zeilen, die ausschließlich NULL-Werte enthalten. COUNT(spalte) zählt nur diejenigen Zeilen, bei denen die angegebene Spalte einen definierten Wert ungleich NULL hat. COUNT(DISTINCT spalte) zählt die Anzahl unterschiedlicher, nicht-NULL Ausprägungen einer Spalte, was zur Duplikate-Eliminierung dient. Diese drei Varianten liefern in der Regel unterschiedliche Zahlen und werden gerne in Multiple-Choice-Fragen verwechselt.

Stern zählt Zeilen, Spalte zählt Werte, DISTINCT zählt Ausprägungen - NULL zählt nur der Stern mit.

NULL-Behandlung in Aggregaten

Die meisten DBMS ignorieren NULL-Werte in Aggregatfunktionen wie SUM, AVG, MIN, MAX und COUNT(spalte). Ein AVG(note) rechnet nur den Durchschnitt der nicht-NULL Noten, wodurch der Nenner kleiner sein kann als die Gesamtzahl der Tupel. Bei einer Spalte, die ausschließlich NULL enthält, liefert SUM/AVG/MIN/MAX ebenfalls NULL zurück, nur COUNT(*) liefert die Zeilenzahl. Diese Semantik weicht bewusst von einfachen Rechenoperationen ab, weil NULL als unbekannt statt als 0 interpretiert wird.

NULL wird in Aggregaten übersprungen - AVG mittelt nur was da ist, nicht was fehlt.

GROUP BY - Gruppierung

GROUP BY bildet Gruppen aus Tupeln, die in einer oder mehreren Spalten gleiche Werte haben, und liefert pro Gruppe genau ein Ergebnistupel. Alle in der SELECT-Klausel aufgeführten Attribute müssen entweder in der GROUP BY-Klausel stehen oder durch eine Aggregatfunktion umschlossen sein. Ohne diese Regel wäre unklar, welcher Wert einer Gruppe in einer nicht aggregierten Spalte ausgegeben werden soll. Die Gruppierung erlaubt Fragen wie Summe pro Professor oder Anzahl pro Semester.

Was nicht gruppiert wird, muss aggregiert werden - sonst Fehler.

HAVING - Filter für Gruppen

HAVING enthält Filterbedingungen, die nach der Gruppierung auf die entstandenen Gruppen angewendet werden, ähnlich zu WHERE für ungruppierte Anfragen. In HAVING dürfen Aggregatfunktionen wie AVG(sws) >= 3 verwendet werden, in WHERE dagegen nicht. Der Unterschied ist zeitlich: WHERE filtert einzelne Zeilen vor GROUP BY, HAVING filtert ganze Gruppen nach GROUP BY. In der Ausführungsreihenfolge steht HAVING an Position 4, nach FROM/JOIN, WHERE und GROUP BY.

WHERE filtert Zeilen vor Gruppierung, HAVING filtert Gruppen danach.

Ausführungsreihenfolge von SQL-Klauseln

SQL wird logisch in folgender Reihenfolge ausgeführt: 1. FROM/JOIN, 2. WHERE, 3. GROUP BY, 4. HAVING, 5. SELECT, 6. DISTINCT, 7. ORDER BY, 8. Mengenoperationen. Diese Reihenfolge erklärt, warum Aliase aus SELECT nicht in WHERE genutzt werden können, aber in ORDER BY schon. Sie ist zentral für das Verstehen komplexer Abfragen mit Aggregatfunktionen. Die Ausführungsreihenfolge unterscheidet sich absichtlich von der Schreibreihenfolge, die mit SELECT beginnt.

FROM-WHERE-GROUP-HAVING-SELECT-DISTINCT-ORDER: erst filtern, dann gruppieren, dann aggregieren.

Die fünf Standardaggregate

AVG berechnet den arithmetischen Mittelwert nicht-NULL Werte einer numerischen Spalte. SUM addiert alle nicht-NULL Werte, MIN und MAX liefern den kleinsten bzw. größten definierten Wert. COUNT ist die einzige Aggregatfunktion, die auch mit dem Sternoperator arbeiten kann und dann alle Zeilen zählt. Alle Aggregate reduzieren eine Menge auf einen Skalarwert und können per AS umbenannt werden, zum Beispiel SUM(sws) AS deputat.

AVG, SUM, MIN, MAX, COUNT - fünf Aggregate reduzieren Mengen zu einem Wert.

Skalare Unterabfrage mit Aggregat

Ein häufiges Muster ist die skalare Unterabfrage, die genau einen Wert per Aggregat liefert und in WHERE mit einem Attribut verglichen wird. Ein typisches Beispiel ist WHERE semester = (SELECT MAX(semester) FROM studenten), um den Studenten mit den meisten Semestern zu ermitteln. Wichtig: Die Unterabfrage muss genau ein Tupel liefern, sonst gibt es einen Laufzeitfehler. Dies löst das klassische Problem, dass MAX allein nur den Wert, aber nicht die zugehörige Zeile liefert.

MAX kennt nur den Wert - für die Zeile braucht es eine Unterabfrage.

AS-Alias für Aggregatspalten

Ergebnisse von Aggregatfunktionen bekommen ohne AS-Klausel oft einen unlesbaren automatischen Spaltennamen wie SUM(sws). Mit AS deputat wird die Spalte klar benannt und kann in ORDER BY (nicht aber in HAVING oder WHERE der gleichen Anfrage) referenziert werden. Dies dient der Lesbarkeit und erleichtert die Weiterverwendung in Programmen. Der AS-Operator kann auch Relationen umbenennen, zum Beispiel FROM studenten AS s.

Ohne AS bleibt SUM(sws) - mit AS wird deputat draus.

Wichtige Details

  • COUNT(*) zählt alle Zeilen, auch solche mit ausschliesslich NULL-Werten
  • COUNT(spalte) ignoriert Zeilen mit NULL in dieser Spalte
  • COUNT(DISTINCT spalte) zählt nur unterschiedliche nicht-NULL Werte
  • SUM, AVG, MIN, MAX ignorieren NULL-Werte in den meisten DBMS
  • AVG auf Spalte mit NULL-Werten berechnet den Mittelwert der definierten Werte, nicht der Gesamtzeilen
  • Alle nicht aggregierten Attribute in SELECT müssen in GROUP BY vorkommen
  • HAVING kann Aggregate nutzen (HAVING AVG(sws) >= 3), WHERE nicht
  • Ausführungsreihenfolge: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY
  • MAX allein liefert nur den Wert, nicht den zugehörigen Datensatz - dafür braucht es eine Unterabfrage
  • MIN/MAX profitieren stark von Indizes auf der Zielspalte
  • Skalare Unterabfrage muss genau ein Tupel liefern - sonst Laufzeitfehler
  • Aggregate ohne GROUP BY liefern ein einziges Ergebnistupel über die gesamte Relation

Beispiele

Durchschnittliche Semesterzahl aller Studenten

Einfaches Aggregat über alle Tupel ohne Gruppierung. Ein einzelner Skalarwert wird zurückgegeben.

SELECT AVG(semester)
  FROM studenten;

Ergebnis: eine Zeile, eine Spalte, z.B. 7.3

Lehrdeputat pro Professor mit Sortierung

Kombination aus GROUP BY und SUM: Für jeden Professor wird die Summe der SWS über alle gehaltenen Vorlesungen gebildet. ORDER BY DESC bringt den mit dem höchsten Deputat an die Spitze.

SELECT gelesenvon, SUM(sws) AS deputat
  FROM vorlesungen
 GROUP BY gelesenvon
 ORDER BY deputat DESC;

Ergebnis:
2125 | 10
2137 | 8

HAVING - hochrangige Professoren mit Mindest-SWS

Vollständiges Beispiel mit JOIN, WHERE-artiger ON-Bedingung, GROUP BY und HAVING. Nur Gruppen (Prof + Name), deren Durchschnitts-SWS mindestens 3 beträgt, werden ausgegeben.

SELECT gelesenvon, name, SUM(sws) AS deputat
  FROM vorlesungen
 INNER JOIN professoren
    ON gelesenvon = persnr
   AND rang = 'C4'
 GROUP BY gelesenvon, name
HAVING AVG(sws) >= 3;

Ablauf: FROM+JOIN -> WHERE/ON-Filter -> GROUP BY -> HAVING-Filter -> SELECT

COUNT DISTINCT und NULL

Vergleich der drei COUNT-Varianten. Bei einer Tabelle mit 10 Zeilen, davon 3 mit NULL im Semester und Werte 1,2,2,3,3,5,5 in den restlichen 7 Zeilen: COUNT(*) = 10, COUNT(semester) = 7, COUNT(DISTINCT semester) = 4.

SELECT COUNT(*)            AS alle,       -- 10
       COUNT(semester)     AS gefuellt,   -- 7
       COUNT(DISTINCT semester) AS unique  -- 4
  FROM studenten;

MAX per Unterabfrage - Wer hat die meisten Semester?

Klassisches Muster: MAX() allein liefert nur den Wert 18, aber nicht die zugehörige Zeile. Mit skalarer Unterabfrage wird auf den Maximalwert gefiltert und der komplette Datensatz zurückgegeben.

SELECT matrnr, name
  FROM studenten
 WHERE semester = (SELECT MAX(semester)
                     FROM studenten);

Visualisierung: GROUP BY Pipeline

Schritt 1: Ausgangstabelle Vorlesungen vorlnr titel sws gelesenvon 5001 Grundzüge 4 2137 5041 Ethik 4 2125 5043 Erkenntnistheorie 3 2126 5049 Mass und Messen 2 2125 5052 Wissenschaftstheorie 3 2126 GROUP BY gelesenvon Schritt 2: Gruppierung in Blöcke pro Professor Prof 2137 Sokrates sws: 4 1 Vorlesung Prof 2125 Kant sws: 4, 2 2 Vorlesungen Prof 2126 Russel sws: 3, 3 2 Vorlesungen SUM(sws) pro Gruppe Schritt 3: Aggregat-Ergebnistabelle gelesenvon deputat 2137 4 2125 6 2126 6 Ausführungsreihenfolge 1 FROM / JOIN 2 WHERE Zeilenfilter 3 GROUP BY Gruppierung 4 HAVING Gruppenfilter 5 SELECT Aggregate 6 DISTINCT 7 ORDER BY Alias aus SELECT funktioniert erst ab ORDER BY. Legende Sokrates (2137) Kant (2125) Russel (2126) Merke: HAVING filtert Gruppen nach der Aggregation, WHERE filtert Zeilen davor.

Häufige Fragen

Was ist der Unterschied zwischen COUNT(*) und COUNT(spalte)?
COUNT(*) zählt jede Zeile, unabhängig vom Inhalt. COUNT(spalte) zählt nur Zeilen, in denen die angegebene Spalte einen nicht-NULL Wert hat. Deshalb kann COUNT(*) größer oder gleich COUNT(spalte) sein, aber nie kleiner.
Warum darf ich in WHERE keine Aggregatfunktion verwenden?
Weil WHERE vor GROUP BY ausgeführt wird. Zum Zeitpunkt der WHERE-Auswertung sind noch keine Gruppen gebildet, also gibt es keine Aggregatwerte. Aggregatbedingungen gehören in HAVING, das nach GROUP BY ausgeführt wird.
Wie behandeln Aggregatfunktionen NULL-Werte?
Die meisten DBMS ignorieren NULL-Werte in AVG, SUM, MIN, MAX und COUNT(spalte). Nur COUNT(*) zählt auch Zeilen, die ausschliesslich NULL enthalten. Ein AVG über eine Spalte mit NULL-Werten liefert also den Durchschnitt der definierten Werte.
Was passiert bei nicht-aggregierten Spalten ohne GROUP BY-Eintrag?
Die meisten DBMS werfen einen Fehler, weil pro Gruppe genau ein Ergebnistupel erzeugt wird und unklar ist, welchen Wert die nicht aggregierte, nicht gruppierte Spalte annehmen soll.
Wie finde ich den Datensatz mit dem Maximum eines Attributs?
Mit einer skalaren Unterabfrage: SELECT * FROM tabelle WHERE spalte = (SELECT MAX(spalte) FROM tabelle). MAX() allein liefert nur den Wert, nicht die zugehörige Zeile.
Was macht DISTINCT innerhalb von COUNT?
COUNT(DISTINCT spalte) eliminiert Duplikate vor dem Zählen und liefert die Anzahl unterschiedlicher Ausprägungen. NULL-Werte werden weiterhin nicht mitgezählt.
Kann ich einen AS-Alias in HAVING verwenden?
In der Regel nein, da SELECT logisch nach HAVING ausgeführt wird. Man muss den Aggregatausdruck in HAVING erneut hinschreiben, zum Beispiel HAVING SUM(sws) > 5 statt HAVING deputat > 5. In ORDER BY funktioniert der Alias hingegen.
Was ist die Ausführungsreihenfolge einer SQL-Anfrage mit Aggregatfunktion?
FROM/JOIN, dann WHERE, dann GROUP BY, dann HAVING, dann SELECT (inklusive Aggregatberechnung), dann DISTINCT, dann ORDER BY, zuletzt Mengenoperationen wie UNION.

Prüfungsfragen

0 von 12 Fragen beantwortet