By Frank Kalis
Angenommen, wir haben die Aufgabe eine Urlaubsliste zu generieren, die zur Urlaubsplanung verwendet werden soll. Die Basistabelle ist recht einfach aufgebaut:
CREATE TABLE #PermanentVacation ( EmpName VARCHAR(20) , StartDate SMALLDATETIME , EndDate SMALLDATETIME) INSERT #PermanentVacation VALUES ('Kalis', '20051223', '20060109') INSERT #PermanentVacation VALUES ('Kalis', '20060111', '20060723')
Gewünscht ist jetzt eine Auswertung, welche uns auflistet, wie viele Tage die Mitarbeiter in welchem Monat Urlaub haben. Der wahrscheinlich einfachste Ansatz besteht darin, eine kalendarische Hilfstabelle zu verwenden. So eine Tabelle macht generell Sinn, weshalb man direkt überlegen sollte, eine solche in der model Datenbank zu erstellen, um diese somit in jeder neuen Datenbank automatisch zu Verfügung zu haben. In ihrer simpelsten Form sieht die Tabelle so aus:
CREATE TABLE Dates ( cDate SMALLDATETIME CONSTRAINT pk_Dates PRIMARY KEY(cDate) ) INSERT INTO Dates SELECT DATEADD(day, Number, '20051201') FROM master..spt_values WHERE Number BETWEEN 0 AND 256 AND Type='P'
Je nach den Bedürfnissen können weitere Spalten hinzukommen, die Feiertage kennzeichnen, oder Wochenende usw... Für das Beispiel hier reicht uns der Zahlenbereiche, den die master..spt_values Tabelle bereitstellt. In Produktionscode würde man sowieso generell entweder eine eigene nummerische Hilfstabelle anstelle von master..spt_values verwenden oder, falls bevorzugt, eine Schleifenkonstruktion und der Datumsbereich würde entsprechend größer sein. Zurück zur Aufgabe. Durch die Verwendung der kalendarischen Hilfstabelle "Dates" kann unsere Aufgabe auf simple, aber elegante Weise folgendermaßen gelöst werden:
SELECT MONTH(D.cDate) AS Monat, YEAR(D.cDate) AS Jahr, EmpName , MIN(D.cDate) Monatsbeginn, MAX(D.cDate) AS Monatsende , COUNT(*) AS Tage FROM #PermanentVacation AS V JOIN Dates AS D ON D.cDate BETWEEN StartDate AND EndDate GROUP BY MONTH(D.cDate), YEAR(D.cDate), EmpName ORDER BY Monatsbeginn Monat Jahr EmpName Monatsbeginn Monatsende Tage ----------- ----------- ------- ------------------- ------------------- ---- 12 2005 Kalis 2005-12-23 00:00:00 2005-12-31 00:00:00 9 1 2006 Kalis 2006-01-01 00:00:00 2006-01-31 00:00:00 30 2 2006 Kalis 2006-02-01 00:00:00 2006-02-28 00:00:00 28 3 2006 Kalis 2006-03-01 00:00:00 2006-03-31 00:00:00 31 4 2006 Kalis 2006-04-01 00:00:00 2006-04-30 00:00:00 30 5 2006 Kalis 2006-05-01 00:00:00 2006-05-31 00:00:00 31 6 2006 Kalis 2006-06-01 00:00:00 2006-06-30 00:00:00 30 7 2006 Kalis 2006-07-01 00:00:00 2006-07-23 00:00:00 23 (8 row(s) affected)
Angemerkt werden sollte vielleicht noch, daß man von dem BETWEEN JOIN in der Abfrage keine übermäßige Performance erwarten sollte. Falls die Performance zu stark in den Keller gehen sollte, sollte man sich überlegen, die Angelegenheit durch die Reporting Funktionalitäten seines Front-Ends erledigen zu lassen. Dies sollte eigentlich eine einfache Aufgabe für solche Tools sein und wahrscheinlich würde man auch eher direkt hierauf zurückgreifen als eine T-SQL Lösung zu suchen.
Für diese Beispiel wird eine 7 Tage Woche unterstellt, d.h. es werden nur die Anzahl der Kalendertage gezählt, nicht die Anzahl der Arbeitstage, die für gewöhnlich von Montag bis Freitag reichen. Möchte man diese auch in seiner Liste auftauchen lassen, könnte eine mögliche Lösung folgendermaßen aussehen:
SELECT MONTH(D.cDate) AS Monat, YEAR(D.cDate) AS Jahr, EmpName , MIN(D.cDate) Monatsbeginn, MAX(D.cDate) AS Monatsende , COUNT(*) AS KalenderTage , SUM(CASE WHEN DATEPART(dw, D.cDate) IN (1,7) THEN 0 ELSE 1 END) AS UrlaubsTage FROM #PermanentVacation AS V JOIN Dates AS D ON D.cDate BETWEEN StartDate AND EndDate GROUP BY MONTH(D.cDate), YEAR(D.cDate), EmpName ORDER BY Monatsbeginn Monat Jahr EmpName Monatsbeginn Monatsende Tage Urlaubstage ----------- ----------- ------- ------------------- ------------------- ---- ----------- 12 2005 Kalis 2005-12-23 00:00:00 2005-12-31 00:00:00 9 6 1 2006 Kalis 2006-01-01 00:00:00 2006-01-31 00:00:00 30 21 2 2006 Kalis 2006-02-01 00:00:00 2006-02-28 00:00:00 28 20 3 2006 Kalis 2006-03-01 00:00:00 2006-03-31 00:00:00 31 23 4 2006 Kalis 2006-04-01 00:00:00 2006-04-30 00:00:00 30 20 5 2006 Kalis 2006-05-01 00:00:00 2006-05-31 00:00:00 31 23 6 2006 Kalis 2006-06-01 00:00:00 2006-06-30 00:00:00 30 22 7 2006 Kalis 2006-07-01 00:00:00 2006-07-23 00:00:00 23 15 (8 row(s) affected)
Die Überprüfung, ob diese Tage auch wirklich Arbeitstage sind, oder nicht doch vielleicht in irgendeinem Bundesland ein Feiertag dabei ist, überlasse ich dem geneigten Leser.