我有一个SQL查询(遗憾地以SQL Server为目标),看起来像这样:
SELECT
DATETIMEFROMPARTS(YEAR(ts), MONTH(ts), DAY(ts), 0, 0, 0, 0) AS day,
AVG(CASE WHEN DATEPART(HOUR, ts) = 0 THEN val ELSE NULL END) AS hr0,
AVG(CASE WHEN DATEPART(HOUR, ts) = 1 THEN val ELSE NULL END) AS hr1,
-- ...etc for other hours...
FROM
sample_data
GROUP BY
DATETIMEFROMPARTS(YEAR(ts), MONTH(ts), DAY(ts), 0, 0, 0, 0)
这非常有效,计算(每天)每小时的平均值。
然而,我的要求正在发生变化:我现在每小时只要求第一个样本。因此,hr0
应该只反映value
满足ts
的最小DATEPART(HOUR, ts) = 0
(当然,仍然在同一天内),如果没有,则{NULL}存在。
我想到的一个明显的方法是每小时使用一个子查询,但这让我觉得运行效率显着降低(而且我的实现尝试不仅慢而且丑陋)。有没有更好的选择?我不考虑?
答案 0 :(得分:4)
要从datetime
截断时间组件,您只需转换为date
。
查找top-n-per-group
或greatest-n-per-group
。对于SQL Server,请参阅Retrieving n rows per group。
以下是使用ROW_NUMBER()
的一种可能变体。
WHERE rn=1
过滤器每小时最多产生一行。这一小时的每一行都包含第一个val
。
GROUP BY dt
和24 MIN(CASE WHEN DATEPART(HOUR, ts) = ...
将结果集转换为每天一行,每小时24列。除了MIN
之外,您可以放置任何其他聚合函数(MAX
,SUM
,AVG
)。结果不会改变,因为在第一个过滤器后,每小时最多只能有一行。
WITH
CTE
AS
(
SELECT
ts
,CAST(ts as date) AS dt
,val
,ROW_NUMBER()
OVER(PARTITION BY CAST(ts as date), DATEPART(HOUR, ts) ORDER BY ts) AS rn
FROM sample_data
)
SELECT
dt
,MIN(CASE WHEN DATEPART(HOUR, ts) = 0 THEN val ELSE NULL END) AS hr0
,MIN(CASE WHEN DATEPART(HOUR, ts) = 1 THEN val ELSE NULL END) AS hr1
,MIN(CASE WHEN DATEPART(HOUR, ts) = 2 THEN val ELSE NULL END) AS hr2
-- ...etc for other hours...
FROM CTE
WHERE rn=1
GROUP BY dt
ORDER BY dt;
以下是SQL Fiddle,其中包含您的示例数据。