쿼리 최적화 : 시간 간격


10

기본적으로 두 가지 종류의 시간 간격이 있습니다.

presence timeabsence time

absence time 다른 유형 (예 : 휴식, 부재, 특별한 날 등) 일 수 있으며 시간 간격이 겹치거나 교차 할 수 있습니다.

그것은 것입니다 하지 간격의 그럴듯한 조합 예를 들어, 원시 데이터에 있는지, 확실히. 존재 간격이 겹치는 것은 의미가 없지만 존재할 수 있습니다. 나는 현재 여러 가지 방법으로 존재 시간 간격을 식별하려고 노력했습니다. 가장 편안한 방법은 다음과 같습니다.

;with "timestamps"
as
(
    select
        "id" = row_number() over ( order by "empId", "timestamp", "opening", "type" )
        , "empId"
        , "timestamp"
        , "type"
        , "opening"
    from
    (
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 1 as "type" from "worktime" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 2 as "type" from "break" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
        union all
        select "empId", "timestamp", "type", case when "types" = 'starttime' then 1 else -1 end as "opening" from
        ( select "empId", "starttime", "endtime", 3 as "type" from "absence" ) as data
        unpivot ( "timestamp" for "types" in ( "starttime", "endtime" ) ) as pvt
    ) as data
)
select 
      T1."empId"
    , "starttime"   = T1."timestamp"
    , "endtime"     = T2."timestamp"
from 
    "timestamps" as T1
    left join "timestamps" as T2
        on T2."empId" = T1."empId"
        and T2."id" = T1."id" + 1
    left join "timestamps" as RS
        on RS."empId" = T2."empId"
        and RS."id" <= T1."id"      
group by
    T1."empId", T1."timestamp", T2."timestamp"
having
    (sum( power( 2, RS."type" ) * RS."opening" ) = 2)
order by 
    T1."empId", T1."timestamp";

일부 데모 데이터는 SQL-Fiddle 을 참조하십시오 .

원시 데이터는 "starttime" - "endtime"또는 형식으로 다른 테이블에 존재합니다 "starttime" - "duration".

아이디어는 존재 시간을 추정하기 위해 매번 열린 간격의 "비트 마스킹 된"롤링 간격을 가진 모든 타임 스탬프의 정렬 된 목록을 얻는 것입니다.

간격이 다른 스타 타임이 같더라도 바이올린이 작동하고 예상 결과를 제공합니다. 이 예제에서는 인덱스가 사용되지 않습니다.

이것이 의문의 과제를 달성하는 올바른 방법입니까, 아니면 더 우아한 방법입니까?

응답과 관련이있는 경우 : 데이터 양은 테이블 당 직원당 최대 10 만 개입니다. sql-2012를 사용하여 선행 작업의 롤링 합계를 인라인으로 계산할 수 없습니다.


편집하다:

방대한 양의 테스트 데이터 (1000, 10.000, 100.000, 1 백만)에 대해 쿼리를 실행하면 런타임이 기하 급수적으로 증가 함을 알 수 있습니다. 분명히 경고 깃발 이죠?

쿼리를 변경하고 기발한 업데이트로 롤링 합계 집계를 제거했습니다.

보조 테이블을 추가했습니다.

create table timestamps
(
  "id" int
  , "empId" int
  , "timestamp" datetime
  , "type" int
  , "opening" int
  , "rolSum" int
)

create nonclustered index "idx" on "timestamps" ( "rolSum" ) include ( "id", "empId", "timestamp" )

롤링 합계 계산을이 곳으로 옮겼습니다.

declare @rolSum int = 0
update "timestamps" set @rolSum = "rolSum" = @rolSum + power( 2, "type" ) * "opening" from "timestamps"

여기 SQL-Fiddle 참조

"근무 시간"테이블에서 1 백만 개의 항목에 대해 런타임이 3 초로 감소했습니다.

질문은 동일하게 유지됩니다. 이것을 해결하는 가장 효과적인 방법은 무엇입니까?


나는 이것에 대해 논쟁이있을 것이라고 확신하지만 CTE 에서는 그렇게 하지 않을 수도 있습니다 . 대신 임시 테이블을 사용하고 더 빠른지 확인하십시오.
rottengeek

스타일 질문 : 나는 모든 열 이름과 테이블 이름을 큰 따옴표로 묶는 것을 본 적이 없습니다. 이것이 회사 전체의 관행입니까? 나는 확실히 불편하다고 생각합니다. 내 견해로는 필요하지 않으므로 신호에 대한 잡음이 증가합니다.
ErikE

@ErikE 위의 방법은 거대한 애드온의 일부입니다. 일부 개체는 동적으로 생성되며 최종 사용자 입력 선택에 따라 다릅니다. 따라서 공백은 테이블 이름이나 뷰 이름으로 나타날 수 있습니다. 그 주위에 큰 따옴표는 쿼리 충돌을 일으키지 않습니다 ...!
니코

내 세계에서 @Nico는 보통 대괄호로 끝나고 다음과 같습니다 [this]. 큰 따옴표보다 낫습니다.
ErikE

@ErikE 대괄호는 tsql입니다. 표준 은 큰 따옴표입니다! 어쨌든, 나는 그것을 그렇게 배웠고 어떻게 든 익숙해졌습니다!
니코

답변:


3

절대적으로 최선의 방법에 대한 귀하의 질문에 대답 할 수 없습니다. 그러나 문제를 해결 하는 다른 방법을 제공 할 수 있습니다 . 합리적으로 실행 계획이 있으며, 제대로 수행 될 것이라고 생각합니다. (알고 싶어서 결과를 공유하십시오!)

귀하 대신 고유 한 구문 스타일을 사용하여 죄송합니다. 모든 것이 일반적인 위치에있을 때 쿼리 마법사가 나에게 도움이됩니다.

쿼리는 SqlFiddle에서 사용할 수 있습니다 . 나는 EmpID 1에 대해 겹친 부분을 던졌습니다. 현재 상태 데이터에서 겹침이 발생할 수없는 경우 최종 쿼리와 Dense_Rank계산을 제거 할 수 있습니다 .

WITH Points AS (
  SELECT DISTINCT
    T.EmpID,
    P.TimePoint
  FROM
    (
      SELECT * FROM dbo.WorkTime
      UNION SELECT * FROM dbo.BreakTime
      UNION SELECT * FROM dbo.Absence
    ) T
    CROSS APPLY (VALUES (StartTime), (EndTime)) P (TimePoint)
), Groups AS (
  SELECT
    P.EmpID,
    P.TimePoint,
    Grp =
      Row_Number()
      OVER (PARTITION BY P.EmpID ORDER BY P.TimePoint, X.Which) / 2
  FROM
    Points P
    CROSS JOIN (VALUES (1), (2)) X (Which)
), Ranges AS (
  SELECT
    G.EmpID,
    StartTime = Min(G.TimePoint),
    EndTime = Max(G.TimePoint)
  FROM Groups G
  GROUP BY
    G.EmpID,
    G.Grp
  HAVING Count(*) = 2
), Presences AS (
  SELECT
    R.*,
    P.Present,
    Grp =
       Dense_Rank() OVER (PARTITION BY R.EmpID ORDER BY R.StartTime)
       - Dense_Rank() OVER (PARTITION BY R.EmpID, P.Present ORDER BY R.StartTime)
  FROM
    Ranges R
    CROSS APPLY (
      SELECT
        CASE WHEN EXISTS (
          SELECT *
          FROM dbo.WorkTime W
          WHERE
            R.EmpID = W.EmpID
            AND R.StartTime < W.EndTime
            AND W.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.BreakTime B
          WHERE
            R.EmpID = B.EmpID
            AND R.StartTime < B.EndTime
            AND B.StartTime < R.EndTime
        ) AND NOT EXISTS (
          SELECT *
          FROM dbo.Absence A
          WHERE
            R.EmpID = A.EmpID
            AND R.StartTime < A.EndTime
            AND A.StartTime < R.EndTime
        ) THEN 1 ELSE 0 END
    ) P (Present)
)
SELECT
  EmpID,
  StartTime = Min(StartTime),
  EndTime = Max(EndTime)
FROM Presences
WHERE Present = 1
GROUP BY
  EmpID,
  Grp
ORDER BY
  EmpID,
  StartTime;

참고 :이 쿼리의 성능이 향상되면 세 개의 테이블을 결합하고 작업, 중단 또는 부재 등의 시간을 나타내는 열을 추가했습니다.

그리고 왜 모든 CTE를 물어 보십니까? 각 데이터는 데이터에 필요한 작업으로 인해 강제 실행되기 때문입니다. 집계가 있거나 창 함수에 WHERE 조건을 배치하거나 창 함수가 허용되지 않는 절에서 사용해야합니다.

이제 저는이 목표를 달성하기위한 다른 전략을 생각할 수 없는지 살펴 보겠습니다. :)

오락을 위해 여기에 문제를 해결하기 위해 만든 "도표"가 포함됩니다.

------------
   -----------------
                ---------------
                           -----------

    ---    ------   ------       ------------

----   ----      ---      -------

3 개의 대시 세트 (공백으로 구분)는 존재 데이터, 부재 데이터 및 원하는 결과의 순서로 나타냅니다.


이 접근법에 감사드립니다. 사무실로 돌아 왔을 때 확인하고 더 큰 데이터베이스로 런타임 결과를 제공합니다.
니코

런타임은 첫 번째 접근 방식보다 훨씬 높습니다. 추가 지수가 아직 하락할 수 있는지 확인할 시간이 없었습니다. 가능한 빨리 확인하겠습니다!
니코

일할 시간이 없었던 또 다른 아이디어가 있습니다. 가치있는 결과를 위해 쿼리는 모든 테이블에서 범위가 겹치는 잘못된 결과를 반환합니다.
ErikE

나는 이것을 다시 체크 아웃했다. 3 개의 테이블에서 완전히 겹치는 간격을 가진 이 바이올린 을 보십시오 . 내가 볼 수 있듯이 올바른 결과를 반환합니다. 잘못된 결과가 반환되는 경우를 제공 할 수 있습니까? 바이올린으로 데모 데이터를 자유롭게 조정하십시오!
니코

좋아, 당신의 요점을 알았습니다. 하나의 테이블에서 간격이 교차하는 경우 결과가 화를 냈습니다. 이것을 확인합니다.
니코
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.