SQL按任意时间段对时间间隔进行分组

时间:2015-12-28 18:14:57

标签: sql postgresql query-optimization

我需要有关此SQL查询的帮助。我有一个包含以下模式的大表:

  • time_start(时间戳) - 测量的开始时间,
  • duration(双倍) - 以秒为单位的测量持续时间,
  • count_event1(int) - 类型1的测量事件数,
  • count_event2(int) - 类型2的测量事件数

我保证没有行重叠 - 在SQL中,没有两行time_start1 < time_start2 AND time_start1 + duration1 > time_start2

我想设计一个有效的SQL查询,它将测量按一些任意时间段(我称之为group_period)进行分组,例如 3小时。我已经尝试过这样的事情:

SELECT
    ROUND(time_start/group_period,0) AS time_period,
    SUM(count_event1) AS sum_event1,
    SUM(count_event2) AS sum_event2 
FROM measurements
GROUP BY time_period;

然而,似乎存在问题。如果有duration大于group_period的衡量标准,我希望将此类测量值分组到它所属的所有时间段,但由于持续时间从未被考虑过,因此仅进行分组进入第一个。有办法解决这个问题吗?

性能是我关注的问题,因为随着时间的推移,我希望表的大小能够大幅增长,达到数百万,可能是数十或数亿行。您对索引或任何其他优化有任何建议,以提高此查询的速度吗?

2 个答案:

答案 0 :(得分:0)

根据Timekiller的建议,我提出了以下问题:

ON DELETE CASCADE

它完全符合我的目标,所以任务完成了。但是,如果有人能够就以下条件对此查询的性能给出一些反馈,我仍然会感激:

  • 我希望-- Since there's a problem with declaring variables in PostgreSQL, -- we will be using aliases for the arguments required by the script. -- First some configuration: -- group_period = 3600 -- group by 1 hour (= 3600 seconds) -- min_time = 1440226301 -- Sat, 22 Aug 2015 06:51:41 GMT -- max_time = 1450926301 -- Thu, 24 Dec 2015 03:05:01 GMT -- Calculate the number of started periods in the given interval in advance. -- period_count = CEIL((max_time - min_time) / group_period) SET TIME ZONE UTC; BEGIN TRANSACTION; -- Create a temporary table and fill it with all time periods. CREATE TEMP TABLE periods (period_start TIMESTAMP) ON COMMIT DROP; INSERT INTO periods (period_start) SELECT to_timestamp(min_time + group_period * coefficient) FROM generate_series(0, period_count) as coefficient; -- Group data by the time periods. -- Note that we don't require exact overlap of intervals: -- A. [period_start, period_start + group_period] -- B. [time_start, time_start + duration] -- This would yield the best possible result but it would also slow -- down the query significantly because of the part B. -- We require only: period_start <= time_start <= period_start + group_period SELECT period_start, COUNT(measurements.*) AS count_measurements, SUM(count_event1) AS sum_event1, SUM(count_event2) AS sum_event2 FROM periods LEFT JOIN measurements ON time_start BETWEEN period_start AND (period_start + group_period) GROUP BY period_start; COMMIT TRANSACTION; 表有大约5到8亿行。
  • measurements列是主键,并且具有唯一的btree索引。
  • 我不保证time_startmin_time。我只知道将选择组时段以便max_time

答案 1 :(得分:0)

(这对于评论来说太大了,所以我会将其作为答案发布。)

添加我对你的答案的评论,你可能应该首先获得最佳结果,如果结果变慢则优化。

至于性能,我在使用数据库时学到的一件事是你无法真正预测性能。高级DBMS中的查询优化器很复杂,并且在小型和大型数据集上的行为往往不同。你必须让你的表填满一些大的样本数据,试验索引并阅读EXPLAIN的结果,没有别的办法。

有一些事情需要建议,但我知道Oracle优化器比Postgres好得多,所以其中一些可能不起作用。

  • 如果您要检查的所有字段都包含在索引中,事情会更快。由于您正在执行左连接并且periods是基础,因此可能没有理由对其进行索引,因为它将完全包含在内。 duration应该包含在索引中,如果你要使用适当的间隔重叠 - 这样,Postgres就不必获取行来计算连接条件,索引就足够了。有可能它根本不会获取表行,因为它不需要除索引中存在的其他数据。我认为如果将它作为time_start索引的第二个字段包含在内,它会表现得更好,至少在Oracle中它会如此,但IIRC Postgres能够将索引连接在一起,所以也许一秒钟index会表现得更好 - 您必须使用EXPLAIN进行检查。

  • 索引和数学混合不好。即使索引中包含duration,也不能保证它会在(time_start + duration)中使用 - 但是,请再次查看EXPLAIN。如果没有使用它,尝试创建一个基于函数的索引(即,包括time_start + duration作为字段),或者稍微改变表的结构,以便time_start + duration是一个单独的列,而是索引该列。

  • 如果你真的不需要左连接(也就是说你没有空缺时间),那么使用内连接 - 优化器可能会从一个更大的表开始(测量)和连接时间段它,可能使用散列连接而不是嵌套循环。如果你这样做,那么你也应该以相同的方式索引你的周期表,并且可能以相同的方式重构它,以便它显式包含开始和结束周期,因为优化器在不必执行时有更多选项列上的任何操作。

  • 也许最重要的是,如果您有max_timemin_time,请在加入之前使用IT来限制measurements的结果!您的设置越小,它的工作速度就越快。