SQL:在每个列的单独子范围内有效地查找最小数据点

时间:2016-01-17 00:23:37

标签: sql sql-server

我有一个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}存在。

我想到的一个明显的方法是每小时使用一个子查询,但这让我觉得运行效率显着降低(而且我的实现尝试不仅慢而且丑陋)。有没有更好的选择?我不考虑?

1 个答案:

答案 0 :(得分:4)

要从datetime截断时间组件,您只需转换为date

查找top-n-per-groupgreatest-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之外,您可以放置​​任何其他聚合函数(MAXSUMAVG)。结果不会改变,因为在第一个过滤器后,每小时最多只能有一行。

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,其中包含您的示例数据。