By Frank Kalis
Solche und ähnliche Fragen sind häufig zu beobachten. Die Antwort ist allerdings nicht immer einfach, da es sich hier eigentlich um Zeilenpositionierungsprobleme handelt, die streng genommen einer relationalen Datenbank mit ihrem set-basierten Ansatz widersprechen. Trotzdem gibt es auch hier diverse Lösungen...
Nehmen wir mal zum Beispiel die Pubs Beispieldatenbank. Anhand ihrer sollen wir feststellen, wann das drittspäteste Eintrittsdatum eines Mitarbeiters war. Anders ausgedrückt, wann hat derjenigen Mitarbeiter angefangen, der als drittletztes eingestellt wurde?
Diverse Möglichkeiten gibt es dafür. Betrachten wir zunächst einmal die verschiedenen Methoden und schauen uns anschließend diese unter Performancegesichtspunkten an.
Nummer 1 (Die vielleicht intuitivste):
SELECT TOP 1
hire_date
FROM
employee
WHERE
hire_date
NOT IN(
SELECT TOP 2
hire_date
FROM
employee
ORDER BY
hire_date DESC)
ORDER BY
hire_date DESC
hire_date
------------------------------------------------------
1994-01-19 00:00:00.000
(1 row(s) affected)
Nummer 2. Hier nutzen wir die SQL Server Eigenheit, einer Variablen den Wert der "letzten" Zeile zuzuweisen:
DECLARE @dt DATETIME
SELECT TOP 3
@dt = hire_date
FROM
employee
ORDER BY
hire_date DESC
SELECT @dt
------------------------------------------------------
1994-01-19 00:00:00.000
(1 row(s) affected)
Konkret an diesem Beispiel heißt dies, daß die Variable @dt jedem Wert des Resultset zugewiesen wird, aber durch TOP 3 zusammen mit ORDER BY der gewünschten Zeile als letztes und damit durchaus einsetzbar ist.
Nummer 3. Einsatz einer temporären Tabelle. Hier direkt in einer Stored Procedure verpackt, der der gewünschte Datensatz als Parameter übergeben wird.
USE PUBS
GO
CREATE PROC dbo.GetNthLatestEntry (@NthLatest INT)
AS
SET NOCOUNT ON
BEGIN
CREATE TABLE #Entry
(
ID INT PRIMARY KEY NOT NULL IDENTITY(1,1)
, Entry DATETIME NOT NULL
)
INSERT INTO #Entry (Entry) SELECT hire_date FROM employee ORDER BY hire_date DESC
SELECT
Entry
FROM
#Entry
WHERE
ID = @NthLatest
DROP TABLE #Entry
END
SET NOCOUNT OFF
GO
EXEC dbo.GetNthLatestEntry 3
DROP PROCEDURE dbo.GetNthLatestEntry
Entry
------------------------------------------------------
1994-01-19 00:00:00.000
Nummer 4. Bisher haben wir stets dieses oder jenes SQL Server spezifische Feature eingesetzt. Mal TOP, mal die IDENTITY Eigenschaft. Jetzt wollen wir das Ganze mal portabel gestalten und ANSI SQL verwenden.
SELECT
e1.hire_date
FROM
employee AS e1
INNER JOIN
employee AS e2
ON
e1.hire_date <= e2.hire_date
GROUP BY
e1.hire_date
HAVING COUNT(DISTINCT e2.hire_date) = 3
hire_date
------------------------------------------------------
1994-01-19 00:00:00.000
(1 row(s) affected)
Für die Funktionsweise dieses Statements seien dem Leser die Bücher von Joe Celko ans Herz gelegt.
So, vier verschiedene Möglichkeiten um an ein Ziel zu gelangen. Aber welche soll man nun verwenden? Nun, klassische Antwort: "Das hängt davon ab." Wovon? Nun, wenn jemand eine Applikation so portabel als möglich halten will, wird er wahrscheinlich zur ANSI SQL Methode greifen. Kümmert sich jemand hingegen überhaupt nicht um Portabilität, bleiben immer noch 3 Möglichkeiten zur Auswahl. Hier ein Blick auf die Ergebnisse von SET STATISTICS IO ON:
Table 'employee'. Scan count 4, logical reads 8, physical reads 0, read-ahead reads 0.
Table 'employee'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0.
Table 'employee'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0.
Table 'employee'. Scan count 44, logical reads 88, physical reads 0, read-ahead reads 0.
Wie man sieht, fällt die letzte Methode deutlich heraus. Dies ist die ANSI SQL Methode. Nun, Portabilität hat ihren Preis...
Die TOP Methode verursacht 4x den IO der Temp Table Methode und der Variablen Zuweisungsmethode. Zwar handelt es sich hier "nur" um logischen IO, aber immerhin.
So, also die Entscheidung zwischen dem Einsatz einer temporären Tabelle oder einer Variablenzuweisung. Nun, eine Entscheidung hier hängt auch damit zusammen, wie "beschäftigt" das Gesamtsytem ist. Der Einsatz von temp Tabellen kann zu "Problemen" mit der tempDB führen. Aus diesem Grund verwende ich für solche einfachen Fragestellungen gerne die Variablenzuweisungsmethode.