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
Nếu bạn làm điều này trong hơn 1 năm, bạn sẽ cần phải thay đổi dòng:
OPTION (MAXRECURSION 366)
Nếu không, bạn sẽ gặp lỗi - Con số cần phải cao hơn số ngày bạn đang truy vấn.
CHỈNH SỬA
Tôi vừa xem qua câu trả lời cũ này của mình và thực sự không thích nó, có rất nhiều điều mà bây giờ tôi coi là thực hành không tốt, vì vậy tôi sẽ sửa lại tất cả các vấn đề:
- Tôi đã không chấm dứt các tuyên bố với dấu chấm phẩy đúng
- Đã sử dụng CTE đệ quy để tạo danh sách ngày
- Không bao gồm danh sách cột cho một phụ trang
- Đã sử dụng DATENAME để giảm bớt các ngày cuối tuần, vốn là ngôn ngữ cụ thể, tốt hơn nhiều để đặt
DATEFIRST
một cách rõ ràng và sử dụngDATEPART
- Đã sử dụng
LEFT JOIN/IS NULL
thay vìNOT EXISTS
để loại bỏ các bản ghi khỏi bảng kỳ nghỉ. Trong SQL Server LEFT JOIN / IS NULL kém hiệu quả hơn NOT EXISTS
Tất cả đều là những điều nhỏ nhặt, nhưng chúng là những thứ tôi sẽ phê bình (ít nhất là trong đầu nếu không muốn nói là lớn tiếng) khi xem xét truy vấn của người khác, vì vậy không thể thực sự không sửa chữa tác phẩm của chính mình! Viết lại truy vấn sẽ mang lại.
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;
Cuối cùng, truy vấn này trở nên đơn giản hơn nhiều với bảng lịch lưu trữ tất cả các ngày và có cờ cho ngày làm việc, ngày lễ, v.v. thay vì sử dụng bảng ngày lễ chỉ lưu trữ ngày lễ.