我试图以十五分钟的间隔返回数据。我想做的第一件事是:
select * from myTable where DATEPART(minute, Timestamp) % 15 = 0
但这种方法存在两个问题。第一个是在给定的分钟内不一定总是存在具有时间戳的数据,另一个是在给定的分钟有时存在具有不同的第二值的多个数据点。我想在每个十五分钟组中只有一行,分别是:00,:15,:30等。
此数据仅在发生变化时记录,因此,如果我在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中的方法时遇到了麻烦。有可能吗?
答案 0 :(得分:4)
给定一个固定的开始时间,您只需要一个数字表来添加您的间隔。如果您还没有数字表(这些数据很有用),那么快速生成数字的方法就是
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的值,并且执行之间可能会有所不同,具体取决于统计信息和执行计划。同样,如果您按Timestamp
和Value
订购,那么您不知道Field1
是1还是2。
答案 1 :(得分:2)
与Shnugo一样,您可以使用计数表以15分钟的间隔获取数据,就像这样。
我正在使用CTE创建动态计数表,但您甚至可以根据需要使用物理日历表。
DECLARE @StartTime DATETIME = '2015-01-01 00:00:00',@EndTime DATETIME = '2015-01-01 14:00:00'
DECLARE @TimeData TABLE ([Timestamp] datetime, [Value] int);
INSERT INTO @TimeData([Timestamp], [Value])
VALUES ('2015-01-01 12:30:00', 25),
('2015-01-01 12:45:00', 41),
('2015-01-01 01:00:00', 45);
;WITH CTE(rn) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), CTE2 as
(
SELECT C1.rn
FROM CTE C1 CROSS JOIN CTE C2
), CTE3 as
(
SELECT TOP (CEILING(DATEDIFF(minute,@StartTime,@EndTime)/15)) ROW_NUMBER()OVER(ORDER BY C1.rn) - 1 rn
FROM CTE2 C1 CROSS JOIN CTE2 C2
)
SELECT DATEADD(minute,rn*15,@StartTime) CurrTime,T.Value
FROM CTE3
CROSS APPLY (SELECT TOP 1 Value FROM @TimeData WHERE [Timestamp] <= DATEADD(minute,rn*15,@StartTime) ORDER BY [Timestamp] DESC) T;
<强>输出强>
CurrTime Value
2015-01-01 01:00:00.000 45
2015-01-01 01:15:00.000 45
.
.
.
2015-01-01 12:00:00.000 45
2015-01-01 12:15:00.000 45
2015-01-01 12:30:00.000 25
2015-01-01 12:45:00.000 41
2015-01-01 13:00:00.000 41
2015-01-01 13:15:00.000 41
2015-01-01 13:30:00.000 41
2015-01-01 13:45:00.000 41
答案 2 :(得分:1)
现在你真的有足够的方法来创建你的计数表: - )
DECLARE @startdate DATETIME={ts'2015-06-01 00:00:00'};
WITH JumpsOf15 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY object_id) * 15 AS Step
FROM sys.objects --take any large table here (should have many rows...)
)
SELECT Step,steppedDate.steppedDate
FROM JumpsOf15
CROSS APPLY(SELECT DATEADD(MINUTE,Step,@startdate) AS steppedDate ) AS steppedDate
WHERE GETDATE()>steppedDate.steppedDate;
答案 3 :(得分:1)
问题是缺少原始数据和架构信息,因此我将主要以一般形式解决问题。
您正在寻找不会丢失任何记录的范围内的结果,涵盖可能丢失记录的数据。鉴于这一要求,通常的解决方案是使用像这样的数字表来源,只为左侧需要的值创建 投影 没有与您的实际数据有关。我们保证Numbers表不会遗漏您范围内的任何记录。对于日期预测,您只需在起始值中添加适当的天数或分钟数,即为结果中预期的记录数。
完成投影后,您可以根据实际数据从投影中进行OUTER JOIN。在这种情况下,由于您有一些日期值额外记录,JOIN变得复杂。我知道解决这个问题的两种方法。一种方法是GROUP BY投影中的值。另一种是使用OUTER APPLY
而不是连接。使用OUTER APPLY,您可以在应用的查询中使用TOP 1过滤器将结果限制为一个项目。
总之,这里有一些伪代码可以帮助你到达你需要的地方:
WITH Numbers AS
(
--select numbers here
),
DateProjection As
(
SELECT DATEADD(minute, 15*Numbers.Number, '2015-01-01') As RangeStart,
DATEADD(minute, 15*(Numbers.Number+1), '2015-01-01') AS RangeEnd
FROM Numbers
)
SELECT dp.RangeStart as TimeStamp, oa.Value
FROM DateProjection dp
OUTER APPLY (SELECT TOP 1 Value FROM [myTable] WHERE myTable.TimeStamp >= dp.RangeStart AND myTable.TimeStamp < dp.RangeEnd) oa
答案 4 :(得分:0)
我会使用OVER子句按时间戳划分行,四舍五入到最接近的四分之一小时。然后按时间戳和舍入时间戳之间的差异对每个分区进行排序,升序,并抓住每个分区的第一行。我想那会做你想要的。这将为您提供最接近15分钟标记的行。但是,如果在15分钟内没有行,则不会添加外推值。
SELECT ROW_NUMBER() OVER(PARTITION BY [Timestamp Moded to 15 minutes] ORDER BY [Diff timestamp - timestamp moded to 15 minutes] ASC) AS RowNum, *
FROM MyTable where RowNum = 1
答案 5 :(得分:0)
您可以使用下一个查询以15分钟的间隔对数据进行分组:
select *, CASE DATEPART(minute, timestamp) /15
WHEN 0 THEN '0-15' WHEN 1 THEN '15-30' WHEN 2 THEN '30-45' WHEN 3 THEN '45-60' END
AS [Time Group]
from myTable where
DATEPART(minute, timestamp) /15 = 2 /* for group 30-45 min*/
考虑日期和时间:
select *,
CAST(CAST(timestamp as date) AS VARCHAR(MAX))+ ' ' +
CAST(DATEPART(hour, timestamp) AS VARCHAR(MAX)) + ':' +
CAST(
CASE DATEPART(minute, timestamp) /15
WHEN 0 THEN '0-15'
WHEN 1 THEN '15-30'
WHEN 2 THEN '30-45'
WHEN 3 THEN '45-60' END
AS VARCHAR(MAX)) AS [Interval]
from myTable
order by [Interval]
答案 6 :(得分:0)
非常棘手,但这些内容可能有效:
select * from mytable where TimeStamp in (
select max(TimeStamp) from (
select date(TimeStamp) dt, hour(TimeStamp) as hr,
case when minute(TimeStamp) < 15 then 15 else
case when minute(TimeStamp) < 30 then 30 else
case when minute(TimeStamp) < 45 then 45 else 60 end end end as mint
from mytable where TimeStamp between <some TS> and <some other TS>
) t group by dt, hr, mint
)
当然,如果有两个具有完全相同时间戳的读数,这将不起作用,在这种情况下,您需要另一个组。凌乱的查询无论如何。