合成范围内的SQL行

时间:2016-05-22 18:06:53

标签: sql sql-server

使用SQL服务器,我有一个类似于以下内容的表:

id | time                | measurement
---+---------------------+-------------
1  | 2014-01-01T05:00:00 | 1.0
1  | 2014-01-01T06:45:00 | 2.0
1  | 2014-01-01T09:30:00 | 3.0
1  | 2014-01-01T11:00:00 | NULL
1  | 2014-02-05T03:00:00 | 1.0
1  | 2014-02-05T05:00:00 | NULL

在为同一个id提供新值之前,假定存储的测量值是准确的;给定id的最后一次测量是序列的结束。

我有兴趣创建一个查询或视图,如果它们不存在(前一个点既不是0也不是NULL),则会在这些跨度定义的每个小时内合成新数据点,因此:< / p>

id | time                | measurement
---+---------------------+-------------
1  | 2014-01-01T05:00:00 | 1.0
1  | 2014-01-01T06:00:00 | 1.0
1  | 2014-01-01T06:45:00 | 2.0
1  | 2014-01-01T07:00:00 | 2.0
1  | 2014-01-01T08:00:00 | 2.0
1  | 2014-01-01T09:00:00 | 2.0
1  | 2014-01-01T09:30:00 | 3.0
1  | 2014-01-01T10:00:00 | 3.0
1  | 2014-02-05T03:00:00 | 1.0
1  | 2014-02-05T04:00:00 | 1.0

这可行吗?

如果每个输入行都有一个&#34;持续时间&#34;是否更可行,指定其测量有效的时间量? (在这种情况下,我们将在SQL中有效地解包运行长度编码)。 [我的目标是SQL Server 2012,它具有LEAD和LAG功能,允许轻松构建]。

以SQL Server可以使用的格式提供该数据:

select id, cast(stime as datetime) as [time], measurement 
from 
(values
    (1, '2014-01-01T05:00:00', 1.0), 
    (1, '2014-01-01T05:00:00', 1.0), 
    (1, '2014-01-01T06:45:00', 2.0), 
    (1, '2014-01-01T09:30:00', 3.0), 
    (1, '2014-01-01T11:00:00', NULL), 
    (1, '2014-02-05T03:00:00', 1.0), 
    (1, '2014-02-05T05:00:00', NULL)
) t(id, stime, measurement) 

3 个答案:

答案 0 :(得分:4)

它复杂但有效(对于你提供的数据集)

;WITH cte AS (
SELECT *
FROM (VALUES
(1, '2014-01-01T05:00:00', '1.0'),(1, '2014-01-01T06:45:00', '2.0'),
(1, '2014-01-01T09:30:00', '3.0'),(1, '2014-01-01T11:00:00', NULL),
(1, '2014-02-05T03:00:00', '1.0'),(1, '2014-02-05T05:00:00', NULL)
) as t (id, [time], measurement)
)
--Get intervals for every date
, dates AS (
SELECT MIN([time]) [min], DATEADD(hour,-1,MAX([time])) [max]
FROM cte
GROUP BY CAST([time] as date)
)
--Create table with gaps datetimes
, add_dates AS (
SELECT CAST([min] as datetime) as date_
FROM dates
UNION ALL
SELECT DATEADD(hour,1,a.date_)
FROM add_dates a
INNER JOIN dates d 
    ON a.date_ between d.[min] and d.[max]
WHERE a.date_ < d.[max]
)
--Get intervals of datetimes with ids and measurements
, res AS (
SELECT  id,
        [time],
        LEAD([time],1,NULL) OVER (ORDER BY [time])as [time1],
        measurement
FROM cte
)
--Final select
SELECT DISTINCT *
FROM (
    SELECT  r.id,
            a.date_,
            r.measurement
    FROM add_dates a
    LEFT JOIN res r
        ON a.date_ between r.time and r.time1
    WHERE measurement IS NOT NULL
    UNION ALL
    SELECT * 
    FROM cte
    WHERE measurement IS NOT NULL
) as t
ORDER BY t.date_

输出:

id  date_                   measurement
1   2014-01-01 05:00:00.000 1.0
1   2014-01-01 06:00:00.000 1.0
1   2014-01-01 06:45:00.000 2.0
1   2014-01-01 07:00:00.000 2.0
1   2014-01-01 08:00:00.000 2.0
1   2014-01-01 09:00:00.000 2.0
1   2014-01-01 09:30:00.000 3.0
1   2014-01-01 10:00:00.000 3.0
1   2014-02-05 03:00:00.000 1.0
1   2014-02-05 04:00:00.000 1.0

修改

第一部分

如果使用dates cte更改此部分,请执行以下操作:

, dates AS (
SELECT DATEADD(hour,DATEPART(hour,MIN([time])),CAST(CAST(MIN([time]) as date) as datetime)) [min], DATEADD(hour,-1,MAX([time])) [max]
FROM cte
GROUP BY CAST([time] as date)
)
  

这会截断日期中的分钟和秒值。

第二部分

  

partition by id语句中添加LEAD会有所不同   数据项被合并在一起

, res AS (
SELECT  id,
        [time],
        LEAD([time],1,NULL) OVER (PARTITION BY id ORDER BY [time])as [time1],
        measurement
FROM cte
)

对于原始数据集输出将是相同的。

答案 1 :(得分:2)

tal AS(SELECT -1 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
       FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) a(i)
       CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) b(i)
       CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) c(i))

<强>故障:

<强> 1

0, 1, 2, 3, 4, 5 ..... 999

这将返回数字rnk AS(SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY t) AS rn FROM @t) 。这是连续1小时的约41天。如果需要更大的间隔,只需添加更多交叉连接。

<强> 2

id

这会将您的行排在id t m rn 1 2014-01-01 05:00:00.000 1.00 1 1 2014-01-01 06:45:00.000 2.00 2 1 2014-01-01 09:30:00.000 3.00 3 1 2014-01-01 11:00:00.000 NULL 4 1 2014-02-05 03:00:00.000 1.00 5 1 2014-02-05 05:00:00.000 NULL 6 之内并返回:

itr AS(SELECT lr.id, rr.t, DATEADD(mi, 60 - DATEPART(mi, lr.t) , lr.t) AS wt, lr.m
       FROM rnk lr 
      LEFT JOIN rnk rr ON lr.id = rr.id AND lr.rn = rr.rn - 1
       WHERE lr.m IS NOT NULL AND lr.m <> 0)

第3:

wt

这是主要部分。它产生间隔。 t将保持开始时间,id t wt m 1 2014-01-01 06:45:00.000 2014-01-01 06:00:00.000 1.00 1 2014-01-01 09:30:00.000 2014-01-01 07:00:00.000 2.00 1 2014-01-01 11:00:00.000 2014-01-01 10:00:00.000 3.00 1 2014-02-05 05:00:00.000 2014-02-05 04:00:00.000 1.00 将保持间隔结束:

NULL

<强> 4

最后一部分从输入表中获取所有行,过滤掉0和{{1}}值。并且你可以通过在计数表上加入前一个时间间隔来获得另一个集合,以产生间隔中的所有小时数。

答案 2 :(得分:1)

由于我没有SQL Server环境,因此无法提供实际示例。但是,这是非常可行的。

您可以通过使用CTE连接行生成器来完成此操作。这是一个日期的行生成器: https://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-sequences/

类似这样的事情

With DateSequence( Date ) as
(
Select '2014-01-01T05:00:00' as Date
    union all
Select dateadd(hour, 1, Date)
    from DateSequence
    where Date < '2014-02-05T05:00:00'
)
Select * from DateSequence option (MaxRecursion 1000)

将为您提供所需时间的表格。然后将其连接到数据表并使用分析函数获取最后一个非空值。