我试图以十五分钟的间隔返回数据。我想到要做的第一件事是:
从 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 的序列。有关这方面的更多阅读,请参阅以下系列:
- Generate a set or sequence without loops – part 1
- Generate a set or sequence without loops – part 2
- Generate a set or sequence without loops – part 3
一旦你有了你的数字,你就可以生成你的间隔:
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的值,并且可能会有所不同执行之间取决于统计数据和执行计划。同样,如果您按 Timestamp
和 Value
排序,那么您不知道 Field1
是 1 还是 2。
关于sql - 如果确切时间不可用,如何根据最后一个可用时间戳返回值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31408089/