Postgres 在非常大的分区表上加入与聚合

Postgres join vs aggregation on very large partitioned tables

我有一个很大的 table,其中包含数百个数百万行。因为太大了,所以先按日期范围分区,然后那个分区也按period_id.

分区
CREATE TABLE research.ranks
(
    security_id           integer                  NOT NULL,
    period_id             smallint                 NOT NULL,
    classificationtype_id smallint                 NOT NULL,
    dtz                   timestamp with time zone NOT NULL,
    create_dt             timestamp with time zone NOT NULL DEFAULT now(),
    update_dt             timestamp with time zone NOT NULL DEFAULT now(),
    rank_1                smallint,
    rank_2                smallint,
    rank_3                smallint
)
CREATE TABLE zpart.ranks_y1990 PARTITION OF research.ranks
    FOR VALUES FROM ('1990-01-01 00:00:00+00') TO ('1991-01-01 00:00:00+00')
    PARTITION BY LIST (period_id);

CREATE TABLE zpart.ranks_y1990p1 PARTITION OF zpart.ranks_y1990
    FOR VALUES IN ('1');

每年都有一个分区,每年还有十几个分区。

我需要并排查看 security_ids 的不同 period_id 的结果。

所以我最初使用的连接是这样的:

select          c1.security_id, c1.dtz,c1.rank_2 as rank_2_1, c9.rank_2 as rank_2_9
from            research.ranks c1 
left join      research.ranks c9 on c9.dtz=c9.dtz and c1.security_id=c9.security_id and c9.period_id=9
where           c1.period_id =1 and c1.dtz>now()-interval'10 years' 

速度很慢,但接受table。我将其称为 JOIN 版本。

然后,我们想再显示两个 period_ids 并扩展上面的内容以在新的 period_ids 上添加额外的连接。 这减慢了连接速度,足以让我们考虑不同的解决方案。

我们发现以下类型的查询运行速度大约快 6 或 7 倍:

            select          c1.security_id, c1.dtz
                            ,sum(case when c1.period_id=1 then c1.rank_2 end) as rank_2_1
                            ,sum(case when c1.period_id=9 then c1.rank_2 end) as rank_2_9
                            ,sum(case when c1.period_id=11 then c1.rank_2 end) as rank_2_11
                            ,sum(case when c1.period_id=14 then c1.rank_2 end) as rank_2_14
            from            research.ranks c1
            where           c1.period_id in (1,11,14,9) and c1.dtz>now()-interval'10 years'
            group by        c1.security_id, c1.dtz;

我们可以使用总和,因为 table 有唯一的索引,所以我们知道永远只有一个记录被“求和”。我将其称为 SUM 版本。

速度好多了,之前写的一半代码都在质疑!两个问题:

  1. 我是否应该尝试在任何地方都使用 SUM 版本而不是 JOIN 版本,或者效率可能是特定结构的一个因素并且在其他情况下不太可能有用?

  2. 是不是我没有考虑的情况下SUM版本的逻辑有问题?

老实说,我认为您的“加入”版本无论如何都不是一个好主意。您只有一个(已分区)table,因此不需要任何连接。

SUM() 是要走的路,但我会使用 SUM(...) FILTER(WHERE ..) 而不是 CASE:

SELECT
    security_id,
    dtz,
    SUM(rank_2) FILTER (WHERE period_id = 1) AS rank_2_1,
    SUM(rank_2) FILTER (WHERE period_id = 9) AS rank_2_9,
    SUM(rank_2) FILTER (WHERE period_id = 11) AS rank_2_11,
    SUM(rank_2) FILTER (WHERE period_id = 14) AS rank_2_14,
FROM
    research.ranks
WHERE
    period_id IN ( 1, 11, 14, 9 ) 
AND dtz > now( ) - INTERVAL '10 years' 
GROUP BY
    security_id,
    dtz;