连接表

时间:2018-01-08 16:37:26

标签: postgresql

我想使用与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      |
------------------------------*

1 个答案:

答案 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中的行),这将显着减少行数需要在查询的每次运行中加入和汇总。一旦您进入数百万行必须连接到数百万行的领域,这可能是唯一的高性能解决方案。