2

In this case in mysql database i've inserted new leave in "leave" table:

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

When I sumarize time of leave in mysql query in below:

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

the i have reasult LEAVE TIME = 32:00:00

But i see it counts weekends (Saturdays and sundays) too. I've no idea how what should i change if could count without weekends. In this case Leave time should be 16:00:00. Can someone please what kind of query can I change. Thank you for any adivce. :)

Prochu1991
  • 443
  • 5
  • 20

3 Answers3

2

You can use the following solution using a calendar table (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

Sebastian Brosch
  • 42,106
  • 15
  • 72
  • 87
  • This is what I've been trying to do. I only end up creating a `calendar` table but have no idea how to join them and get the correct result. :'( – FanoFN Apr 12 '19 at 07:09
  • AS for as as second (edited) code is concerned i tested and changed in "leave" values and something went wrong: https://dbfiddle.uk/?rdbms=mariadb_10.3&fiddle=d04280070f35bb7d0bdd613a67da7f2c – Prochu1991 Apr 14 '19 at 16:25
  • @Prochu1991 - It looks like there are some problems in some specfic cases. thanks for this info. I remove the math query again. The calendar table solution is working. It was a try. – Sebastian Brosch Apr 14 '19 at 20:10
0

Quite challenging this @Prochu1991 but I think I manage to construct a query for you.

EDIT: The query below have a few issue in some condition. Therefore, I don't recommend to use it but I leave it here in case you can do something about it:

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

Hopefully you can understand my explanation. I'll still be working with this query and see if there's a way to reduce some processes (less queries).

EDIT 2: Ok, I've been doing this @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;
FanoFN
  • 6,815
  • 2
  • 13
  • 33
  • i have a question as for as `leave_dates` is concerned is created not from table? – Prochu1991 Apr 10 '19 at 05:30
  • No but its a range between begin_date and end_date in your leave table. I was working on some reporting myself and I found that I have date range but not all the dates between the date range; which is needed for my reporting purpose. While working on your condition, I find it challenging to use date range because I might not get the correct result. Therefore, I thought that if I have the total time of each date with the condition to exclude weekend and sum them up, I would get the result. – FanoFN Apr 10 '19 at 06:03
  • ok, i've tested this code and works extremelly correctly. Thanks a lot mate! :) – Prochu1991 Apr 10 '19 at 06:11
  • For the time being @Prochu1991 . I'm still working on how to make a shorter and hopefully more efficient query. I will update to you if I manage to make one. And instead of using `DATEDIFF` on your other queries, you might want to try using `TIMEDIFF`. See if that will help you. – FanoFN Apr 10 '19 at 06:18
  • In mysql database works ok yet, but when i add this code to .net calculates it with weekends (which it should'nt). Maybe i will find any solution of like that. – Prochu1991 Apr 10 '19 at 06:35
  • That's right @Prochu1991. My field of knowledge is only limited to using MySQL queries as my job is usually to retrieve data directly from database. So the problem is, I don't know how to implement a query into a program because I'm not sure which function works and not. Like you said, in a MySQL environment, this query can return the result you desired. Sorry but I am limited to this only. Hopefully you'll find a way to solve this issue. I'll vote you up and see if this question can get more attention. – FanoFN Apr 10 '19 at 06:50
  • i have one more question what does it mean `LCALC` – Prochu1991 Apr 10 '19 at 08:42
  • its nothing @Prochu1991. It's just an alias for the subquery. You can change it to anything that you'll understand. I was making that abbreviation from "LEAVE CALCULATION" > hence it become LCALC. – FanoFN Apr 10 '19 at 08:45
  • 1
    ahh ok @tcadidot0 as for as your code is concerned i've dealt with in. net code. ;) Now i'm wondering with code which i send as an answer. – Prochu1991 Apr 10 '19 at 09:00
  • Hi @tcadidot0 i've tested this case: worker has leave_time `2019-03-23 - 2019-04-01`. Instead calculating how much time is then... query is gone - shows nothing. Have you updated your query? – Prochu1991 Apr 11 '19 at 06:39
  • No, I didn't edit anything. It's probably because of the date range. The above query works because your previous date range is within the same month while `2019-03-23 - 2019-04-01` is between two different month. I must say, I did see this coming. Maybe it's time to try `TIMEDIFF`? – FanoFN Apr 11 '19 at 07:04
  • i changed to `TIMEDIFF` from the line `SEC_TO_TIME(TIME_TO_SEC(TIME(end_date))-TIME_TO_SEC(TIME(begin_date)))`. But didn't so much help. – Prochu1991 Apr 11 '19 at 07:22
  • 1
    Hey @tcadidot0 as for as your second edited code is concerned it counts to next month too, but it still calculates weekends too. bacause when `2019-03-22 07:00:00 2019-03-31 15:00:00` it has `56:00:00` it should be 48:00:00. In case 2019-03-22 07:00:00 2019-03-31 15:00:00` instead of `64:00:00` should be too 48:00:00 – Prochu1991 Apr 11 '19 at 18:24
  • But i don't know why: in case counts that in case `2019-03-20 07:00:00 - 2019-03-21 15:00:00` it counts 08:00:00 instead 16:00:00 – Prochu1991 Apr 11 '19 at 18:36
  • @Prochu1991 .. I've been thinking about that since I update the code.. It doesn't work.. sigh.. this is more complicated than I thought – FanoFN Apr 11 '19 at 23:57
0

Sorry, I posted another answer. Can you try this? It's a modification of the second query above with a checking of 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;

For your comment "But i don't know why: in case counts that in case 2019-03-20 07:00:00 - 2019-03-21 15:00:00 it counts 08:00:00 instead 16:00:00", I figured out that DATEDIFF does not include the BEGIN_DATE or END_DATE into the calculation. Lets say in your case, if you do DATEDIFF(END_DATE,BEGIN_DATE) it will instead count like this, END_DATE-BEGIN_DATE so 21/03-20/03 it gets 1 day only! Oh my, I just figure out about this as well. I have checked if MySQL have such function like DATE_COUNT but it doesn't. Therefore I made a slight modification on the bottom query where I add DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif'. So the - INTERVAL 1 DAY makes the function to start counting days from BEGIN_DATE.

P/S: You can also do like this DATEDIFF(end_date + INTERVAL 1 DAY,begin_date) AS 'datedif'.

EDIT: These are the result I get with my test data by running the above query.

+------------+-------------+-----------------------+-----------------------+-----------+-----------------------+---------------------+---------------------------------+--------------------+
| "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"         |
+------------+-------------+-----------------------+-----------------------+-----------+-----------------------+---------------------+---------------------------------+--------------------+
FanoFN
  • 6,815
  • 2
  • 13
  • 33
  • I appreciate your answer and help. :) But i've just tested that code and in that case when I launched for case `28-03-2019 7:00:00` to `01-04-2019 15:00:00` in total time should be `24:00:00` not `264:00:00`. But i see that you are nearly there. That dies only the last hope. ;) – Prochu1991 Apr 12 '19 at 05:34
  • as for as the code line is concerned `SELECT *,DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' FROM LEAVE` is this an original query (like first)? – Prochu1991 Apr 12 '19 at 05:58
  • The `SELECT *,DATEDIFF(end_date,begin_date-INTERVAL 1 DAY) AS 'datedif' FROM LEAVE` I've added `-INTERVAL 1 DAY` to include count `BEGIN_DATE` as 1 day. As for the `28-03-2019 7:00:00 to 01-04-2019 15:00:00` returning 264:00:00, I'm not sure because I have tested here with same date range but it returns `24:00:00`. Please see updated answer. – FanoFN Apr 12 '19 at 06:07
  • I updated with the query result. I haven't change anything from the query. :) – FanoFN Apr 12 '19 at 06:11
  • Can you give please mysql database which you've tested? – Prochu1991 Apr 12 '19 at 06:18
  • Between which mysql version do you have? I have 10.1.37-MariaDB – Prochu1991 Apr 12 '19 at 06:28
  • Currently I have MySQL 4.1, MySQL 5.5 and MariaDB 10.3. And I've tested this on MySQL 4.1 and MariaDB 10.3 – FanoFN Apr 12 '19 at 06:32
  • Have you tested on MariaDB 10.1.37? – Prochu1991 Apr 12 '19 at 06:41
  • Just tested on MariaDB 10.1.37 64bit and get same result. :) – FanoFN Apr 12 '19 at 06:47
  • I'm afraid I don't have XAMPP @Prochu1991. I suggest you try Sebastian's solution. I have tested that query and it is much better than what I've made – FanoFN Apr 12 '19 at 07:06
  • 1
    But on the other hand you started new career in SQL almost as me! :) – Prochu1991 Apr 12 '19 at 07:17
  • haha, thats true @Prochu1991 , it's a good learning exercise and I'm glad eventually someone come up with a very good answer. – FanoFN Apr 12 '19 at 07:19