Như những người khác đã nói, điều này là một chút mơ hồ nếu không giả định một vài điều. Có nhiều cách nhanh hơn, ít phức tạp hơn cho vấn đề này nhưng tôi đã cố gắng thực hiện giải pháp của mình linh hoạt nhất có thể để phù hợp với định nghĩa mơ hồ. Đây là những giả định của tôi:
Đây là SQL Fiddle: Bản trình diễn SQL Fiddle
Giả định
- Giả sử SQL Server 2005+
- Giả sử phần ngày của bảng thay đổi là 1900-01-01
- Giả sử bảng Break có ngày thích hợp cho StartTime / EndTime
- Giả sử chỉ có 1 người trong cơ sở dữ liệu theo dõi ra vào (NoEFasteeID được bao gồm trong bảng @BREAK)
- Đảm nhận công việc của họ trong toàn bộ ca làm việc. Tính giờ chính xác khi bắt đầu ca làm việc và kết thúc khi kết thúc ca làm việc.
Bảng
DECLARE @SHIFT Table (ID INT IDENTITY(1,1) PRIMARY KEY, StartTime DATETIME, EndTime DATETIME)
INSERT INTO @SHIFT (StartTime, EndTime) VALUES
('07:20:00','15:20:00'),
('15:20:00','23:20:00'),
('23:20:00','07:20:00')
DECLARE @BREAK Table (ID INT IDENTITY(1,1) PRIMARY KEY, StartTime DATETIME, EndTime DATETIME)
INSERT INTO @BREAK (StartTime, EndTime) VALUES
('1/1/2013 09:10:00','1/1/2013 09:25:00'),
('1/1/2013 11:30:00','1/1/2013 12:05:00'),
('1/1/2013 13:30:00','1/1/2013 13:45:00'),
('1/1/2013 17:10:00','1/1/2013 17:25:00'),
('1/1/2013 19:30:00','1/1/2013 20:05:00'),
('1/1/2013 21:30:00','1/1/2013 21:45:00'),
('1/2/2013 01:10:00','1/2/2013 01:25:00'),
('1/2/2013 03:30:00','1/2/2013 04:05:00'),
('1/2/2013 05:30:00','1/2/2013 05:45:00'),
('1/2/2013 09:10:00','1/2/2013 09:25:00'),
('1/2/2013 11:30:00','1/2/2013 12:05:00'),
('1/2/2013 13:30:00','1/2/2013 13:45:00'),
('1/2/2013 17:10:00','1/2/2013 17:25:00'),
('1/2/2013 19:30:00','1/2/2013 20:05:00'),
('1/2/2013 21:30:00','1/2/2013 21:45:00'),
('1/2/2013 01:10:00','1/2/2013 01:25:00'),
('1/2/2013 03:30:00','1/2/2013 04:05:00'),
('1/2/2013 05:30:00','1/2/2013 05:45:00')
Giải pháp
;WITH
MinMaxDates AS --FINDS THE MINIMUM AND MAXIMUM DATE RANGES NEEDING SHIFTS ASSOCIATED.
(
SELECT
CAST(MIN(B.StartTime) AS DATE) AS MinDate,
CAST(MAX(B.EndTime) AS DATE) AS MaxDate
FROM @BREAK AS B
),
RecursiveDateBuilder AS --RECURSIVELY BUILDS A LIST OF DATES BETWEEN THE MINIMUM AND MAXIMUM RANGES IN BREAKS
(
SELECT MinDate AS ShiftStartDate FROM MinMaxDates
UNION ALL
SELECT DATEADD(dd,1,ShiftStartDate) FROM RecursiveDateBuilder WHERE DATEADD(dd,1,ShiftStartDate) <= (SELECT MaxDate FROM MinMaxDates)
),
ShiftSets AS --CREATE A SHIFT SET FOR EVERY DATE
(
SELECT
ROW_NUMBER() OVER (ORDER BY R.ShiftStartDate ASC, S.ID ASC) AS NewShiftID,
S.ID AS OldShiftID,
DATEADD(dd,DATEDIFF(dd,S.StartTime, R.ShiftStartDate),S.StartTime) AS StartDate,
DATEADD(dd,DATEDIFF(dd,S.EndTime, R.ShiftStartDate),S.EndTime) AS EndDate,
R.ShiftStartDate AS ShiftGroup
FROM
@SHIFT AS S
CROSS JOIN RecursiveDateBuilder AS R
),
Shifts AS --FIXES ANY SHIFTS THAT CROSS MIDNIGHT SETTING THEM TO THE NEXT DAY
(
SELECT
S.NewShiftID AS ShiftID,
S.StartDate,
CASE
WHEN S.EndDate <= Min2.MinStartDate THEN DATEADD(DAY,1,S.EndDate)
ELSE S.EndDate
END AS EndDate
FROM
ShiftSets AS S
CROSS APPLY (SELECT MIN(Mins.StartDate) AS MinStartDate FROM ShiftSets AS Mins WHERE Mins.ShiftGroup = S.ShiftGroup) AS Min2
),
BreaksToShifts AS --ASSOCIATES THE PUNCHES TO THE SHIFTS
(
SELECT
B.StartTime AS ClockIn,
B.EndTime AS ClockOut,
S.ShiftID,
S.StartDate,
S.EndDate
FROM
@BREAK AS B
INNER JOIN Shifts AS S ON (B.StartTime BETWEEN S.StartDate AND S.EndDate AND B.EndTime BETWEEN S.StartDate AND S.EndDate)
),
Punches AS
(
SELECT ROW_NUMBER() OVER (ORDER BY S.TheTime ASC) AS ID, S.TheTime FROM
(
SELECT BS.ShiftID, BS.ClockIn AS TheTime FROM BreaksToShifts AS BS
UNION ALL
SELECT BS.ShiftID, MIN(BS.StartDate) AS TheTime FROM BreaksToShifts AS BS GROUP BY BS.ShiftID
UNION ALL
SELECT BS.ShiftID, BS.ClockOut AS TheTime FROM BreaksToShifts AS BS
UNION ALL
SELECT BS.ShiftID, MAX(BS.EndDate) AS TheTime FROM BreaksToShifts AS BS GROUP BY BS.ShiftID
) AS S
)
SELECT
*
FROM
Punches AS P1
INNER JOIN Punches AS P2 ON (P2.ID = P1.ID + 1)
WHERE
P1.ID % 2 > 0