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
Jika Anda melakukan ini selama lebih dari 1 tahun, Anda perlu mengubah baris:
OPTION (MAXRECURSION 366)
Jika tidak, Anda akan mendapatkan kesalahan - Jumlahnya harus lebih tinggi dari jumlah hari yang Anda tanyakan.
EDIT
Saya baru saja menemukan jawaban lama saya ini dan benar-benar tidak menyukainya, ada begitu banyak hal yang sekarang saya anggap sebagai praktik yang buruk, jadi saya akan memperbaiki semua masalah:
- Saya tidak menghentikan pernyataan dengan titik koma dengan benar
- Menggunakan CTE rekursif untuk membuat daftar tanggal
- Tidak menyertakan daftar kolom untuk sisipan
- Menggunakan DATENAME untuk menghilangkan akhir pekan, yang merupakan bahasa khusus, jauh lebih baik untuk menetapkan
DATEFIRST
secara eksplisit dan gunakanDATEPART
- Menggunakan
LEFT JOIN/IS NULL
bukannyaNOT EXISTS
untuk menghilangkan catatan dari tabel liburan. Dalam SQL Server LEFT JOIN/IS NULL kurang efisien daripada NOT EXISTS
Ini semua adalah hal-hal kecil, tetapi itu adalah hal-hal yang akan saya kritik (setidaknya di kepala saya jika tidak dengan keras) ketika meninjau permintaan orang lain, jadi tidak dapat benar-benar tidak memperbaiki pekerjaan saya sendiri! Menulis ulang kueri akan memberikan.
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;
Sebagai poin terakhir, kueri ini menjadi lebih sederhana dengan tabel kalender yang menyimpan semua tanggal, dan memiliki bendera untuk hari kerja, hari libur, dll, daripada menggunakan tabel hari libur yang hanya menyimpan hari libur.