ค้นหาจำนวนวันที่ไม่ซ้ำกัน


11

ฉันต้องการที่จะเขียนแบบสอบถาม SQL 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' |
*---------------------------------------*

ผลลัพธ์ที่คาดหวัง:

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

ฉันได้เขียนsqlfiddleแบบสอบถามซึ่งให้expectedผลลัพธ์กับฉันแต่สำหรับความอยากรู้มีวิธีที่ดีกว่าในการเขียนแบบสอบถามนี้หรือไม่ ฉันสามารถใช้ตารางปฏิทินหรือ Tally ได้หรือไม่

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

ข้อเสนอแนะใด ๆ จะดี


สำหรับค่า(1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'monday', 'tuesday');empid_1 ได้ทำงาน 3 วันที่แตกต่างกัน (วันจันทร์วันอังคารวันพุธ) ซอ / เคียวรีส่งคืน 4
lptr

1
@lptr มันคือ (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'friday', 'friday');
zealous

3
แบบสอบถามของคุณใช้งานไม่ได้จริง ถ้าคุณเปลี่ยน1 2 'monday' 'tuesday'ไป1 2 'monday' 'wednesday'ผลที่ควรจะยังคง 4 วัน แต่ก็จะส่งกลับ 5
นิค

คำตอบ:


5

คุณจำเป็นต้องโดยทั่วไปพบว่าจุดตัดของวันทำงานโดยแต่ละคนemp_idในแต่ละtaskกับทุกวันของสัปดาห์ที่แล้วนับวันที่แตกต่าง:

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

เอาท์พุท:

emp_id  distinct_days
1       4
2       5

การสาธิตเกี่ยวกับ SQLFiddle


ฉันไม่เห็นคำตอบของคุณเมื่อฉันเขียน ตอนนี้ฉันเห็นว่าฉันกำลังทำสิ่งที่ซับซ้อนเกินความจำเป็น ฉันชอบทางออกของคุณ
Thorsten Kettner

2
@ThorstenKettner ใช่ - ตอนแรกฉันเริ่มต้นเส้นทาง CTE แบบเรียกซ้ำด้วยตัวเอง แต่รู้ตัวว่าใช้ a joinด้วยbetweenเนื่องจากเงื่อนไขบรรลุผลแบบเดียวกันมากขึ้นอย่างง่ายดาย ...
นิค

6

วิธีหนึ่งที่เป็นไปได้ในการทำให้คำสั่งในคำถามง่ายขึ้น (fiddle) คือใช้VALUESตัวสร้างค่าของตารางและการรวมที่เหมาะสม:

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

แต่ถ้าคุณต้องการที่จะนับที่แตกต่างวันที่คำสั่งที่แตกต่างกัน คุณต้องค้นหาทุกวันระหว่างstart_dayและend_dayช่วงและนับจำนวนวันที่แตกต่างกัน:

;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

แบบสอบถามนี้ (เช่นเดียวกับตรวจการณ์แบบสอบถามเดิม) ไม่ทำงานถ้าคุณเปลี่ยน1 2 'monday' 'tuesday' ไป1 2 'monday' 'wednesday' ผลที่ควรจะยังคง 4 วัน แต่ก็จะส่งกลับ 5
นิค

@Nick ขอโทษฉันไม่เข้าใจ ขึ้นอยู่กับคำอธิบายตรวจการณ์มี 2 วันระหว่างวันที่และmonday wednesdayฉันพลาดอะไรไปรึเปล่า?
Zhorov

เปลี่ยนข้อมูลที่ป้อนตามที่ฉันอธิบายและแบบสอบถามของคุณจะส่งคืน 5 อย่างไรก็ตามคำตอบควรเป็น 4 เนื่องจากมีการทำงานที่ไม่ซ้ำกันเพียง 4 วัน
นิค

@ นิคตอนนี้ฉันเข้าใจประเด็นของคุณแล้ว แต่ถ้าผมเปลี่ยนค่าในตรวจการณ์ซอผลจะไม่5 4คำตอบนี้แนะนำเพียงคำสั่งที่ง่ายกว่า ขอบคุณ
Zhorov

OPs การสืบค้นก็ผิดเช่นกัน ที่ถูกต้องคำตอบที่มีข้อมูลที่เป็น 4 ขณะที่มีเพียง 4 วันไม่ซ้ำกัน
นิค

2

ข้อความค้นหาของคุณไม่ถูกต้อง ลองวันจันทร์ถึงวันอังคารกับวันพุธถึงวันพฤหัสบดี ซึ่งจะส่งผลใน 4 วัน แต่แบบสอบถามของคุณส่งคืน 2 วัน ข้อความค้นหาของคุณไม่ได้ตรวจพบว่ามีสองช่วงที่อยู่ติดกันหรือทับซ้อนกันหรือไม่

วิธีหนึ่งในการแก้ไขปัญหานี้คือการเขียน CTE แบบเรียกซ้ำเพื่อรับทั้งวันจากช่วงและนับจำนวนวันที่แตกต่างกัน

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;

การสาธิต: http://sqlfiddle.com/#!18/4a5ac/16

(เท่าที่เห็นฉันไม่สามารถใช้ค่าคอนสตรัคเตอร์ได้โดยตรงเหมือนwith weekdays (day_name, day_number) as (values ('monday', 1), ...)กันฉันไม่รู้ว่าทำไม SQL Server นั้นหรือฉันใช่ไหมด้วยตัวเลือกเพิ่มเติมที่ใช้งานได้ :-)


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
คำตอบของรหัสเท่านั้นที่สามารถปรับปรุงได้ตลอดเวลาโดยการเพิ่มคำอธิบายเกี่ยวกับวิธีการและสาเหตุที่ทำให้พวกเขาทำงาน
Jason Aller

1
ยินดีต้อนรับสู่ Stack Overflow! แม้ว่ารหัสนี้อาจแก้ปัญหาได้รวมถึงคำอธิบายว่าทำไมและวิธีแก้ปัญหานี้จะช่วยปรับปรุงคุณภาพการโพสต์ของคุณได้อย่างไรและอาจส่งผลให้มีการลงคะแนนมากขึ้น จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตไม่ใช่เพียงแค่คนที่ถามตอนนี้ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้ จากการตรวจสอบ
ดับเบิ้ล

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
ขอให้คุณเขียนคำอธิบายรหัสของคุณว่ามันทำงานอย่างไร
Suraj Kumar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.