【问题标题】:How to sumarize leave time without weekends如何总结没有周末的休假时间
【发布时间】:2019-04-09 17:20:05
【问题描述】:

在这种情况下,在 mysql 数据库中,我在 "leave" 表中插入了新的休假:

+--------+---------+---------+-------------+----------+--------------------------
|ID_LEAVE|ID_WORKER| FNAME   | LNAME | BEGIN_DATE         | END_DATE            | 
+--------+---------+---------+---------+-------------+--------------------+------
| 5      |   10    | MARIO   | NEED  |2019-03-22 07:00:00 |2019-03-25 15:00:00  | 
+--------+---------+---------+-------------+----------+-------------------------- 

当我在下面的 mysql 查询中总结休假时间时:

SELECT leave.ID_LEAVE, 
leave.ID_WORKER, 
leave.BEGIN_DATE, 
leave.END_DATE, 
time_format(SUM((datediff(leave.END_DATE, leave.BEGIN_DATE) + 1) * (time(leave.END_DATE) - time(leave.BEGIN_DATE))), '%H:%i:%s') AS 'LEAVE TIME'
FROM leave 
GROUP BY leave.ID_LEAVE

我有 LEAVE TIME = 32:00:00

但我看到它也算周末(周六和周日)。我不知道如果没有周末我应该怎么改变。在这种情况下,请假时间应为 16:00:00。有人可以请我改变什么样的查询。谢谢你的建议。 :)

【问题讨论】:

    标签: mysql datetime sum datediff


    【解决方案1】:

    您可以通过日历表 (based on this solution) 使用以下解决方案:

    SELECT ID_LEAVE, SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(TIME(end_date), TIME(begin_date)))))
    FROM (
        SELECT ADDDATE('1970-01-01', t4 * 10000 + t3 * 1000 + t2 * 100 + t1 * 10 + t0) AS date_value
        FROM
            (SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
            (SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
            (SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
            (SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
            (SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4
    ) calendar INNER JOIN `leave` ON calendar.date_value BETWEEN DATE(leave.BEGIN_DATE) AND DATE(leave.END_DATE)
    WHERE NOT WEEKDAY(date_value) IN (5, 6)
    GROUP BY ID_LEAVE
    

    demo on db-fiddle.com

    【讨论】:

    • 这就是我一直在尝试做的。我最终只创建了一个calendar 表,但不知道如何加入它们并获得正确的结果。 :'(
    • AS for as as as as as second (edited) 代码涉及到我测试并更改了“离开”值并且出了点问题:dbfiddle.uk/…
    • @Prochu1991 - 在某些特定情况下似乎存在一些问题。感谢您提供此信息。我再次删除数学查询。日历表解决方案正在运行。这是一个尝试。
    【解决方案2】:

    @Prochu1991 很有挑战性,但我想我设法为你构建了一个查询。

    编辑: 以下查询在某些情况下存在一些问题。因此,我不建议使用它,但我将其留在这里,以防您可以做点什么:

    -- Query 6: Final calculation add SUM of total leave time GROUP BY ID_LEAVE,ID_WORKER.
    SELECT ID_LEAVE,ID_WORKER,BEGIN_DATE,END_DATE,
    SEC_TO_TIME(SUM(TIME_TO_SEC(leave_TIME))) AS 'LEAVE TIME' 
    FROM (
    Query 5: Calculating leave time on each date only if VALID_LEAVE_DATES=1.
    SELECT ID_LEAVE,ID_WORKER,BEGIN_DATE,END_DATE,
     IF(VALID_LEAVE_DATES=1,SEC_TO_TIME(TIME_TO_SEC(TIME(end_date))-TIME_TO_SEC(TIME(begin_date))),0) AS 'LEAVE_TIME' 
    FROM (
    -- Query 4: Add checking' if any of the dates are in the weekend, it will be set as 0.
    SELECT leave_dates,
    IF(DAYNAME(LEAVE_DATES) IN ('Saturday','Sunday'),0,1) AS 'VALID_LEAVE_DATES',
    ID_LEAVE,ID_WORKER,BEGIN_DATE,END_DATE FROM (
    -- Query 3: In this part, the main reason is to create dates between BEGIN_DATE and END_DATE.
    SELECT ID_LEAVE,ID_WORKER,BEGIN_DATE,END_DATE,
    -- concatenating extracted year-month with days generated from Query 1.
    CONCAT_WS('-',DATE_FORMAT(BEGIN_DATE, '%Y-%m'),LPAD(days,2,0)) AS 'LEAVE_DATES' FROM
    -- Query 1: This part is creating day value directly from query. If you run this individually, you'll get a day value from 0 to 39.
    (SELECT 1 AS 'id'
    a+b AS 'days' FROM
    (SELECT 0 a UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
    UNION SELECT 8 UNION SELECT 9) a,
    (SELECT 0 b UNION SELECT 10 UNION SELECT 20 UNION SELECT 30) dd
    -- Query 1 end here.
    ) ee 
    LEFT JOIN 
    -- Query 2: This is your original query. I removed the SUM in select.
    (SELECT 1 AS 'id',
    leave.ID_LEAVE, 
    leave.ID_WORKER, 
    leave.BEGIN_DATE, 
    leave.END_DATE
    FROM leave GROUP BY leave.ID_LEAVE) cd 
    -- Query 2 end here.
    ON ee.id=cd.id 
    WHERE days BETWEEN DAY(BEGIN_DATE) AND DAY(END_DATE) -- `WHERE` condition only take date value between BEGIN_DATE and END_DATE from Query 2.
    ORDER BY LEAVE_DATES) LCALC -- Query 3 end here.
    ) vvv GROUP BY ID_LEAVE,LEAVE_DATES -- Query 4 end here.
    ) tuv -- Query 5 end here.
    GROUP BY ID_LEAVE,ID_WORKER; -- Query 6 end here.
    

    希望你能理解我的解释。我仍然会处理这个查询,看看是否有办法减少一些流程(更少的查询)。

    编辑 2: 好的,我一直在这样做@Prochu1991:

    SELECT *,IF(valid_leave_days=0, TIMEDIFF(end_date,begin_date),
    -- Assuming that normal working hours is '08:00:00'. If more, you just need to change here.
    SEC_TO_TIME(TIME_TO_SEC('08:00:00')*Valid_leave_days)) AS 'Total_leave_time' 
    -- So I convert 8 hours to seconds multiply with valid_leave_days calculated and convert it back to time. I think you understand this part.
    FROM
    (SELECT *,
    -- This part where the CASE start is actually just determining how many leave days per person. 
    -- Then minus with the total of weekend per week (sat & sun = 2 days).
    CASE 
    WHEN datedif<6 THEN datedif --if leave days are less than 6 days, it return datedif.
    WHEN datedif=6 THEN datedif-1 --if leave days=6, datedif-1 day > because in any day you start you will surely get one weekend.
    WHEN datedif BETWEEN 7 AND 12 THEN datedif-2 --if leave days between 7 and 12, datedif-2.
    WHEN datedif=13 THEN datedif-3 -- from here you should get the idea.
    WHEN datedif BETWEEN 14 AND 19 THEN datedif-4
    WHEN datedif=20 THEN datedif-5
    WHEN datedif BETWEEN 21 AND 26 THEN datedif-6
    WHEN datedif=27 THEN datedif-7
    WHEN datedif BETWEEN 28 AND 34 THEN datedif-8 
    -- Note that this is only up to 34 days. if you want to add more days, just make sure the calculation is correct.
    END AS 'Valid_leave_days' 
    FROM
    (SELECT *,DATEDIFF(end_date,begin_date) AS 'datedif' FROM LEAVE) a) b;
    

    【讨论】:

    • 我有一个问题,leave_dates 是不是从表中创建的?
    • 不,但它是您的休假表中 begin_date 和 end_date 之间的范围。我自己正在做一些报告,我发现我有日期范围,但不是日期范围之间的所有日期;这是我的报告目的所需要的。在根据您的情况工作时,我发现使用日期范围很有挑战性,因为我可能无法得到正确的结果。因此,我想如果我有每个日期的总时间,加上排除周末的条件,然后加起来,我就会得到结果。
    • 好的,我已经测试了这段代码并且工作得非常正确。非常感谢队友! :)
    • 啊,好的@tcadidot0 至于你的代码,我已经在.net 代码中处理过。 ;) 现在我想知道我作为答案发送的代码。
    • 嘿@tcadidot0 至于你第二次编辑的代码,它也算到下个月,但它仍然计算周末。因为当 2019-03-22 07:00:00 2019-03-31 15:00:00 它有 56:00:00 它应该是 48:00:00。如果 2019-03-22 07:00:00 2019-03-31 15:00:00` 而不是 64:00:00 应该太 48:00:00
    【解决方案3】:

    对不起,我发布了另一个答案。你能试试这个吗?这是对上面第二个查询的修改,检查了 begin_date:

    SELECT *,TIMEDIFF(end_date,begin_date),IF(valid_leave_days2=0, TIMEDIFF(end_date,begin_date),SEC_TO_TIME(TIME_TO_SEC('08:00:00')*Valid_leave_days2)) AS 'Total_leave_time' FROM
    (SELECT *,DAYNAME(begin_date),
    CASE
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF=6 THEN datedif-1 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF > 6 AND datedif < 13 THEN datedif-2 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF=13 THEN datedif-3 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF > 13 AND datedif < 20 THEN datedif-4 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF=20 THEN datedif-5 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF > 20 AND datedif < 27 THEN datedif-6
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF=27 THEN datedif-7 
    WHEN DAYNAME(begin_date)='Monday' AND DATEDIF > 27 AND datedif < 34 THEN datedif-8 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF=5 THEN datedif-1 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF > 5 AND datedif < 12 THEN datedif-2 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF=12 THEN datedif-3 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF > 12 AND datedif < 19 THEN datedif-4 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF=19 THEN datedif-5 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF > 19 AND datedif < 26 THEN datedif-6
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF=26 THEN datedif-7 
    WHEN DAYNAME(begin_date)='Tuesday' AND DATEDIF > 26 AND datedif < 33 THEN datedif-8 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF=4 THEN datedif-1 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF > 4 AND datedif < 11 THEN datedif-2 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF=11 THEN datedif-3 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF > 11 AND datedif < 18 THEN datedif-4 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF=18 THEN datedif-5 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF > 18 AND datedif < 25 THEN datedif-6
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF=25 THEN datedif-7 
    WHEN DAYNAME(begin_date)='Wednesday' AND DATEDIF > 25 AND datedif < 32 THEN datedif-8 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF=3 THEN datedif-1 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF > 3 AND datedif < 10 THEN datedif-2 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF=10 THEN datedif-3 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF > 10 AND datedif < 17 THEN datedif-4 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF=17 THEN datedif-5 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF > 17 AND datedif < 24 THEN datedif-6
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF=24 THEN datedif-7 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF > 24 AND datedif < 31 THEN datedif-8 
    WHEN DAYNAME(begin_date)='Thursday' AND DATEDIF=31 THEN datedif-9 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF=3 THEN datedif-1 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF > 3 AND datedif < 9 THEN datedif-2 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF=9 THEN datedif-3 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF > 9 AND datedif < 16 THEN datedif-4 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF=16 THEN datedif-5 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF > 16 AND datedif < 23 THEN datedif-6
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF=23 THEN datedif-7 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF > 23 AND datedif < 30 THEN datedif-8 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF=30 THEN datedif-9 
    WHEN DAYNAME(begin_date)='Friday' AND DATEDIF > 30 AND datedif < 37 THEN datedif-10 
    ELSE datedif END AS 'valid_leave_days2' FROM
    (SELECT *,DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' FROM LEAVE) a) b;
    

    对于您的评论“但我不知道为什么:如果 2019-03-20 07:00:00 - 2019-03-21 15:00:00 它计数 08:00:00 16:00:00”,我发现DATEDIFF 不包括BEGIN_DATEEND_DATE 到计算中。假设在您的情况下,如果您这样做 DATEDIFF(END_DATE,BEGIN_DATE) 它将改为这样计算,END_DATE-BEGIN_DATE 所以 21/03-20/03 它只有 1 天!哦,我的,我也只是想明白这一点。我已经检查过 MySQL 是否有像 DATE_COUNT 这样的功能,但它没有。因此,我对添加DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' 的底部查询进行了轻微修改。所以- INTERVAL 1 DAY 使函数从BEGIN_DATE 开始计算天数。

    P/S:你也可以这样做DATEDIFF(end_date + INTERVAL 1 DAY,begin_date) AS 'datedif'

    编辑:这些是我通过运行上述查询获得的测试数据结果。

    +------------+-------------+-----------------------+-----------------------+-----------+-----------------------+---------------------+---------------------------------+--------------------+
    | "ID_LEAVE" | "ID_WORKER" |     "BEGIN_DATE"      |      "END_DATE"       | "datedif" | "DAYNAME(begin_date)" | "valid_leave_days2" | "TIMEDIFF(end_date,begin_date)" | "Total_leave_time" |
    +------------+-------------+-----------------------+-----------------------+-----------+-----------------------+---------------------+---------------------------------+--------------------+
    | "3"        | "26"        | "2019-03-20 07:00:00" | "2019-04-01 15:00:00" | "13"      | "Wednesday"           | "9"                 | "296:00:00"                     | "72:00:00"         |
    | "4"        | "22"        | "2019-03-20 07:00:00" | "2019-03-20 15:00:00" | "1"       | "Wednesday"           | "1"                 | "08:00:00"                      | "08:00:00"         |
    | "5"        | "27"        | "2019-03-01 07:00:00" | "2019-03-31 15:00:00" | "31"      | "Friday"              | "21"                | "728:00:00"                     | "168:00:00"        |
    | "6"        | "28"        | "2019-03-22 07:00:00" | "2019-03-31 15:00:00" | "10"      | "Friday"              | "6"                 | "224:00:00"                     | "48:00:00"         |
    | "7"        | "29"        | "2019-03-20 07:00:00" | "2019-03-21 15:00:00" | "2"       | "Wednesday"           | "2"                 | "32:00:00"                      | "16:00:00"         |
    | "8"        | "30"        | "2019-03-20 07:00:00" | "2019-03-22 15:00:00" | "3"       | "Wednesday"           | "3"                 | "56:00:00"                      | "24:00:00"         |
    | "9"        | "31"        | "2019-03-28 07:00:00" | "2019-04-01 15:00:00" | "5"       | "Thursday"            | "3"                 | "104:00:00"                     | "24:00:00"         |
    +------------+-------------+-----------------------+-----------------------+-----------+-----------------------+---------------------+---------------------------------+--------------------+
    

    【讨论】:

    • 感谢您的回答和帮助。 :) 但是我刚刚测试了该代码,在这种情况下,当我为案例 28-03-2019 7:00:00 启动到 01-04-2019 15:00:00 时,总时间应该是 24:00:00 而不是 264:00:00。但我看到你快到了。那只是最后的希望。 ;)
    • 至于代码行SELECT *,DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' FROM LEAVE这是一个原始查询(如第一个)?
    • SELECT *,DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' FROM LEAVE 我已添加 -INTERVAL 1 DAY 以将 BEGIN_DATE 计数为 1 天。至于返回 264:00:00 的 28-03-2019 7:00:00 to 01-04-2019 15:00:00,我不确定,因为我在这里用相同的日期范围进行了测试,但它返回 24:00:00。请查看更新的答案。
    • 我更新了查询结果。我没有从查询中更改任何内容。 :)
    • 但另一方面,您几乎和我一样开始了 SQL 的新职业! :)
    猜你喜欢
    • 2019-11-12
    • 2019-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 2021-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多