Zurück zur Artikelliste Artikel
10 Leseminuten

Was ist die OVER()-Klausel in SQL?

Fensterfunktionen sind eine der leistungsfähigsten Ressourcen von SQL, werden aber vom durchschnittlichen SQL-Entwickler nicht häufig verwendet. In diesem Artikel wird erklärt, wie Sie verschiedene Arten von Fensterrahmen mit Hilfe der OVER Klausel definieren können.

Die OVER Klausel ist für SQL-Fensterfunktionen unerlässlich. Wie Aggregationsfunktionen führen Fensterfunktionen Berechnungen auf der Grundlage einer Reihe von Datensätzen durch - z. B. die Ermittlung des Durchschnittsgehalts einer Gruppe von Mitarbeitern.

In manchen Fällen können Aggregatfunktionen nicht verwendet werden, da sie alle einzelnen Datensätze zu einer Gruppe zusammenfassen; dies macht es unmöglich, sich auf bestimmte Werte zu beziehen (z. B. auf das Gehalt eines Mitarbeiters aus der Gruppe). In diesen Fällen sind Fensterfunktionen vorzuziehen, da sie die Zeilen nicht kollabieren; Sie können sich sowohl auf einen Spaltenwert auf Zeilenebene als auch auf den aggregierten Wert beziehen.

Es gibt noch andere Szenarien, in denen Fensterfunktionen nützlich sind. So kann es beispielsweise erforderlich sein, eine einzelne Spalte zu berechnen und eine Berechnung auf der Grundlage einer Reihe von Zeilen durchzuführen. Ein Beispiel aus der Praxis ist die Berechnung der Differenz zwischen dem durchschnittlichen Abteilungsgehalt und dem Gehalt der einzelnen Mitarbeiter in der Abteilung.

Bei der Verwendung von Fensterfunktionen ist die Definition der Datensatzgruppe, in der die Funktion berechnet werden soll, von entscheidender Bedeutung. Dieser Satz von Datensätzen wird als Fensterrahmen bezeichnet; wir definieren ihn mit der SQL-Klausel OVER.

In diesem Artikel werden wir SQL-Abfragen anhand der Datenbank eines kleinen Luxusuhrenherstellers demonstrieren. Das Unternehmen speichert seine Verkaufsinformationen in einer Tabelle namens sales:

sale_day sale_month sale_time branch article quantity revenue
2021-08-11 AUG 11:00 New York Rolex P1 1 3000.00
2021-08-14 AUG 11:20 New York Rolex P1 2 6000.00
2021-08-17 AUG 10:00 Paris Omega 100 3 4000.00
2021-08-19 AUG 10:00 London Omega 100 1 1300.00
2021-07-17 JUL 09:30 Paris Cartier A1 1 2000.00
2021-07-11 JUL 10:10 New York Cartier A1 1 2000.00
2021-07-10 JUL 11:40 London Omega 100 2 2600.00
2021-07-15 JUL 10:30 London Omega 100 3 4000.00

Der Fensterrahmen ist ein Satz von Zeilen, der von der aktuellen Zeile abhängt; der Satz von Zeilen kann sich also für jede von der Abfrage verarbeitete Zeile ändern. Wir definieren Fensterrahmen mit der Klausel OVER. Die Syntax lautet:

  OVER ([PARTITION BY columns] [ORDER BY columns])

Die Unterklausel PARTITION BY definiert die Kriterien, die die Datensätze erfüllen müssen, um Teil des Fensterrahmens zu sein. Mit anderen Worten: PARTITION BY definiert die Gruppen, in die die Zeilen eingeteilt werden; dies wird in unserer nächsten Beispielabfrage deutlicher werden. Schließlich definiert die ORDER BY Klausel die Reihenfolge der Datensätze im Fensterrahmen.

Schauen wir uns die SQL OVER Klausel in Aktion an. Hier ist eine einfache Abfrage, die die Gesamtmenge der verkauften Einheiten für jeden Artikel zurückgibt.

SELECT sale_day, sale_time, 
       branch, article, quantity, revenue,
       SUM(quantity) OVER (PARTITION BY article) AS total_units_sold
FROM   sales

Diese Abfrage zeigt alle Datensätze der Tabelle sales mit einer neuen Spalte, die die Gesamtzahl der verkauften Einheiten für den betreffenden Artikel anzeigt. Wir können die Menge der verkauften Einheiten mit der Aggregationsfunktion SUM ermitteln, aber dann könnten wir nicht die einzelnen Datensätze anzeigen.

In dieser Abfrage gibt die OVER PARTITION BY article Subklausel an, dass der Fensterrahmen durch die Werte in der article Spalte bestimmt wird; alle Datensätze mit dem gleichen article Wert werden in einer Gruppe zusammengefasst. Unten sehen Sie das Ergebnis dieser Abfrage:

sale day sale time branch article quantity revenue total units sold
2021-07-11 10:10 New York Cartier A1 1 2000.00 2
2021-07-17 9:30 Paris Cartier A1 1 2000.00 2
2021-08-19 10:00 London Omega 100 1 1300.00 9
2021-07-15 10:30 London Omega 100 3 4000.00 9
2021-08-17 10:00 Paris Omega 100 3 4000.00 9
2021-07-10 11:40 London Omega 100 2 2600.00 9
2021-08-11 11:00 New York Rolex P1 1 3000.00 3
2021-08-14 11:20 New York Rolex P1 2 6000.00 3

Die Spalte total_units_sold des Berichts wurde durch den Ausdruck ermittelt:

SUM(quantity) OVER (PARTITION BY article) total_units_sold

Den Lesern, die sich eingehender mit dem Thema befassen möchten, empfehle ich die folgenden beiden Artikel: Was ist der Unterschied zwischen GROUP BY und PARTITION BY und Fensterfunktionen in SQL Server: Teil Eins: Die OVER()-Klausel

Die SQL OVER-Klausel in Aktion

Angenommen, wir wollen für jeden Artikel die Gesamtmenge dieses Artikels, die in jedem Monat des Jahres 2021 verkauft wurde, mit der Gesamtmenge dieses Artikels im gesamten Jahr vergleichen. Zu diesem Zweck erstellen wir einen einfachen Bericht mit den Spalten article, month, units_sold_month und units_sold_year. Die Abfrage lautet:

SELECT DISTINCT article,
       EXTRACT('month' FROM sale_day) AS month,
       SUM(quantity) OVER (PARTITION BY article, sale_month) AS units_sold_month,
       SUM(quantity) OVER (PARTITION BY article) AS units_sold_year
FROM  sales 
WHERE EXTRACT('YEAR' FROM sale_day) = 2021
ORDER BY article, month

Die Ergebnisse der Abfrage sind:

article month units_sold_month units_sold_year
Cartier A1 7 2 2
Omega 100 7 5 9
Omega 100 8 4 9
Rolex P1 8 3 3

Hier haben wir die Gesamtzahl der verkauften Einheiten anhand von zwei verschiedenen Gruppierungsgranularitäten berechnet: Monat und Jahr. Die erste Klausel OVER...

OVER (PARTITION BY article, sale_month) 

... ermöglicht es uns, die Anzahl der verkauften Einheiten jedes Artikels in einem Monat zu erhalten. Die zweite Klausel OVER...

OVER (PARTITION BY article)

... ermöglicht es uns, die Gesamtzahl der verkauften Einheiten eines bestimmten Artikels im gesamten Jahr zu berechnen.

In der nächsten Abfrage fügen wir einfach die Spalte month_percentage hinzu, um den prozentualen Anteil eines bestimmten Monats an der Gesamtzahl des Jahres anzuzeigen. Wir können dies mit der folgenden Abfrage berechnen:

SELECT DISTINCT article,
       EXTRACT('month' FROM sale_day) as month,
       SUM(quantity) OVER (PARTITION BY article, sale_month) AS units_sold_month,
       SUM(quantity) OVER (PARTITION BY article) AS units_sold_year,
       ( ( SUM(quantity) OVER (PARTITION BY article, sale_month)::decimal /
           SUM(quantity) OVER (PARTITION BY article)::decimal ) * 100
       ) AS month_percentage
FROM sales 
WHERE extract('YEAR' FROM sale_day) = 2021
ORDER BY article, month

Beachten Sie, dass wir in der vorherigen Abfrage keine unterschiedlichen OVER Klauseln verwendet haben; wir haben einfach dieselben Fensterfunktionen wiederverwendet und einen Prozentsatz berechnet. Die Ergebnisse sind unten zu sehen:

article month units_sold_month units_sold_year month_percentage
Cartier A1 7 2 2 100.00
Omega 100 7 5 9 55.55
Omega 100 8 4 9 45.44
Rolex P1 8 3 3 100.00

Wir werden nun einen anderen Bericht erstellen, der sich mit der Leistung der verschiedenen Zweige befasst. Wir möchten die Spalten branch und month anzeigen. Wir brauchen auch Berechnungen, um die:

  • Gesamtumsatz für diesen Monat.
  • Umsatz gruppiert nach Filiale und Monat.
  • Monatlicher Durchschnittsumsatz der Filiale.
  • Differenz zwischen dem Umsatz der einzelnen Filialen und dem monatlichen Durchschnittsumsatz.
SELECT DISTINCT branch,
       EXTRACT('month' FROM sale_day) AS month,
       SUM(revenue) OVER (PARTITION BY sale_month) AS total_revenue_month,
       SUM(revenue) OVER (PARTITION BY branch, sale_month) AS branch_revenue_month,
      
 -- Next column is the branch average revenue in the current month
	 ( 
		  SUM(revenue) OVER (PARTITION BY sale_month)::decimal 
		   / 
		  (SELECT COUNT(DISTINCT branch) FROM sales)::decimal 
	 ) AS average_month_branch,
 
 -- Next column is the difference between branch revenue and average branch revenue

        SUM(revenue) OVER (PARTITION BY branch, sale_month) -
	 ( 
		  SUM(revenue) OVER (PARTITION BY sale_month)::decimal 
		   / 
		  (SELECT COUNT(DISTINCT branch) FROM sales)::decimal 
	 ) AS gap_branch_average

FROM sales 
WHERE extract('YEAR' from sale_day) = 2021
ORDER BY branch, month

Wiederum haben wir nur zwei OVER Klauseln verwendet, aber wir haben verschiedene arithmetische Ausdrücke benutzt, um bestimmte Werte zu erhalten. Wir verwenden ...

SUM(revenue) OVER (PARTITION BY sale_month) 

... verwendet, um den Gesamtumsatz des Monats zu berechnen, aber wir haben ihn auch in einem arithmetischen Ausdruck verwendet, um den monatlichen Durchschnittsumsatz der Filialen zu erhalten.

Wir haben ...

SUM(revenue) OVER (PARTITION BY branch, sale_month) 

..., um den monatlichen Umsatz der Filiale und die Differenz zwischen dem monatlichen Umsatz dieser Filiale und dem Durchschnitt zu berechnen.

Die nächste Tabelle ist das Ergebnis der Abfrage. Beachten Sie, dass die Spalte gap_branch_average positive oder negative Zahlen enthalten kann. Eine negative Zahl bedeutet, dass der Monatsumsatz dieser Filiale unter dem Durchschnittsumsatz lag.

Branch Month total_revenue_month branch_revenue_month average_month_branch gap_branch_average
London 7 10600 6600 3533.33 3066.66
London 8 14300 1300 4766.66 -3466.66
New York 7 10600 2000 3533.33 -1533.33
New York 8 14300 9000 4766.66 4233.33
Paris 7 10600 2000 3533.33 -1533.33
Paris 8 14300 4000 4766.66 -766.66

Für weitere Informationen über Fensterfunktionen in SQL empfehle ich Beispiel für SQL Fensterfunktionen mit erklärungen , ein Artikel für Einsteiger über Fensterfunktionen. Für fortgeschrittene Leser: How to Rank Rows Within a Partition in SQL zeigt, wie Sie mit der Fensterfunktion RANK() Rangfolgen in Ihren Berichten erstellen können.

Die OVER-Klausel und Analytical Fensterfunktionen

In den vorherigen Abfragen haben wir Fensterfunktionen verwendet, um monatliche Zahlen (Umsatz bzw. verkaufte Einheiten) mit jährlichen Zahlen zu vergleichen. In diesem Abschnitt werden wir geordnete Fensterrahmen verwenden, die es uns ermöglichen, einen Datensatz im Rahmen auf der Grundlage seiner Position auszuwählen. Wir können zum Beispiel den ersten Datensatz im Fensterrahmen, den Datensatz vor dem aktuellen Datensatz oder den Datensatz nach dem aktuellen Datensatz auswählen. Diese analytischen Fensterfunktionen bieten SQL eine große Ausdruckskraft.

In der folgenden Abfrage wollen wir den Umsatzanstieg/-rückgang für dieselbe Branche in zwei zusammenhängenden Monaten anzeigen. Dazu müssen wir die Differenz zwischen dem Umsatz des aktuellen Monats und dem Umsatz des Vormonats berechnen. Dazu ist die analytische Fensterfunktion LAG() erforderlich, die einen Spaltenwert aus einer Zeile vor der aktuellen Zeile ermitteln kann.

WITH branch_month_sales AS (
	SELECT    DISTINCT
		    branch,
		    EXTRACT('MONTH' FROM sale_day) AS month,
		    SUM(revenue) OVER (PARTITION BY branch, sale_month ) AS revenue
	FROM sales
)
SELECT branch, 
	month,
	revenue AS revenue_current_month,
	LAG(revenue) OVER (PARTITION BY branch ORDER BY month) AS revenue_prev_month,
	revenue - LAG(revenue) OVER (PARTITION BY branch ORDER BY month) AS revenue_delta
FROM branch_month_sales
ORDER BY branch, month

In dieser Abfrage wurde ein gemeinsamer Tabellenausdruck (CTE) mit der Bezeichnung branch_month_sales verwendet, um die Gesamteinnahmen der einzelnen Zweige und Monate zu speichern. Dann haben wir eine zweite Abfrage geschrieben, die die Fensterfunktion LAG() verwendet, um den Umsatz des Vormonats zu erhalten (unter Verwendung der Informationen aus branch_month_sales). Beachten Sie, dass der Fensterrahmen nach Monat geordnet ist.

Hier sind die Ergebnisse:

Branch Month revenue_current_month revenue_prev_month revenue_delta
London 7 6600 null null
London 8 1300 6600 -5300
New York 7 2000 null null
New York 8 9000 2000 7000
Paris 7 2000 null null
Paris 8 4000 2000 2000

In allen in diesem Artikel gezeigten Abfragen haben wir nur einige wenige Fensterfunktionen verwendet. Es gibt noch eine ganze Reihe anderer Fensterfunktionen in SQL. Hier ist eine Liste der einzelnen Funktionen:

function syntax return value
AVG() AVG(expression) The average within the OVER partition.
COUNT() COUNT() The number of rows within the OVER partition.
MAX() MAX(expression) The maximum value of a column or expression for each partition.
MIN() MIN(expression) The minimum value of a column or expression for each partition.
SUM() SUM(expression) The total of all values in a column within a partition.
ROW_NUMBER() ROW_NUMBER() Assigns a unique number to each row within a partition. Rows with identical values are given row different numbers.
RANK() RANK() Ranks rows by column values within a partition. Gaps and tied rankings are permitted.
DENSE_RANK() DENSE_RANK() Ranks row by column values within a partition. There are no gaps in the ranking, but tied rankings are permitted.
PERCENT_RANK() PERCENT_RANK() Assigns a percentile ranking number to each row in a partition. To calculate a value in the [0, 1] interval, we use (rank - 1) / (total number of rows - 1).
CUME_DIST() CUME_DIST() Shows the cumulative distribution of a value within a group of values, i.e. the number of rows with values less than or equal to the current row’s value divided by the total number of rows.
LEAD() LEAD(expr, offset, default) The value of the row n number of rows after the current row. The offset and default arguments are optional; it will return the next row value by default.
LAG() LAG(expr, offset, default) The value of the row n number of rows before the current row. The offset and default arguments are optional; it will return the previous row value by default.
NTILE() NTILE(n) Divides rows within a partition into n groups and assigns each row a group number.
FIRST_VALUE() FIRST_VALUE(expr) The value for the first row within the window frame.
LAST_VALUE() LAST_VALUE(expr) The value for the last row within the window frame.
NTH_VALUE() NTH_VALUE(expr, n) The value for the n-th row within the window frame.

Erfahren Sie mehr über die SQL OVER-Klausel

In diesem Artikel haben wir uns mit der OVER Klausel und den Unterklauseln PARTITION BY und ORDER BY beschäftigt.

Wenn Sie mehr über Fensterfunktionen erfahren möchten, möchte ich Ihnen zwei Artikel ans Herz legen. Der erste ist 8 Best SQL Window Function Articles, der Sie auf weitere großartige Artikel hinweist. Der zweite ist ein Spickzettel für Fensterfunktionen, der Syntax, Beispiele und Bilder enthält; es ist mein Lieblingsartikel über Fensterfunktionen.

Für diejenigen, die tiefer einsteigen wollen, empfehle ich unseren interaktiven Fensterfunktionen SQL-Kurs. Wenn Sie Ihre SQL-Kenntnisse im Allgemeinen verbessern möchten, sollten Sie den Kurs SQL von A bis Z ausprobieren. Er bietet einen umfassenden Überblick über alles, was Sie wissen müssen, um effektiv mit SQL zu arbeiten.