FIRST_VALUE und LAST_VALUE praktisch erklaert
AI generated
SELECT
JOIN
SQL · Window Functions · Datenanalyse
FIRST_VALUE und LAST_VALUE praktisch erklaert
und die Frame-Falle, die fast jeder uebersieht

LAST_VALUE liefert ohne angepasste Frame-Klausel fast nie den tatsaechlich letzten Wert einer Partition, weil der Standard-Fensterrahmen an der aktuellen Zeile endet. FIRST_VALUE und LAST_VALUE praktisch richtig einzusetzen bedeutet, den Fensterrahmen explizit auf die gesamte Partition auszudehnen, statt sich auf einen unsichtbaren Standardwert zu verlassen.

14 Min. Lesezeit FIRST_VALUE · LAST_VALUE · Frame-Klausel PostgreSQL · MySQL 8 · SQL Server · Oracle

1. Wofuer FIRST_VALUE und LAST_VALUE gedacht sind

FIRST_VALUE und LAST_VALUE sind Window Functions, die den ersten beziehungsweise letzten Wert einer bestimmten Spalte innerhalb eines Fensters zurueckgeben, ohne die Ergebniszeilen zu reduzieren. Typische Anwendungsfaelle sind der Startpreis und Endpreis eines Produkts in einer Preishistorie, die erste und letzte Bestellung eines Kunden innerhalb eines Zeitraums, oder der Ausgangswert und der aktuellste Wert einer Kennzahl fuer eine Veraenderungsberechnung.

Auf den ersten Blick wirken beide Funktionen symmetrisch: FIRST_VALUE holt den Wert der ersten Zeile, LAST_VALUE den Wert der letzten Zeile innerhalb des durch PARTITION BY und ORDER BY definierten Fensters. In der Praxis verhaelt sich LAST_VALUE jedoch fundamental anders, als die meisten Entwickler erwarten, und liefert ohne eine zusaetzliche Anpassung fast nie den gewuenschten Wert.

Dieser Artikel erklaert, warum das so ist, wie die korrekte Frame-Klausel LAST_VALUE repariert, wie IGNORE NULLS mit beiden Funktionen zusammenspielt und wie sich FIRST_VALUE und LAST_VALUE in echten Reporting-Abfragen einsetzen lassen.

2. FIRST_VALUE: der unkomplizierte Fall

FIRST_VALUE funktioniert intuitiv und macht in der Praxis selten Probleme. Mit FIRST_VALUE(spalte) OVER (PARTITION BY gruppe ORDER BY sortierspalte) liefert die Funktion fuer jede Zeile den Wert der ersten Zeile in der jeweiligen Partition, sortiert nach der angegebenen Spalte. Der Grund, warum FIRST_VALUE mit dem Standard-Frame problemlos funktioniert: Der Standard-Fensterrahmen bei vorhandenem ORDER BY ist RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, und der erste Wert dieses Bereichs ist immer identisch mit dem ersten Wert der gesamten Partition, unabhaengig von der aktuellen Zeilenposition.

Ein praktisches Beispiel: Fuer jede Bestellung eines Kunden soll der Preis der allerersten Bestellung angezeigt werden, um Preisaenderungen ueber die Zeit sichtbar zu machen. FIRST_VALUE(order_amount) OVER (PARTITION BY customer_id ORDER BY order_date) liefert genau das, ohne dass eine explizite Frame-Klausel noetig waere, weil der Standardrahmen bei FIRST_VALUE bereits das richtige Ergebnis produziert.


-- FIRST_VALUE works correctly with the default frame
SELECT
    customer_id,
    order_date,
    order_amount,
    FIRST_VALUE(order_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
    ) AS first_order_amount
FROM orders
ORDER BY customer_id, order_date;

-- customer_id | order_date | order_amount | first_order_amount
-- 1           | 2026-01-05 | 89.00        | 89.00
-- 1           | 2026-03-12 | 120.00       | 89.00
-- 1           | 2026-06-01 | 65.00        | 89.00

3. Die Frame-Falle: warum LAST_VALUE meist falsch liegt

Der haeufigste Fehler bei Window Functions in SQL ist die Annahme, LAST_VALUE liefere symmetrisch zu FIRST_VALUE automatisch den letzten Wert der Partition. Der Standard-Fensterrahmen RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW endet aber an der aktuellen Zeile, nicht am Ende der Partition. Fuer LAST_VALUE bedeutet das: Der Fensterrahmen wandert mit jeder Zeile mit, und der letzte Wert innerhalb dieses wandernden Rahmens ist immer der Wert der aktuellen Zeile selbst.

Das Ergebnis ist ernuechternd: Ohne angepasste Frame-Klausel liefert LAST_VALUE fuer jede Zeile schlicht den Wert der aktuellen Zeile, nicht den Wert der letzten Zeile der Partition. Das ist kein Bug, sondern exaktes Standardverhalten laut SQL-Standard, aber es widerspricht der intuitiven Erwartung fast aller Entwickler, die LAST_VALUE zum ersten Mal verwenden, und fuehrt regelmaessig zu stillschweigend falschen Reports.


-- WRONG: LAST_VALUE with the default frame just returns the current row
SELECT
    customer_id,
    order_date,
    order_amount,
    LAST_VALUE(order_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
    ) AS last_order_amount_wrong
FROM orders
ORDER BY customer_id, order_date;

-- customer_id | order_date | order_amount | last_order_amount_wrong
-- 1           | 2026-01-05 | 89.00        | 89.00   <- equals current row
-- 1           | 2026-03-12 | 120.00       | 120.00  <- equals current row
-- 1           | 2026-06-01 | 65.00        | 65.00   <- equals current row
-- Expected: every row should show 65.00, the true last order amount

4. Die Loesung: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

Die korrekte Loesung fuer LAST_VALUE ist eine explizite Frame-Klausel, die den Rahmen auf die gesamte Partition ausdehnt: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. Damit umfasst der Fensterrahmen fuer jede Zeile immer alle Zeilen der Partition, unabhaengig von der aktuellen Position, und LAST_VALUE liefert tatsaechlich den letzten Wert in der durch ORDER BY definierten Sortierung.

Wichtig ist, diese explizite Frame-Klausel konsequent bei jedem LAST_VALUE-Aufruf zu setzen, unabhaengig davon, ob das aktuelle Testergebnis zufaellig korrekt aussieht. Gerade bei kleinen Testdatensaetzen mit wenigen Zeilen pro Partition faellt der Fehler oft nicht auf, weil die letzte Zeile zufaellig mit der aktuellen Zeile uebereinstimmt, bricht dann aber in Produktion bei laengeren Partitionen sichtbar.


-- RIGHT: explicit frame extends to the entire partition
SELECT
    customer_id,
    order_date,
    order_amount,
    FIRST_VALUE(order_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS first_order_amount,
    LAST_VALUE(order_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS last_order_amount
FROM orders
ORDER BY customer_id, order_date;

-- customer_id | order_date | order_amount | first_order_amount | last_order_amount
-- 1           | 2026-01-05 | 89.00        | 89.00               | 65.00
-- 1           | 2026-03-12 | 120.00       | 89.00               | 65.00
-- 1           | 2026-06-01 | 65.00        | 89.00               | 65.00

5. NULL-Werte behandeln: IGNORE NULLS und RESPECT NULLS

Enthaelt die Zielspalte NULL-Werte, liefert LAST_VALUE standardmaessig auch NULL, wenn die letzte Zeile der Partition in dieser Spalte NULL traegt. Das ist beim Standardverhalten RESPECT NULLS so vorgesehen, in vielen Reporting-Faellen aber unerwuenscht, etwa wenn der zuletzt bekannte gueltige Wert einer Kennzahl gesucht wird, nicht buchstaeblich die letzte Zeile. PostgreSQL, Oracle und SQL Server unterstuetzen dafuer IGNORE NULLS, das NULL-Zeilen bei der Ermittlung des ersten oder letzten Werts uebergeht.

MySQL kennt IGNORE NULLS bis einschliesslich Version 8.0 nicht direkt, hier hilft ein Workaround mit COALESCE in Kombination mit einer zusaetzlichen Sortierung, die NULL-Werte ans Ende schiebt, bevor LAST_VALUE angewendet wird. Wichtig: IGNORE NULLS aendert nichts an der Notwendigkeit der Frame-Klausel aus Abschnitt vier, beide Anpassungen sind unabhaengig voneinander erforderlich und muessen bei Bedarf kombiniert werden.


-- IGNORE NULLS: skip NULL rows when finding the last known value
SELECT
    sensor_id,
    reading_time,
    temperature,
    LAST_VALUE(temperature) IGNORE NULLS OVER (
        PARTITION BY sensor_id
        ORDER BY reading_time
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS last_known_temperature
FROM sensor_readings
ORDER BY sensor_id, reading_time;
-- PostgreSQL, Oracle, SQL Server support IGNORE NULLS directly

6. FIRST_VALUE und LAST_VALUE mit PARTITION BY kombinieren

PARTITION BY begrenzt den Bereich, innerhalb dessen FIRST_VALUE und LAST_VALUE suchen, auf eine logische Gruppe, etwa alle Bestellungen eines Kunden, alle Preisaenderungen eines Produkts oder alle Messungen eines Sensors. Ohne PARTITION BY gilt die gesamte Ergebnismenge als eine einzige Partition, was in der Praxis selten gewuenscht ist. Die Kombination aus PARTITION BY und der korrekten Frame-Klausel ist deshalb das Standardmuster fuer nahezu jeden produktiven Einsatz von LAST_VALUE.

Mehrere PARTITION BY-Spalten sind ebenfalls moeglich, etwa PARTITION BY customer_id, product_category, um den ersten und letzten Kaufpreis pro Kunde und Kategorie separat zu ermitteln. Die Frame-Klausel muss dabei nicht angepasst werden, sie bezieht sich immer auf die durch PARTITION BY aktuell aktive Gruppe, unabhaengig davon, wie viele Spalten an der Partitionierung beteiligt sind.

7. NTH_VALUE als dritte Variante

Neben FIRST_VALUE und LAST_VALUE existiert mit NTH_VALUE(spalte, n) eine dritte, weniger bekannte Window Function, die den Wert der n-ten Zeile innerhalb des Fensters liefert. Sie ist nuetzlich, wenn weder der erste noch der letzte, sondern ein bestimmter mittlerer Wert gebraucht wird, etwa die dritte Bestellung eines Kunden zur Analyse von fruehen Wiederholungskaeufen. Wie LAST_VALUE benoetigt auch NTH_VALUE in den meisten Faellen eine explizit auf die gesamte Partition ausgedehnte Frame-Klausel, sonst ist n nur innerhalb des Standardrahmens bis zur aktuellen Zeile gueltig.

NTH_VALUE wird seltener verwendet als FIRST_VALUE und LAST_VALUE, ist aber in allen vier grossen Datenbanken PostgreSQL, MySQL 8, SQL Server und Oracle verfuegbar. Sie ergaenzt das Werkzeugset fuer Situationen, in denen ein Report nicht nur Anfang und Ende, sondern auch einen bestimmten Zwischenwert einer geordneten Sequenz braucht.

8. Anwendungsfall: Start- und Endwert einer Kennzahl pro Gruppe

Ein realistischer Anwendungsfall ist ein Preisverlaufs-Report, der fuer jedes Produkt den urspruenglichen Listenpreis, den aktuellen Preis und die prozentuale Veraenderung in einer einzigen Zeile pro Produkt anzeigt. FIRST_VALUE liefert den urspruenglichen Preis, LAST_VALUE mit korrekter Frame-Klausel den aktuellen Preis, und eine einfache Prozentrechnung auf Basis dieser beiden Spalten ergibt die Veraenderung, alles in einer Abfrage ohne Self-Join.

Ein zweiter haeufiger Anwendungsfall ist die Fuellung von Luecken in Zeitreihen, das sogenannte Forward-Fill-Muster: LAST_VALUE mit IGNORE NULLS und einer auf die aktuelle Zeile begrenzten Frame-Klausel ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW uebernimmt den zuletzt bekannten Nicht-NULL-Wert fuer jede Zeile mit fehlenden Daten, etwa bei sporadisch gemeldeten Sensormesswerten.


-- Product price change report: first vs. last known price
SELECT DISTINCT
    product_id,
    FIRST_VALUE(price) OVER (
        PARTITION BY product_id ORDER BY changed_at
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS original_price,
    LAST_VALUE(price) OVER (
        PARTITION BY product_id ORDER BY changed_at
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS current_price
FROM price_history
ORDER BY product_id;
Funktion Standard-Frame korrekt? Benoetigte Frame-Klausel Typischer Einsatz
FIRST_VALUE Ja, funktioniert sofort Optional, fuer Klarheit trotzdem empfohlen Urspruenglicher Wert, erste Zeile pro Gruppe
LAST_VALUE Nein, liefert aktuelle Zeile ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING Aktuellster Wert, letzte Zeile pro Gruppe
NTH_VALUE Nein, wie LAST_VALUE ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING Bestimmte n-te Position in der Sequenz

9. FIRST_VALUE und LAST_VALUE im direkten Vergleich

Die Tabelle zeigt den entscheidenden strukturellen Unterschied: FIRST_VALUE funktioniert mit dem Standardrahmen zufaellig korrekt, weil der erste Wert des Standard-Frames immer dem ersten Wert der Partition entspricht. LAST_VALUE und NTH_VALUE teilen dieses Glueck nicht, weil ihr gesuchter Wert ausserhalb des wandernden Standard-Frames liegen kann. Wer sich das einmal bewusst gemacht hat, vergisst die explizite Frame-Klausel bei LAST_VALUE nicht mehr.

Ein pragmatischer Rat aus der Praxis: Statt sich auf implizites Verhalten zu verlassen, sollte jede Verwendung von LAST_VALUE und NTH_VALUE die Frame-Klausel explizit ausschreiben, selbst wenn ein Code-Review sie theoretisch aus dem Kontext ableiten koennte. Explizite Frame-Klauseln sind selbstdokumentierend und verhindern, dass ein spaeterer Refactoring-Schritt versehentlich den impliziten Standardrahmen wieder aktiviert.

10. Zusammenfassung

FIRST_VALUE und LAST_VALUE wirken auf den ersten Blick symmetrisch, verhalten sich aber grundlegend unterschiedlich, solange keine explizite Frame-Klausel gesetzt ist. FIRST_VALUE liefert mit dem SQL-Standard-Frame bereits den korrekten ersten Wert einer Partition, waehrend LAST_VALUE ohne ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING lediglich den Wert der aktuellen Zeile zurueckgibt. Diese Frame-Falle ist der mit Abstand haeufigste Fehler beim praktischen Einsatz dieser beiden Funktionen.

IGNORE NULLS ergaenzt beide Funktionen dort, wo der erste oder letzte gueltige Wert statt des buchstaeblich ersten oder letzten Werts gesucht wird, unabhaengig von der Frame-Klausel. Kombiniert mit PARTITION BY und der korrekten Frame-Klausel bilden FIRST_VALUE und LAST_VALUE ein zuverlaessiges Werkzeug fuer Start-Ende-Vergleiche, Preisverlaufsreports und Forward-Fill-Muster in Zeitreihen.

FIRST_VALUE und LAST_VALUE, das Wichtigste auf einen Blick

FIRST_VALUE

Funktioniert mit dem Standard-Frame korrekt, weil der Frame immer am Anfang der Partition startet.

LAST_VALUE: die Frame-Falle

Ohne explizite Frame-Klausel liefert LAST_VALUE nur die aktuelle Zeile, nicht den letzten Wert der Partition.

Die Loesung

ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING explizit setzen, immer bei LAST_VALUE und NTH_VALUE.

NULL-Handling

IGNORE NULLS uebergeht NULL-Zeilen bei der Suche nach dem ersten oder letzten gueltigen Wert.

11. FAQ: FIRST_VALUE und LAST_VALUE

1Warum liefert LAST_VALUE nicht den letzten Wert?
Der Standard-Frame endet an der aktuellen Zeile, nicht am Partitionsende. Ohne Anpassung liefert LAST_VALUE nur die aktuelle Zeile.
2Wie repariere ich LAST_VALUE?
Explizit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING als Frame-Klausel setzen.
3Warum funktioniert FIRST_VALUE ohne Anpassung?
Der Standard-Frame startet am Partitionsanfang, das entspricht bereits dem ersten Wert der gesamten Partition.
4Was macht IGNORE NULLS?
Ueberspringt NULL-Zeilen und liefert den ersten oder letzten tatsaechlich vorhandenen Wert statt NULL.
5Unterstuetzt MySQL IGNORE NULLS?
Nein, bis 8.0 nicht direkt. Workaround mit Sortierung, die NULL-Werte ans Ende verschiebt.
6Was ist NTH_VALUE?
Liefert den Wert der n-ten Zeile im Fenster, braucht wie LAST_VALUE meist die volle Frame-Klausel.
7Muss ich PARTITION BY verwenden?
Nicht zwingend, aber fast immer noetig, sonst gilt die gesamte Ergebnismenge als eine einzige Partition.
8Luecken in Zeitreihen fuellen?
LAST_VALUE IGNORE NULLS mit ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, das Forward-Fill-Muster.
9Betrifft die Frame-Falle nur eine Datenbank?
Nein, sie folgt dem SQL-Standard und betrifft PostgreSQL, MySQL, SQL Server und Oracle gleichermassen.
10Frame-Klausel auch bei FIRST_VALUE setzen?
Nicht zwingend, aber empfehlenswert fuer Konsistenz, besonders wenn beide Funktionen gemeinsam verwendet werden.