Benzersiz gün sayısını bulun


11

Tablodan her çalışan için benzersiz iş günü sayısını bulmak için bir SQL sorgusu yazmak istiyorum 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' |
*---------------------------------------*

Beklenen çıktı:

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

Bana çıktı veren sorgu sqlfiddle yazdım expectedama merak için bu sorguyu yazmak için daha iyi bir yolu var mı? Takvim veya Tally tablosunu kullanabilir miyim?

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

Herhangi bir öneri harika olurdu.


değerleri için (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'monday', 'tuesday');çalışmıştır empid_1 3 ayrı gün (Pazartesi, Salı, Çarşamba), kemençe / sorgu 4 döndürür
LPTR

1
@lptr it is (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'friday', 'friday');
gayretli

3
Sorgunuz gerçekten çalışmıyor. Eğer değiştirirseniz 1 2 'monday' 'tuesday'için 1 2 'monday' 'wednesday'hala 4 gün olmalıdır sonucu ancak 5 döndürür
Nick

Yanıtlar:


5

Temel olarak, haftanın tüm günleri ile her emp_idbiri tarafından çalışılan günlerin kesişimini bulmanız taskve ardından farklı günleri saymanız gerekir:

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

Çıktı:

emp_id  distinct_days
1       4
2       5

SQLFiddle'da demo


Benimkini yazarken cevabınızı görmedim. Şimdi görüyorum ki işleri gereğinden fazla karmaşık hale getirdim. Çözümünü beğendim.
Thorsten Kettner

2
@ThorstenKettner evet - Başlangıçta özyinelemeli CTE yolunu kendim başlattım, ancak durum aynı sonucu daha kolay elde ettiği için bir joinile birlikte gerçekleştirdim between...
Nick

6

Sorudaki ifadeyi (keman) basitleştirmek için olası bir yaklaşım, VALUEStablo değeri yapıcısını ve uygun birleştirmeleri kullanmaktır:

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

Ancak, farklı günleri saymak istiyorsanız , ifade farklıdır. start_dayVe end_dayaralık arasındaki tüm günleri bulmanız ve farklı günleri saymanız gerekir:

;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

Eğer değiştirirseniz (OP'lerle orijinal sorgu olduğu gibi) Bu sorgu, işi yapmaz 1 2 'monday' 'tuesday' için 1 2 'monday' 'wednesday' hala 4 gün olmalıdır sonucu ancak 5. döndürür
Nick

@Nick, üzgünüm, anlayamıyorum. OP'lerin açıklamalarına dayanarak, mondayve arasında 2 gün vardır wednesday. Bir şey mi kaçırıyorum?
Zhorov

giriş verilerini açıkladığım gibi değiştirin ve sorgunuz 5 döndürür. Ancak yine de sadece 4 benzersiz gün çalıştığı için cevap 4 olmalıdır.
Nick

@Nick, şimdi anladığımı anlıyorum. Ben OP keman değerleri değiştirmek Ama eğer sonuç olacak 5, değil 4. Bu cevap sadece daha basit bir ifade önermektedir. Teşekkürler.
Zhorov

OPs sorgusu da yanlış. Bu verilerle doğru cevap 4'tür, çünkü sadece 4 benzersiz gün vardır.
Nick

2

Sorgunuz doğru değil. Pazartesi - Salı günlerini Çarşamba ve Perşembe günleri arasında deneyin. Bu 4 gün ile sonuçlanmalıdır, ancak sorgunuz 2 gün döndürür. Sorgunuz, iki aralığın bitişik veya üst üste binmiş olup olmadığını algılamaz.

Bunu çözmenin bir yolu, tüm aralıkları bir aralıktan almak için tekrarlanan bir CTE yazmak ve daha sonra farklı günleri saymaktır.

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;

Demo: http://sqlfiddle.com/#!18/4a5ac/16

(Görülebileceği gibi doğrudan değerleri gibi yapıcı uygulayamadı with weekdays (day_name, day_number) as (values ('monday', 1), ...). Neden bilmiyorum. Bu SQL Server veya ben mi? Eh, ek seçim ile çalışır :-)


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
Yalnızca kod yanıtları, nasıl ve neden çalıştıklarına dair bazı açıklamalar eklenerek neredeyse her zaman iyileştirilebilir.
Jason Aller

1
Stack Overflow'a hoş geldiniz! Bu kod soruyu çözebilir, ancak bunun sorunun nasıl ve neden çözüldüğüne dair bir açıklama da dahil olmak üzere , yayınınızın kalitesini artırmaya yardımcı olabilir ve muhtemelen daha fazla oyla sonuçlanır. Sadece şimdi soran kişi için değil, gelecekte okuyucular için soruyu cevapladığınızı unutmayın. Lütfen açıklama eklemek için yanıtınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğunu belirtin. Şu kaynaktan
double-beep

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
Kodunuzun nasıl çalıştığını açıklamanızı ister misiniz?
Suraj Kumar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.