我的SQL是有点生疏了,我有相当多的困难这个问题。 假设我有一个表,timestamp列和数字列。 我们的目标是返回包含一些任意选择规则的间隔的平均值的结果集。
因此,例如,如果我有以下初始数据,用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的具体解决方案将很好地工作。 这可能,当然,可以用一个存储过程完成,但我希望能完成任务在一个单一的查询。
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
编辑:由Justin打倒,像往常一样... :-)
就像是
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
应该管用。 而不是生成10个间隔和硬编码的最低间隔时间,可以提高查询导出的出发点和从所述行数MIN(time)
和MAX(time)
在表中。
贾斯汀和Sebas的答案可以用左扩展JOIN消除‘空白’,这是经常需要。
如果这是没有必要的,作为替代,我们可以去老同学Oracle日期运算...
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不提供的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
1月1日的选择,公元0001的基准日是任意的,但我不想与负值的混乱,并搞清楚,如果地板是正确的,或者我们是否需要使用CEIL负数。 (幻数288是在一天除以51440分钟的结果)。 在这种情况下,我们正在采取分数天,由1440乘以5分,并采取的是整数部分,然后把它回小数天。
我们很容易拉说,“基准日”从一个PL / SQL包,或者把它从一个子查询,但这样做无论是那些可能会阻止这种确定性的表达。 我们很想继续开放创建功能基于索引的选项。
我的选择是避免在计算中包括一个“基准日”的必要性。
这是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
开始日期是任意的。 这个想法是,你通过这个号码由间隔分开计算分钟从开始日期的数字,然后组。
它应该适应甲骨文容易使用等效DATEADD
和DATEDIFF