Get list with start and end values from table of datetimes

后端 未结 7 1464
难免孤独
难免孤独 2020-12-17 23:01

Currently i have a table built up like this way

DeviceID      Timestamp            Value
----------------------------------------
Device1       1.1.2011 10:0         


        
相关标签:
7条回答
  • 2020-12-17 23:13
    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);
    
    0 讨论(0)
  • 2020-12-17 23:13

    You should be able to use window functions for this (assuming 15 minutes defines a new session below):

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

    Depending on your where clause, you might want to remove the coalesce/true part since the first/last row fetched may become invalid.

    If you need only the boundaries, you could use the above in a subquery and group by DeviceId, session_begins, session_ends having session_begins or session_ends. Also, if you do this, don't forget to put the where clause in the subquery, rather than the main one, else you'll end up doing a seq scan on the whole table because of the window aggregate.

    0 讨论(0)
  • 2020-12-17 23:16

    Try this, though I'm not sure how well it will perform with lots of data

    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)
    

    my table is called TestTime and the column is called TS so tweak it for your table. I've used the NOT EXISTS to check for a timestamp < the current record and within 5 seconds of it - so display if not found, i.e. a start time (or the first record in the table and then it will look for the lowest timestamp that is greater than any records found that is >= that timestamp (in case it's a single entry, so a start/stop one) and that again uses NOT EXISTS to check for a record that is greater than it and within 5 seconds - so, again, display if a record isn't found (only the 1st). You can probably tweak and improve this, but it might be a good basis.

    Note that if it is still running it will list the last time found as the stop time for the last start event.

    I haven't put a device name in here, for simplicity, so you would need to put that in the StopTime and WHERE clauses

    0 讨论(0)
  • 2020-12-17 23:23

    Try this:

    select DeviceID,MIN(Timestamp),MAX(Timestamp) 
              from @table group by DATEPART(hh,Timestamp),DeviceID
    
    0 讨论(0)
  • 2020-12-17 23:29

    The basic idea for the below solution has been borrowed from this answer.

    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
    
    0 讨论(0)
  • 2020-12-17 23:30
    -- 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       
    
    0 讨论(0)
提交回复
热议问题