RANK, DENSE_RANK, ROW_NUMBER: Rangfolgen erstellen

Wenn du mit SQL arbeitest, stößt du schnell auf Situationen, in denen du Daten nicht nur abrufen, sondern auch sinnvoll „in eine Reihenfolge bringen“ musst. Genau hier kommen Rangfunktionen ins Spiel. Sie helfen dir dabei, Datensätze zu bewerten, zu sortieren und nachvollziehbare Platzierungen zu erstellen – zum Beispiel für Bestenlisten, Umsatzrankings oder Top-N-Auswertungen.

Typische Anwendungsfälle sind etwa:

  • Die Top-Verkäufer eines Monats ermitteln
  • Schüler oder Produkte nach Punkten oder Umsatz bewerten
  • Daten innerhalb von Gruppen vergleichen (z. B. pro Kategorie oder Region)

In SQL gibt es dafür drei zentrale Fensterfunktionen:

  • ROW_NUMBER()
  • RANK()
  • DENSE_RANK()

Diese drei Funktionen sehen auf den ersten Blick ähnlich aus, verhalten sich aber unterschiedlich, sobald es gleiche Werte (Gleichstände) gibt. Genau diese Unterschiede entscheiden oft darüber, ob deine Auswertung korrekt oder verfälscht ist.

In diesem Artikel lernst du Schritt für Schritt:

  • wie diese drei Funktionen funktionieren
  • wann du welche Funktion einsetzen solltest
  • und wie du typische Fehler vermeidest

Am Ende kannst du Rangfolgen in SQL sicher erstellen und weißt genau, welche Funktion in welcher Situation die richtige ist.

Wichtig: Dieses Wissen ist nicht nur für klassische Ranking-Probleme relevant, sondern auch für viele praktische SQL-Szenarien – unabhängig davon, mit welchen SQL Datenbanktypen du arbeitest. Ob relationale Datenbanken wie MySQL, PostgreSQL oder SQL Server: Fensterfunktionen gehören überall zu den wichtigsten Werkzeugen für moderne Datenanalysen.

Datenbank Constraints

Grundlagen: Syntax & Funktionsweise

Bevor wir uns die einzelnen Funktionen im Detail anschauen, ist es wichtig zu verstehen, wie Fensterfunktionen in SQL grundsätzlich funktionieren. Denn ROW_NUMBER(), RANK() und DENSE_RANK() basieren alle auf demselben Prinzip: der OVER()-Klausel.

Die OVER()-Klausel

Die OVER()-Klausel definiert den „Fensterbereich“, in dem eine Funktion arbeitet. Anders als klassische Aggregatfunktionen wie SUM() oder COUNT() reduziert sie nicht die Anzahl der Zeilen, sondern berechnet Werte pro Zeile.

Dabei spielen zwei wichtige Bestandteile eine Rolle:

  • PARTITION BY: Teilt die Daten in Gruppen (z. B. nach Kategorie, Region oder Produkt)
  • ORDER BY: Bestimmt die Reihenfolge innerhalb dieser Gruppen

Ein einfaches Beispiel:

SELECT 
  produkt,
  kategorie,
  umsatz,
  ROW_NUMBER() OVER (PARTITION BY kategorie ORDER BY umsatz DESC) AS rang
FROM verkauf;

Hier wird für jede Kategorie eine eigene Rangfolge erstellt – sortiert nach Umsatz.

Gemeinsamkeiten der drei Funktionen

Alle drei Funktionen (ROW_NUMBER(), RANK(), DENSE_RANK()) teilen sich dieselbe Grundstruktur:

FUNKTION() OVER (PARTITION BY ... ORDER BY ...)

Unabhängig von der verwendeten SQL-Datenbank – egal ob du mit MySQL, PostgreSQL, SQL Server oder anderen SQL Datenbanktypen arbeitest – bleibt dieses Grundprinzip gleich. Das macht Fensterfunktionen zu einem sehr mächtigen und portablen Werkzeug in der Datenanalyse.

Der entscheidende Unterschied zwischen den drei Funktionen liegt später ausschließlich in der Art, wie gleiche Werte (Ties) behandelt werden.

ROW_NUMBER() – Der eindeutige Zähler

Die Funktion ROW_NUMBER() vergibt für jede Zeile eine eindeutige, fortlaufende Nummer innerhalb eines definierten Fensters. Dabei spielt es keine Rolle, ob Werte gleich sind – jede Zeile bekommt immer einen eigenen Rang.

Definition

ROW_NUMBER() erzeugt eine eindeutige Nummerierung ohne Duplikate und ohne Berücksichtigung von Gleichständen.

Beispiel

Angenommen, du hast eine Tabelle mit Verkäufen und möchtest jede Zeile eindeutig nummerieren:

SELECT 
  verkaufs_id,
  produkt,
  umsatz,
  ROW_NUMBER() OVER (ORDER BY umsatz DESC) AS rn
FROM verkauf;

Ergebnis: Selbst wenn zwei Produkte denselben Umsatz haben, bekommen sie unterschiedliche Nummern.

Typische Anwendung

  • Paginierung (z. B. Seite 1, 2, 3 in Webanwendungen)
  • Auswahl der ersten Zeile pro Gruppe
  • Eindeutige Nummerierung von Datensätzen

Ein klassisches Beispiel ist die Auswahl der besten Zeile pro Kategorie:

SELECT *
FROM (
  SELECT 
    produkt,
    kategorie,
    umsatz,
    ROW_NUMBER() OVER (PARTITION BY kategorie ORDER BY umsatz DESC) AS rn
  FROM verkauf
) t
WHERE rn = 1;

Hier bekommst du für jede Kategorie genau den besten Datensatz zurück.

Im Vergleich zu anderen SQL-Funktionen und unabhängig von den eingesetzten SQL Datenbanktypen ist ROW_NUMBER() besonders streng: Es ignoriert Gleichstände komplett und sorgt immer für eine klare, eindeutige Reihenfolge.

RANK() – Der Lücken-Rang

Die Funktion RANK() wird verwendet, wenn gleiche Werte auch denselben Rang bekommen sollen. Im Gegensatz zu ROW_NUMBER() berücksichtigt RANK() also Gleichstände. Allerdings entstehen dabei sogenannte Ranglücken.

Definition

RANK() vergibt denselben Rang für identische Werte und überspringt anschließend die entsprechenden Rangpositionen.

Beispiel

Stell dir eine Tabelle mit Verkäufen vor:

SELECT 
  produkt,
  umsatz,
  RANK() OVER (ORDER BY umsatz DESC) AS rang
FROM verkauf;

Angenommen, zwei Produkte haben den höchsten Umsatz, dann erhalten beide Rang 1. Der nächste Rang ist dann nicht 2, sondern 3.

Typisches Ergebnis könnte so aussehen:


Produkt A | 1000 | 1
Produkt B | 1000 | 1
Produkt C | 900  | 3
Produkt D | 800  | 4

Vergleich zu ROW_NUMBER()

  • ROW_NUMBER(): immer eindeutige Nummern, keine Gleichstände
  • RANK(): gleiche Werte = gleicher Rang, aber mit Lücken danach

Typische Anwendung

  • Wettbewerbsplatzierungen (z. B. Sportergebnisse)
  • Top-N-Auswertungen mit echten Gleichständen
  • Ranking-Reports, bei denen Lücken logisch sind

Gerade in Analysen über verschiedene SQL Datenbanktypen hinweg ist RANK() hilfreich, wenn die „echte Platzierung“ wichtig ist und Gleichstände sichtbar bleiben sollen.

Direkter Vergleich der drei Funktionen an einer Tabelle

Um die Unterschiede zwischen ROW_NUMBER(), RANK() und DENSE_RANK() wirklich zu verstehen, schauen wir uns alle drei Funktionen im direkten Vergleich an. Dafür nutzen wir eine einfache Beispieltabelle mit Verkäufen.

Beispieldaten


produkt     umsatz
-------------------
A           1000
B           1000
C           900
D           800

SQL-Abfrage mit allen drei Funktionen

SELECT 
  produkt,
  umsatz,
  ROW_NUMBER() OVER (ORDER BY umsatz DESC) AS row_num,
  RANK() OVER (ORDER BY umsatz DESC) AS rank_num,
  DENSE_RANK() OVER (ORDER BY umsatz DESC) AS dense_rank_num
FROM verkauf;

Ergebnisvergleich


produkt | umsatz | row_num | rank_num | dense_rank_num
------------------------------------------------------
A       | 1000   | 1       | 1        | 1
B       | 1000   | 2       | 1        | 1
C       | 900    | 3       | 3        | 2
D       | 800    | 4       | 4        | 3

Visueller Vergleich

  • ROW_NUMBER(): immer eindeutig, keine Rücksicht auf gleiche Werte
  • RANK(): gleiche Werte teilen sich Rang, danach entstehen Lücken
  • DENSE_RANK(): gleiche Werte teilen sich Rang, ohne Lücken

Dieser direkte Vergleich ist besonders wichtig, wenn du Auswertungen über verschiedene SQL Datenbanktypen hinweg erstellst, da alle drei Funktionen zwar überall verfügbar sind, aber oft falsch interpretiert werden.

Praxisbeispiele (mit Code)

Nachdem du die Unterschiede zwischen ROW_NUMBER(), RANK() und DENSE_RANK() kennst, schauen wir uns jetzt typische Praxisfälle an. Genau hier zeigt sich, warum diese Funktionen im Alltag mit SQL so wichtig sind – unabhängig davon, mit welchen SQL Datenbanktypen du arbeitest.

1. Top-3 pro Kategorie mit ROW_NUMBER()

Wenn du pro Kategorie nur die besten drei Einträge möchtest, eignet sich ROW_NUMBER() besonders gut, weil du damit eindeutig begrenzen kannst:

SELECT *
FROM (
  SELECT 
    produkt,
    kategorie,
    umsatz,
    ROW_NUMBER() OVER (
      PARTITION BY kategorie 
      ORDER BY umsatz DESC
    ) AS rn
  FROM verkauf
) t
WHERE rn <= 3;

Hier bekommst du pro Kategorie exakt drei Zeilen – ohne Gleichstände zu berücksichtigen.

2. Rangliste mit gleichen Plätzen (RANK() vs. DENSE_RANK())

Wenn du echte Platzierungen darstellen willst, kannst du beide Funktionen vergleichen:

SELECT 
  produkt,
  umsatz,
  RANK() OVER (ORDER BY umsatz DESC) AS rank_pos,
  DENSE_RANK() OVER (ORDER BY umsatz DESC) AS dense_rank_pos
FROM verkauf;

Wann ist das wichtig?

  • RANK(): wenn Gleichstände „Lücken“ in der Platzierung verursachen dürfen (z. B. Sport)
  • DENSE_RANK(): wenn du eine kompakte Rangliste ohne Sprünge brauchst (z. B. interne Reports)

3. Beste 5 Einträge – wann welche Funktion?

Für Top-N-Abfragen ist die Wahl der Funktion entscheidend:

  • ROW_NUMBER(): wenn du exakt N Zeilen brauchst
  • RANK(): wenn Gleichstände mehrere Plätze „teilen“ dürfen
  • DENSE_RANK(): wenn du Gleichstände zusammenfassen willst, ohne Lücken

Beispiel mit DENSE_RANK():

SELECT *
FROM (
  SELECT 
    produkt,
    umsatz,
    DENSE_RANK() OVER (ORDER BY umsatz DESC) AS dr
  FROM verkauf
) t
WHERE dr <= 5;

So kannst du sicherstellen, dass alle relevanten Einträge berücksichtigt werden, auch bei gleichen Werten.

Wichtige Fallstricke & Best Practices

Auch wenn ROW_NUMBER(), RANK() und DENSE_RANK() relativ einfach wirken, gibt es in der Praxis einige typische Fehlerquellen. Wenn du sie kennst, sparst du dir später viel Debugging – besonders in komplexeren Projekten mit unterschiedlichen SQL Datenbanktypen.

1. ORDER BY in OVER() ist zwingend

Ein häufiger Fehler ist das Vergessen von ORDER BY innerhalb der OVER()-Klausel. Ohne diese Sortierung ist das Ergebnis entweder fehlerhaft oder nicht deterministisch.

-- korrekt
ROW_NUMBER() OVER (ORDER BY umsatz DESC)

-- falsch (führt zu undefiniertem Verhalten)
ROW_NUMBER() OVER ()

Ohne klare Sortierung gibt es keine sinnvolle Rangfolge.

2. PARTITION BY bewusst einsetzen

Mit PARTITION BY teilst du deine Daten in Gruppen. Wenn du das vergisst, wird die Rangfolge über die gesamte Tabelle berechnet.

ROW_NUMBER() OVER (PARTITION BY kategorie ORDER BY umsatz DESC)

Das ist besonders wichtig bei Top-N-Analysen pro Gruppe.

3. Performance und Indexierung

Fensterfunktionen können bei großen Datenmengen teuer werden. Ein wichtiger Best Practice ist daher die Indexierung der Spalten, die in ORDER BY verwendet werden.

  • Index auf Umsatz- oder Datumsfelder kann Sortierung beschleunigen
  • Besonders relevant bei Millionen von Datensätzen

4. Verhalten bei NULL-Werten

NULL-Werte können je nach Datenbank unterschiedlich behandelt werden. Sie können entweder als kleinste oder größte Werte sortiert werden.

ORDER BY umsatz DESC

Je nach SQL-System (z. B. PostgreSQL vs. MySQL vs. SQL Server) kann sich die Platzierung von NULL unterscheiden. Das ist wichtig, wenn du mit verschiedenen SQL Datenbanktypen arbeitest.

5. Best Practices im Überblick

  • Immer explizit ORDER BY in Fensterfunktionen verwenden
  • PARTITION BY nur einsetzen, wenn wirklich Gruppenbildung nötig ist
  • Bei großen Datenmengen Indexe prüfen
  • NULL-Sortierung datenbankspezifisch testen

Wenn du diese Punkte beachtest, vermeidest du die häufigsten Fehler bei Ranking-Abfragen und bekommst stabile, reproduzierbare Ergebnisse.

Zusammenfassung & Cheatsheet

Zum Abschluss fassen wir die wichtigsten Unterschiede der drei Fensterfunktionen noch einmal kompakt zusammen. Wenn du diese Merkmale verinnerlichst, kannst du in Zukunft schnell entscheiden, welche Funktion du in deiner SQL-Abfrage brauchst.

Merkregeln

  • ROW_NUMBER() → immer eindeutige Nummern ohne Gleichstände
  • RANK() → gleiche Werte bekommen denselben Rang, danach entstehen Lücken
  • DENSE_RANK() → gleiche Werte bekommen denselben Rang, ohne Lücken

Cheatsheet (tabellarisch)

Funktion Gleichstände Lücken in Rangfolge Typischer Einsatz
ROW_NUMBER() Nein Nein Paginierung, eindeutige Auswahl
RANK() Ja Ja Wettbewerbe, klassische Platzierungen
DENSE_RANK() Ja Nein Komprimierte Rankings ohne Sprünge

Wichtiger Kontext

Diese Funktionen gehören zu den wichtigsten Werkzeugen in der SQL-Analyse und funktionieren unabhängig von den eingesetzten SQL Datenbanktypen. Egal ob du MySQL, PostgreSQL, SQL Server oder andere Systeme nutzt – das Grundprinzip bleibt gleich.

Wenn du diese drei Funktionen sicher beherrschst, kannst du viele komplexe Ranking- und Analyseprobleme in SQL deutlich einfacher lösen.

Weiterführende Links & Übungsaufgaben

Wenn du Rangfunktionen wirklich sicher beherrschen willst, reicht reine Theorie nicht aus. Am besten lernst du sie durch eigene Abfragen und kleine Experimente. Gerade im Zusammenspiel mit unterschiedlichen SQL Datenbanktypen wirst du schnell merken, dass sich das Verhalten zwar grundsätzlich gleicht, aber die Praxis oft Nuancen hat.

Übungsplattformen

  • db<>fiddle – ideal, um SQL direkt im Browser zu testen
  • SQL Fiddle – klassische Umgebung für SQL-Experimente

Übungsaufgaben

Versuche folgende Aufgaben selbst umzusetzen:
  • Erstelle eine Rangliste von Produkten nach Umsatz mit RANK()
  • Gib pro Kategorie nur den besten Datensatz mit ROW_NUMBER() aus
  • Vergleiche DENSE_RANK() und RANK() anhand eines Datensatzes mit Gleichständen

Weiterführende Fensterfunktionen

Wenn du noch tiefer in SQL einsteigen möchtest, solltest du dir auch folgende Funktionen anschauen:
  • LEAD() – Zugriff auf nächste Zeilenwerte
  • LAG() – Zugriff auf vorherige Zeilenwerte
  • NTILE() – Aufteilung in gleich große Gruppen

Fazit

Rangfunktionen gehören zu den wichtigsten Konzepten in der SQL-Analyse. Sobald du sie sicher einsetzen kannst, wirst du viele Auswertungen deutlich einfacher lösen können – unabhängig davon, mit welchen SQL Datenbanktypen du arbeitest.