Neueste Zeile je Kunde: Gleichstände richtig behandeln
Vergleichen Sie ROW_NUMBER und OUTER APPLY, behalten Sie Kunden ohne Ereignisse und definieren Sie eine eindeutige Gleichstandsregel.
Die neueste Bestellung je Kunde klingt nach einer einfachen MAX-Abfrage. MAX findet den höchsten Zeitstempel, identifiziert bei Gleichständen aber keine vollständige eindeutige Zeile. Der Rückjoin kann mehrere Bestellungen liefern. Unabhängige MAX-Ausdrücke für weitere Spalten können sogar Werte kombinieren, die nie zu derselben Bestellung gehörten.
Neueste fachlich definieren
Das Beispiel sortiert OccurredAt absteigend und verwendet OrderId absteigend als eindeutigen letzten Vergleich. Damit entsteht genau eine Zeile je Kunde mit Bestellungen. Die höhere Identity löst hier nur Gleichstände; sie beweist weder spätere Geschäftszeit noch späteren Commit.
CREATE TABLE #Customers(CustomerId int PRIMARY KEY);
INSERT #Customers VALUES(1),(2),(3);
CREATE TABLE #Orders(OrderId int PRIMARY KEY, CustomerId int NOT NULL,
OccurredAt datetime2(0) NOT NULL, Amount decimal(10,2) NOT NULL);
INSERT #Orders VALUES(11,1,'20230101',10),(12,1,'20230101',20),
(13,2,'20230102',30);
CREATE INDEX IX_Latest ON #Orders(CustomerId,OccurredAt DESC,OrderId DESC)
INCLUDE(Amount);
;WITH ranked AS
(SELECT *, ROW_NUMBER() OVER(PARTITION BY CustomerId
ORDER BY OccurredAt DESC,OrderId DESC) AS rn FROM #Orders)
SELECT c.CustomerId,r.OrderId,r.OccurredAt,r.Amount
FROM #Customers AS c LEFT JOIN ranked AS r
ON r.CustomerId=c.CustomerId AND r.rn=1
ORDER BY c.CustomerId;
Kunde 1 besitzt zwei Bestellungen zum gleichen Zeitpunkt. Nach der festgelegten Regel gewinnt OrderId 12. Kunde 2 hat eine Bestellung, Kunde 3 keine. LEFT JOIN erhält Kunde 3 mit NULL-Bestellfeldern. Die Bedingung rn = 1 gehört in den Join. Im WHERE würde sie Kunden ohne passende Zeile ausschließen.
Sollen alle zeitgleichen neuesten Bestellungen erscheinen, ist diese Gewinnerregel falsch. Verwenden Sie RANK oder DENSE_RANK nur über den Zeitstempel und akzeptieren Sie mehrere Ergebnisse. Eine zusätzliche OrderId in dieser Rangfolge würde den fachlichen Gleichstand aufheben. Entscheiden Sie zuerst zwischen einem Vertreter und allen gleich aktuellen Zeilen.
Zwei Zugriffswege vergleichen
ROW_NUMBER passt gut zu einer breiten Kundenauswahl, weil ein größerer Bestellstrom gemeinsam verarbeitet werden kann. OUTER APPLY formuliert eine Suche je ausgewähltem Kunden, die mit passendem Index nach der ersten qualifizierten Zeile enden kann.
SELECT c.CustomerId,o.OrderId,o.OccurredAt,o.Amount
FROM #Customers AS c
OUTER APPLY
(SELECT TOP(1) OrderId,OccurredAt,Amount FROM #Orders AS o
WHERE o.CustomerId=c.CustomerId
ORDER BY OccurredAt DESC,OrderId DESC) AS o
ORDER BY c.CustomerId;
Keine Schreibweise ist grundsätzlich schneller. Eine kleine Kundenauswahl kann von einzelnen Seeks profitieren. Ein Bericht über fast alle Kunden kann mit gemeinsamer Verarbeitung günstiger sein. Der Optimierer darf Pläne umformen. Vergleichen Sie tatsächliche Ausführung, logische Zugriffe und Schätzungen, statt die physische Strategie aus dem SQL-Text abzuleiten.
Der Index beginnt mit CustomerId, gefolgt von der gewünschten Reihenfolge. Amount wird zur Abdeckung aufgenommen. Dadurch können Sortierung oder zusätzliche Lookups entfallen. Jede weitere Spalte erhöht jedoch Platzbedarf und Schreibaufwand. Ein fehlender oder ungeeigneter Index kann bei APPLY wiederholte große Scans verursachen.
Das Auswahlversprechen testen
Unterscheiden Sie die neueste erfolgreiche Bestellung von der neuesten Bestellung, sofern diese erfolgreich ist. Filtern vor der Rangbildung liefert das letzte erfolgreiche Ereignis. Rangbildung über alle Ereignisse mit anschließendem Statusfilter kann nichts liefern, wenn das jüngste Ereignis gescheitert ist. Beide Varianten sind plausibel, beantworten aber unterschiedliche Fragen.
Ein Stichtagsbericht braucht außerdem eine Grenze innerhalb der Kandidatenauswahl. Filtern Sie OccurredAt vor der Gewinnerbestimmung auf Werte kleiner als die exklusive Grenze. Müssen mehrere Anweisungen denselben Zustand sehen, definieren Sie eine passende Isolationsgrenze. Getrennte Ausführungen können sonst unterschiedliche Datenstände beobachten.
Testen Sie fehlende Bestellungen, gleiche Zeitstempel, einen sehr großen Kunden und viele kleine Kunden. Prüfen Sie die beiden Statusinterpretationen separat. Kontrollieren Sie Kundenzahl und Zugehörigkeit jedes Amount zur ausgegebenen OrderId. Verwenden Sie für den Planvergleich dieselben Eingaben und denselben fachlichen Vertrag. Eine schnellere Abfrage mit anderer Gewinnerregel ist keine erfolgreiche Optimierung des ursprünglichen Berichts.
Bei nachträglich eingetroffenen Ereignissen muss außerdem geklärt werden, ob Ereigniszeit oder Eingangszeit entscheidet. Speichern Sie beide Werte, wenn der Unterschied fachlich wichtig ist. Sonst kann eine verspätete Bestellung einen Bericht unerwartet verändern.
Technische Referenzen: Microsoft Learn: ROW_NUMBER · Microsoft Learn: TOP.