クエリの最適化:時間間隔


10

主に、2種類の時間間隔があります。

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"ます。

アイデアは、プレゼンス時間を推定するために、各時間で開いた間隔の「ビットマスクされた」ローリング合計ですべてのタイムスタンプの順序付きリストを取得することでした。

フィドルは機能し、異なる間隔のスタータイムが等しい場合でも、推定結果を提供します。この例では、インデックスは使用されていません。

これは疑わしいタスクを達成するための正しい方法ですか、これよりエレガントな方法はありますか?

回答に関連する場合:データ量は、テーブルごとに従業員あたり最大数万のデータセットになります。sql-2012は、先行バージョンのローリング合計をインラインで集計して計算するために使用できません。


編集:

大量のテストデータ(1000、10000、100.000、100万)に対してクエリを実行すると、実行時間が指数関数的に増加することがわかります。明らかに警告フラグですよね?

クエリを変更し、風変わりな更新によってローリングサムの集計を削除しました。

補助テーブルを追加しました:

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を参照してください

「worktime」テーブルの100万エントリに関して、実行時間は3秒に減少しました。

質問は変わりません:これを解決する最も効果的な方法は何ですか?


私はこれについて論争があると確信していますが、CTEでそれをしないように試みるかもしれません。代わりに一時テーブルを使用して、それが高速かどうかを確認してください。
rottengeek 2013

単なるスタイルの質問です。列名とテーブル名をすべて二重引用符で囲むのを見たことがありません。これはあなたの会社全体の慣行ですか?私は間違いなく不快だと思います。それは私の見解では必要ないため、信号のノイズが増加します...
ErikE

@ErikE上記のメソッドは、巨大なアドオンの一部です。一部のオブジェクトは動的に作成され、エンドユーザーの入力選択に依存します。したがって、たとえば、空白がテーブル名またはビュー名に現れる可能性があります。それらを二重引用符で囲むとクエリがクラッシュしません...!
ニコ

@Nico私の世界では、通常、角かっこを使用して行われます[this]。私はそれを二重引用符よりも好きだと思います。
ErikE 2013

@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;

注:このクエリのパフォーマンスは向上します。3つのテーブルを組み合わせて、作業時間、休憩時間、不在時間などの時間を示す列を追加しました。

そして、なぜすべてのCTEが必要なのでしょうか。それぞれが私がデータに何をする必要があるかによって強制されるからです。集計があるか、ウィンドウ関数にWHERE条件を設定するか、ウィンドウ関数が許可されていない句でそれを使用する必要があります。

次に、これを達成するための別の戦略を考えられないかどうかを確認します。:)

娯楽のために、私はここに問題を解決するのを助けるために作った「図」を含めます:

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

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

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

3つのダッシュ(スペースで区切られた)のセットは、プレゼンスデータ、不在データ、および目的の結果を順番に表します。


このアプローチをありがとう。私はオフィスに戻ったときにチェックアウトし、より大きなデータベースでの実行時結果を提供します。
ニコ

ランタイムは最初のアプローチよりもはるかに高いです。さらなる指標がまだそれを減らすかどうかを確認する時間はありませんでした。お早めにチェックします!
ニコ

仕事をする時間がなかった別のアイデアがあります。それだけの価値がある場合、クエリはすべてのテーブルで範囲が重複している誤った結果を返します。
ErikE 2013

私はこれを再度チェックアウトしました。3つのテーブルすべてで完全に重複する間隔があるこのフィドルを参照してください。私が見ることができるように、それは正しい結果を返します。間違った結果が返されるケースを提供できますか?フィドルでデモデータを自由に調整してください!
ニコ

大丈夫、私はあなたのポイントを得ました。1つのテーブルで間隔が交差している場合、結果が狂ってしまいます。これをチェックします。
ニコ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.