Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Изчислете работното време от таблицата за смени и почивка

Както други казаха, това е малко неясно, без да се предполагат няколко неща. Има по-бързи, по-малко сложни начини за този проблем, но се опитах да направя решението си възможно най-динамично, за да отговаря на неясната дефиниция. Ето моите предположения:

Ето SQL Fiddle:Демонстрация на SQL Fiddle

Предположения

  • Приемаме SQL Server 2005+
  • Ако приемем, че частта от датата на таблиците за смени е 1900-01-01
  • Предполага се, че таблицата Break има правилна дата за начален / краен час
  • Предполага се, че само 1 човек в базата данни влиза и излиза (NoEmployeeID е включен в таблицата @BREAK)
  • Поема работата през цялата им смяна. Прецизно включване в началото на смяната и изключване в края на смяната.

Маси

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')

Решение

;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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Емулиране/симулиране на SQL Azure локално?

  2. Опасност от използването на 'IF EXISTS... UPDATE .. ELSE .. INSERT' и каква е алтернативата?

  3. Актуализиране на низови стойности в таблица, които трябва да се променят, като низ ГГГГММДД до низ ММДДГГГГ

  4. Обединете цялата геометрия в таблица на SQL Server като GeomUnion в Postgres

  5. Как да използвате израза IF/ELSE за актуализиране или създаване на нов запис на xml възел в Sql