postgresql - 连接表上的窗口函数

标签 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      |
------------------------------*

最佳答案

@user554481,来自评论。

正如您所说,窗口函数在算法上可能更高效。

equi-join 是一种带有 = 的连接,它可以找到直接匹配(即最常见的连接类型,而不是带有 > 的非 equi-join )。

如果对销售额列进行求和,很明显我们现在只需要直接匹配。因此加入 report_date = snapshot_date 将为我们提供 272017-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/

相关文章:

php - Doctrine SQL/表生成失败

sql - 表中同一列的两条记录相减

postgresql - 如何将 uuid 与 postgresql gist 索引类型一起使用?

sql - 设计带有类型的组和带有类型的项目,其中项目类型与组类型相关

postgresql `syntax error at or near "SUPPORT"` 更新 postgis 扩展

java - 使用 ImprovedNamingStrategy 创建表时出错

python - 如何在 Django 过滤器中做小于或等于和大于等于?

postgresql - 使用 "pg_basebackup"实用程序进行备份时收到错误

postgresql - 如何从 cron.d 运行 cronjob?

c# - 更新记录时性能非常慢