我想使用与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 |
------------------------------*
答案 0 :(得分:1)
@ user554481,来自评论。
正如您所说,窗口函数可能在算法上更有效。
equi-join是与=
的联接,可以找到直接匹配(即最常见的联接类型,而不是与>
的非等联接)。
如果对销售列进行运行求和,很明显我们现在只需要直接匹配。因此,加入report_date = snapshot_date
会为27
提供2017-01-03
的总和。
如果您只想要所有之前行的总和,那么您只需减去匹配日期的sales
数字 - 在本例中为13
,我们想要27 - 13 = 14
的结果。同样的逻辑适用于2017-01-06
。
这当然取决于每个可能snapshot_date
report_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
)以填补空白。
如果这些都不可接受,那么你不可避免地必须以你原来的方式实施不平等加入。
但仍然考虑我的解决方案的另一个方面,即缓存先前总和的结果。这允许您在加入之前将where子句引入time_series
(基于自创建最后一个缓存值以来仅选择time_series
中的行),这将显着减少行数需要在查询的每次运行中加入和汇总。一旦您进入数百万行必须连接到数百万行的领域,这可能是唯一的高性能解决方案。