고유 한 일수 찾기


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했지만 호기심 을 위해이 쿼리를 작성하는 더 좋은 방법이 있습니까? 캘린더 또는 탈리 테이블을 사용할 수 있습니까?

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 개의 다른 날 (월요일, 화요일, 수요일)에 대해 작업 한 경우, 바이올린 / 질문은
lptr을

1
@lptr (1, 1, 'monday', 'wednesday'),(1, 2, 'monday', 'tuesday'),(1, 3, 'friday', 'friday');
열심 인

3
귀하의 검색어가 실제로 작동하지 않습니다. 당신은 변경하는 경우 1 2 'monday' 'tuesday'1 2 'monday' 'wednesday'여전히 사일해야 결과 만 5 반환

답변:


5

당신은 기본적으로 각으로 일한 일의 교차 찾을 필요가 emp_idtask별개의 일 계산 후주의 모든 일에, 그리고 :

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 경로를 직접 시작했지만 조건이 동일한 결과를보다 쉽게 ​​달성 할 때 joinwith between를 사용하여 깨달았습니다 ...
Nick

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_dayend_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

변경할 경우 (OPS 원래 쿼리와 같은)이 쿼리는, 일을하지 않습니다 1 2 'monday' 'tuesday' 1 2 'monday' 'wednesday' 여전히 사일해야 결과이지만 5. 반환

@Nick, 죄송합니다, 이해할 수 없습니다. OPS의 설명을 바탕으로, 사이 이일이있다 monday하고 wednesday. 뭔가 빠졌습니까?
Zhorov

내가 설명한대로 입력 데이터를 변경하면 쿼리가 5를 반환합니다. 그러나 여전히 4 일 동안 만 일 했으므로 대답은 여전히 ​​4 여야합니다.
Nick

@ 닉, 이제 당신의 요점을 이해합니다. 그러나 OP 바이올린에서 값을 변경하면 결과는 5그렇지 않습니다 4. 이 답변은 더 간단한 진술을 제안합니다. 감사.
Zhorov

OP 쿼리도 잘못되었습니다. 올바른 단지 4 고유의 일이 있기 때문에 데이터와 답은 4입니다.
Nick

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
스택 오버플로에 오신 것을 환영합니다! 이 코드가 문제를 해결하는 방법과 이유에 대한 설명포함 하여 질문을 해결할 수는 있지만 게시물의 품질을 향상시키는 데 도움이되고 더 많은 투표를 할 수 있습니다. 지금 질문하는 사람이 아니라 독자들에게 질문에 대답하고 있음을 기억하십시오. 제발 편집 설명을 추가하고 제한 및 가정이 적용 무엇의 표시를 제공하는 답변을. 검토에서
이중 경고음

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.