SQL Server-Praxis

SQL Server: Fensterfunktionen mit korrekter Reihenfolge

Berechnen Sie laufende Salden und neueste Datensätze korrekt mit eindeutiger Sortierung, expliziten Fensterrahmen und bewusst platzierten Filtern.

Fensterfunktionen ergänzen jede Zeile um einen laufenden Saldo, einen vorherigen Wert oder eine Rangnummer. Anders als GROUP BY reduzieren sie die Ausgabe dabei nicht auf eine Zeile pro Gruppe. Hinter einem kurzen Ausdruck können jedoch wichtige Fachentscheidungen stecken: Welche Buchung kommt zuerst? Teilen Buchungen desselben Tages einen Saldo? Zählen auch Datensätze, die der Bericht ausblendet?

Unterscheiden Sie Partition, Reihenfolge und Rahmen. PARTITION BY trennt unabhängige Gruppen. ORDER BY innerhalb von OVER definiert die Berechnungsreihenfolge. Der Rahmen legt bei unterstützten Funktionen fest, welche Zeilen zum aktuellen Ergebnis beitragen. Das abschließende ORDER BY steuert nur die Ausgabe und ersetzt diese Entscheidungen nicht.

Gleiche Sortierwerte sichtbar machen

Das Beispiel enthält absichtlich zwei Buchungen am selben Tag.

DECLARE @Ledger table (
    AccountId int, EntryId int, PostedOn date, Amount decimal(12,2)
);
INSERT @Ledger VALUES
(1, 1, '20250101', 100.00),
(1, 2, '20250101', -20.00),
(1, 3, '20250102', 50.00),
(2, 4, '20250101', 7.00);

SELECT AccountId, EntryId, Amount,
    SUM(Amount) OVER (
        PARTITION BY AccountId ORDER BY PostedOn
    ) AS DatePeerTotal,
    SUM(Amount) OVER (
        PARTITION BY AccountId ORDER BY PostedOn, EntryId
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS RunningBalance,
    LAST_VALUE(Amount) OVER (
        PARTITION BY AccountId ORDER BY PostedOn, EntryId
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS FinalEntryAmount
FROM @Ledger
ORDER BY AccountId, PostedOn, EntryId;

Für Konto 1 liefert DatePeerTotal die Werte 80, 80 und 130. Bei einem geordneten Aggregat ohne expliziten Rahmen gilt standardmäßig ein RANGE-Rahmen, der gleichwertige Sortierwerte gemeinsam berücksichtigt. Beide Buchungen vom 1. Januar erhalten daher bereits die Summe des ganzen Tages.

RunningBalance liefert dagegen 100, 80 und 130. ROWS summiert entlang der eindeutigen Reihenfolge PostedOn, EntryId. Nur ROWS anzugeben würde bei weiterhin unklarer Sortierung nicht festlegen, welche gleich datierten Zeilen zuerst kommen. Benötigt das Geschäft eine eigene Buchungsreihenfolge, muss diese gespeichert und statt einer zufällig passenden Kennung verwendet werden.

FinalEntryAmount ergibt für jede Zeile von Konto 1 den Wert 50. Der Rahmen reicht ausdrücklich bis zum Ende der Partition. LAST_VALUE mit einem Rahmen bis zur aktuellen Zeile liefert häufig den aktuellen Wert oder den letzten gleich sortierten Nachbarn. Das entspricht nicht automatisch dem letzten Wert des Kontos.

Konto 2 wird unabhängig berechnet und ergibt 7. Ohne PARTITION BY würden verschiedene Konten vermischt. Eine richtige Gesamtsumme allein beweist deshalb keine korrekten Einzelwerte.

Die Wirkung eines Filters festlegen

Diese Abfrage wählt die neueste Buchung jedes Kontos. Die absteigende Kennung löst Gleichstände beim Datum auf.

;WITH Ranked AS (
    SELECT AccountId, EntryId, PostedOn, Amount,
        ROW_NUMBER() OVER (
            PARTITION BY AccountId
            ORDER BY PostedOn DESC, EntryId DESC
        ) AS rn
    FROM @Ledger
)
SELECT AccountId, EntryId, PostedOn, Amount
FROM Ranked
WHERE rn = 1
ORDER BY AccountId;

rn muss in der äußeren Abfrage gefiltert werden, weil das Fensterergebnis im WHERE derselben Abfrageebene noch nicht verfügbar ist. Eine äußere Abfrage hilft auch, wenn nur ein Datumsbereich angezeigt werden soll, die Berechnung aber frühere Buchungen berücksichtigen muss.

Wird die Eingabe vor der Summierung auf den 2. Januar eingeschränkt, erhält Konto 1 den Wert 50 statt des vollständigen Saldos 130. Für einen Bericht mit Anfangssaldo kann man zunächst die relevante Historie berechnen und erst anschließend die Anzeige filtern. Alternativ wird ein Anfangsbetrag getrennt ermittelt und zu den Bewegungen des Berichtszeitraums addiert. Beide Teile brauchen dabei eine konsistente Sicht auf die Daten.

LAG bezeichnet die vorherige Zeile der definierten Reihenfolge, nicht automatisch den vorherigen Kalendertag. Fehlende Tage erzeugen keine zusätzlichen Zeilen. Ein Tagesvergleich benötigt unter Umständen zunächst Tagesaggregate und eine Kalendertabelle.

Aufwand am tatsächlichen Bericht messen

Ein Index mit Partitionsspalten gefolgt von Sortierspalten kann eine passende Reihenfolge bereitstellen und Sortierarbeit verringern. Zusätzliche eingeschlossene Spalten müssen durch den Zugriff begründet sein. Verschiedene Fenster mit unterschiedlichen Reihenfolgen können trotzdem mehrere Sortierungen benötigen.

Prüfen Sie tatsächliche Zeilenanzahlen, ausgelagerte Sortierungen, Speicherzuteilungen und die Eingangsmenge der Fensteroperatoren. Wenige sichtbare Zeilen können umfangreiche historische Berechnungen voraussetzen. Ein äußeres TOP macht ein Fenster über die vollständige Partition nicht automatisch günstig.

Sinnvolle Fälle enthalten gleiche Zeitstempel, mehrere Konten, negative Beträge, ein Konto mit nur einer Zeile und einen später beginnenden Anzeigezeitraum. Wenn Amount NULL zulässt, muss auch die Bedeutung unbekannter Beträge feststehen. Der SQL-Ausdruck sollte diese fachlichen Regeln nachvollziehbar abbilden.

Technische Referenzen: Microsoft Learn: OVER clause · Microsoft Learn: ROW_NUMBER · Microsoft Learn: LAST_VALUE.

Frage zu diesem Artikel

Haben Sie eine Frage zu diesem Thema?

Beschreiben Sie, was Sie bewerten oder wo Sie nicht weiterkommen. Wir antworten mit einer praktischen Empfehlung.

Inquiries are not enabled in this preview.

Eine Frage zu diesem Artikel stellen