0

SQL の世界に足を踏み入れたとき、私はこの質問に対する答えを求めて昼夜を問わず検索しました。私のニーズにこれに似たものを見つけることができなかったので、他の人が私のように助けを必要とする場合に備えて、自分の質問をして答えることにしました.

これが私が持っているデータの例です。簡単にするために、すべて Job テーブルからのものです。各 JobID には、基本的にランダムな独自の開始時刻と終了時刻があり、重複したり、ギャップがあったり、他のジョブと同時に開始および終了したりする可能性があります。

--Available--
JobID  WorkerID  JobStart             JobEnd
1      25        '2012-11-17 16:00'  '2012-11-17 17:00'
2      25        '2012-11-18 16:00'  '2012-11-18 16:50'
3      25        '2012-11-19 18:00'  '2012-11-19 18:30'
4      25        '2012-11-19 17:30'  '2012-11-19 18:10'
5      26        '2012-11-18 16:00'  '2012-11-18 17:10'
6      26        '2012-11-19 16:00'  '2012-11-19 16:50'

クエリの結果を表示したいのは次のとおりです。

WorkerID  TotalTime(in Mins)
25        170
26        120

編集:オーバーラップを無視する必要があることを忘れていました。基本的に、これはこれらの労働者とその仕事を、請負業者ではなく時間給の従業員のように扱うことになっています。たとえば、私が 2 つのジョブ ID を担当し、午後 12 時から午後 12 時 30 分まで両方を開始して終了した場合、従業員としては 30 分間しか支払われませんが、請負業者は 60 分間支払われる可能性があります。仕事ごとに報酬を得る。このクエリのポイントは、従業員に関連付けられているデータベース内のジョブを分析し、その従業員が従業員として扱われているかどうか、特定の時間内に合計で何時間働いたかを調べる必要があることです。

EDIT2: 自分の質問に 7 時間答えさせてくれません。後でそこに移動します。

わかりました、今質問に答えます。基本的に、一時テーブルを使用して、検索しているジョブの最小日時と最大日時の間の各分を作成します。

IF OBJECT_ID('tempdb..#time') IS NOT NULL
BEGIN
drop table #time
END
DECLARE @FromDate AS DATETIME,
     @ToDate AS DATETIME,
     @Current AS DATETIME
SET @FromDate = '2012-11-17 16:00'
SET @ToDate = '2012-11-19 18:30'

create table #time  (cte_start_date datetime)
set @current = @FromDate
while (@current < @ToDate)
begin

insert into #time (cte_start_date)
values (@current)

set @current = DATEADD(n, 1, @current)

end

これで、一時テーブルにすべての分があります。ここで、すべての Job テーブル情報をそれに結合し、必要なものを一度に選択する必要があります。

SELECT J.WorkerID
,COUNT(DISTINCT t.cte_start_date) AS TotalTime
FROM #time AS t
INNER JOIN Job AS J ON t.cte_start_date >= J.JobStart AND t.cte_start_date < J.JobEnd --Thanks ErikE
GROUP BY J.WorkerID --Thanks Martin Parkin

drop table #time

これは非常に単純化された答えであり、誰かが始めるのに適しています。

4

3 に答える 3

1

このクエリも同様に機能します。そのパフォーマンスは非常に優れています (実行計画はそれほど良くないように見えますが、実際の CPU と IO は他の多くのクエリを上回っています)。

Sql Fiddle で動作することを確認してください

WITH Times AS (
   SELECT DISTINCT
      H.WorkerID,
      T.Boundary
   FROM
      dbo.JobHistory H
      CROSS APPLY (VALUES (H.JobStart), (H.JobEnd)) T (Boundary)
), Groups AS (
   SELECT
      WorkerID,
      T.Boundary,
      Grp = Row_Number() OVER (PARTITION BY T.WorkerID ORDER BY T.Boundary) / 2
   FROM
      Times T
      CROSS JOIN (VALUES (1), (1)) X (Dup)
), Boundaries AS (
   SELECT
      G.WorkerID,
      TimeStart = Min(Boundary),
      TimeEnd = Max(Boundary)
   FROM
      Groups G
   GROUP BY
      G.WorkerID,
      G.Grp
   HAVING
      Count(*) = 2
)
SELECT
   B.WorkerID,
   WorkedMinutes = Sum(DateDiff(minute, 0, B.TimeEnd - B.TimeStart))
FROM
   Boundaries B
WHERE
   EXISTS (
      SELECT *
      FROM dbo.JobHistory H
      WHERE
         B.WorkerID = H.WorkerID
         AND B.TimeStart < H.JobEnd
         AND B.TimeEnd > H.JobStart
   )
GROUP BY
   WorkerID
;

上のクラスター化インデックスを使用しWorkerID, JobStart, JobEnd, JobID、上記のサンプル 7 行を使用して、新しいワーカー/ジョブ データのテンプレートをフィドルし、14,336 行のテーブルを生成するのに十分な回数繰り返します。パフォーマンスの結果は次のとおりです。ページに他の有効な/正しい回答を含めました(これまでのところ):

Author  CPU  Elapsed  Reads   Scans
------  ---  -------  ------  -----
  Erik  157    166      122       2
Gordon  375    378    106964  53251

別の (より遅い) サーバーからより徹底的なテストを行い (各クエリを 25 回実行し、各メトリックの最良値と最悪値を除外し、残りの 23 個の値を平均しました)、次の結果を得ました。

Query     CPU   Duration  Reads   Notes
--------  ----  --------  ------  ----------------------------------
Erik 1    215   231       122     query as above
Erik 2    326   379       116     alternate technique with no EXISTS
Gordon 1  578   682       106847  from j
Gordon 2  584   673       106847  from dbo.JobHistory

確実に改善できると思った代替テクニック。6 回の読み取りを節約できましたが、CPU のコストが大幅に増加しました (これは理にかなっています)。各タイムスライスの開始/終了統計を最後まで実行する代わりにEXISTS、元のデータに対してどのスライスを保持するかを再計算するのが最善です。多くのジョブを持つ少数のワーカーのプロファイルが異なると、さまざまなクエリのパフォーマンス統計が変わる可能性があります。

誰かがそれを試してみたい場合は、私のフィドルのCREATE TABLEandINSERTステートメントを使用してから、これを 11 回実行してください。

INSERT dbo.JobHistory
SELECT
   H.JobID + A.MaxJobID,
   H.WorkerID + A.WorkerCount,
   DateAdd(minute, Elapsed + 45, JobStart),
   DateAdd(minute, Elapsed + 45, JobEnd)
FROM
   dbo.JobHistory H
   CROSS JOIN (
      SELECT
         MaxJobID = Max(JobID),
         WorkerCount = Max(WorkerID) - Min(WorkerID) + 1,
         Elapsed = DateDiff(minute, Min(JobStart), Min(JobEnd))
      FROM dbo.JobHistory
   ) A
;

このクエリに対して他に 2 つのソリューションを作成しましたが、パフォーマンスが約 2 倍の最良のソリューションには致命的な欠陥がありました (完全に囲まれた時間範囲を正しく処理していません)。もう1つは非常に高い/悪い統計を持っていました(私は知っていましたが、試してみる必要がありました)。

説明

各行のすべての終点時刻を使用して、各終点時刻を複製し、次に可能な時刻と毎回ペアになるようにグループ化することにより、対象となる可能性のあるすべての時刻範囲の個別のリストを作成します。実際のワーカーの作業時間と一致する場合は常に、これらの範囲の経過分を合計します。

于 2013-03-28T01:45:38.463 に答える
1

次のようなクエリは、探している答えを提供する必要があります。

SELECT  WorkerID,
    SUM(DATEDIFF(minute, JobStart, JobEnd)) AS TotalTime
  FROM  Job
  GROUP BY WorkerID

テストされていないことをお詫びします(ここでテストするSQL Serverはありません)が、うまくいくはずです。

于 2013-03-27T23:29:18.540 に答える
0

これは複雑なクエリです。説明は次のとおりです。

with j as (
     select j.*,
            (select 1
             from jobs j2
             where j2.workerid = j.workerid and
                   j2.starttime < j.endtime and
                   j2.starttime > j.starttime
            ) as HasOverlap
     from jobs j
    )
select workerId,
       sum(datediff(minute, periodStart, PeriodEnd)) as NumMinutes
from (select workerId, min(startTime) as periodStart, max(endTime) as PeriodEnd
      from (select j.*,
                   (select min(starttime)
                    from j j2
                    where j2.workerid = j.workerid and
                          j2.starttime >= j.starttime and
                          j2.HasOverlap is null
                   ) as thegroup
            from j
           ) j
      group by workerId, thegroup
     ) j
group by workerId;

このアプローチを理解する鍵は、「重複」ロジックを理解することです。次の開始時刻が前の終了時刻より前である場合、ある期間が次の期間と重複します。各レコードに重複フラグを割り当てることで、「次の」レコードと重複しているかどうかがわかります。上記のロジックは、これに開始時間を使用しています。特に同じワーカーの 2 つのジョブが同時に開始される可能性がある場合は、JobId を使用することをお勧めします。

オーバーラップ フラグの計算では、相関サブクエリが使用されます (これはj節にありwithます)。

次に、レコードごとに戻って、overlap値が NULL である最初のレコードを見つけます。これにより、特定のオーバーラップ セット内のすべてのレコードのグループ化キーが提供されます。

残りは、最初にworkerId/group レベルで、次にworkerId最終結果を取得するレベルで結果を集計するだけです。

この SQL は実行していないため、構文エラーがある可能性があります。

于 2013-03-27T23:49:06.367 に答える