SQL 从多行的另一张表的余额中扣除一个表中的值

Posted

技术标签:

【中文标题】SQL 从多行的另一张表的余额中扣除一个表中的值【英文标题】:SQL Deduct value in one table from balance in another over multiple rows 【发布时间】:2021-01-11 21:04:09 【问题描述】:

我的工作每天针对两张表运行一次,一张包含员工当前的假期和 PTO 余额 (dbo.Balance),另一张包含新的缺勤 (dbo.Absence)。所有值都以分钟为单位。

我正在尝试使用逻辑来确定每次缺勤(前一天)应该将多少时间应用于假期、pto 或无薪。它应该按以下顺序申请:1)假期,2)PTO,3)未付。我对下面的代码很好,直到一名员工在同一天两次单独缺勤。我已经为此苦苦挣扎了一个星期,无法弄清楚如何让它在运行时基本上为每个存储桶保持运行平衡。

实际的工作更复杂,涉及根据此输出修改每条记录的开始和结束时间 - 所以我确实需要为每条缺勤记录单独一行。

dbo.Balance

| EmpId | HolidayBal | PTOBal |
|:------|:----------:|-------:|
|   1   |     60     |  120   |
|   2   |    240     |  600   |
|   3   |      0     |    0   |

dbo.Absence

| EmpId | Time Taken | 
|:------|-----------:|
|   1   |    210     |
|   1   |     45     |
|   2   |    120     |
|   3   |    120     |

我希望结果是什么样的:

| EmpId | TimeTaken  | HolidayUsed | PTOUsed | Unpaid |
|:------|:----------:|:-----------:|:-------:|:------:|
|   1   |    210     |      60     |   120   |   30   |
|   1   |     45     |       0     |     0   |   45   |
|   2   |    120     |     120     |     0   |    0   |
|   3   |    120     |       0     |     0   |  120   |

这是我到目前为止的代码,只是当一天缺勤超过 1 次时无法正确应用。

SELECT 
        BAL.EmpId
        ,BAL.HolidayBalance
        ,BAL.PTOBalance
        ,AB.TimeTaken

        ,CASE --HOLIDAY CALCULATION
            WHEN BAL.HolidayBalance - AB.TimeTaken >= 0 THEN -- HOLIDAY BALANCE EXCEEDS TIME USED, ALL TIME USED AS HOLIDAY
                AB.TimeTaken
            WHEN BAL.HolidayBalance > 0 AND BAL.HolidayBalance - AB.TimeTaken < 0 THEN -- TIME USED EXCEEDS HOLIDAY BALANCE, ALL HOLIDAY BALANCE USED
                BAL.HolidayBalance
            ELSE 
                0
            END AS HolidayUsed
        ,CASE -- PTO CALCULATION
            WHEN BAL.HolidayBalance - AB.TimeTaken >= 0 THEN -- ALL TIME USED AS HOLIDAY
                0
            WHEN BAL.HolidayBalance > 0 AND BAL.HolidayBalance - AB.TimeTaken < 0 THEN -- HOLIDAY BALANCE WAS LESS THAN TIME USED, HAVE A REMAINDER
                CASE
                    WHEN ((BAL.PTOBalance) - (AB.TimeTaken - BAL.HolidayBalance)) >= 0 THEN -- HAVE ENOUGH PTO BALANCE TO COVER REMAINDER
                        ((BAL.PTOBalance) - (AB.TimeTaken - BAL.HolidayBalance))
                    WHEN  BAL.PTOBalance > 0 AND ((BAL.PTOBalance) - (AB.TimeTaken - BAL.HolidayBalance)) < 0 THEN -- HAVE A PTO BALANCE BUT DOES NOT COVER REMAINDER
                        BAL.PTOBalance
                    ELSE
                        0
                END
            WHEN BAL.PTOBalance > 0 AND BAL.HolidayBalance = 0 AND BAL.PTOBalance - AB.TimeTaken >=0 THEN -- HAVE PTO TO COVER IT AND NO HOLIDAY BALLANCE.
                AB.TimeTaken
            WHEN BAL.PTOBalance > 0 AND BAL.HolidayBalance = 0 AND AB.TimeTaken > BAL.PTOBalance THEN-- HAVE NO HOLIDAY AND PTO BALANCE DOES NOT COVER EVERYTHING
                BAL.PTOBalance
            ELSE 0
            END AS PTOUsed
            ,CASE -- UNPAID CALCULATION
                WHEN BAL.PTOBalance + BAL.HolidayBalance - AB.TimeTaken < 0 THEN --NOT ENOUGH PTO OR HOLIDAY TO COVER ABSENCE
                    AB.TimeTaken - (BAL.PTOBalance + BAL.HolidayBalance)
                ELSE    
                    0
            END AS Unpaid
FROM    dbo.Balance BAL
        INNER JOIN dbo.Absence AB ON BAL.EmpId = AB.EmpId

【问题讨论】:

【参考方案1】:

感谢大家的意见。我终于弄明白了。基本上必须添加缺席值(AbsenceSum)的运行总和,然后进行一些棘手的计算以使所有内容正确平衡。如果其他人有类似的情况,想发布我的解决方案!

SELECT
    EmpId
    ,TimeTaken
    ,StartTime
    ,HolidayUsed
    ,PTOUsed
    ,CASE
        WHEN TimeTaken - (HolidayUsed + PTOUsed) > 0 THEN -- HAVE A BALANCE NEEDING UNPAID
            TimeTaken - (HolidayUsed + PTOUsed)
        ELSE
            0
    END AS Unpaid
FROM (
    SELECT 
        AB.EmpId
        ,TimeTaken
        ,AB.AbsenceSum
        ,StartTime
        ,CASE 
            WHEN BAL.HolidayBal > (AbsenceSum - TimeTaken) THEN -- HAVE HOL BALANCE AVAILABLE TO USE
                CASE 
                    WHEN TimeTaken >= BAL.HolidayBal - (AbsenceSum - TimeTaken) THEN -- NOT ENOUGH HOL TO COVER ENTIRE ABSENCE?
                        BAL.HolidayBal - (AbsenceSum - TimeTaken) -- NOT ENOUGH, USE WHAT'S LEFT
                    ELSE 
                        TimeTaken 
                    END -- ELSE USE ENTIRE ABSENCE AS HOL
            ELSE 
                0 
            END HolidayUsed -- NO AVAILABLE HOL
        ,CASE WHEN BAL.HolidayBal > (AbsenceSum - TimeTaken) THEN -- HAVE HOL BALANCE AVAILABLE TO USE
                (CASE 
                    WHEN TimeTaken >= BAL.HolidayBal - (AbsenceSum - TimeTaken) THEN -- HOL COVERED SOME BUT NOT ALL
                        CASE
                            WHEN BAL.PTOBal > 0 THEN --HAVE PTO AVAILABLE
                                CASE
                                    WHEN BAL.PTOBal >= TimeTaken - (BAL.HolidayBal - (AbsenceSum - TimeTaken)) THEN -- PTO COVERS ALL OF REMAINDER
                                        TimeTaken - (BAL.HolidayBal - (AbsenceSum - TimeTaken))
                                    ELSE --PTO ONLY COVERS SOME
                                        BAL.PTOBal
                                END
                            ELSE
                                0
                        END
                    ELSE --HOL COVERED ENTIRE ABSENCE
                        0
                END)                
            ELSE -- NO HOL TO USE, MOVE TO PTO
                CASE 
                    WHEN BAL.PTOBal - ((AbsenceSum - TimeTaken) - BAL.HolidayBal) > 0 THEN -- HAVE PTO TO USE?
                        CASE 
                            WHEN TimeTaken >= BAL.PTOBal - ((AbsenceSum - TimeTaken) - BAL.HolidayBal) THEN -- HAVE ENOUGH PTO TO COVER ENTIRE ABSENCE?
                                BAL.PTOBal - ((AbsenceSum - TimeTaken) - BAL.HolidayBal) -- NOT ENOUGH, USE WHAT IS LEFT
                            ELSE 
                                TimeTaken 
                        END
                    ELSE
                        0
                END
            END AS PTOUsed
    FROM (
        SELECT EmpId, TimeTaken, ScheduleID, StartTime,
        SUM(TimeTaken) OVER (PARTITION BY EmpId ORDER BY ScheduleID, StartTime) AbsenceSum
        FROM dbo.Absence
    )AB 
        INNER JOIN dbo.Balance BAL ON BAL.EmpId = AB.EmpId
    )A
ORDER BY EmpId

【讨论】:

【参考方案2】:

这不会为每种情况提供单独的行,但它会正确地进行计算。这可能是一个很好的临时解决方案。

使用当天所有休息时间的总和创建一个 CTE,如下所示:

WITH total_absense AS 
(
    SELECT DISTINCT EmpID, SUM([Time Taken]) OVER(PARTITION BY EmpID)
    FROM dbo.Absense
)

然后在您的脚本中将 dbo.absense 替换为 total_absense。

【讨论】:

感谢您的帮助!这项工作实际上有点复杂,我需要为每个缺勤记录单独读出以进行下游处理。我试图为这个问题简化它。 SELECT EmpID, SUM([Time Taken]) FROM dbo.Absense GROUP BY EmpID 是正确的做法 听起来您可能需要另一列。也许只有一个 ROW_NUMBER 列和 EMP_ID。如果你有一个有效的 ORDER BY 算法,你也许可以在 ROW_NUMBER 大于 1 时添加一些额外的逻辑来考虑已经改变的余额。

以上是关于SQL 从多行的另一张表的余额中扣除一个表中的值的主要内容,如果未能解决你的问题,请参考以下文章

在 MySQL 中,如何将一张表的内容复制到同一个数据库中的另一张表中?

在 MySQL 中,如何将一张表的内容复制到同一个数据库中的另一张表中?

在 MySQL 中,如何将一张表的内容复制到同一个数据库中的另一张表中?

两个表匹配,匹配上把一张表的值复制到另一张表的sql语句怎么写

Oracle中的多行插入查询(从一张表中选择多行并插入到另一张表中[重复]

sql语句 怎么从一张表中查询数据插入到另一张表中