Tìm số ngày duy nhất


11

Tôi muốn viết một truy vấn SQL để tìm số ngày làm việc duy nhất cho mỗi nhân viên từ bảng 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' |
*---------------------------------------*

Sản lượng dự kiến:

*-------------------*
|emp_id  no_of_days |
*-------------------*
|  1        4       |
|  2        5       |
*-------------------*

Tôi đã viết truy vấn sqlfiddle đang cho tôi expectedđầu ra nhưng vì tò mò có cách nào tốt hơn để viết truy vấn này không? Tôi có thể sử dụng bảng Calender hoặc Tally không?

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

Bất kỳ đề xuất nào cũng sẽ rất tuyệt.


đối với các giá trị (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'monday', 'tuesday');empid_1 đã hoạt động được 3 ngày (thứ hai, thứ ba, thứ tư), fiddle / truy vấn trả về 4
lptr

1
@lptr đó là (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'friday', 'friday');
nhiệt tình

3
Truy vấn của bạn không thực sự hoạt động. Nếu bạn thay đổi 1 2 'monday' 'tuesday'thành 1 2 'monday' 'wednesday'kết quả vẫn là 4 ngày nhưng nó sẽ trả về 5
Nick

Câu trả lời:


5

Về cơ bản, bạn cần tìm giao điểm của các ngày làm việc của từng emp_idngày taskvới tất cả các ngày trong tuần và sau đó đếm các ngày khác nhau:

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

Đầu ra:

emp_id  distinct_days
1       4
2       5

Bản trình diễn trên SQLFiddle


Tôi không thấy câu trả lời của bạn khi viết của tôi. Bây giờ tôi thấy tôi đã làm cho mọi thứ phức tạp hơn mức cần thiết. Tôi thích giải pháp của bạn.
Thorsten Kettner

2
@ThorstenKettner yeah - Ban đầu tôi tự mình bắt đầu con đường CTE đệ quy nhưng nhận ra bằng cách sử dụng joinvới betweenđiều kiện đạt được kết quả tương tự dễ dàng hơn ...
Nick

6

Một cách tiếp cận khả thi để đơn giản hóa câu lệnh trong câu hỏi (fiddle), là sử dụng hàm tạo VALUESgiá trị bảng và các phép nối thích hợp:

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

Nhưng nếu bạn muốn đếm những ngày khác biệt , tuyên bố là khác nhau. Bạn cần tìm tất cả các ngày giữa start_dayend_dayphạm vi và đếm các ngày khác nhau:

;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

Truy vấn này (như với truy vấn ban đầu của OP) không hoạt động, nếu bạn thay đổi 1 2 'monday' 'tuesday' thành 1 2 'monday' 'wednesday' kết quả thì vẫn phải là 4 ngày nhưng nó sẽ trả về 5.
Nick

@Nick, xin lỗi, tôi không thể hiểu. Dựa trên các giải thích của OP, có 2 ngày giữa mondaywednesday. Tui bỏ lỡ điều gì vậy?
Zhorov

thay đổi dữ liệu đầu vào như tôi đã mô tả và truy vấn của bạn trả về 5. Tuy nhiên, câu trả lời vẫn là 4 vì vẫn chỉ có 4 ngày làm việc.
Nick

@Nick, bây giờ tôi hiểu quan điểm của bạn. Nhưng nếu tôi thay đổi các giá trị trong fiddle OP, kết quả sẽ là 5không 4. Câu trả lời này chỉ gợi ý tuyên bố đơn giản hơn. Cảm ơn.
Zhorov

Truy vấn OP cũng sai. Các đúng câu trả lời với dữ liệu đó là 4, như chỉ có 4 ngày độc đáo.
Nick

2

Truy vấn của bạn không chính xác. Hãy thử thứ Hai đến thứ Ba với Thứ Tư đến Thứ Năm. Điều này sẽ dẫn đến 4 ngày, nhưng truy vấn của bạn trả về 2 ngày. Truy vấn của bạn thậm chí không phát hiện xem hai phạm vi liền kề hay chồng chéo hay không.

Một cách để giải quyết điều này là viết CTE đệ quy để có được tất cả các ngày từ một phạm vi và sau đó đếm các ngày khác nhau.

with weekdays (day_name, day_number) as
(
  select * from (values ('monday', 1), ('tuesday', 2), ('wednesday', 3),
                        ('thursday', 4), ('friday', 5)) as t(x,y)
)
, emp_days(emp_id, day, last_day)
as
(
  select emp_id, wds.day_number, wde.day_number
  from times t
  join weekdays wds on wds.day_name = t.start_day
  join weekdays wde on wde.day_name = t.end_day
  union all
  select emp_id, day + 1, last_day
  from emp_days
  where day < last_day
)
select emp_id, count(distinct day)
from emp_days
group by emp_id
order by emp_id;

Bản trình diễn: http://sqlfiddle.com/#!18/4a5ac/16

(Như có thể thấy, tôi không thể áp dụng hàm tạo giá trị trực tiếp như trong with weekdays (day_name, day_number) as (values ('monday', 1), ...). Tôi không biết tại sao. Máy chủ SQL đó có phải là tôi không?


2
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

3
Mã câu trả lời hầu như luôn luôn có thể được cải thiện bằng cách thêm một số giải thích về cách thức và lý do tại sao chúng hoạt động.
Jason Aller

1
Chào mừng bạn đến với Stack Overflow! Mặc dù mã này có thể giải quyết câu hỏi, bao gồm giải thích về cách thức và lý do giải quyết vấn đề này thực sự sẽ giúp cải thiện chất lượng bài đăng của bạn và có thể dẫn đến nhiều lượt bình chọn hơn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ người hỏi bây giờ. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng. Từ đánh giá
tiếng bíp đôi

1
declare @times table
(
  emp_id int,
  task_id int,
  start_day varchar(50),
  end_day varchar(50)
);

insert into @times(emp_id, task_id, start_day, end_day)
values
(1, 1, 'monday', 'wednesday'),
(1, 2, 'monday', 'tuesday'),
(1, 3, 'friday', 'friday'),
--
(2, 1, 'monday', 'friday'),
(2, 2, 'tuesday', 'wednesday'),
--
(3, 1, 'monday', 'wednesday'),
(3, 2, 'monday', 'tuesday'),
(3, 3, 'monday', 'tuesday');

--for sql 2019, APPROX_COUNT_DISTINCT() eliminates distinct sort (!!)...
-- ...with a clustered index on emp_id (to eliminate the hashed aggregation) the query cost gets 5 times cheaper ("overlooking" the increase in memory) !!??!!
/*
select t.emp_id, APPROX_COUNT_DISTINCT(v.val) as distinctweekdays
from
(
select *, .........
*/


select t.emp_id, count(distinct v.val) as distinctweekdays
from
(
select *, 
case start_day when 'monday' then 1
      when 'tuesday' then 2
      when 'wednesday' then 3
      when 'thursday' then 4
      when 'friday' then 5
    end as start_day_num,
case end_day when 'monday' then 1
      when 'tuesday' then 2
      when 'wednesday' then 3
      when 'thursday' then 4
      when 'friday' then 5
    end as end_day_num
from @times
) as t
join (values(1),(2), (3), (4), (5)) v(val) on v.val between t.start_day_num and t.end_day_num
group by t.emp_id;

1
Yêu cầu bạn viết một mô tả về mã của bạn làm thế nào nó hoạt động?
Suraj Kumar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.