Reputation: 31
I need a query that counts all days of the type "Vacation" of a given IT specialist from the current year, not including weekends, the vacation may last several days or one day. If the leave lasts for several days, the beginning of the leave is the start, and the end of the leave is the end, if the leave is one day, the start and end have the same date
At the moment, I have added up the leave, it verifies whether the leave lasts one day or several days
I need to exclude weekends from the sum of vacation days
Table: kal_termin
id,start,end,id_typ
Table: kal_uczestnik
id,id_termin,id_informatyk
My query
SELECT kal_uczestnik.id_informatyk, SUM(
CASE
WHEN start = end THEN 1
ELSE DATEDIFF(end, start) + 1
END
) as days_off
FROM kal_termin,kal_uczestnik
WHERE kal_termin.id=kal_uczestnik.id_termin AND kal_uczestnik.id_informatyk = 119 AND kal_termin.id_typ = 1 AND YEAR(start) = YEAR(CURRENT_DATE)
GROUP BY kal_uczestnik.id_informatyk;
Upvotes: 0
Views: 62
Reputation: 12998
You need to use a calendar table or a recursive Common Table Expression (CTE), to build a calendar, as in this example:
WITH RECURSIVE `calendar` (`date`, `is_weekend`) AS (
SELECT
MAKEDATE(YEAR(CURRENT_DATE), 1), -- first day of current year
DAYOFWEEK(MAKEDATE(YEAR(CURRENT_DATE), 1)) IN (1, 7) -- is day Sat or Sun
UNION ALL
SELECT
`date` + INTERVAL 1 DAY,
DAYOFWEEK(`date` + INTERVAL 1 DAY) IN (1, 7)
FROM `calendar`
WHERE `date` + INTERVAL 1 DAY < MAKEDATE(YEAR(CURRENT_DATE) + 1, 1) -- first day of next year
)
SELECT `u`.`id_informatyk`, COUNT(`c`.`date`) AS `days_off`
FROM `kal_termin` `t`
JOIN `kal_uczestnik` `u`
ON `t`.`id` = `u`.`id_termin`
AND `u`.`id_informatyk` = 119
AND `t`.`id_typ` = 1
JOIN `calendar` `c`
ON `c`.`date` BETWEEN `t`.`start` AND `t`.`end`
AND `c`.`is_weekend` = 0
GROUP BY u.id_informatyk;
The calendar
CTE builds a list of all days in the current year, along with whether each day is_weekend
. We then join that to your existing query based on whether the calendar.date
is between start
and end
, only including the days where is_weekend
= 0. This assumes that start
and end
are both included in the days off. You can change the join criteria if that is not the case.
Thanks to @shanmukhavangaru for flagging this question. I was unaware of this approach. Unfortunately, the accepted answer has the wrong matrix for both this question, and the one referenced, as it does not include the end date. There is an answer way down the page which provides the correct string.
If you want to use the suggested solution the correct matrix, when including the start and end dates is:
| M T W T F S S
-|--------------
M| 1 2 3 4 5 5 5
T| 5 1 2 3 4 4 4
W| 4 5 1 2 3 3 3
T| 3 4 5 1 2 2 2
F| 2 3 4 5 1 1 1
S| 1 2 3 4 5 0 0
S| 1 2 3 4 5 5 0
And, the concatenated string is:
1234555512344445123333451222234511112345001234550
So given your existing query, the solution would be:
SELECT
u.id_informatyk,
SUM(
5 * (DATEDIFF(t.end, t.start) DIV 7) + MID('1234555512344445123333451222234511112345001234550', 7 * WEEKDAY(t.start) + WEEKDAY(t.end) + 1, 1)
) as days_off
FROM kal_termin t
JOIN kal_uczestnik u
ON t.id = u.id_termin AND u.id_informatyk = 119
WHERE t.id_typ = 1
AND t.start >= MAKEDATE(YEAR(CURRENT_DATE), 1) -- start of current year
AND t.start < MAKEDATE(YEAR(CURRENT_DATE) + 1, 1) -- start of next year
GROUP BY u.id_informatyk;
Upvotes: 1