我想使用类似于here 的窗口函数的效率.在链接的示例中,我能够使用窗口函数,这样我就不必将表连接到自身上。加速是戏剧性的——大约是 O(n^2) 到 O(n)。在这个问题中,没有办法绕过连接,但我的两个表都非常大(数百万行),我想再次避免 O(n^2) 炸毁数据。在这种情况下,窗口函数或类似的函数是否仍然有效?
我有两个这样的表:
CREATE TABLE reports (
report_date DATE,
PRIMARY KEY (report_date));
CREATE TABLE time_series (
snapshot_date DATE,
sales INTEGER,
PRIMARY KEY (snapshot_date));
具有这样的值:
INSERT INTO time_series SELECT '2017-01-01'::DATE AS snapshot_date,10 AS sales;
INSERT INTO time_series SELECT '2017-01-02'::DATE AS snapshot_date,4 AS sales;
INSERT INTO time_series SELECT '2017-01-03'::DATE AS snapshot_date,13 AS sales;
INSERT INTO time_series SELECT '2017-01-04'::DATE AS snapshot_date,7 AS sales;
INSERT INTO time_series SELECT '2017-01-05'::DATE AS snapshot_date,15 AS sales;
INSERT INTO time_series SELECT '2017-01-06'::DATE AS snapshot_date,8 AS sales;
INSERT INTO reports SELECT '2017-01-03'::DATE AS report_date;
INSERT INTO reports SELECT '2017-01-06'::DATE AS report_date;
我想执行这样的连接(但效率更高):
SELECT r.report_date,
SUM(sales) AS total_sales
FROM reports AS r
JOIN time_series AS ts
ON r.report_date > ts.snapshot_date
GROUP BY r.report_date
ORDER BY r.report_date
得到这样的结果:
*---------------*-------------*
| report_date | total_sales |
*---------------*-------------*
| 2017-01-03 | 14 |
| 2017-01-06 | 49 |
------------------------------*
最佳答案
@user554481,来自评论。
正如您所说,窗口函数在算法上可能更高效。
equi-join 是一种带有 =
的连接,它可以找到直接匹配(即最常见的连接类型,而不是带有 > 的非 equi-join
)。
如果对销售额列进行求和,很明显我们现在只需要直接匹配。因此加入 report_date = snapshot_date
将为我们提供 27
的 2017-01-03
运行总和。
如果您只想要所有前行的总和,那么您只需减去匹配日期的sales
数字 - 在本例中为13
,给我们你想要的结果 27 - 13 = 14
。同样的逻辑适用于 2017-01-06
。
这当然取决于 每个 可能的 report_date
都有一个 snapshot_date
,否则连接将失败。
我还没有测试过这段代码(我也不特别熟悉 Postgres),但你明白了要点:
SELECT
r.report_date
,(ts.sales_run_sum - ts.sales) AS sales_prev_run_sum
FROM
reports AS r
LEFT JOIN
(
SELECT
snapshot_date
,sales
,SUM(sales) OVER (ORDER BY snapshot_date ASC) AS sales_run_sum
FROM
time_series
) AS ts
ON r.report_date = ts.snapshot_date
ORDER BY
r.report_date
编辑:顺便说一句,如果此报告定期运行但仅针对新报告日期,并且您说您有数百万行,那么您最好的做法是在每次运行报表时缓存销售额总和,然后在下一次运行时仅选择 time_series
中比上次缓存值更新的行,然后添加缓存的值作为您对新的 time_series
值执行的运行总和的偏移量。这是处理大量数据时的基本方法,需要动态平衡,并且有适当的日期索引。
编辑 2:根据您在下方的评论。那为什么这两个表中有“数百万行”呢?这种性质的数据似乎有点极端。
无论哪种方式,如果您不能保证快照表中每天至少有一行,那么可以考虑从日期表左连接,以确保每天至少有一行,甚至将虚拟行物理地插入 time_series
(sales
为零)以填补空白。
如果这些都 Not Acceptable ,那么您将不可避免地必须按照最初的方式实现不等式连接。
但仍要考虑我的解决方案的另一个方面,即缓存以前求和的结果。这允许您在加入 time_series
之前引入一个 where 子句(基于仅选择 time_series
自上次创建缓存值以来的行),这将显着减少每次运行查询时需要连接和求和的行数。一旦您进入必须将数百万行连接到数百万行的领域,这可能是唯一的高性能解决方案。
关于postgresql - 连接表上的窗口函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48154449/