쿼리 최적화 : 시간 간격


10

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

presence time 과 absence 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.