在给定时间间隔内的聚合函数

时间:2012-06-26 20:15:06

标签: sql oracle oracle10g aggregate

我的SQL有点生疏,我遇到这个问题有点困难。假设我有一个带有Timestamp列和Number列的表。目标是返回一个结果集,其中包含一些任意选择的常规间隔的平均值。

因此,例如,如果我有以下初始数据,则间隔为5分钟的结果输出如下:

time                               value
-------------------------------    -----
06-JUN-12 12.40.00.000000000 PM      2
06-JUN-12 12.41.35.000000000 PM      3
06-JUN-12 12.43.22.000000000 PM      4
06-JUN-12 12.47.55.000000000 PM      5
06-JUN-12 12.52.00.000000000 PM      2
06-JUN-12 12.54.59.000000000 PM      3
06-JUN-12 12.56.01.000000000 PM      4

OUTPUT:

start_time                         avg_value
-------------------------------    ---------
06-JUN-12 12.40.00.000000000 PM      3
06-JUN-12 12.45.00.000000000 PM      5
06-JUN-12 12.50.00.000000000 PM      2.5
06-JUN-12 12.55.00.000000000 PM      4

请注意,这是一个Oracle数据库,因此特定于Oracle的解决方案可以正常工作。当然,这可以通过存储过程完成,但我希望在单个查询中完成任务。

4 个答案:

答案 0 :(得分:8)

CREATE TABLE tt (time TIMESTAMP, value NUMBER);

INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.40.00.000000000 PM', 2);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.41.35.000000000 PM', 3);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.43.22.000000000 PM', 4);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.47.55.000000000 PM', 5);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.52.00.000000000 PM', 2);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.54.59.000000000 PM', 3);
INSERT INTO tt (time, value) VALUES ('06-JUN-12 12.56.01.000000000 PM', 4);


WITH tmin AS (
    SELECT MIN(time) t FROM tt
),   tmax AS (
    SELECT MAX(time) t FROM tt
)
SELECT ranges.inf, ranges.sup, AVG(tt.value)
FROM
     (
        SELECT 
            5*(level-1)*(1/24/60) + tmin.t as inf,
            5*(level)*(1/24/60) + tmin.t as sup
        FROM tmin, tmax
        CONNECT BY (5*(level-1)*(1/24/60) + tmin.t) < tmax.t
    ) ranges JOIN tt ON tt.time BETWEEN ranges.inf AND ranges.sup
GROUP BY ranges.inf, ranges.sup
ORDER BY ranges.inf

小提琴:http://sqlfiddle.com/#!4/9e314/11

编辑:像往常一样被贾斯汀击败......: - )

答案 1 :(得分:5)

这样的东西
with st 
  as (SELECT to_timestamp( '2012-06-06 12:40:00', 'yyyy-mm-dd hh24:mi:ss') + 
               numtodsinterval((level-1)*5, 'MINUTE') start_time,
             to_timestamp( '2012-06-06 12:40:00', 'yyyy-mm-dd hh24:mi:ss') + 
               numtodsinterval(level*5, 'MINUTE') end_time
        from dual
     connect by level <= 10)
SELECT st.start_time, avg( yt.value )
  FROM your_table yt,
       st
 WHERE yt.time between st.start_time and st.end_time

应该有效。您可以增强查询以从表中的MIN(time)MAX(time)派生起点和行数,而不是生成10个间隔并对最低间隔进行硬编码。

答案 2 :(得分:3)

贾斯汀和塞巴斯的答案可以通过LEFT JOIN延长,以消除“差距”,这通常是可取的。

如果没有必要,作为替代方案,我们可以去旧学校Oracle DATE算术......

SELECT TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400 AS time
     , AVG(t.value) AS avg_value
  FROM foo t
 WHERE t.time IS NOT NULL
 GROUP BY TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400
 ORDER BY TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400

让我们解开一下。我们可以分隔日期和时间组件,使用TRUNC获取日期部分,并使用TO_CHAR返回自午夜以来的秒数。我们知道5分钟是300秒,我们知道一天有86400秒。所以我们可以将秒数除以300,并取出那个FLOOR(只是整数部分),它将我们向下舍入到最近的5分钟边界。我们将其乘以(乘以300),再次获得秒数,然后将其除以一天中的秒数(86400),然后我们可以将其添加回(截断的)日期部分。

痛苦,是的。但是速度非常快。

注意:这会将舍入时间值返回为DATE,如果需要,可以将其转换回时间戳,但即使是5分钟边界,DATE也有足够的分辨率。

作为此方法的一个优点,对于大型表,我们可以通过为此查询添加覆盖索引来提高查询性能:

CREATE INDEX foo_FBX1
ON foo (TRUNC(t.time)+FLOOR(TO_CHAR(t.time,'sssss')/300)*300/86400,value);

附录:

MiMo为SQL Server提供了答案,表明它可以适应Oracle。以下是Oracle中该方法的改编。请注意,Oracle不提供DATEDIFF和DATEADD函数的等效项。 Oracle使用简单的算法。

SELECT TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288
       AS time
     , AVG(t.value) AS avg_value
  FROM foo t
 WHERE t.time IS NOT NULL
 GROUP BY TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288
 ORDER BY TO_DATE('00010101','YYYYMMDD')+FLOOR((t.time-TO_DATE('00010101','YYYYMMDD'))*288)/288

作为基准日期的公元0001年1月1日的选择是任意的,但我不想弄乱负值,并且弄清楚FLOOR是否正确,或者我们是否需要使用负数的CEIL 。 (幻数288是一天中1440分钟除以5的结果)。在这种情况下,我们采用小数日,乘以1440并除以5,并取整数部分,然后将其重新调整为小数天。

从PL / SQL包中提取“基准日期”或从子查询中获取它是很诱人的,但是执行其中任何一个都可能会阻止此表达式具有确定性。我们真的想继续打开创建基于函数的索引的选项。

我的偏好是避免在计算中包含“基准日期”。

答案 3 :(得分:1)

这是SQL Server的解决方案:

declare @startDate datetime = '2000-01-01T00:00:00'

declare @interval int = 5

select 
  DATEADD(mi, (DATEDIFF(mi, @startDate, time)/@interval)*@interval, @startDate), 
  AVG(value)
from 
  table
group by
  DATEDIFF(mi, @startDate, time)/@interval
order by   
  DATEDIFF(mi, @startDate, time)/@interval

开始日期是任意的。这个想法是你计算从开始日期开始的分钟数,然后按这个数除以间隔。

使用DATEADDDATEDIFF

的等价物,可以轻松适应Oracle