3

重複を無視して、複数の日付範囲に何分あるかを調べる方法を尋ねる既存の質問があります。

与えられたサンプルデータは次のとおりです(ユーザーIDは特に関係ありません)

--Available--
ID  userID  availStart          availEnd
1   456     '2012-11-19 16:00'  '2012-11-19 17:00'
2   456     '2012-11-19 16:00'  '2012-11-19 16:50'
3   456     '2012-11-19 18:00'  '2012-11-19 18:30'
4   456     '2012-11-19 17:30'  '2012-11-19 18:10'
5   456     '2012-11-19 16:00'  '2012-11-19 17:10'
6   456     '2012-11-19 16:00'  '2012-11-19 16:50'

カーソルを使用して問題を解決できますが、CTEに適応できるはずだと思いますが、その方法がわかりません。

その方法は、各範囲を開始時間で並べることです。次に、合体した範囲と重ならない範囲が見つかるまで、範囲を順番に合体する範囲を作成します。次に、合体した範囲に何分あるかを計算し、これを記憶して、次の範囲を続行し、重複する範囲を再度合体します。重複しない開始点を取得するたびに分を累積します 最後に、累積した分を最後の範囲の長さに追加します

順序のおかげで、範囲が以前の範囲とは異なる場合、開始日がすべて大きくなるため、範囲が以前の範囲と重複することはありません。

Declare
  @UserID int = 456,
  @CurStart datetime, -- our current coalesced range start
  @CurEnd datetime, -- our current coalesced range end
  @AvailStart datetime, -- start or range for our next row of data
  @AvailEnd datetime, -- end of range for our next row of data
  @AccumMinutes int = 0 -- how many minutes so far accumulated by distinct ranges

Declare MinCursor Cursor Fast_Forward For
Select
  AvailStart, AvailEnd
From
  dbo.Available
Where
  UserID = @UserID
Order By
  AvailStart

Open MinCursor

Fetch Next From MinCursor Into @AvailStart, @AvailEnd
Set @CurStart = @AvailStart
Set @CurEnd = @AvailEnd

While @@Fetch_Status = 0
Begin
  If @AvailStart <= @CurEnd -- Ranges Overlap, so coalesce and continue
    Begin
    If @AvailEnd > @CurEnd 
      Set @CurEnd = @AvailEnd
    End
  Else -- Distinct range, coalesce minutes from previous range
  Begin
    Set @AccumMinutes = @AccumMinutes + DateDiff(Minute, @CurStart, @CurEnd)
    Set @CurStart = @AvailStart -- Start coalescing a new range
    Set @CurEnd = @AvailEnd
  End
  Fetch Next From MinCursor Into @AvailStart, @AvailEnd
End

Select @AccumMinutes + DateDiff(Minute, @CurStart, @CurEnd) As TotalMinutes

Close MinCursor
Deallocate MinCursor;

CTE が機能するようになりましたが、これは再帰のばかげたエラーでした。クエリ プランの急増は非常に印象的です。

With OrderedRanges as (
  Select
    Row_Number() Over (Partition By UserID Order By AvailStart) AS RN,
    AvailStart,
    AvailEnd
  From
    dbo.Available
  Where
    UserID = 456
),
AccumulateMinutes (RN, Accum, CurStart, CurEnd) as (
  Select
    RN, 0, AvailStart, AvailEnd
  From
    OrderedRanges
  Where 
    RN = 1
  Union All
  Select
    o.RN, 
    a.Accum + Case When o.AvailStart <= a.CurEnd Then
        0
      Else 
        DateDiff(Minute, a.CurStart, a.CurEnd)
      End,
    Case When o.AvailStart <= a.CurEnd Then 
        a.CurStart
      Else
        o.AvailStart
      End,
    Case When o.AvailStart <= a.CurEnd Then
        Case When a.CurEnd > o.AvailEnd Then a.CurEnd Else o.AvailEnd End
      Else
        o.AvailEnd
      End
  From
    AccumulateMinutes a
        Inner Join 
    OrderedRanges o On 
        a.RN = o.RN - 1
)

Select Max(Accum + datediff(Minute, CurStart, CurEnd)) From AccumulateMinutes 

これは CTE に適応できますか? また、この方法でリストを累積するための一般的なパターンはありますか?

http://sqlfiddle.com/#!6/ac021/2

4

2 に答える 2

6

次のクエリは、定義に従って、データ内の期間を検索します。最初に相関サブクエリを使用して、レコードが期間の開始であるかどうか (つまり、以前の期間と重複していないかどうか) を判断します。次に、重複しない期間の開始点である最新の開始点として「periodStart」を割り当てます。

次の (テストされていない) クエリは、このアプローチを採用しています。

with TimeWithOverlap as (
     select t.*,
            (case when exists (select * from dbo.Available tbefore where t.availStart > tbefore.availStart and tbefore.availEnd >= t.availStart)
                  then 0
                  else 1
             end) as IsPeriodStart
     from dbo.Available t 
    ),
    TimeWithPeriodStart as (
     select two.*,
            (select MAX(two1.AvailStart) from TimeWithOverlap two1 where IsPeriodStart = 1 and two1.AvailStart <= two.AvailStart
            ) as periodStart
     from TimeWithOverlap two
    )
select periodStart, MAX(AvailEnd) as periodEnd
from TimeWithPeriodStart twps
group by periodStart;

http://sqlfiddle.com/#!6/3483c/20 (2 番目のクエリ)

2 つの期間が同時に開始された場合でも、AvailStart の値が同じであるため、機能します。サブクエリが相関しているため、これは中規模のデータ セットでもうまく機能しない可能性があります。

これにアプローチする他の方法があります。たとえば、SQL Server 2012 を使用している場合は、より単純な方法を提供する累積合計関数を使用できます。

于 2012-11-20T22:54:52.830 に答える
0

私は、日付と時刻 (分単位で正確) を 1 つの列 (PK) に、秒を少しだけ持つダム テーブルを作成することで、(ある意味では) 非常に効率的に解決しました。「1」はユーザーが利用可能であることを意味し、0 は利用できないことを意味します。

残りは非常に単純です。部分的に重複する時間範囲で分を取得しようとする際に、無限の複雑なクエリを作成する必要があることにうんざりしていました。

実際、これはマシンの効率を計算するためのものでした。

これが本当の取引ではないことはわかっていますが、私が思いついた最も簡単な解決策です。そのテーブルを作成する関数/SPを作成する場合があります..

于 2012-11-20T22:37:24.977 に答える