sql >> データベース >  >> RDS >> Sqlserver

シフトと休憩表から作業時間を計算する

    他の人が述べているように、これはいくつかのことを仮定せずに少しあいまいです.この問題には、より迅速で複雑でない方法がありますが、あいまいな定義に合わせて、できるだけ動的に解決策を実行しようとしました。ここに私の仮定があります:

    これが SQL Fiddle です:SQL Fiddle Demo

    仮定

    • SQL Server 2005 以降を想定
    • シフト テーブルの日付部分が 1900-01-01 であると仮定
    • Break テーブルに StartTime / EndTime の適切な日付があると仮定します
    • データベース内で 1 人だけが出勤と退勤を行っていると仮定します (@BREAK テーブルに従業員 ID が含まれていません)
    • 全体がシフトする仕事を想定しています。シフト開始時に正確に出勤し、シフト終了時に出勤する

    テーブル

    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. MySQLのUNIONALLとLIMIT

    2. PreparedStatementキャッシング-それはどういう意味ですか(どのように機能しますか)

    3. SQLエラー:ORA-14006:無効なパーティション名

    4. PHP MySQL PDO:zerofillint列の先行ゼロを保持する方法