I wish to write a SQL query to find the number of unique working days for each employee from table times
.
*---------------------------------------*
|emp_id task_id start_day end_day |
*---------------------------------------*
| 1 1 'monday' 'wednesday' |
| 1 2 'monday' 'tuesday' |
| 1 3 'friday' 'friday' |
| 2 1 'monday' 'friday' |
| 2 1 'tuesday' 'wednesday' |
*---------------------------------------*
Expected output:
*-------------------*
|emp_id no_of_days |
*-------------------*
| 1 4 |
| 2 5 |
*-------------------*
I have written the query sqlfiddle which is giving me the expected
output but for curiosity is there a better way to write this query? Can I use Calender or Tally table?
with days_num as
(
select
*,
case
when start_day = 'monday' then 1
when start_day = 'tuesday' then 2
when start_day = 'wednesday' then 3
when start_day = 'thursday' then 4
when start_day = 'friday' then 5
end as start_day_num,
case
when end_day = 'monday' then 1
when end_day = 'tuesday' then 2
when end_day = 'wednesday' then 3
when end_day = 'thursday' then 4
when end_day = 'friday' then 5
end as end_day_num
from times
),
day_diff as
(
select
emp_id,
case
when
(end_day_num - start_day_num) = 0
then
1
else
(end_day_num - start_day_num)
end as total_diff
from days_num
)
select emp_id,
sum(total_diff) as uniq_working_days
from day_diff
group by
emp_id
Any suggestions would be great.
with cte as
(Select id, start_day as day
group by id, start_day
union
Select id, end_day as day
group by id, end_day
)
select id, count(day)
from cte
group by id
One possible approach to simplify the statement in the question(fiddle), is to use VALUES
table value constructor and appropriate joins:
SELECT
t.emp_id,
SUM(CASE
WHEN d1.day_no = d2.day_no THEN 1
ELSE d2.day_no - d1.day_no
END) AS no_of_days
FROM times t
JOIN (VALUES ('monday', 1), ('tuesday', 2), ('wednesday', 3), ('thursday', 4), ('friday', 5)) d1 (day, day_no)
ON t.start_day = d1.day
JOIN (VALUES ('monday', 1), ('tuesday', 2), ('wednesday', 3), ('thursday', 4), ('friday', 5)) d2 (day, day_no)
ON t.end_day = d2.day
GROUP BY t.emp_id
But if you want to count the distinct days, the statement is different. You need to find all days between the start_day
and end_day
range and count the distinct days:
;WITH daysCTE (day, day_no) AS (
SELECT 'monday', 1 UNION ALL
SELECT 'tuesday', 2 UNION ALL
SELECT 'wednesday', 3 UNION ALL
SELECT 'thursday', 4 UNION ALL
SELECT 'friday', 5
)
SELECT t.emp_id, COUNT(DISTINCT d3.day_no)
FROM times t
JOIN daysCTE d1 ON t.start_day = d1.day
JOIN daysCTE d2 ON t.end_day = d2.day
JOIN daysCTE d3 ON d3.day_no BETWEEN d1.day_no AND d2.day_no
GROUP BY t.emp_id
You need to basically find the intersection of the days worked by each emp_id
on each task
with all the days of the week, and then count the distinct days:
with days_num as (
SELECT *
FROM (
VALUES ('monday', 1), ('tuesday', 2), ('wednesday', 3), ('thursday', 4), ('friday', 5)
) AS d (day, day_no)
),
emp_day_nums as (
select emp_id, d1.day_no AS start_day_no, d2.day_no AS end_day_no
from times t
join days_num d1 on d1.day = t.start_day
join days_num d2 on d2.day = t.end_day
)
select emp_id, count(distinct d.day_no) AS distinct_days
from emp_day_nums e
join days_num d on d.day_no between e.start_day_no and e.end_day_no
group by emp_id
Output:
emp_id distinct_days
1 4
2 5
Demo on SQLFiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With