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
Pokud to budete dělat déle než 1 rok, budete muset změnit řádek:
OPTION (MAXRECURSION 366)
Jinak se zobrazí chyba – Číslo musí být vyšší než počet dní, na které se dotazujete.
UPRAVIT
Právě jsem narazil na tuto svou starou odpověď a opravdu se mi nelíbí, je tolik věcí, které nyní považuji za špatné praktiky, takže opravím všechny problémy:
- Neukončil jsem prohlášení pomocí správně středníkem
- Použil rekurzivní CTE k vygenerování seznamu dat
- Nezahrnuje seznam sloupců pro vložení
- Použil DATENAME k vyloučení víkendů, což je jazyk specifický, mnohem lepší je explicitně nastavit
DATEFIRST
a použijteDATEPART
- Použito
LEFT JOIN/IS NULL
místoNOT EXISTS
k odstranění záznamů ze svátečního stolu. V SQL Serveru je LEFT JOIN/IS NULL méně efektivní než NOT EXISTS
To všechno jsou drobné věci, ale jsou to věci, které bych kritizoval (alespoň v hlavě, ne-li nahlas), když přezkoumávám dotaz někoho jiného, takže opravdu nemohu neopravit svou vlastní práci! Přepsání dotazu by dalo.
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;
Jako poslední bod je tento dotaz mnohem jednodušší díky kalendářní tabulka který ukládá všechna data a má příznaky pro pracovní dny, svátky atd., namísto použití tabulky svátků, která pouze ukládá svátky.