Zurück zur Artikelliste Artikel
12 Leseminuten

Lernen Sie die OVER()-Klausel in SQL mit 7 Beispielen

Lernen Sie, wie Sie die OVER()-Klausel in Ihren SQL-Abfragen verwenden können, und Sie werden in Ihren SQL-Kenntnissen einen Schritt nach vorne machen.

Die meisten Datenanalysten und SQL-Entwickler sind mit den Klauseln SELECT, FROM, WHERE, GROUP BY und ORDER BY gut vertraut. Diese Klauseln stellen jedoch nur eine begrenzte Teilmenge der SQL-Sprache dar. Es gibt noch andere Klauseln, wie OVER(), die es uns ermöglichen, flexible SQL-Berichte zu erstellen und der SQL-Sprache eine enorme Ausdruckskraft zu verleihen.

In diesem Artikel werden wir die OVER() Klausel anhand einiger Beispiele für ihre Verwendung erklären. Fangen wir an!

Die OVER()-Klausel in SQL

SQL ist relativ leicht zu erlernen; jeder kann lernen, wie man einfache Abfragen in SQL erstellt. Tatsächlich kann der durchschnittliche IT-Profi oder SQL-Entwickler die grundlegenden Klauseln SELECT, FROM, WHERE, GROUP BY und ORDER BY mit Leichtigkeit handhaben. Diese Klauseln gab es schon in SQL-92, das 30 Jahre alt ist! Die SQL-Sprache ist jedoch noch nicht am Ende; seit 1992 sind viele neue Elemente hinzugekommen. Eines davon ist die OVER()-Klausel, die es uns ermöglicht, Fensterfunktionen in SQL-Abfragen zu verwenden.

In SQL sind Fensterfunktionen ähnlich wie GROUP BY, da sie eine Gruppe von Zeilen bearbeiten. Allerdings basieren Window-Funktionen auf einem Datenfenster oder einer Gruppe von Zeilen, die mit der aktuellen Zeile zusammenhängen. Im Gegensatz zu GROUP BY werden bei Fensterfunktionen die Zeilen nicht komprimiert; die Details der einzelnen Zeilen bleiben intakt.

Für diejenigen, die sich eingehender mit OVER() und Fensterfunktionen in SQL beschäftigen möchten, empfehle ich unseren Online-KursFensterfunktionen . Er enthält eine Fülle von Beispielen für die Verwendung verschiedener Fensterfunktionen.

Ok, kommen wir zurück zur OVER()-Funktion. Schauen wir uns zunächst unsere Daten an.

Einführung in die Daten

Unsere Beispielabfragen basieren auf den folgenden Daten.

Nehmen wir an, wir haben ein Meeresfrüchte-Restaurant an der Mittelmeerküste. Wir haben auch eine Datenbanktabelle, in der wir den Verlauf der Aktivitäten des Restaurants speichern. Unsere Datenbank hat eine Tabelle namens restaurant_activity mit diesen Spalten:

  • table_number
  • waiter_name
  • start_date
  • start_time
  • served_time
  • end_time
  • total_diners
  • amount_payment
  • total_tips.

Unten sehen Sie ein Beispiel für die Daten:

Restaurant_activity

table_numberwaiter_namestart_datestart_ timeserved_timeend_timetotal_dinersamount_paymenttotal_tips
1John5/5/202211:0311:1711:453350.0037
2Peter5/5/202211:1011:3213:104430.5050
3Mary5/5/202211:3012:0512:402260.3520
1John5/5/202212:0012:3813:104670.1230
3Mary5/5/202212:4313:1213:503320.5020
2Peter6/5/202211:1011:2111:405560.7560
3Mary6/5/202211:4011:5312:403240.1025
1John6/5/202211:3011:5312:301150.0010
3Mary6/5/202214:1014:2014:401240.1025
1Mary6/5/202214:3014:3514:502150.0030

Das alles sollte selbsterklärend sein, aber lassen Sie uns kurz ein paar Spalten durchgehen. start_date und start_time sind das Datum und die Uhrzeit, zu der die Kunden unter table_number eine Mahlzeit bestellt haben; served_time ist der Zeitpunkt, zu dem die Mahlzeit serviert wurde, und end_time ist der Zeitpunkt, zu dem die Kunden ihre Rechnung verlangt haben. amount_payment ist der Preis der Mahlzeit, ohne Trinkgeld (das als total_tips gespeichert wird).

Beispielabfrage Nr. 1: Eine einfache Anwendung von OVER()

Beginnen wir mit der OVER() Klausel, um den prozentualen Anteil jeder Bestellung am gesamten Tagesumsatz auf 5/5/2022 zu berechnen. Wir können die Gesamteinnahmen des Restaurants an diesem Tag (ohne Trinkgeld) mit OVER() in Kombination mit der Fensterfunktion SUM() berechnen. Die Klausel OVER() ist immer mit einer Fensterfunktion verbunden; Fensterfunktionen berechnen einen Wert auf der Grundlage einer durch die Klausel OVER() definierten Gruppe von Datensätzen. Die Abfrage lautet:

SELECT 
  start_date AS date,
  SUM(amount_payment) OVER () AS daily_revenue,
  amount_payment AS total_order,
  (amount_payment / SUM(amount_payment) OVER ())*100 AS order_percentage
FROM   restaurant_activity
WHERE  start_date = ’5/5/2022’

Die vorherige Abfrage berechnet daily_revenue für 5/5/2022, indem jeder Zahlungsbetrag für diesen Tag addiert wird. Die Klausel OVER() ist leer, was bedeutet, dass das Fenster der Datensätze, die zur Berechnung der Funktion SUM() verwendet werden, der gesamte Satz von Datensätzen ist, der von der Abfrage zurückgegeben wird. Mit anderen Worten, das Fenster besteht aus allen Datensätzen für dieses Datum.

Bei der Berechnung der Spalte order_percentage dividieren wir den einzelnen Bestellbetrag durch den gesamten Tagesumsatz, um den Prozentsatz zu erhalten; dies ist ein zentraler Punkt in der Abfrage, da wir Spalten auf Zeilenebene mit den Ergebnissen der Fensterfunktion in einem Ausdruck kombinieren.

Zu verstehen, welche Datensätze Teil des Fensters sind, ist von zentraler Bedeutung für das Verständnis der Funktionsweise von Fensterfunktionen; wir werden später in diesem Artikel auf diesen Punkt zurückkommen. Schauen wir uns jetzt erst einmal die Ergebnisse an:

datedaily_revenuetotal_orderorder_percentage
2022-05-052031.47350.0017.23
2022-05-052031.47430.5021.19
2022-05-052031.47260.3512.82
2022-05-052031.47670.1232.99
2022-05-052031.47320.5015.78

Bevor wir uns mit komplexeren OVER() Beispielen beschäftigen, möchte ich Ihnen unser Fensterfunktionen Übungsset empfehlen. Wenn Sie den Umgang mit OVER() wirklich lernen wollen, sind diese 100 interaktiven Übungen genau das Richtige für Sie.

Beispielabfrage Nr. 2: Verwendung der Unterklausel PARTITION BY

In diesem Beispiel verwenden wir die Subklausel PARTITION BY, die mit OVER() zusammenarbeitet, um Datenfenster zu definieren.

Nehmen wir an, wir wollen einen Bericht ähnlich dem vorherigen, aber erweitert auf jeden Tag im Mai 2022. Für jeden Tag möchten wir den gesamten Tagesumsatz, den individuellen Auftragswert für alle Aufträge und den prozentualen Anteil der einzelnen Aufträge am Tagesumsatz sehen. Hier ist die Abfrage:

SELECT start_date AS date,
  		SUM(amount_payment) OVER (PARTITION BY start_date) AS daily_revenue,
  		amount_payment AS total_order,
  		(amount_payment / SUM(amount_payment) OVER (PARTITION BY start_date)) * 100 AS
    order_percentage
FROM restaurant_activity
WHERE start_date BETWEEN ’5/1/2022’ AND ’5/31/2022’ 

Wir haben bereits erwähnt, dass Fensterfunktionen auf der Grundlage eines Fensters von Datensätzen (oder einer Gruppe von Datensätzen) arbeiten, die sich auf den aktuellen Datensatz beziehen. Die vorherige Abfrage verwendet die Unterklausel PARTITION BY start_date, um zu definieren, welche Datensätze in jedes Fenster gehören. In unserem Beispiel sind alle Datensätze mit demselben start_date Wert wie die aktuelle Zeile Teil des Fensters. Da wir nur zwei eindeutige Werte für start_date haben, haben wir nur zwei verschiedene Fenster. Sie werden im folgenden Ergebnis in grün und rot dargestellt:

datedaily_revenuetotal_orderorder_percentage
5/5/20222031.47350.0017.23
5/5/20222031.47430.5021.19
5/5/20222031.47260.3512.82
5/5/20222031.47670.1232.99
5/5/20222031.47320.5015.78
5/6/20221340.95560.7541.82
5/6/20221340.95240.1017.91
5/6/20221340.95150.0011.19
5/6/20221340.95240.1017.91
5/6/20221340.95150.0011.19

Beispielabfrage Nr. 3: Verwendung der ORDER BY Subklausel

Lassen Sie uns nun die ORDER BY Subklausel einführen, die es Ihnen ermöglicht, Zeilen in eine bestimmte Reihenfolge innerhalb des Fensters zu bringen.

Nehmen wir an, wir möchten einen einfachen Bericht mit den fünf teuersten Bestellungen zusammen mit dem Kellner, dem Datum und der Tischnummer erstellen. Die Abfrage lautet:

WITH ranking AS (
	SELECT 
table_number, 
amount_payment, 
waiter_name, 
start_date, 
	       RANK() OVER (ORDER BY amount_payment DESC) AS position
	FROM restaurant_activity
)
SELECT 
  amount_payment, 
  waiter_name, 
  start_date, 
  table_number, 
  position
FROM ranking
WHERE position <= 5
ORDER BY position

Hier haben wir eine CTE (oder Common Table Expression) verwendet, um die Rangfolge zu erstellen. In der CTE wurde die Position jeder Bestellung mit Hilfe der Fensterfunktion RANK() in Kombination mit der folgenden OVER() Klausel berechnet:

RANK() OVER (ORDER BY amount_payment DESC)

Die obige Klausel definiert ein Fenster von Datensätzen, das durch alle Datensätze in der Tabelle gebildet wird, so dass alle Bestellungen des Restaurants enthalten sind. Dieses Fenster wird von amount_payment in absteigender Reihenfolge sortiert: Der größte Betrag ist der erste Datensatz im Fenster usw. Die Funktion RANK() gibt die Position der aktuellen Zeile im geordneten Fenster zurück und speichert diesen Wert in der Spalte Position der CTE.

Nachdem wir die CTE für die Rangfolge erstellt haben, verwendet der Rest der Abfrage ranking wie jede andere Tabelle. Wir filtern die Datensätze, um nur die ersten 5 Ränge zu erhalten, und ordnen die Ergebnisse dann nach position. Dies ist das Ergebnis:

amount_paymentwaiter_namestart_datetable_numberposition
670.12John2022-05-0511
560.75Peter2022-05-0622
430.50Peter2022-05-0523
350.00John2022-05-0514
320.50Mary2022-05-0535

An dieser Stelle möchte ich Ihnen den Artikel SQL Fensterfunktionen vs. GROUP BY: What's the Difference? Er erklärt die Unterschiede zwischen der GROUP BY Klausel und den Fensterfunktionen anhand mehrerer SQL-Abfragebeispiele. Ein weiterer interessanter Artikel mit zusätzlichen Details ist Was ist die OVER()-Klausel in SQL?

Beispielabfrage Nr. 4: Berechnung der tabellenfreien Zeit mit OVER() und LAG()

Window-Funktionen bieten viele Möglichkeiten, um komplexe Berechnungen zu vereinfachen. Eine davon ist die Funktion LAG(), die einen Wert aus einer beliebigen Spalte der vorherigen Zeile in Bezug auf die aktuelle Zeile des Fensters zurückgibt.

Der Besitzer des Restaurants möchte wissen, wie lange die Tische frei sind, z. B. die Zeit, die zwischen den Gästen vergeht. Dazu kann ein Bericht mit den Spalten table_number, date, free_start, free_end und free_time_duration erstellt werden.

Um die freie Zeit zu berechnen, müssen wir auf zwei Zeilen zugreifen. Wir benötigen die end_time der vorherigen Belegung und die start_time der nächsten Belegung; dann können wir die verstrichene Zeit zwischen den beiden Belegungen berechnen. Hier kommt die Fensterfunktion LAG() ins Spiel, denn LAG() ermöglicht den Zugriff auf eine beliebige Spalte im vorherigen Datensatz. Hier ist die Abfrage, die wir verwenden würden:

    SELECT 
start_date AS date,
table_number,
-- ending time of the previous occupation 
COALESCE(LAG(end_time) OVER (PARTITION BY start_date, table_number 
                             ORDER BY start_time),'11:00') AS start_free_time,
-- starting time of current occupation
start_time AS end_free_time,
-- calculating the free time when the table was unoccupied
start_time - 
COALESCE(LAG(end_time) OVER (PARTITION BY start_date, table_number 
                             ORDER BY start_time),'11:00') AS free_time_duration
    FROM   restaurant_activity

Der Schlüssel in der vorherigen Abfrage ist die Fensterfunktion LAG(). Wir haben sie verwendet, um die end_time des vorherigen Berufs zu erhalten. Die Klausel ...

OVER (PARTITION BY start_date, table_number ORDER BY start_time)

... definiert ein Fenster (oder eine Reihe von Zeilen) für jedes eindeutige Paar von <start_date, table_number>, und jedes dieser Fenster ist nach start_time geordnet. Dann gibt LAG(end_time) die Endzeit der vorherigen Belegung der Tabelle zurück.

Sie haben wahrscheinlich bemerkt, dass wir LAG() zweimal verwendet haben. Das erste Mal wird verwendet, um die Anfangszeit des freien Zeitraums zu erhalten, und das zweite Mal, um die Dauer der freien Zeit mit Hilfe des folgenden Ausdrucks zu berechnen:

start_time - 
coalesce(LAG(end_time) OVER (PARTITION BY start_date, table_number 
                             ORDER BY start_time),'11:00')

Die Ergebnisse der Abfrage sind:

datetable_numberstart_free_timeend_free_timefree_time_duration
5/5/2022111:00:0011:03:0000:03:00
5/5/2022111:45:0012:00:0000:15:00
5/5/2022211:00:0011:10:0000:10:00
5/5/2022311:00:0011:30:0000:30:00
5/5/2022312:40:0012:43:0000:03:00
6/5/2022111:00:0011:30:0000:30:00
6/5/2022112:30:0014:30:0002:00:00
6/5/2022211:00:0011:10:0000:10:00
6/5/2022311:00:0011:40:0000:40:00
6/5/2022312:40:0014:10:0001:30:00

Bevor wir zum nächsten Abschnitt übergehen, empfehle ich die folgenden Artikel für weitere Details:

Beispielabfrage #5: Berechnen von Rangfolgen mit der OVER()-Klausel

In diesem Abschnitt werden wir eine Geschäftssituation behandeln, in der die SQL OVER() Klausel angewendet werden kann, um eine Rangliste zu erstellen, z. B. die 10 besten Verkäufer oder die 5 meistverkauften Produkte. Sie können die OVER() Klausel in Kombination mit der RANK() Fensterfunktion verwenden, um diese Art von Bericht zu erhalten. Sehen wir uns eine Beispielabfrage an, die die beiden höchsten Trinkgelder des Tages und den Kellner, der sie erhalten hat, zurückgibt:

SELECT  *
FROM (
  		SELECT 	waiter_name,
  			start_date AS date,
			total_tips AS tip_amount,
			RANK() OVER(PARTITION BY start_date ORDER BY total_tips DESC) AS ranking
  	  	FROM restaurant_activity
     	     ) AS ranking_table
WHERE ranking <= 2;

Wir haben eine Unterabfrage in der FROM Klausel verwendet, um eine temporäre "Tabelle" namens ranking_table zu erstellen. Sie verwendet die Spalte ranking, um die Position des Trinkgelds in der täglichen Trinkgeldrangliste zu speichern. Die Position in der Rangliste wird mit der Fensterfunktion RANK() berechnet. Andere Spalten in der temporären Tabelle sind waiter_name, date und tip_amount.

In der äußeren Abfrage filtern wir nur die Tipps mit den Rängen 1 und 2. Das Ergebnis der Abfrage ist unten dargestellt:

waiter_namedatetip_amountranking
John5/5/2022501
John5/5/2022372
Peter6/5/2022601
Mary6/5/2022302

Beispielabfrage Nr. 6: Berechnen von Unterschieden zu einem früheren Zeitraum

Eine weitere interessante Möglichkeit ist die Anzeige der Wertedifferenz zwischen dem vorherigen und dem aktuellen Zeitraum. Die OVER() Klausel kombiniert mit Fensterfunktionen wie LEAD() und LAG() werden verwendet, um diese Art von Bericht zu erstellen. Weitere Hintergrundinformationen finden Sie unter Wie man die Differenz zwischen zwei Zeilen in SQL berechnet.

Nehmen wir an, wir möchten die Tageseinnahmen der einzelnen Kellner zusammen mit der Differenz zum Vortag anzeigen. In derselben Zeile möchten wir auch die Differenz in Prozent anzeigen. Hier ist die Abfrage:

SELECT	
  waiter_name,
  date,
  today_revenue,
  -- revenue variation ----------------------------------------------
  LAG(today_revenue) OVER (
    PARTITION BY waiter_name ORDER BY date) yesterday_revenue,
  today_revenue - LAG(today_revenue) OVER (
    PARTITION BY waiter_name ORDER BY date) AS revenue_variation,
  -- -----------------------------------------------------------------
  -- revenue variation percentage ------------------------------------------------
  round((today_revenue - 
   LAG(today_revenue) OVER (PARTITION BY waiter_name ORDER BY date)) /
   LAG(today_revenue) OVER (PARTITION BY waiter_name ORDER BY date),2) * 100 
AS revenue_variation_percentage
  -- -----------------------------------------------------------------------------		
FROM (
  SELECT  DISTINCT 
    start_date::date AS "date",
    waiter_name,
    SUM(total_tips) OVER ( 
      PARTITION BY waiter_name, start_date::date ORDER BY start_date)
AS today_revenue
  FROM 	restaurant_activity
) AS daily_revenue_per_waiter;

Wir haben eine Unterabfrage in der FROM -Klausel mit dem Namen daily_revenue_per_waiter erstellt, die waiter_name, date und den Gesamtumsatz dieses Kellners an diesem Tag enthält. In der äußeren Abfrage verwenden wir die Fensterfunktion LAG(), um die Einnahmen des Vortages zu erhalten, dann erhalten wir die Abweichung der Einnahmen zwischen gestern und heute und die prozentuale Abweichung. Das Ergebnis ist:

waiter_namedatetoday_revenueyesterday_revenuerevenue_variationrevenue_variation_percentage
John2022-05-0567nullnullnull
John2022-05-061067-57-85.00
Mary2022-05-0540nullnullnull
Mary2022-05-06804040100.00
Peter2022-05-0550nullnullnull
Peter2022-05-0660501020.00

Beispielabfrage Nr. 7: Gleitender Durchschnitt

Gleitende Durchschnitte sind eine Metrik, die häufig verwendet wird, um kurzfristige Schwankungen auszugleichen. Mehr dazu erfahren Sie in dem Artikel Was ein gleitender Durchschnitt ist und wie man ihn in SQL berechnet; sehen wir uns an, wie wir einen solchen mit der OVER() Klausel erstellen können.

Nehmen wir an, der Restaurantbesitzer möchte wissen, wie viel er im Durchschnitt pro Person an dem zuletzt bedienten Tisch bezahlt hat. Er verwendet diesen Durchschnittswert als Kennzahl, um zu wissen, wie viel Geld die Gäste zahlen, und um einige Werbeaktionen oder Rabatte zu aktivieren. Wir können diesen Durchschnittswert leicht mit dem Ausdruck amount_payment/total_diners berechnen. Der Eigentümer hat jedoch festgestellt, dass diese Kennzahl starken Schwankungen unterliegt, weshalb er beschlossen hat, den durchschnittlichen Betrag zu verwenden, der pro Person an den letzten 3 und 6 bedienten Tischen bezahlt wurde. Die Abfrage zur Berechnung dieses Berichts lautet:

SELECT start_date AS "date",
start_time AS "time",
table_number,
amount_payment AS total_amount,
total_diners,
       	ROUND(amount_payment/total_diners,2) AS diner_avg,
ROUND(AVG(amount_payment/total_diners) 
     OVER (ORDER BY start_date, start_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) 
     AS diner_moving_avg_last_3_tables_served,
	ROUND(AVG(amount_payment/total_diners) 
     OVER (ORDER BY start_date, start_time ROWS BETWEEN 5 PRECEDING AND CURRENT ROW),2) 
     AS diner_moving_avg_last_6_tables_served
FROM   restaurant_activity

Die vorherige Abfrage berechnet 3 verschiedene Durchschnittswerte. Der erste ist ein einfacher Durchschnitt auf der Grundlage des Ausdrucks:

ROUND(amount_payment/total_diners,2)

Der zweite Durchschnitt ist der gleitende Durchschnitt für die letzten 3 bedienten Tische; der dritte Durchschnitt ist genau derselbe, aber für die letzten 6 bedienten Tische:

ROUND(AVG(amount_payment/total_diners) 
     OVER (ORDER BY start_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2)
…
OVER (ORDER BY start_date, start_time ROWS BETWEEN 5 PRECEDING AND CURRENT ROW),2) 

Der Ausdruck "ROWS BETWEEN 2 PRECEDING AND CURRENT ROW" gibt an, dass der Durchschnitt auf der Grundlage von 3 Zeilen berechnet wird: der aktuellen Zeile und den beiden unmittelbar vorangehenden Zeilen. Das Fenster ist nach der Anfangszeit des Tisches geordnet. Hier ist das Ergebnis:

datetimetable_numbertotal_amountdinersdiner_avgdiner_moving_avg_last_3_tables_serveddiner_moving_avg_last_6_tables_served
2022-05-0511:031350.003116.67116.67116.67
2022-05-0511:102430.504107.63112.15112.15
2022-05-0511:303260.352130.18118.16118.16
2022-05-0512:001670.124167.53135.11130.50
2022-05-0512:433320.503106.83134.85125.77
2022-05-0611:102560.755112.15128.84123.50
2022-05-0611:403240.10380.0399.67117.39
2022-05-0611:301150.001150.00114.06124.45
2022-05-0614:103240.101240.10156.71142.77
2022-05-0614:301150.00275.00155.03127.35

Andere geschäftliche Verwendungszwecke der OVER() -Klausel umfassen die Berechnung laufender Summen (nützlich in allen Arten von Finanzanalyseszenarien) und die Berechnung der Länge einer Datenreihe.

Sind Sie bereit, die SQL-Klausel OVER() zu üben?

Wir haben mehrere Abfragen mit der SQL-Klausel OVER() demonstriert. Da OVER() in Kombination mit einer Fensterfunktion verwendet werden muss, haben wir auch einige dieser Funktionen behandelt: SUM(), AVG(), LAG() und RANK().

Wenn Sie Ihr neues Wissen über die SQL-Klausel OVER() in die Praxis umsetzen möchten, empfehle ich Ihnen unseren interaktiven KursFensterfunktionen , gefolgt von unserem Fensterfunktionen Practice Set. Sie können über den Kurs im Artikel SQL-Kurs des Monats - Fensterfunktionen lesen. Sie können auch eine Kopie unseres kostenlosen SQL Fensterfunktionen Cheat Sheets erhalten, das Ihnen beim Lernen hilft. Entwickeln Sie Ihre Fähigkeiten und steigern Sie Ihr Vermögen!