重疊範圍的最大 sum()
本質上我的問題是:如何在 PostgreSQL 9.3(或 9.4)中進行涉及重疊範圍的聚合操作?我手頭的具體問題是給定一個範圍,我想找到適用重疊範圍的最大 sum() 。一個簡單的例子:
create table event ( event_id int primary key, event_type_id int not null, period tstzrange not null, quantity int not null ); insert into event (event_id, event_type_id, period, quantity) values (1, 1,'[2016-01-06 09:00:00+00,2016-01-08 17:00:00+00]',1), (2, 1,'[2016-01-07 09:00:00+00,2016-01-07 11:00:00+00]',1), (3, 1,'[2016-01-07 13:00:00+00,2016-01-07 17:00:00+00]',1), (4, 2,'[2016-01-07 12:00:00+00,2016-01-07 17:00:00+00]',1);
給定帶有以下子句的查詢:
select ... where event_type_id = 1 and period && '[2016-01-07 00:00:00+00,2016-01-07 23:59:00+00]'::tstzrange group by event_type_id
期望的結果將是:3,即
sum(quantity)
相同的範圍event_type_id
在給定時間戳範圍內重疊的最大值。
我理解的任務
從表中選擇
period
與給定時間範圍重疊的行。確定該集合內重疊時段的不同範圍,並sum(quantity)
從任何範圍返回最大值。需要 Postgres 9.2 +,因為舊版本中沒有範圍類型。
假設
- “重疊”是指層疊的方式,就像傳統屋頂上的瓦片一樣:這些是“重疊的”(防雨),儘管最高的瓦片不直接與最低的瓦片重疊。
- 中的所有值
period
都有包含範圍 ([]
)。否則,您必須調整排他邊界。(輸入參數的範圍仍然可以有任意範圍。)- 我們過濾了一個
event_type_id
。否則,您必須添加PARTITION BY event_type_id
到視窗定義中。quantity
是一個integer
。否則,您必須針對計算類型進行調整。- 重疊期間的數量將被完全計算在內,即使該期間的某些部分超出了您的給定時間範圍。
- 甚至適用於
(event_type_id, period)
.單個子查詢的最佳性能
這應該是炸藥。
SELECT running_sum - lag(running_sum, 1, 0) OVER (ORDER BY p_start) AS sum_quantity FROM ( SELECT lower(period) AS p_start ,(sum(quantity) OVER w)::int AS running_sum , lead(lower(period), 1, 'infinity') OVER w > max(upper(period)) OVER w AS range_end FROM event WHERE event_type_id = 1 AND period && '[2016-01-01 0:0+0,2016-01-10 0:0+0]'::tstzrange WINDOW w AS (ORDER BY lower(period)) ) sub WHERE range_end ORDER BY 1 DESC LIMIT 1;
子查詢中的所有三個視窗函式都可以使用同一個視窗。這避免了額外的排序操作,應該是最快的。
帶有更多解釋的詳細 CTE 變體
相同的查詢,只是更冗長和更慢,因為 CTE 實現派生表並構成優化障礙。
WITH cte1 AS ( SELECT quantity , lower(period) AS p_start , upper(period) AS p_end FROM event WHERE event_type_id = 1 AND period && '[2016-01-01 0:0+0,2016-01-10 0:0+0]'::tstzrange ) , cte2 AS ( SELECT (sum(quantity) OVER w)::int AS running_sum , lead(p_start, 1, 'infinity') OVER w -- next start .. > max(p_end) OVER w AS range_end -- .. after last end , p_start, p_end FROM cte1 WINDOW w AS (ORDER BY p_start) ) SELECT running_sum - lag(running_sum, 1, 0) OVER (ORDER BY p_start) AS sum_quantity -- subtract the previous sum to get the sum of this range , p_end::text FROM cte2 WHERE range_end -- only rows at the end of each range ORDER BY 1 DESC -- biggest sum first LIMIT 1; -- only return the winner
sqlfiddle for Postgres 9.3
db<>fiddle here for Postgres 12
您需要一個索引才能快速處理大表。最好的選擇是在
(event_type_id, period)
. 細節:解釋
篩選符合條件的行,然後按時間範圍的開始 (
lower(period)
) 排序併計算:
- 數量的執行總和 (
running_sum
)。- 下一個時期的開始:(
lead(lower(period), 1, 'infinity')
)。最後一行預設為“無窮大”以包括最後一個範圍。- 迄今為止任何時期的最晚結束
max(upper(period))
。如果*2.晚於3.*它是(子)範圍 (
range_end
) 的結尾。在外部
SELECT
過濾器行中,使用range_end
並減去先前的總數以獲得範圍的總和。ORDER BY
該結果並返回第一個 (LIMIT 1
) 最大的sum_quantity
。瞧。在旁邊
要選擇 2016 年 1 月 7 日的所有內容,簡潔的表達式是:
'[2016-01-07 00:00:00+00,2016-01-**08 00:00:00+00)**'::tstzrange
不是:
'[2016-01-07 00:00:00+00,2016-01-**07 23:59:00+00]**'::tstzrange
細節:
由於時間戳值的預設精度是 6 位十進制數字(微秒解析度),您還可以使用:
'[2016-01-07 00:00:00+00,2016-01-**07 23:59:59.999999+00]**'::tstzrange
但這很混亂,並且取決於可能會更改(即使不太可能)的實現細節。它不受舍入錯誤的影響,因為時間戳在現代 Postgres 中儲存為整數值: