sql - 如果确切时间不可用,如何根据最后一个可用时间戳返回值?

标签 sql sql-server tsql sql-server-2014

我试图以十五分钟的间隔返回数据。我想到要做的第一件事是:

从 myTable 中选择 *,其中 DATEPART(分钟, 时间戳) % 15 = 0

但是这种方法有两个问题。第一个是在给定分钟不一定总是有带有时间戳的数据,另一个是有时在给定分钟有多个具有不同秒值的数据点。我想在 :00、:15、:30 等每 15 分钟组恰好有一行。

此数据仅在发生变化时才会记录,因此,例如,如果我没有 12:30 的数据点,我可以采用之前最接近的数据点并使用 12:30 的该值,这样就会一定要正确。

所以基本上我需要能够准确地返回 :00、:30 等的时间戳以及最接近该时间的记录中的数据。

数据可能跨越数年,但更有可能是更短的时间、几天或几周。这就是预期的输出:

Timestamp               Value
1/1/2015 12:30:00       25
1/1/2015 12:45:00       41
1/1/2015 1:00:00        45

我无法想出在 SQL 中执行此操作的方法。可能吗?

最佳答案

给定固定的开始时间,您所需要的只是一个用于添加间隔的数字表。如果您还没有数字表(这很有用),那么一种快速生成数字表的方法是

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N2 AS N1 CROSS JOIN N2 AS N2)
SELECT *
FROM Numbers;

这只是生成一个从 1 到 10,000 的序列。有关这方面的更多阅读,请参阅以下系列:

一旦你有了你的数字,你就可以生成你的间隔:

DECLARE @StartDateTime SMALLDATETIME = '20150714 14:00',
        @EndDateTime SMALLDATETIME = '20150715 15:00';

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N2 AS N1 CROSS JOIN N2 AS N2)

SELECT  Interval = DATEADD(MINUTE, 15 * (N - 1), @StartDateTime)
FROM    Numbers
WHERE   DATEADD(MINUTE, 15 * (N - 1), @StartDateTime) <= @EndDateTime

这给出了类似的东西:

Interval
----------------------
2015-07-14 14:00:00
2015-07-14 14:15:00
2015-07-14 14:30:00
2015-07-14 14:45:00
2015-07-14 15:00:00
2015-07-14 15:15:00
2015-07-14 15:30:00

然后您只需使用 APPLY 找到每个间隔上或之前的最接近值和TOP:'

/*****************************************************************
SAMPLE DATA
*****************************************************************/

DECLARE @T TABLE ([Timestamp] DATETIME, Value INT);
INSERT @T ([Timestamp], Value)
SELECT  DATEADD(SECOND, RAND(CHECKSUM(NEWID())) * -100000, GETDATE()),
        CEILING(RAND(CHECKSUM(NEWID())) * 100)
FROM sys.all_objects;

/*****************************************************************
QUERY
*****************************************************************/

DECLARE @StartDateTime SMALLDATETIME = '20150714 14:00',
        @EndDateTime SMALLDATETIME = '20150715 15:00';

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N2 AS N1 CROSS JOIN N2 AS N2),
Intervals AS
(   SELECT  Interval = DATEADD(MINUTE, 15 * (N - 1), @StartDateTime)
    FROM    Numbers
    WHERE   DATEADD(MINUTE, 15 * (N - 1), @StartDateTime) <= @EndDateTime
)
SELECT  i.Interval, t.[Timestamp], t.Value
FROM    Intervals AS i
        OUTER APPLY
        (   SELECT  TOP 1 t.[Timestamp], t.Value
            FROM    @T AS t
            WHERE   t.[Timestamp] <= i.Interval
            ORDER BY t.[Timestamp] DESC, t.Value
        ) AS t
ORDER BY i.Interval;
<小时/>

编辑

需要注意的一点是,如果有两个相等的时间戳都在某个间隔上或最接近某个间隔,我会按 Value 应用二级排序:

SELECT  i.Interval, t.[Timestamp], t.Value
FROM    Intervals AS i
        OUTER APPLY
        (   SELECT  TOP 1 t.[Timestamp], t.Value
            FROM    @T AS t
            WHERE   t.[Timestamp] <= i.Interval
            ORDER BY t.[Timestamp] DESC, t.Value --- ORDERING HERE
        ) AS t
ORDER BY i.Interval;

这是任意的,可以是您选择的任何内容,建议确保您按足够的项目排序以确保结果是确定性的,也就是说,如果您对相同的数据多次运行相同的查询将返回结果,因为只有一行满足条件。如果您有这样的两行:

    Timestamp    |  Value  | Field1
-----------------+---------+--------
2015-07-14 14:00 |   100   |   1
2015-07-14 14:00 |   100   |   2
2015-07-14 14:00 |   50    |   2

如果您只是按时间戳排序,对于间隔2015-07-14 14:00,您不知道您是否会得到50或100的值,并且可能会有所不同执行之间取决于统计数据和执行计划。同样,如果您按 TimestampValue 排序,那么您不知道 Field1 是 1 还是 2。

关于sql - 如果确切时间不可用,如何根据最后一个可用时间戳返回值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31408089/

相关文章:

sql-server - 使用 T-SQL 高效删除记录而不使用 IN 子句

mysql - 如何选择最小月份和年份?

sql - 通过在大表上进行操作来加速

c# - MSSQL中子字符串的更新操作

sql-server - 为什么窗口函数在 CROSS APPLY 中不起作用?

sql-server - 查找多边形重叠

mysql - 如何从 A 表中选择 B 表中不存在的值?

MYSQL 多次更新

sql-server - SQL Server - Tempdb 与数据库日志的使用

sql-server - 将 SQL Server 实例重定向到新实例?