9

現在、私はこのようにテーブルを構築しています

DeviceID      Timestamp            Value
----------------------------------------
Device1       1.1.2011 10:00:00    3
Device1       1.1.2011 10:00:01    4
Device1       1.1.2011 10:00:02    4
Device1       1.1.2011 10:00:04    3
Device1       1.1.2011 10:00:05    4
Device1       1.1.2011 14:23:14    8
Device1       1.1.2011 14:23:15    7
Device1       1.1.2011 14:23:17    4
Device1       1.1.2011 14:23:18    2

ご覧のとおり、特定のタイムスタンプを持つデバイスからいくつかの値が入力されています(列の種類は日時です)。

問題は、デバイスはいつでも開始および停止でき、データ内に開始または停止が発生したという直接的な情報がないことです。ただし、2つの行のタイムスタンプが5秒以内の場合は常に同じ測定値に属するため、指定されたタイムスタンプのリストから、開始と停止がいつ発生したかを簡単に判断できます。

今、私はこのデータから次のようなリストを取得したいと思います:

DeviceID      Started              Ended
Device1       1.1.2011 10:00:00    1.1.2011 10:00:05
Device1       1.1.2011 14:23:14    1.1.2011 14:23:18

では、これをすばやく行う方法はありますか?私が考えることができるのは、ある種のカーソルを使用して、各日時のペアを手動で比較することだけです。しかし、各行の各値を検査する必要があるため、これは非常に遅くなると思います。

では、カーソルで機能しないより良いSQLソリューションはありますか?

アップデート

現在、私は与えられたすべての答えをテストしました。そして、読むことによって、それらはすべて見栄えがよく、いくつかの興味深いアプローチがありました。残念ながら、それらすべて(これまでのところ)は実際のデータで失敗しました。最大の問題はデータの量にあるようです(現在、それらはテーブル内の約350万のエントリです)。小さなサブセットに対してのみ特定のクエリを実行すると、期待される結果が得られますが、テーブル全体にクエリをロールすると、パフォーマンスが非常に悪くなります。

データをチャンク化して、データの一部のみをこれらの指定されたアルゴリズムの1つに渡して、この処理を実行できるかどうかをさらにテストして調べる必要があります。しかし、おそらくあなたの1人は、結果をもう少し速く取得するための別の賢いアイデアを持っています。

更新(構造に関する詳細情報)

さて、これらの情報も役立つかもしれません:現在、テーブルには約350万のエントリがあります。そして、ここに与えられた列タイプと表示があります:

  • _ID
    • int
    • 主キー
    • グループ化されたインデックス
    • 私の例ではこの列について言及していません。このクエリには必要ないためです。
  • デバイスID
    • int
    • nullではない
    • 索引
  • タイムスタンプ
    • 日付時刻
    • nullではない
    • 索引
  • 価値
    • 異なるタイプ(int、real、tinyint)のいくつかのインデックス付けされていない列
    • すべてnullにすることができます

たぶん、これは与えられた問題に対するあなたのすでに(または新しい)解決策を改善するのに役立ちます。

4

7 に答える 7

2
-- Table var to store the gaps
declare @T table
(
  DeviceID varchar(10),
  PrevPeriodEnd datetime,
  NextPeriodStart datetime
)

-- Get the gaps
;with cte as 
(
  select *,
    row_number() over(partition by DeviceID order by Timestamp) as rn
  from data
)
insert into @T
select
  C1.DeviceID,
  C1.Timestamp as PrevPeriodEnd,
  C2.Timestamp as NextPeriodStart
from cte as C1
  inner join cte as C2
    on C1.rn = C2.rn-1 and
       C1.DeviceID = C2.DeviceID and
       datediff(s, C1.Timestamp, C2.Timestamp) > 5

-- Build islands from gaps in @T
;with cte1 as
(
  -- Add first and last timestamp to gaps
  select DeviceID, PrevPeriodEnd, NextPeriodStart
  from @T
  union all
  select DeviceID, max(TimeStamp) as PrevPeriodEnd, null as NextPeriodStart
  from data
  group by DeviceID
  union all
  select DeviceID, null as PrevPeriodEnd, min(TimeStamp) as PrevPeriodEnd
  from data
  group by DeviceID
),
cte2 as
(
  select *,
    row_number() over(partition by DeviceID order by PrevPeriodEnd) as rn
  from cte1
)
select
  C1.DeviceID,
  C1.NextPeriodStart as PeriodStart,
  C2.PrevPeriodEnd as PeriodEnd
from cte2 as C1
  inner join cte2 as C2
    on C1.DeviceID = C2.DeviceID and
       C1.rn = C2.rn-1
order by C1.DeviceID, C1.NextPeriodStart       
于 2011-05-18T10:24:06.330 に答える
0

これを試して:

select DeviceID,MIN(Timestamp),MAX(Timestamp) 
          from @table group by DATEPART(hh,Timestamp),DeviceID
于 2011-05-16T14:42:57.377 に答える
0

以下の解決策の基本的な考え方は、この回答から借用したものです。

WITH data (DeviceID, Timestamp, Value) AS (
  SELECT 'Device1', CAST('1.1.2011 10:00:00' AS datetime), 3 UNION ALL
  SELECT 'Device1',      '1.1.2011 10:00:01',              4 UNION ALL
  SELECT 'Device1',      '1.1.2011 10:00:02',              4 UNION ALL
  SELECT 'Device1',      '1.1.2011 10:00:04',              3 UNION ALL
  SELECT 'Device1',      '1.1.2011 10:00:05',              4 UNION ALL
  SELECT 'Device1',      '1.1.2011 14:23:14',              8 UNION ALL
  SELECT 'Device1',      '1.1.2011 14:23:15',              7 UNION ALL
  SELECT 'Device1',      '1.1.2011 14:23:17',              4 UNION ALL
  SELECT 'Device1',      '1.1.2011 14:23:18',              2
),
ranked AS (
  SELECT
    *,
    rn = ROW_NUMBER() OVER (PARTITION BY DeviceID ORDER BY Timestamp)
  FROM data
),
starts AS (
  SELECT
    r1.DeviceID,
    r1.Timestamp,
    rank = ROW_NUMBER() OVER (PARTITION BY r1.DeviceID ORDER BY r1.Timestamp)
  FROM ranked r1
    LEFT JOIN ranked r2 ON r1.DeviceID = r2.DeviceID
      AND r1.rn = r2.rn + 1
      AND r1.Timestamp <= DATEADD(second, 5, r2.Timestamp)
  WHERE r2.DeviceID IS NULL
),
ends AS (
  SELECT
    r1.DeviceID,
    r1.Timestamp,
    rank = ROW_NUMBER() OVER (PARTITION BY r1.DeviceID ORDER BY r1.Timestamp)
  FROM ranked r1
    LEFT JOIN ranked r2 ON r1.DeviceID = r2.DeviceID
      AND r1.rn = r2.rn - 1
      AND r1.Timestamp >= DATEADD(second, -5, r2.Timestamp)
  WHERE r2.DeviceID IS NULL
)
SELECT
  s.DeviceID,
  Started = s.Timestamp,
  Ended = e.Timestamp
FROM starts s
  INNER JOIN ends e ON s.DeviceID = e.DeviceID AND s.rank = e.rank
于 2011-05-17T15:01:56.020 に答える
0
DECLARE @t TABLE
(DeviceID      VARCHAR(10),
 [Timestamp]    DATETIME,
 VALUE          INT
)

INSERT @t
SELECT 'Device1','20110101 10:00:00',    3
UNION SELECT 'Device1','20110101 10:00:01',    4
UNION SELECT 'Device1','20110101 10:00:02',    4
UNION SELECT 'Device1','20110101 10:00:04',   3
UNION SELECT 'Device1','20110101 10:00:05',    4
UNION SELECT 'Device1','20110101 14:23:14',    8
UNION SELECT 'Device1','20110101 14:23:15',    7
UNION SELECT 'Device1','20110101 14:23:17',    4
UNION SELECT 'Device1','20110101 14:23:18',    2


;WITH myCTE
AS
(
    SELECT DeviceID, [Timestamp],
           ROW_NUMBER() OVER (PARTITION BY DeviceID
                              ORDER BY [TIMESTAMP]
                             ) AS rn
    FROM @t
)
, recCTE
AS
(
    SELECT DeviceID, [Timestamp],  0 as groupID, rn FROM myCTE
    WHERE rn = 1

    UNION ALL

    SELECT r.DeviceID, g.[Timestamp],  CASE WHEN DATEDIFF(ss,r.[Timestamp], g.[Timestamp]) <= 5 THEN r.groupID ELSE r.groupID + 1 END, g.rn 
    FROM recCTE AS r
    JOIN myCTE AS g
    ON g.rn = r.rn + 1
)
SELECT DeviceID, MIN([Timestamp]) AS [started], MAX([Timestamp]) AS ended
FROM recCTE
GROUP BY DeviceId, groupId
OPTION (MAXRECURSION 0);
于 2011-05-16T14:51:42.057 に答える
0

私はいくつかのデータ型と名前を試してみました(私ができるという理由だけで、タイムスタンプは予約語であるため)、サンプルデータを使用して要求された結果を取得できます。

サンプルデータ:

create table Measures (
    DeviceID int not null,
    Occurred datetime not null,
    Value int not null,
    constraint PK_Measures PRIMARY KEY (DeviceID,Occurred)
)
go
insert into Measures (DeviceID,Occurred,Value)
select 1,'2011-01-01T10:00:00',3 union all
select 1,'2011-01-01T10:00:01',4 union all
select 1,'2011-01-01T10:00:02',4 union all
select 1,'2011-01-01T10:00:04',3 union all
select 1,'2011-01-01T10:00:05',4 union all
select 1,'2011-01-01T14:23:14',8 union all
select 1,'2011-01-01T14:23:15',7 union all
select 1,'2011-01-01T14:23:17',4 union all
select 1,'2011-01-01T14:23:18',2

そして今、クエリ:

;with StartPeriods as (
    select m1.DeviceID,m1.Occurred as Started
    from Measures m1 left join Measures m2 on m1.DeviceID = m2.DeviceID and m2.Occurred < m1.Occurred and DATEDIFF(second,m2.Occurred,m1.Occurred) < 6
    where m2.DeviceID is null
), ExtendPeriods as (
    select DeviceID,Started,Started as Ended from StartPeriods
    union all
    select
        ep.DeviceID,ep.Started,m2.Occurred
    from
        ExtendPeriods ep
            inner join
        Measures m2
            on
                ep.DeviceID = m2.DeviceID and
                ep.Ended < m2.Occurred and
                DATEDIFF(SECOND,ep.Ended,m2.Occurred) < 6
)
select DeviceID,Started,MAX(Ended) from ExtendPeriods group by DeviceID,Started

Common Table Expression(CTE)はStartPeriods、メジャーテーブルから5秒以内に前の行がない行を検索します。次に、ExtendPeriodsCTEは、検出された期間の現在の終了から最大5秒後に発生するメジャーから新しい行を検索することにより、これらの期間を再帰的に延長します。

次に、期間の終わりが開始から可能な限り離れている行を見つけます。

于 2011-05-16T14:46:31.823 に答える
0

これにはウィンドウ関数を使用できるはずです(15分で以下の新しいセッションが定義されると仮定します)。

SELECT DeviceId,
       Timestamp,
       COALESCE((Timestamp - lag(Timestamp) OVER w) > interval '15 min', TRUE)
       as session_begins
       COALESCE((lead(Timestamp) OVER w - Timestamp) > interval '15 min', TRUE)
       as session_ends
FROM YourTable
WINDOW w AS (PARTITION BY DeviceId ORDER BY Timestamp);

where句によっては、フェッチされた最初/最後の行が無効になる可能性があるため、colesce/true部分を削除することをお勧めします。

境界のみが必要な場合は、サブクエリとで上記を使用できますgroup by DeviceId, session_begins, session_ends having session_begins or session_ends。また、これを行う場合は、メインのクエリではなく、サブクエリにwhere句を配置することを忘れないでください。そうしないと、ウィンドウ集約のためにテーブル全体でseqスキャンを実行することになります。

于 2011-05-16T15:02:36.817 に答える
0

これを試してみてください。ただし、大量のデータでどれだけうまく機能するかはわかりません。

SELECT a.TS AS [StartTime], (SELECT TOP 1 c.TS FROM TestTime c WHERE c.TS >= a.TS AND
    NOT EXISTS(SELECT * FROM TestTime d WHERE d.TS > c.TS AND DATEDIFF(SECOND, c.TS, d.TS) <= 5) ORDER BY c.TS) AS [StopTime]
FROM TestTime a WHERE NOT EXISTS (SELECT * FROM TestTime b WHERE a.TS > b.TS AND DATEDIFF(SECOND, b.TS, a.TS) <= 5)

私のテーブルはTestTimeと呼ばれ、列はTSと呼ばれるので、テーブルに合わせて調整します。NOT EXISTSを使用して、タイムスタンプ<現在のレコードとその5秒以内をチェックしました-見つからない場合は表示します。つまり、開始時刻(またはテーブルの最初のレコードで、最小のタイムスタンプを検索します)これは、そのタイムスタンプ(単一のエントリの場合は開始/停止)であり、5秒以内にそれより大きいレコードをチェックするためにNOT EXISTSを再び使用する、見つかったレコードよりも大きいです。 、繰り返しますが、レコードが見つからない場合は表示します(1番目のみ)。おそらくこれを微調整して改善することができますが、それは良い基礎になる可能性があります。

まだ実行中の場合は、最後に見つかった時刻が最後の開始イベントの停止時刻としてリストされることに注意してください。

簡単にするために、ここにはデバイス名を入れていません。そのため、StopTime句とWHERE句にデバイス名を入れる必要があります。

于 2011-05-17T15:14:15.343 に答える