根据24小时内的不同班次计算工资率

时间:2015-03-27 20:56:57

标签: sql sql-server

Existing Tables:

Projects
- projectID
- name
- rate

Shifts
- shiftID (PK)
- projectID (FK)
- name
- startTime
- rate

Weekdays
- weekdayID (PK)
- weekday

Shifts_Weekdays
- shifts_weekdaysID (PK)
- shiftID
- weekdayID
- addition

WorkSegments
- worksegmentID (PK)
- userID
- startTime
- endTime

Table [Weekdays] contains 7 entries, one per day (e.q. Monday, Tuesday, Wednesday, etc..)

Table [Shifts] contains 2 entries:
    1. name: "Day Shift", startTime: "1900-01-01 08:00:00.000" rate: 10
    2. name: "Night Shift", startTime: "1900-01-01 22:00:00.000" rate: 12

Table [Shifts_Weekdays] contains all entries so that each shift is bound to each day, in other words both shifts are active each day of the week

== Requirement ==

I need to find a way to apply the correct rate to the work segments. (UDF or similar)

Ex.     On Monday, user #1 worked from 6am - 5pm

    --> rate from shift #2 ($12) should apply for 2 hrs
    --> rate from shift #1 ($10) should apply for 9 hrs

Please note that shifts have startTime only, meaning the end of shift is beginning of the next shift

========================================================
[Shifts]
ID  projectID   userID  shift       startTime           rate
1   1       1   Day Shift   1900-01-01 08:00:00.000     10.00
2   1       1   Night Shift 1900-01-01 22:00:00.000     12.00

[Weekdays]
ID  weekday     code
1   Monday      mon
2   Tuesday     tue
3   Wednesday   wed
4   Thursday    thur
5   Friday      fri
6   Saturday    sat
7   Sunday      sun

[Shifts_Weekdays]
ID  shiftID weekdayID   addition
231 2   1       0
232 2   2       0
233 2   3       0
234 2   4       0   
235 2   5       0   
236 2   6       10% 
237 2   7       10% 
260 1   1       0   
261 1   2       0   
262 1   3       0   
263 1   4       0   
264 1   5       0   
265 1   6       10% 
266 1   7       10%

[Projects]
ID  companyID   project         rate
1   2       Truck Driving       8.2

[WorkSegments]
ID  userID  projectID   startTime           endTime 
1   1   1       2015-03-10 07:00:00.000     2015-03-10 10:25:00.000 
2   1   1       2015-03-10 10:45:00.000     2015-03-10 17:00:00.000 
3   1   1       2015-03-10 19:05:00.000     2015-03-10 22:15:00.000 
4   1   1       2015-03-11 07:00:00.000     2015-03-11 10:00:00.000 
5   1   1       2015-03-11 10:30:00.000     2015-03-11 17:00:00.000 
6   1   1       2015-03-11 19:05:00.000     2015-03-11 22:15:00.000 
========================================================


ADDITIONAL [WorkSegments] DATA THAT BREAKS THE FUNCTIONALITY
========================================================
ID       startTime                  endTime
83       2015-04-20 12:00:00.000    2015-04-21 00:30:00.000
84       2015-04-21 15:30:00.000    2015-04-22 03:45:00.000
85       2015-04-23 13:45:00.000    2015-04-24 04:00:00.000
86       2015-04-24 15:30:00.000    2015-04-25 03:45:00.000
87       2015-04-25 12:00:00.000    2015-04-26 03:00:00.000

1 个答案:

答案 0 :(得分:0)

以下是使用函数计算工作段工资的解决方案:

--Build schema
CREATE TABLE dbo.Shifts
(
    ID          INT PRIMARY KEY
    ,projectID  INT
    ,userID     INT
    ,shift      VARCHAR(25)
    ,startTime  TIME        
    ,rate       MONEY
)

INSERT INTO dbo.Shifts
VALUES
     (1 ,1  ,1  ,'Day Shift'    ,'08:00'    ,10.00)
    ,(2 ,1  ,1  ,'Night Shift'  ,'22:00'    ,12.00)

GO

CREATE TABLE dbo.Weekdays
(
    ID          INT PRIMARY KEY
    ,weekday    VARCHAR(25)
    ,code       VARCHAR(5)
)
INSERT INTO dbo.Weekdays
VALUES
    (1   ,'Monday'      ,'mon')
    ,(2   ,'Tuesday'    ,'tue')
    ,(3   ,'Wednesday'  ,'wed')
    ,(4   ,'Thursday'   ,'thur')
    ,(5   ,'Friday'     ,'fri')
    ,(6   ,'Saturday'   ,'sat')
    ,(7   ,'Sunday'     ,'sun')

GO

CREATE TABLE dbo.Shifts_Weekdays
(
    ID          INT PRIMARY KEY
    ,shiftID    INT
    ,weekdayID  INT
    ,absoluteIncrease   MONEY
    ,percentIncrease FLOAT
)
INSERT INTO dbo.Shifts_Weekdays
VALUES
     (231 ,2   ,1       ,0.0    ,0)
    ,(232 ,2   ,2       ,0.0    ,0)
    ,(233 ,2   ,3       ,0.0    ,0)
    ,(234 ,2   ,4       ,0.0    ,0)  
    ,(235 ,2   ,5       ,0.0    ,0)  
    ,(236 ,2   ,6       ,0.1    ,0) 
    ,(237 ,2   ,7       ,0.1    ,0) 
    ,(260 ,1   ,1       ,0.0    ,0) 
    ,(261 ,1   ,2       ,0.0    ,0) 
    ,(262 ,1   ,3       ,0.0    ,0) 
    ,(263 ,1   ,4       ,0.0    ,0) 
    ,(264 ,1   ,5       ,0.0    ,0) 
    ,(265 ,1   ,6       ,0.1    ,0) 
    ,(266 ,1   ,7       ,0.1    ,0)

GO

CREATE TABLE dbo.Projects
(
    ID          INT PRIMARY KEY
    ,companyID  INT
    ,project    VARCHAR(50)    
    ,rate       FLOAT
)
INSERT INTO dbo.Projects
VALUES
    (1   ,2       ,'Truck Driving'       ,8.2)

GO

CREATE TABLE dbo.WorkSegments
(
    ID          INT PRIMARY KEY 
    ,userID     INT
    ,projectID  INT 
    ,startTime  DATETIME2         
    ,endTime    DATETIME2
)

INSERT INTO dbo.WorkSegments
VALUES
    (1   ,1   ,1       ,'2015-03-10 07:00'     ,'2015-03-10 10:25:00.000') 
    ,(2   ,1   ,1       ,'2015-03-10 10:45'     ,'2015-03-10 17:00:00.000') 
    ,(3   ,1   ,1       ,'2015-03-10 19:05'     ,'2015-03-10 22:15:00.000') 
    ,(4   ,1   ,1       ,'2015-03-11 07:00'     ,'2015-03-11 10:00:00.000') 
    ,(5   ,1   ,1       ,'2015-03-11 10:30'     ,'2015-03-11 17:00:00.000') 
    ,(6   ,1   ,1       ,'2015-03-11 19:05'     ,'2015-03-11 22:15:00.000')

GO

/***********************************************************************************

    DESCRIPTION:
        Calculates wages based on work segment

    NOTES:
        Based on question from Stack Overflow:
        http://stackoverflow.com/questions/29310072/calculate-pay-rate-based-on-different-shifts-within-24-hrs/

    Revision History
        2015-03-30 brennan pope created
        2015-04-05 brennan pope modified to include shift and segment start times

***********************************************************************************/
ALTER FUNCTION [dbo].[GetWagesByWorkSegment]
(
    @WorkSegmentId INT
)
RETURNS @Totals TABLE
(
    WorkSegmentID       INT
    ,userID             INT
    ,projectID          INT
    ,ShiftId            INT
    ,WeekdayId          INT
    ,Rate               FLOAT
    ,ShiftStart         DATETIME2
    ,ShiftEnd           DATETIME2
    ,MinutesInShift     INT
    ,GrossShiftPay      MONEY
    ,SegmentStart       DATETIME2
    ,SegmentEnd         DATETIME2
    ,MinutesInSegment   INT
    ,GrossSegmentPay    MONEY
)
AS
BEGIN

    DECLARE @StartDate DATE       = (SELECT DATEADD(DAY,-1,MIN(StartTime)) FROM WorkSegments WHERE ID = @WorkSegmentId);
    DECLARE @EndDate DATE         = DATEADD(DAY,1,GETDATE());
    DECLARE @Dates TABLE
    (
        ThisDate DATETIME2 PRIMARY KEY
        ,ThisWeekDay VARCHAR(25)
        ,UNIQUE(ThisWeekDay,ThisDate)
    );

    WITH 
    Dates_CTE AS
    (
        SELECT 
            @StartDate AS ThisDate
            ,DATENAME(DW,@StartDate) AS THisWeekDay
        UNION ALL

        SELECT 
            NextDate 
            ,DATENAME(DW,NextDate) 
        FROM Dates_CTE
        CROSS APPLY (VALUES(DATEADD(DAY,1,ThisDate))) NextDates(NextDate)
        WHERE NextDate <= @EndDate
    )
    INSERT INTO @Dates 
    SELECT ThisDate,ThisWeekDay 
    FROM Dates_CTE OPTION (MAXRECURSION 0);

    DECLARE @ShiftStartDates TABLE
    (
        ProjectId INT
        ,StartDate DATETIME2
        ,Rate FLOAT
        ,ShiftId INT
        ,WeekdayId INT
        ,UNIQUE(StartDate,ProjectId)
    )

    INSERT INTO @ShiftStartDates 
    SELECT
        P.ID AS ProjectID
        ,DATEADD(MINUTE, DATEDIFF(MINUTE,'00:00',S.startTime),D.ThisDate) AS startDate
        ,(CASE WHEN ISNULL(S.rate,0) = 0 THEN P.rate ELSE S.rate END + SW.absoluteIncrease) * (1+SW.percentIncrease) AS Rate
        ,S.ID AS ShiftId
        ,W.ID AS WeekdayId
    FROM
        Projects P
    JOIN
        Shifts S
        ON P.ID = S.projectID
    JOIN
        Shifts_Weekdays SW
        ON S.ID = SW.shiftID
    JOIN
        Weekdays W
        ON SW.weekdayId = W.Id
    JOIN
        @Dates D
        ON W.weekday = D.ThisWeekDay


    DECLARE @ShiftDates TABLE
    (
        ProjectId INT
        ,StartDate DATETIME2
        ,EndDate DATETIME2
        ,Rate FLOAT
        ,ShiftId INT
        ,WeekdayId INT
        ,UNIQUE(StartDate,ProjectId)
    )
    INSERT INTO @ShiftDates
    SELECT  
        ProjectId 
        ,StartDate 
        ,(SELECT TOP 1 StartDate FROM @ShiftStartDates WHERE StartDate > S.StartDate) AS EndDate
        ,Rate
        ,ShiftId
        ,WeekdayId 
    FROM @ShiftStartDates S

    --Get results
    INSERT INTO @Totals
    SELECT
        WorkSegmentID
        ,userID
        ,projectID
        ,ShiftId
        ,WeekdayId 
        ,Rate AS ShiftRate
        ,ShiftStart
        ,ShiftEnd
        ,MinutesInShift
        ,(Rate * MinutesInShift)/60 AS GrossShiftPay
        ,SegmentStart
        ,SegmentEnd
        ,MinutesInSegment
        ,SUM((Rate * MinutesInShift)/60) OVER (PARTITION BY WorkSegmentID) AS GrossSegmentPay
    FROM
    (

        SELECT
            WS.ID AS WorkSegmentID
            ,WS.userID
            ,WS.projectID
            ,SD.ShiftId
            ,SD.WeekdayId 
            ,SD.Rate
            ,
            CASE
                WHEN WS.startTime >= SD.StartDate AND WS.endTime >= SD.EndDate THEN WS.startTime
                WHEN WS.startTime >= SD.StartDate AND WS.endTime <= SD.EndDate THEN WS.startTime
                WHEN WS.startTime <= SD.StartDate AND WS.endTime >= SD.EndDate THEN SD.startDate
                WHEN WS.startTime <= SD.StartDate AND WS.endTime <= SD.EndDate THEN SD.startDate
            END AS ShiftStart
            ,CASE
                WHEN WS.startTime >= SD.StartDate AND WS.endTime >= SD.EndDate THEN SD.endDate
                WHEN WS.startTime >= SD.StartDate AND WS.endTime <= SD.EndDate THEN WS.endTime
                WHEN WS.startTime <= SD.StartDate AND WS.endTime >= SD.EndDate THEN SD.endDate
                WHEN WS.startTime <= SD.StartDate AND WS.endTime <= SD.EndDate THEN WS.endTime
            END AS ShiftEnd
            ,CASE
                WHEN WS.startTime >= SD.StartDate AND WS.endTime >= SD.EndDate THEN DATEDIFF(MINUTE,WS.startTime,SD.endDate)
                WHEN WS.startTime >= SD.StartDate AND WS.endTime <= SD.EndDate THEN DATEDIFF(MINUTE,WS.startTime,WS.endTime)
                WHEN WS.startTime <= SD.StartDate AND WS.endTime >= SD.EndDate THEN DATEDIFF(MINUTE,SD.startDate,SD.endDate)
                WHEN WS.startTime <= SD.StartDate AND WS.endTime <= SD.EndDate THEN DATEDIFF(MINUTE,SD.startDate,WS.endTime)
            END AS MinutesInShift
            ,WS.startTime AS SegmentStart
            ,WS.endTime AS SegmentEnd
            ,DATEDIFF(MINUTE,WS.startTime,WS.endTime) AS MinutesInSegment

        FROM
            WorkSegments WS
        JOIN
            @ShiftDates SD
            ON 
                WS.projectID = SD.ProjectId
                AND WS.endTime > SD.StartDate 
                AND WS.startTime < SD.EndDate
        WHERE WS.ID = @WorkSegmentId
    ) PayIntervals

    RETURN;

END

GO

以下是如何使用表值函数:

SELECT * FROM dbo.GetWagesByWorkSegment([WorkSegmentId])

WorkSegmentId = 6的结果:

WorkSegmentID   userID  projectID   ShiftId WeekdayId   Rate    ShiftStart                  ShiftEnd                    MinutesInShift  GrossShiftPay   SegmentStart                SegmentEnd                  MinutesInSegment    GrossSegmentPay
6               1       1           1       3           10      2015-03-11 19:05:00.0000000 2015-03-11 22:00:00.0000000 175             29.1667         2015-03-11 19:05:00.0000000 2015-03-11 22:15:00.0000000 190                 32.1667
6               1       1           2       3           12      2015-03-11 22:00:00.0000000 2015-03-11 22:15:00.0000000 15              3.00            2015-03-11 19:05:00.0000000 2015-03-11 22:15:00.0000000 190                 32.1667

注意:如果在一次考虑多个工作段的查询中使用此函数,则该过程效率低下。在这种情况下,需要不同的实施方案。此外,通过将此函数的某些部分分解为定期更新表,可以实现性能提升。