Werte in aufeinanderfolgenden Zeilen subtrahieren

By Frank Kalis

Posted on Apr 27, 2005 von in SQL Server

Zunächst vielleicht erst einmal eine Erklärung, was hier überhaupt beschrieben werden soll. Mal angenommen, wir haben eine Tabelle, in der Kurse einer Aktie erfaßt werden. Der Einfachheithalber konzentrieren wir uns hier nur auf die entscheidenden Spalten Datum und Kurs und ignorieren alles weitere hier.

IF OBJECT_ID('stocks') IS NOT NULL
DROP TABLE stocks
GO
CREATE TABLE stocks
(
thedate DATETIME PRIMARY KEY
, lastquote DECIMAL(4,2)
)
INSERT INTO stocks values ('20050419', 10.63);
INSERT INTO stocks values ('20050420', 11.12);
INSERT INTO stocks values ('20050421', 10.99);
INSERT INTO stocks values ('20050422', 9.98);
INSERT INTO stocks values ('20050425', 10.00);

Jeden Tag wird hier der Schlußkurs der Aktie eingegeben. So weit, so gut. Jetzt möchte man aber auch mehr mit seinen Daten machen als nur erfassen. Und dann könnte eine der ersten Fragestellungen lauten: Wie hoch ist eigentlich die tägliche wertmäßige Änderung meiner Aktie? Präzise ausgedrückt, um wieviel hat sich der Kurs der Aktie im Vergleich zum Vortag (Börsenvortag) geändert? So, da steht man nun vor einem Dilemma. Der erste Ansatz zur Lösung würde vielleicht lauten:

SELECT
a.thedate
, a.lastquote
, a.lastquote-b.lastquote AS diff
FROM
stocks a
LEFT JOIN
stocks b
ON
a.thedate-1=b.thedate

thedate lastquote diff
------------------------------------------------------ --------- -------
2005-04-19 00:00:00.000 10.63 NULL
2005-04-20 00:00:00.000 11.12 .49
2005-04-21 00:00:00.000 10.99 -.13
2005-04-22 00:00:00.000 9.98 -1.01
2005-04-25 00:00:00.000 10.00 NULL

(5 row(s) affected)

Das ist ja schon mal nicht schlecht. Den ersten NULL Wert kann man ja noch eliminieren. Aber beim zweiten wird dies schon schwieriger. Problem hier ist, daß die Börsen an Wochenende und bestimmten Feiertagen geschlossen haben und deshalb auch keine Kurse vorliegen. Nun kann man natürlich hier schummeln und künstlich Kurse einfügen für diejenigen Tage an denen kein Börsenkurs existiert. Wird meines Wissens nach auch in diversen System so gehandhabt, ist aber meiner Meinung nach unsauber. Ausserdem muß man bei komplexeren statistischen Auswertungen später die Nicht-Börsentage wieder rausnehmen, um keine Verfälschung des Ergebnisses zu provozieren. Diese Methode ist also nur bedingt verwendbar. Eine bessere Methode wäre eine, die sich nicht auf eine lückenlose Sequenz in der Datumsreihe verläßt. Aber wie? Um es vorwegzunehmen: Ja, man kann natürlich einen Cursor verwenden. Dieser wird auch ein richtiges Ergebnis liefern. Aber wer will schon einen Cursor, wenn es auch durchaus anders geht? Ein weitere Lösungsansatz könnte darin bestehen, über eine temporäre Tabelle zu gehen. So etwa:

CREATE TABLE #workaround
(
sid INT IDENTITY PRIMARY KEY
, thedate DATETIME
, lastquote DECIMAL(4,2)
)
INSERT INTO #workaround(thedate, lastquote)
SELECT thedate, lastquote
FROM stocks
ORDER BY thedate

SELECT
a.thedate
, a.lastquote
, ISNULL(a.lastquote-b.lastquote,0) AS diff
FROM
#workaround a
LEFT JOIN
#workaround b
ON
a.sid-1=b.sid

thedate lastquote diff
------------------------------------------------------ --------- -------
2005-04-19 00:00:00.000 10.63 .00
2005-04-20 00:00:00.000 11.12 .49
2005-04-21 00:00:00.000 10.99 -.13
2005-04-22 00:00:00.000 9.98 -1.01
2005-04-25 00:00:00.000 10.00 .02

(5 row(s) affected)

Dies ist eine gute Lösung, die meiner Meinung nach gerade bei größeren Datenmengen vorteilhaft sein wird. Ist der Datenbestand aber eher überschaubar oder man möchte keine temporäre Tabelle verwenden, kann man zu folgendem Ansatz greifen:

SELECT 
a.thedate
, a.lastquote
,ISNULL(a.lastquote -
(SELECT TOP 1
b.lastquote
FROM
stocks b
WHERE
b.thedate < a.thedate
ORDER BY b.thedate DESC ),0) diff
FROM
stocks a

thedate lastquote diff
------------------------------------------------------ --------- -------
2005-04-19 00:00:00.000 10.63 .00
2005-04-20 00:00:00.000 11.12 .49
2005-04-21 00:00:00.000 10.99 -.13
2005-04-22 00:00:00.000 9.98 -1.01
2005-04-25 00:00:00.000 10.00 .02

(5 row(s) affected)

Oder, eine weitere Alternative:

SELECT
x.Thedate
, x.lastquote
, x.lastquote - T3.lastquote AS diff
FROM
(SELECT
t1.thedate
, t1.lastquote
, MAX(t2.thedate) AS PrevDate
FROM
stocks t1
LEFT JOIN
stocks t2
ON
t1.thedate > t2.thedate
GROUP BY
t1.thedate, t1.lastquote) x
LEFT JOIN
stocks t3
ON
x.PrevDate = t3.thedate
ORDER BY
x.Thedate

Thedate lastquote diff
------------------------------------------------------ --------- -------
2005-04-19 00:00:00.000 10.63 NULL
2005-04-20 00:00:00.000 11.12 .49
2005-04-21 00:00:00.000 10.99 -.13
2005-04-22 00:00:00.000 9.98 -1.01
2005-04-25 00:00:00.000 10.00 .02

(5 row(s) affected)

Warning: Null value is eliminated by an aggregate or other SET operation.

Allerdings scheint es so, als ob diese Variante deutlich IO intensiver ist. Hier ist der Output von SET STATISTICS IO ON für die TOP 1 Variante:

Table 'stocks'. Scan count 6, logical reads 12, physical reads 0, read-ahead reads 0.

Und hier der der MAX Variante:

Table 'stocks'. Scan count 10, logical reads 20, physical reads 0, read-ahead reads 0.

Und wir vergleichen hier "nur" 5 Zeilen.

Mit diesen Varianten kann man nun allerlei nette Spielereien anstellen. Zum Beispiel kann man die prozentuale tägliche Abweichung ermitteln:

	, (ISNULL(a.lastquote/b.lastquote,0)-1) * 100 AS proz

indem man obige Berechnung durchführt. Oder man kann Tendenzen kenntlich machen:

   CASE 
WHEN
ISNULL(a.lastquote-b.lastquote,0) > 0 THEN 'Klasse Aktie' ELSE 'Hmphf...'
END Tendenz
, CASE
WHEN
ISNULL(b.lastquote,0) <= ISNULL(a.lastquote,0) THEN 'Positiv' ELSE 'Negativ'
END Tendenz2

Der Fantasie sind hier wenige Grenzen gesetzt.

Tags: Tags:
Dieser Eintrag wurde eingetragen von und ist abgelegt unter SQL Server. Tags: , ,

Noch kein Feedback


Formular wird geladen...