DECLARE @StartDate DATETIME, @EndDate DATETIME
SELECT @StartDate = '01/04/2011',
@EndDate = '31/03/2012'
CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL)
;WITH DaysCTE ([Date]) AS
( SELECT @StartDate
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM DaysCTE
WHERE [Date] <= @Enddate
)
INSERT INTO #Data
SELECT MIN([Date]),
COUNT(*) [Day]
FROM DaysCTE
LEFT JOIN HolidayTable
ON [Date] BETWEEN HolStart AND HolEnd
WHERE HolidayTypeID IS NULL
AND DATENAME(WEEKDAY, [Date]) NOT IN ('Saturday', 'Sunday')
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date])
OPTION (MAXRECURSION 366)
DECLARE @Date DATETIME
SET @Date = (SELECT MIN(FirstDay) FROM #Data)
SELECT Period,
WorkingDays [Days Available (Minus the Holidays)]
FROM ( SELECT DATENAME(MONTH, Firstday) [Period],
WorkingDays,
0 [SortField],
FirstDay
FROM #Data
UNION
SELECT DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
( SELECT SUM(WorkingDays)
FROM #Data b
WHERE b.FirstDay <= a.FirstDay
) [WorkingDays],
1 [SortField],
FirstDay
FROM #Data a
WHERE FirstDay > @Date
) data
ORDER BY SortField, FirstDay
DROP TABLE #Data
Jeśli robisz to przez ponad 1 rok, musisz zmienić linię:
OPTION (MAXRECURSION 366)
W przeciwnym razie pojawi się błąd – liczba musi być wyższa niż liczba dni, o które pytasz.
EDYTUJ
Właśnie natknąłem się na tę moją starą odpowiedź i naprawdę jej się nie podoba, jest tak wiele rzeczy, które uważam za złe praktyki, więc poprawię wszystkie problemy:
- Nie wypowiedziałem średnik prawidłowo
- Użyto rekurencyjnego CTE do wygenerowania listy dat
- Nie uwzględniono listy kolumn do wstawienia
- Użyto DATENAME do wyeliminowania weekendów, które są specyficzne dla języka, znacznie lepiej, aby jawnie ustawić
DATEFIRST
i użyjDATEPART
- Użyto
LEFT JOIN/IS NULL
zamiastNOT EXISTS
aby wyeliminować rekordy ze świątecznego stołu. W SQL Server LEFT JOIN/IS NULL jest mniej wydajny niż NIE ISTNIEJE
To wszystko są drobiazgi, ale są to rzeczy, które krytykowałbym (przynajmniej w mojej głowie, jeśli nie na głos) podczas przeglądania czyjegoś zapytania, więc naprawdę nie mogę nie poprawić własnej pracy! Przepisanie zapytania dałoby.
SET DATEFIRST 1;
DECLARE @StartDate DATETIME = '20110401',
@EndDate DATETIME = '20120331';
CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL);
WITH DaysCTE ([Date]) AS
( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @StartDate)
FROM sys.all_objects a
)
INSERT INTO #Data (FirstDay, WorkingDays)
SELECT FirstDay = MIN([Date]),
WorkingDays = COUNT(*)
FROM DaysCTE d
WHERE DATEPART(WEEKDAY, [Date]) NOT IN (6, 7)
AND NOT EXISTS
( SELECT 1
FROM dbo.HolidayTable ht
WHERE d.[Date] BETWEEN ht.HolStart AND ht.HolEnd
)
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date]);
DECLARE @Date DATETIME = (SELECT MIN(FirstDay) FROM #Data);
SELECT Period,
[Days Available (Minus the Holidays)] = WorkingDays
FROM ( SELECT DATENAME(MONTH, Firstday) [Period],
WorkingDays,
0 [SortField],
FirstDay
FROM #Data
UNION
SELECT DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
( SELECT SUM(WorkingDays)
FROM #Data b
WHERE b.FirstDay <= a.FirstDay
) [WorkingDays],
1 [SortField],
FirstDay
FROM #Data a
WHERE FirstDay > @Date
) data
ORDER BY SortField, FirstDay;
DROP TABLE #Data;
Na koniec to zapytanie staje się znacznie prostsze dzięki tabela kalendarza który przechowuje wszystkie daty i ma flagi dla dni roboczych, świąt itp., zamiast używać tabeli dni świątecznych, która przechowuje tylko święta.