【问题标题】:Calculate pay rate based on different shifts within 24 hrs24小时内根据不同班次计算工资率
【发布时间】:2015-03-27 20:56:57
【问题描述】:
现有表: 项目 - 项目ID - 姓名 - 速度 转移 - shiftID (PK) - 项目 ID (FK) - 姓名 - 开始时间 - 速度 平日 - 工作日ID(PK) - 工作日 Shifts_Weekdays - shifts_weekdaysID (PK) - 班次ID - 工作日ID - 添加 工作段 - 工作段ID (PK) - 用户身份 - 开始时间 - 时间结束 表 [Weekdays] 包含 7 个条目,每天一个(例如星期一、星期二、星期三等) 表 [Shifts] 包含 2 个条目: 1.名称:“白班”,开始时间:“1900-01-01 08:00:00.000” 率:10 2.名称:“夜班”,开始时间:“1900-01-01 22:00:00.000” 率:12 表 [Shifts_Weekdays] 包含所有条目,因此每个班次都绑定到每一天,换句话说,两个班次都在一周中的每一天都处于活动状态 == 要求 == 我需要找到一种方法将正确的费率应用于工作段。 (UDF 或类似) 前任。周一,用户 #1 从早上 6 点到下午 5 点工作 --> 第 2 班次的费率(12 美元)应适用 2 小时 --> 1 号班的费率(10 美元)应适用 9 小时 请注意,班次只有 startTime,这意味着班次的结束是下一个班次的开始 ==================================================== ====== [转移] ID projectID userID shift startTime rate 1 1 1 白班 1900-01-01 08:00:00.000 10.00 2 1 1 夜班 1900-01-01 22:00:00.000 12.00 [平日] 身份证工作日代码 1 星期一 2 周二 周二 3 星期三 星期三 4 星期四 星期四 5周五周五 6 周六坐 7周日太阳 [Shifts_Weekdays] ID shiftID weekdayID 添加 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% [项目] ID公司ID项目费率 1 2 卡车驾驶 8.2 [工作段] 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 ==================================================== ====== 破坏功能的附加 [WorkSegments] 数据 ==================================================== ====== ID 开始时间 结束时间 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

【问题讨论】:

  • 请提供样本数据和预期结果。这将使回答这个问题变得容易得多。
  • @brennan,我不确定这里可以提供哪些额外数据。您有什么具体问题?
  • 为了给你的问题一个正确的、可验证的答案,能够使用实际的样本数据会很有帮助。通过在每行的开头放置四个空格,您可以将每个表中的最少行数据复制到问题中并格式化为代码。此外,您可以模拟数据行作为您的预期结果。
  • 为了在 SSMS 2008 及更高版本中更容易做到这一点,请使用“复制带有标题的结果”并粘贴到新窗口中以获取一些示例数据。然后 alt 选择第一列之前的垂直线并键入四个空格。这将为每一行添加四个空格。
  • 另一种选择是创建一个sqlfiddle

标签: sql sql-server


【解决方案1】:

这是一个使用函数计算工作段工资的解决方案:

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

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

【讨论】:

  • 你几乎明白了。我想要求的唯一更改是 [addition] 列。它是一个 varchar b/c,可以采用美元金额(十进制数)或百分比(10%)。所以区分它们的唯一方法是检查 % 是否存在。
  • 除非您对如何存储这些值有更好的建议 :-)
  • 我将很快编辑我的解决方案以包含添加列的新逻辑。我确实认为最好有两列,一列用于百分比增加,另一列用于绝对增加。
  • 我已经给你积分了。如果可能的话,我还需要一个小小的帮助。我需要能够将 [worksegmentID] 传入此 UDF 并获得总数。如果您可以修改答案以反映这一点,我将不胜感激。谢谢!
  • 嗨艺术,我已在我的答案中包含了请求的更改。祝你在未来的努力中好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多