SQL Server 2012:使用动态SQL获取出现次数并计算持续时间

时间:2017-10-18 03:39:27

标签: sql-server-2012 count dynamic-sql gaps-and-islands

我有一个过程设定值和过程值(PV)表。

如下表所示:

    Timestamp       Setpoint             PV 
    ---------       ---------       -------------
    t1                  100             125
    t2                  100              95  *
    t3                  100              98
    t4                  100              88
    t5                  100             105 
    t6                  100              59  *
    t7                  100              90
    t8                  100             101
    t9                  100              70  *
    t10                 100             101

我想要做的是创建一行作为结果的查询,以计算PV低于设定点并返回到设定点之上的次数。并计算它低于设定值的持续时间。

结果应该是这样的。

    NumberofOccurance       Duration
    ------------------      ---------
            3               (t5-t2)+(t8-t6)+(t10-t9)

注意:正确的出现次数为 3 ,在这种情况下 6 而且我认为最困难的部分是持续时间。

有什么想法吗?

提前致谢

编辑:我在下面得到了几个好的答案,但是,如果我有一个以上的PV怎么样(我实际上在不同的列中有10个不同的PV,在每个PV的两个独立列中有两个设定点,即20列对于设定点。 都在一张桌子上。时间戳以1秒为间隔。 知道如何为每一个PV做同样的查询吗? 我正在考虑将动态SQL与Cursor和CTE选项结合在一起。但这真的很难。

3 个答案:

答案 0 :(得分:3)

您可以使用lag选择之前的pv和设定值,并仅选择当前pv低于设定值但前一个pv不低于之前设定值的行:

select *
from (
    select *,
        lag(Setpoint) over (order by Timestamp) previous_setpoint,
        lag(PV) over (order by Timestamp) previous_pv
    from Table1
) t where PV < Setpoint
and previous_pv >= previous_setpoint

要获得持续时间和上面查询中pv的负值,即-t2 -t6 -t9。棘手的部分是积极的价值观。对于第一行添加任何内容。对于除第一个和最后一个之外的所有行,仅添加previous_pv。对于最后一行,添加next_pvprevious_pv

第1,2,3行中的

Timestamp对应于t2 t6 t9。 第2行中的prev_timestamp对应于t5 第3行(最后一行)中的prev_timestamp对应于t8 第3行(最后一行)中的next_timestamp对应于t10

select count(*), sum(case 
            when rn_asc = 1 then -Timestamp
            when rn_desc = 1 then -Timestamp + next_timestamp + prev_timestamp
            else -Timestamp + prev_timestamp
            end)
from (
    select *, 
     row_number() over (order by Timestamp) rn_asc,
     row_number() over (order by Timestamp desc) rn_desc
    from (
        select *,
            lag(Setpoint) over (order by Timestamp) previous_setpoint,
            lag(PV) over (order by Timestamp) previous_pv,
            lag(Timestamp) over (order by Timestamp) prev_timestamp,
            lead(Timestamp) over (order by Timestamp) next_timestamp
        from Table1
    ) t where PV < Setpoint
    and previous_pv >= previous_setpoint
) t

这是一个演示http://sqlfiddle.com/#!6/b6ac5/5

答案 1 :(得分:2)

漫长而丑陋但它有效

CREATE TABLE MyTable
  (
     TimeSt   INT,
     SetPoint INT,
     PV       INT
  )

INSERT INTO MyTable
VALUES      (1,             100,             122),
            (2,             100,             95),
            (3,             100,             98),
            (4,             100,             88),
            (5,             100,             105),
            (6,             100,             59),
            (7,             100,             90),
            (8,             100,             101),
            (9,             100,             70),
            (10,             100,             101);

WITH CTE
     AS (SELECT
           *
           ,lag(TimeSt, 1) OVER(ORDER BY TimeSt) AS LagTimeSt
           ,lag(SetPoint, 1) OVER(ORDER BY TimeSt) AS LagSetPoint
           ,lag(PV, 1) OVER(ORDER BY TimeSt) AS LagPV
         FROM
           MyTable),
     CTE2
     AS (SELECT
           *
           ,CASE
              WHEN (PV < SetPoint AND LagPV > LagSetPoint) 
                THEN 1 ELSE 0 END AS FirstDrop
         FROM
           CTE
         WHERE
          (PV < SetPoint AND LagPV > LagSetPoint)
           OR (PV > SetPoint AND LagPV < LagSetPoint)),
     CTE3
     AS (SELECT
           Lead(timest) OVER(ORDER BY TimeSt) UpTime
           ,*
         FROM
           CTE2)
SELECT
  sum(firstDrop)        AS Occur
  ,sum(uptime - Timest) AS DownTime
FROM
  CTE3 
Where FirstDrop = 1

答案 2 :(得分:1)

这是一个典型的岛屿问题。解决这个问题的步骤,

  1. 查找范围开始和范围结束
  2. 将范围开始和范围结束合并为一行
  3. 按范围开始分组
  4. 这是代码

    WITH T0 AS
    (
        SELECT [Timestamp], CASE WHEN PV >= SetPoint THEN 1 ELSE 0 END AS pvType
        FROM table
    ),
    T1 AS
    (
        SELECT [Timestamp], 
            CASE 
                WHEN LAG(pvType) OVER(ORDER BY [Timestamp]) = 1 AND pvType = 0 THEN 1 
                ELSE 0 
            END AS pvStart,
            CASE 
                WHEN LAG(pvType) OVER(ORDER BY [Timestamp]) = 0 AND pvType = 1 THEN 1 
                ELSE 0 
            END AS pvEnd
        FROM T0
    ),
    T2 AS 
    (
        SELECT [Timestamp] AS timestampStart, 
            CASE
                WHEN pvEnd = 1 THEN [Timestamp] 
                ELSE LEAD([Timestamp]) OVER(ORDER BY [Timestamp]) 
            END AS timestampEnd, 
            pvStart
        FROM T1
        WHERE pvStart = 1 OR pvEnd = 1
    )
    SELECT 
        COUNT(*) AS Occurance, 
        -- Depending Timestamp type, you may want DATEDIFF
        SUM(timestampEnd - timestampStart) AS Duration
    FROM T2
    WHERE pvStart = 1