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 版本。
速度好多了,之前写的一半代码都在质疑!两个问题:
我是否应该尝试在任何地方都使用 SUM 版本而不是 JOIN 版本,或者效率可能是特定结构的一个因素并且在其他情况下不太可能有用?
是不是我没有考虑的情况下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;
我有一个很大的 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 版本。
速度好多了,之前写的一半代码都在质疑!两个问题:
我是否应该尝试在任何地方都使用 SUM 版本而不是 JOIN 版本,或者效率可能是特定结构的一个因素并且在其他情况下不太可能有用?
是不是我没有考虑的情况下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;