5

T-SQL、SQL Server 2008 以降

与えられたサンプルテーブル

 StatusSetDateTime   | UserID | Status    | StatusEndDateTime   | StatusDuration(in seconds)
============================================================================
 2012-01-01 12:00:00 | myID   | Available | 2012-01-01 13:00:00 | 3600

たとえば、15 分間隔を使用するビューに分割する必要があります。

IntervalStart       | UserID | Status | Duration

===========================================

2012-01-01 12:00:00 | myID | Available | 900 

2012-01-01 12:15:00 | myID | Available | 900

2012-01-01 12:30:00 | myID | Available | 900 

2012-01-01 12:45:00 | myID | Available | 900 

2012-01-01 13:00:00 | myID | Available | 0

etc....

今、私は周りを検索して、故障するいくつかのクエリを見つけることができました.MySqlに似たものを見つけまし:

そして、T-SQLの何かがここにあります

しかし、2 番目の例では、結果を合計していますが、ユーザーごとのステータスごとに合計期間を間隔時間 (900 秒) で割る必要があります。

2 番目のリンクの例を適応させて、すべてを間隔に分割することができましたが、合計継続時間が返され、間隔の継続時間を分割する方法がわかりません (それでも元の合計継続時間になります)。

洞察を事前にありがとう!

編集:最初の試み

 ;with cte as 
    (select MIN(StatusDateTime) as MinDate
          , MAX(StatusDateTime) as MaxDate
          , convert(varchar(14),StatusDateTime, 120) as StartDate
          , DATEPART(minute, StatusDateTime) /15 as GroupID
          , UserID
          , StatusKey
          , avg(StateDuration) as AvgAmount
     from AgentActivityLog
     group by convert(varchar(14),StatusDateTime, 120)
         , DATEPART(minute, StatusDateTime) /15
         , Userid,StatusKey)

  select dateadd(minute, 15*GroupID, CONVERT(datetime,StartDate+'00'))
         as [Start Date]
       , UserID, StatusKey, AvgAmount as [Average Amount]
  from cte

編集:2回目の試み

;With cte As
   (Select DateAdd(minute
                   , 15 * (DateDiff(minute, '20000101', StatusDateTime) / 15)
                   , '20000101') As StatusDateTime
         , userid, statuskey, StateDuration
    From AgentActivityLog)

 Select StatusDateTime, userid,statuskey,Avg(StateDuration)
 From cte
 Group By StatusDateTime,userid,statuskey;
4

5 に答える 5

4
;with cte_max as 
(
   select dateadd(mi, -15, max(StatusEndDateTime)) as EndTime, min(StatusSetDateTime) as StartTime
   from AgentActivityLog
), times as
(
    select StartTime as Time from cte_max
    union all
    select dateadd(mi, 15, c.Time)
    from times as c
        cross join cte_max as cm
    where c.Time <= cm.EndTime
)
select
    t.Time, A.UserID, A.Status,
    case
        when t.Time = A.StatusEndDateTime then 0
        else A.StatusDuration / (count(*) over (partition by A.StatusSetDateTime, A.UserID, A.Status) - 1)
    end as Duration
from AgentActivityLog as A
    left outer join times as t on t.Time >= A.StatusSetDateTime and t.Time <= A.StatusEndDateTime

sql fiddle demo

于 2012-11-30T17:37:45.987 に答える
3

日付計算を使用して物事をパーティションに分割することに慣れていません。陥る落とし穴には、あらゆる種類のものがあるようです。

私が好むのは、日付パーティション範囲ごとに 1 つの行があるテーブル (定義済みのテーブル値関数、テーブル変数) を作成することです。テーブル値関数のアプローチは、必要に応じて任意の範囲とパーティション サイズに対して構築できるため、特に便利です。次に、このテーブルに参加して物事を分割できます。

paritionid starttime     endtime
---------- ------------- -------------
1          8/1/2012 5:00 8/1/2012 5:15
2          8/1/2012 5:15 8/1/2012 5:30
...

このメソッドのパフォーマンスについて話すことはできませんが、クエリははるかに直感的です。

于 2012-11-30T17:20:55.197 に答える
1

15 分ごとのタイムスタンプを持つヘルパー テーブルがあり、それを BETWEEN 経由でベース テーブルに結合する場合は比較的簡単です。ヘルパー テーブルをオンザフライで作成することも、データベースに永続的に保持することもできます。あなたの会社の次の人が理解するのも簡単です:

// declare a table and a timestamp variable
declare @timetbl table(t datetime)
declare @t datetime

// set the first timestamp
set @t = '2012-01-01 00:00:00'

// set the last timestamp, can easily be extended to cover many years
while @t <= '2013-01-01'
begin
    // populate the table with a new row, every 15 minutes
    insert into @timetbl values (@t)
    set @t = dateadd(mi, 15, @t)
end


// now the Select query:
select 
   tt.t, aal.UserID, aal.Status,
   case when aal.StatusEndDateTime <= tt.t then 0 else 900 end as Duration
   // using a shortcut for Duration, based on your comment that Start/End are always on the quarter-hour, and thus always 900 seconds or zero

from 
   @timetbl tt 
      INNER JOIN AgentActivityLog aal 
         on tt.t between aal.StatusSetDateTime and aal.StatusEndDateTime

order by
  aal.UserID, tt.t
于 2012-11-30T19:04:08.767 に答える
0

StatusEndDateTimeがIntervalStartよりも大きい間、期間を追加し続ける再帰共通テーブル式を使用できます。

;with cte as (
    select StatusSetDateTime as IntervalStart
        ,UserID
        ,Status
        ,StatusDuration/(datediff(mi, StatusSetDateTime, StatusEndDateTime)/15) as Duration
        , StatusEndDateTime
    From AgentActivityLog
    Union all
    Select DATEADD(ss, Duration, IntervalStart) as IntervalStart
        , UserID
        , Status
        , case when DATEADD(ss, Duration, IntervalStart) = StatusEndDateTime then 0 else Duration end as Duration
        , StatusEndDateTime
    From cte
    Where IntervalStart < StatusEndDateTime
)

select IntervalStart, UserID, Status, Duration from cte
于 2012-11-30T17:28:21.867 に答える
0

これは、ヘルパーテーブルを必要とせずにあなたのために仕事をするクエリです。(私はヘルパーテーブルに対して何も持っていません。それらは便利であり、使用します。時々使用しないことも可能です。)このクエリを使用すると、:00で終わる分全体でなくても、いつでもアクティビティを開始および終了できます。 :15、:30、:45。ミリ秒の部分がある場合は、いくつかの実験を行う必要があります。これは、モデルに従って、2番目の解像度にしか移動しなかったためです。

ハード最大期間がわかっている場合は、@ MaxDurationを削除し、その値に数分で置き換えます。N <= @MaxDurationクエリがうまく機能するために重要です。

DECLARE @MaxDuration int;
SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);

WITH
L0 AS(SELECT 1 c UNION ALL SELECT 1),
L1 AS(SELECT 1 c FROM L0, L0 B),
L2 AS(SELECT 1 c FROM L1, L1 B),
L3 AS(SELECT 1 c FROM L2, L2 B),
L4 AS(SELECT 1 c FROM L3, L3 B),
L5 AS(SELECT 1 c FROM L4, L4 B),
Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
SELECT
   S.IntervalStart,
   Duration = DateDiff(second, S.IntervalStart, E.IntervalEnd)
FROM
   #AgentActivityLog L
   CROSS APPLY (
      SELECT N, Offset = (N.N - 1) * 900
      FROM Nums N
      WHERE N <= @MaxDuration
   ) N
   CROSS APPLY (
      SELECT Edge =
         DateAdd(second, N.Offset, DateAdd(minute,
            DateDiff(minute, '20000101', L.StatusSetDateTime)
            / 15 * 15, '20000101')
         )
   ) G
   CROSS APPLY (
      SELECT IntervalStart = Max(T.BeginTime)
      FROM (
         SELECT L.StatusSetDateTime
         UNION ALL SELECT G.Edge
      ) T (BeginTime)
   ) S
   CROSS APPLY (
      SELECT IntervalEnd = Min(T.EndTime)
      FROM (
         SELECT L.StatusEndDateTime
         UNION ALL SELECT G.Edge + '00:15:00'
      ) T (EndTime)
   ) E
WHERE
   N.Offset <= L.StatusDuration
ORDER BY
   L.StatusSetDateTime,
   S.IntervalStart;

試してみたい場合のセットアップスクリプトは次のとおりです。

CREATE TABLE #AgentActivityLog (
    StatusSetDateTime datetime,
    StatusEndDateTime datetime,
    StatusDuration AS (DateDiff(second, 0, StatusEndDateTime - StatusSetDateTime))
);

INSERT #AgentActivityLog -- weird end times
SELECT '20120101 12:00:00', '20120101 13:00:00'
UNION ALL SELECT '20120101 13:00:00', '20120101 13:27:56'
UNION ALL SELECT '20120101 13:27:56', '20120101 13:28:52'
UNION ALL SELECT '20120101 13:28:52', '20120120 11:00:00'

INSERT #AgentActivityLog -- 15-minute quantized end times
SELECT '20120101 12:00:00', '20120101 13:00:00'
UNION ALL SELECT '20120101 13:00:00', '20120101 13:30:00'
UNION ALL SELECT '20120101 13:30:00', '20120101 14:00:00'
UNION ALL SELECT '20120101 14:00:00', '20120120 11:00:00'

また、これは、1分が:00、:15、:30、または:45で終わる時間のみを想定しているバージョンです。

DECLARE @MaxDuration int;
SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);

WITH
L0 AS(SELECT 1 c UNION ALL SELECT 1),
L1 AS(SELECT 1 c FROM L0, L0 B),
L2 AS(SELECT 1 c FROM L1, L1 B),
L3 AS(SELECT 1 c FROM L2, L2 B),
L4 AS(SELECT 1 c FROM L3, L3 B),
L5 AS(SELECT 1 c FROM L4, L4 B),
Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
SELECT
   S.IntervalStart,
   Duration = CASE WHEN Offset = StatusDuration THEN 0 ELSE 900 END
FROM
   #AgentActivityLog L
   CROSS APPLY (
      SELECT N, Offset = (N.N - 1) * 900
      FROM Nums N
      WHERE N <= @MaxDuration
   ) N
   CROSS APPLY (
      SELECT IntervalStart = DateAdd(second, N.Offset, L.StatusSetDateTime)
   ) S
WHERE
   N.Offset <= L.StatusDuration   
ORDER BY
   L.StatusSetDateTime,
   S.IntervalStart;

IntervalStartの値が重複しているため、IntervalStartで並べ替えることができないため、最後の0Duration行が正しくないように見えます。合計に0を追加する行を持つことの利点は何ですか?

于 2012-11-30T20:59:03.047 に答える