Temukan jumlah hari yang unik

11

Saya ingin menulis kueri SQL untuk menemukan jumlah hari kerja yang unik untuk setiap karyawan dari tabel 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' |
*---------------------------------------*

Output yang diharapkan:

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

Saya telah menulis kueri sqlfiddle yang memberikan saya expectedoutput tetapi untuk rasa ingin tahu apakah ada cara yang lebih baik untuk menulis kueri ini? Bisakah saya menggunakan tabel Calender atau 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

Setiap saran akan sangat bagus.

tekun
sumber
untuk nilai (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'monday', 'tuesday');empid_1 telah bekerja 3 hari yang berbeda (Senin, Selasa, Rabu), biola / kueri mengembalikan 4
lptr
1
@ lptr itu (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'friday', 'friday');
bersemangat
3
Permintaan Anda sebenarnya tidak berfungsi. Jika Anda mengubah 1 2 'monday' 'tuesday'ke 1 2 'monday' 'wednesday'hasilnya tetap harus 4 hari tapi kembali 5
Nick

Jawaban:

5

Anda pada dasarnya perlu menemukan persimpangan hari yang digunakan oleh masing-masing emp_idpada setiap hari taskdengan semua hari dalam seminggu, dan kemudian menghitung hari yang berbeda:

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

Keluaran:

emp_id  distinct_days
1       4
2       5

Demo di SQLFiddle

Nick
sumber
Saya tidak melihat jawaban Anda ketika menulis jawaban saya. Sekarang saya melihat saya membuat hal-hal lebih rumit daripada yang diperlukan. Saya suka solusi Anda.
Thorsten Kettner
2
@ThorstenKettner ya - Saya awalnya mulai menyusuri jalan CTE rekursif tetapi saya menyadari menggunakan joindengan betweenkarena kondisinya mencapai hasil yang sama lebih mudah ...
Nick
6

Salah satu pendekatan yang mungkin untuk menyederhanakan pernyataan dalam pertanyaan (biola), adalah dengan menggunakan VALUESkonstruktor nilai tabel dan gabungan yang sesuai:

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

Tetapi jika Anda ingin menghitung hari yang berbeda , pernyataannya berbeda. Anda perlu mencari semua hari antara start_daydan end_dayjangkauan dan menghitung hari yang berbeda:

;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
Zhorov
sumber
Query ini (seperti dengan Ops query) tidak bekerja, jika Anda mengubah 1 2 'monday' 'tuesday' ke 1 2 'monday' 'wednesday' hasilnya tetap harus 4 hari tapi kembali 5.
Nick
@Nick, maaf, saya tidak bisa mengerti. Berdasarkan penjelasan OP, ada 2 hari antara mondaydan wednesday. Apakah saya melewatkan sesuatu?
Zhorov
ubah input data seperti yang saya jelaskan, dan permintaan Anda kembali 5. Namun jawabannya tetap harus 4 karena masih ada 4 hari unik yang berfungsi.
Nick
@Nick, sekarang saya mengerti maksud Anda. Tetapi jika saya mengubah nilai-nilai dalam biola OPs, hasilnya akan 5, tidak 4. Jawaban ini hanya menyarankan pernyataan yang lebih sederhana. Terima kasih.
Zhorov
Permintaan OP juga salah. The benar jawaban dengan data yang 4, karena ada hanya 4 hari yang unik.
Nick
2

Permintaan Anda tidak benar. Coba Senin hingga Selasa dengan Rabu hingga Kamis. Ini akan menghasilkan dalam 4 hari, tetapi permintaan Anda kembali 2 hari. Permintaan Anda bahkan tidak mendeteksi apakah dua rentang berdekatan atau tumpang tindih atau tidak.

Salah satu cara untuk mengatasi ini adalah dengan menulis CTE rekursif untuk mendapatkan semua hari dari rentang dan kemudian menghitung hari yang berbeda.

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

(Seperti yang bisa dilihat saya tidak bisa menerapkan konstruktor nilai secara langsung seperti pada with weekdays (day_name, day_number) as (values ('monday', 1), ...). Saya tidak tahu mengapa. Apakah itu SQL Server atau saya? Nah, dengan tambahan pilih berfungsi :-)

Thorsten Kettner
sumber
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
Rahul Gossain
sumber
3
Kode hanya jawaban hampir selalu dapat ditingkatkan dengan penambahan beberapa penjelasan tentang bagaimana dan mengapa mereka bekerja.
Jason Aller
1
Selamat Datang di Stack Overflow! Sementara kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah akan sangat membantu untuk meningkatkan kualitas posting Anda, dan mungkin menghasilkan lebih banyak suara. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang. Harap edit jawaban Anda untuk menambahkan penjelasan dan berikan indikasi tentang batasan dan asumsi apa yang berlaku. Dari Ulasan
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;
lptr
sumber
1
Meminta Anda untuk menulis deskripsi kode Anda bagaimana cara kerjanya?
Suraj Kumar