我的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的解决方案可以正常工作。当然,这可以通过存储过程完成,但我希望在单个查询中完成任务。
答案 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
开始日期是任意的。这个想法是你计算从开始日期开始的分钟数,然后按这个数除以间隔。
使用DATEADD
和DATEDIFF