Periodengerechte Abgrenzung durch GROUP BY

By Frank Kalis

Posted on Aug 12, 2009 von in SQL Server

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.

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

Noch kein Feedback


Formular wird geladen...