15.PG分区表-内置分区表

本文详细介绍了PostgreSQL从10版本开始支持的内置分区表功能,包括如何创建范围和列表分区,如何插入、查询、更新和删除数据。通过示例展示了在不同场景下,如非分区键和分区键查询的性能差异,并讨论了constraint_exclusion参数的影响。此外,还提到了PG14在更新分区数据方面的改进以及使用内置分区表的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

一、PG分区表-内置分区表

PostgreSQL10一个重量级新特性是支持内置分区表,目前支持范围分区和列表分区。

1.创建分区表

创建分区表的主要语法包含两部分:创建主表和创建分区。

创建主表:
create table table_name (...)
   [ partition by { range | list } ({ column_name | ( expression )})]

创建主表时须指定分区方式,可选的分区的方式为RANGE范围分区或LIST列表分区
创建分区:

create table table_name
partition of parent_table [()] for values partition_bound_spec

partition_bound_spec:范围分区,指定每个分区分区键的取值范围;
partition_bound_spec:列表分区,需指定每个分区的分区键值。

pg10创建内置分区表主要分为以下几个步骤:

(1)创建父表,指定分区键和分区策略。
(2)创建分区,创建分区时须指定分区表的父表和分区键的取值范围,
注意分区键的范围不要有重叠,否则会报错。
(3)在分区上创建相应索引,分区键上的索引是必须的,非分区键的索引可
根据实际应用场景选择是否创建。
范围分区表:
create table log_par
(
	id serial,
	user_id int4,
	create_time timestamp(0) without time zone
)partition by range (create_time);
以上分区策略为范围分区。

创建分区,并设置分区的分区键取值范围:
create table log_par_history partition of log_par for values from (UNBOUNDED) to ('2021-01-01');

postgres=# create table log_par_history partition of log_par for values from (UNBOUNDED) to ('2021-01-01');
ERROR:  cannot use column reference in partition bound expression
LINE 1: ...par_history partition of log_par for values from (UNBOUNDED)...

UNBOUNDED

Not sure where the book gets it from, but according to the manual, that should be maxvalue:
The special values MINVALUE and MAXVALUE may be used when
creating a range partition to indicate that there is no lower or upper bound on the column's value


不知道这本书是从哪里得到的,但根据手册,应该是最大值:
创建范围分区时可以使用特殊值 MINVALUE 和 MAXVALUE 来指示列值没有下限或上限

CREATE TABLE logs_gt_2011 PARTITION OF logs
FOR VALUES FROM ('2012-01-01') TO (maxvalue);
Share
Improve this answer
Follow
'


create table log_par_history partition of log_par for values from (MINVALUE) to ('2021-01-01');
create table log_par_202101 partition of log_par for values from ('2021-01-01') to ('2021-02-01');
create table log_par_202102 partition of log_par for values from ('2021-02-01') to ('2021-03-01');
create table log_par_202103 partition of log_par for values from ('2021-03-01') to ('2021-04-01');
create table log_par_202104 partition of log_par for values from ('2021-04-01') to ('2021-05-01');
create table log_par_202105 partition of log_par for values from ('2021-05-01') to ('2021-06-01');
create table log_par_202106 partition of log_par for values from ('2021-06-01') to ('2021-07-01');
create table log_par_202107 partition of log_par for values from ('2021-07-01') to ('2021-08-01');
create table log_par_202108 partition of log_par for values from ('2021-08-01') to ('2021-09-01');
create table log_par_202109 partition of log_par for values from ('2021-09-01') to ('2021-10-01');
create table log_par_202110 partition of log_par for values from ('2021-10-01') to ('2021-11-01');
create table log_par_202111 partition of log_par for values from ('2021-11-01') to ('2021-12-01');
create table log_par_202112 partition of log_par for values from ('2021-12-01') to ('2022-01-01');

注意分区的分区键范围不要有重叠,定义分区键范围实质上给分区创建了约束。

给所有分区的分区键创建索引:
create index idx_log_par_his_ctime on log_par_history using btree (create_time);
create index idx_log_par_202101_ctime on log_par_202101 using btree (create_time);
create index idx_log_par_202102_ctime on log_par_202102 using btree (create_time);
create index idx_log_par_202103_ctime on log_par_202103 using btree (create_time);
create index idx_log_par_202104_ctime on log_par_202104 using btree (create_time);
create index idx_log_par_202105_ctime on log_par_202105 using btree (create_time);
create index idx_log_par_202106_ctime on log_par_202106 using btree (create_time);
create index idx_log_par_202107_ctime on log_par_202107 using btree (create_time);
create index idx_log_par_202108_ctime on log_par_202108 using btree (create_time);
create index idx_log_par_202109_ctime on log_par_202109 using btree (create_time);
create index idx_log_par_202110_ctime on log_par_202110 using btree (create_time);
create index idx_log_par_202111_ctime on log_par_202111 using btree (create_time);
create index idx_log_par_202112_ctime on log_par_202112 using btree (create_time);

postgres=# \d log_par;
                                    Partitioned table "public.log_par"
   Column    |              Type              | Collation | Nullable |               Default
-------------+--------------------------------+-----------+----------+-------------------------------------
 id          | integer                        |           | not null | nextval('log_par_id_seq'::regclass)
 user_id     | integer                        |           |          |
 create_time | timestamp(0) without time zone |           |          |
Partition key: RANGE (create_time)
Number of partitions: 13 (Use \d+ to list them.)

postgres=# \d+ log_par;
                                                               Partitioned table "public.log_par"
   Column    |              Type              | Collation | Nullable |               Default               | Storage | Compression | Stats target | Description
-------------+--------------------------------+-----------+----------+-------------------------------------+---------+-------------+--------------+-------------
 id          | integer                        |           | not null | nextval('log_par_id_seq'::regclass) | plain   |           |              |
 user_id     | integer                        |           |          |                                     | plain   |           |              |
 create_time | timestamp(0) without time zone |           |          |                                     | plain   |
           |              |
Partition key: RANGE (create_time)
Partitions: log_par_202101 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2021-02-01 00:00:00'),
            log_par_202102 FOR VALUES FROM ('2021-02-01 00:00:00') TO ('2021-03-01 00:00:00'),
            log_par_202103 FOR VALUES FROM ('2021-03-01 00:00:00') TO ('2021-04-01 00:00:00'),
            log_par_202104 FOR VALUES FROM ('2021-04-01 00:00:00') TO ('2021-05-01 00:00:00'),
            log_par_202105 FOR VALUES FROM ('2021-05-01 00:00:00') TO ('2021-06-01 00:00:00'),
            log_par_202106 FOR VALUES FROM ('2021-06-01 00:00:00') TO ('2021-07-01 00:00:00'),
            log_par_202107 FOR VALUES FROM ('2021-07-01 00:00:00') TO ('2021-08-01 00:00:00'),
            log_par_202108 FOR VALUES FROM ('2021-08-01 00:00:00') TO ('2021-09-01 00:00:00'),
            log_par_202109 FOR VALUES FROM ('2021-09-01 00:00:00') TO ('2021-10-01 00:00:00'),
            log_par_202110 FOR VALUES FROM ('2021-10-01 00:00:00') TO ('2021-11-01 00:00:00'),
            log_par_202111 FOR VALUES FROM ('2021-11-01 00:00:00') TO ('2021-12-01 00:00:00'),
            log_par_202112 FOR VALUES FROM ('2021-12-01 00:00:00') TO ('2022-01-01 00:00:00'),
            log_par_history FOR VALUES FROM (MINVALUE) TO ('2021-01-01 00:00:00')

2.使用分区表

向分区表插入数据:
insert into log_par (user_id,create_time)
select round(100000000*random()),generate_series('2020-12-01'::date,'2021-12-01'::date,'1 minute');

查询数据:
select count(*) from log_par;
select count(*) from only log_par;

postgres=# select count(*) from log_par;
 count
--------
 525601
(1 row)

postgres=# select count(*) from only log_par;
 count
-------
     0
(1 row)

postgres=# \dt+ log_par*
                                                List of relations
 Schema |      Name       |       Type        |  Owner   | Persistence | Access method |    Size    | Description
--------+-----------------+-------------------+----------+-------------+---------------+------------+-------------
 public | log_par         | partitioned table | postgres | permanent   |               | 0 bytes    |
 public | log_par_202101  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202102  | table             | postgres | permanent   | heap          | 1776 kB    |
 public | log_par_202103  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202104  | table             | postgres | permanent   | heap          | 1904 kB    |
 public | log_par_202105  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202106  | table             | postgres | permanent   | heap          | 1904 kB    |
 public | log_par_202107  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202108  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202109  | table             | postgres | permanent   | heap          | 1904 kB    |
 public | log_par_202110  | table             | postgres | permanent   | heap          | 1968 kB    |
 public | log_par_202111  | table             | postgres | permanent   | heap          | 1904 kB    |
 public | log_par_202112  | table             | postgres | permanent   | heap          | 8192 bytes |
 public | log_par_history | table             | postgres | permanent   | heap          | 1968 kB    |
(14 rows)


查询表所有分区:
select 
	nmsp_parent.nspname as parent_schema,
	parent.relname as parent,
	nmsp_child.nspname as child_schema,
	child.relname as child_schema
from 
	pg_inherits join pg_class parent
    on pg_inherits.inhparent = parent.oid join pg_class child
	on pg_inherits.inhrelid = child.oid join pg_namespace nmsp_parent
	on nmsp_parent.oid = parent.relnamespace join pg_namespace nmsp_child
	on nmsp_child.oid = child.relnamespace
where 
	parent.relname = 'log_par';

3.内置分区表探索

postgres=# select
postgres-# nmsp_parent.nspname as parent_schema,
postgres-# parent.relname as parent,
postgres-# nmsp_child.nspname as child_schema,
postgres-# child.relname as child_schema
postgres-# from
postgres-# pg_inherits join pg_class parent
postgres-#     on pg_inherits.inhparent = parent.oid join pg_class child
postgres-# on pg_inherits.inhrelid = child.oid join pg_namespace nmsp_parent
postgres-# on nmsp_parent.oid = parent.relnamespace join pg_namespace nmsp_child
postgres-# on nmsp_child.oid = child.relnamespace
postgres-# where
postgres-# parent.relname = 'log_par';
 parent_schema | parent  | child_schema |  child_schema
---------------+---------+--------------+-----------------
 public        | log_par | public       | log_par_history
 public        | log_par | public       | log_par_202101
 public        | log_par | public       | log_par_202102
 public        | log_par | public       | log_par_202103
 public        | log_par | public       | log_par_202104
 public        | log_par | public       | log_par_202105
 public        | log_par | public       | log_par_202106
 public        | log_par | public       | log_par_202107
 public        | log_par | public       | log_par_202108
 public        | log_par | public       | log_par_202109
 public        | log_par | public       | log_par_202110
 public        | log_par | public       | log_par_202111
 public        | log_par | public       | log_par_202112
(13 rows)

4.添加分区

create table log_par_202201 partition of log_par for values from ('2022-01-01') to ('2022-02-01');
create table log_par_202202 partition of log_par for values from ('2022-02-01') to ('2022-03-01');

给分区创建索引
create index idx_log_ins_202201_ctime on log_par_202201 using btree (create_time);
create index idx_log_ins_202202_ctime on log_par_202202 using btree (create_time);

5.删除分区

第一种方法:
drop table log_par_202202;

另一种推荐的方法是解绑分区:
alter table log_par detach partition log_par_202201;

如果后续需要恢复这个分区,通过连接分区方式恢复分区即可:
alter table log_par attach partition log_par_202201 for values from ('2022-01-01') to ('2022-02-01');

6.性能测试

explain analyze select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';

postgres=# explain analyze select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';
                                                                        QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using idx_log_par_202101_ctime on log_par_202101 log_par  (cost=0.29..60.57 rows=1464 width=16) (actual time=0.019..0.276 rows=1439 loops=1)
   Index Cond: ((create_time > '2021-01-01 00:00:00'::timestamp without time zone) AND (create_time < '2021-01-02 00:00:00'::timestamp without time zone))
 Planning Time: 0.393 ms
 Execution Time: 0.365 ms
(4 rows)

基于分区表的分区键、非分区键查询和普通表性能有何差异?

在分区表log_par所有子表的user_id上创建索引:
create index idx_log_par_his_userid on log_par_history using btree (user_id);
create index idx_log_par_202101_userid on log_par_202101 using btree (user_id);
create index idx_log_par_202102_userid on log_par_202102 using btree (user_id);
create index idx_log_par_202103_userid on log_par_202103 using btree (user_id);
create index idx_log_par_202104_userid on log_par_202104 using btree (user_id);
create index idx_log_par_202105_userid on log_par_202105 using btree (user_id);
create index idx_log_par_202106_userid on log_par_202106 using btree (user_id);
create index idx_log_par_202107_userid on log_par_202107 using btree (user_id);
create index idx_log_par_202108_userid on log_par_202108 using btree (user_id);
create index idx_log_par_202109_userid on log_par_202109 using btree (user_id);
create index idx_log_par_202110_userid on log_par_202110 using btree (user_id);
create index idx_log_par_202111_userid on log_par_202111 using btree (user_id);
create index idx_log_par_202112_userid on log_par_202112 using btree (user_id);

场景一、根据user_id检索,对于分区表log_par而言这是非分区键
select * from log where user_id = ?;
select * from log_par where user_id = ?;

查找相同的user_id
select a.* from log a,log_par b where a.user_id = b.user_id limit 1;

postgres=# select a.* from log a,log_par b where a.user_id = b.user_id limit 1;
   id   | user_id  |     create_time
--------+----------+---------------------
 132279 | 81924442 | 2021-03-02 20:38:00
(1 row)


普通表log,根据user_id检索:
explain select * from log where user_id = 81924442;
postgres=# explain select * from log where user_id = 81924442;
                                QUERY PLAN
---------------------------------------------------------------------------
 Index Scan using idx_log_userid on log  (cost=0.42..8.44 rows=1 width=16)
   Index Cond: (user_id = 81924442)
(2 rows)

分区表log_par,根据user_id检索:
explain select * from log_par where user_id = 81924442;

postgres=# explain select * from log_par where user_id = 81924442;
                                           QUERY PLAN
-------------------------------------------------------------------------------------------------
 Gather  (cost=1000.00..7763.01 rows=30 width=16)
   Workers Planned: 2
   ->  Parallel Append  (cost=0.00..6760.01 rows=20 width=16)
         ->  Parallel Seq Scan on log_par_history log_par_1  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202101 log_par_2  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202103 log_par_4  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202105 log_par_6  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202107 log_par_8  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202108 log_par_9  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202110 log_par_11  (cost=0.00..570.24 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202104 log_par_5  (cost=0.00..551.65 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202106 log_par_7  (cost=0.00..551.65 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202109 log_par_10  (cost=0.00..551.65 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202111 log_par_12  (cost=0.00..551.65 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202102 log_par_3  (cost=0.00..514.47 rows=1 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202112 log_par_13  (cost=0.00..23.60 rows=5 width=16)
               Filter: (user_id = 81924442)
         ->  Parallel Seq Scan on log_par_202201 log_par_14  (cost=0.00..23.60 rows=5 width=16)
               Filter: (user_id = 81924442)
(31 rows)

log表,sql执行三次,最短时间0.430 ms:
select * from log where user_id = 81924442;

postgres=# \timing
Timing is on.
postgres=# select * from log where user_id = 81924442;
   id   | user_id  |     create_time
--------+----------+---------------------
 132279 | 81924442 | 2021-03-02 20:38:00
(1 row)

Time: 0.705 ms
postgres=# select * from log where user_id = 81924442;
   id   | user_id  |     create_time
--------+----------+---------------------
 132279 | 81924442 | 2021-03-02 20:38:00
(1 row)

Time: 0.533 ms
postgres=# select * from log where user_id = 81924442;
   id   | user_id  |     create_time
--------+----------+---------------------
 132279 | 81924442 | 2021-03-02 20:38:00
(1 row)

Time: 0.430 ms

log_par表,sql执行三次,最短时间57.879 ms:
select * from log_par where user_id = 81924442;

postgres=# select * from log_par where user_id = 81924442;
 id | user_id  |     create_time
----+----------+---------------------
  7 | 81924442 | 2020-12-01 00:06:00
(1 row)

Time: 59.518 ms
postgres=# select * from log_par where user_id = 81924442;
 id | user_id  |     create_time
----+----------+---------------------
  7 | 81924442 | 2020-12-01 00:06:00
(1 row)

Time: 61.964 ms
postgres=# select * from log_par where user_id = 81924442;
 id | user_id  |     create_time
----+----------+---------------------
  7 | 81924442 | 2020-12-01 00:06:00
(1 row)

Time: 57.879 ms

场景二、根据create_time检索,create_time字段分区表log_par分区键。

select * from log where create_time > '2021-01-01' and create_time < '2021-01-02';
select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';

log表,sql执行三次,1.722 ms,1.647 ms,1.863 ms,最短时间1.647 ms:
select * from log where create_time > '2021-01-01' and create_time < '2021-01-02';

log_par表,log_par执行三次,1.668 ms,1.840 ms,1.798 ms,最短时间1.668 ms:
select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';

log_par_202101表,log_par_202101执行三次,1.776 ms,1.629 ms,1.565 ms,最短时间1.565 ms:
select * from log_par_202101 where create_time > '2021-01-01' and create_time < '2021-01-02';


PG14普通表、内置分区表性能对比
查询场景                         普通表:log执行时间     分区表:查询log_par父表执行时间    分区表:查询log_ins子表执行时间 
根据非分区键user_id查询          0.430 ms               57.879 ms                         不支持
根据分区键create_time范围查询    1.647 ms               1.668 ms                           1.565 ms

结论:
(1)内置分区表根据非分区键查询相比普通表性能差距较大,
因为这种场景分区表的执行计划会扫描所有分区。

(2)内置分区表根据分区键查询相比普通表性能有小幅降低,
而查询分区表子表性能比普通表略有提升。

7.constraint_exclusion参数

set constraint_exclusion = off;
explain analyze select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';

postgres=# show constraint_exclusion;
 constraint_exclusion
----------------------
 off
(1 row)

Time: 0.281 ms
postgres=# explain analyze select * from log_par where create_time > '2021-01-01' and create_time < '2021-01-02';
                                                                        QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using idx_log_par_202101_ctime on log_par_202101 log_par  (cost=0.29..60.57 rows=1464 width=16) (actual time
=0.023..0.284 rows=1439 loops=1)
   Index Cond: ((create_time > '2021-01-01 00:00:00'::timestamp without time zone) AND (create_time < '2021-01-02 00:00:00'::timestamp without time zone))
 Planning Time: 0.221 ms
 Execution Time: 0.415 ms
(4 rows)

Time: 1.004 ms

8.PG14更新分区数据

PG10之前内置分区表update操作目前不支持更新记录跨分区的情况,
也就是说只允许分区内的更新:

PG14可以支持更新记录跨分区的情况:
select * from log_par_202101 limit 1;
postgres=# select * from log_par_202101 limit 1;
  id   | user_id  |     create_time
-------+----------+---------------------
 44641 | 91332064 | 2021-01-01 00:00:00
(1 row)

update log_par set create_time = '2021-02-02 01:01:01' where user_id = 91332064;
postgres=# update log_par set create_time = '2021-02-02 01:01:01' where user_id = 91332064;
UPDATE 1
Time: 41.417 ms

9.内置分区注意事项

(1)当往父表上插入数据时,数据会自动根据分区键路由规则插入到分区中,目前仅支持范围分区和列表分区。
(2)分区表上的索引、约束需使用单独的命令创建,目前没有办法一次性自动在所有分区上创建索引、约束。
(3)内置分区表不支持定义(全局)主键,在分区表的分区上创建主键是可以的。
(4)内置分区表的内部实现了继承。
(5)update语句的新记录违反当前分区键的约束则会报错,
(pg10,update语句的新记录目前不支持跨分区的情况;pg14,update语句的新记录目前支持跨分区的情况)
<think>我们正在处理一个关于PostgreSQL查询分区表的问题。用户想要查询指定表的所有分区表。 根据PostgreSQL的文档,从版本10开始引入了声明式分区,分区表的信息存储在系统目录中。 思路: 1.PostgreSQL中,分区表是通过继承实现的(旧方法)或使用内置分区表特性(新方法,10版本及以上)。 2. 对于内置分区表(声明式分区),我们可以查询`pg_inherits`系统表,或者使用`pg_partitioned_table`来获取分区信息。 3. 但是,用户可能是指定了一个分区父表,然后想要获取这个父表的所有子分区(即分区表)。 对于内置分区表(声明式分区): - 分区父表在`pg_class`中的`relkind`为'p'(表示是分区父表),而分区子表的`relkind`为'r'(普通表,但它们是分区子表)。 - 我们可以通过`pg_inherits`表来查找给定父表的所有子表(即分区表)。每个子表在`pg_inherits`中有一条记录指向其父表。 另外,我们也可以使用`pg_partitioned_table`来获取分区父表的信息,然后结合`pg_inherits`来获取子表。 但是,问题是要查询指定表(假设这个表是分区父表)的所有分区表(即子表)。 查询语句建议: 假设我们有一个表名为`parent_table`,位于模式`schema_name`下。 方法1:使用`pg_inherits` ```sql SELECT c.relname AS child_table FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid JOIN pg_class p ON p.oid = i.inhparent WHERE p.relname = 'parent_table' AND p.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_name'); ``` 方法2:使用`pg_partitioned_table`(但这里主要是获取父表信息,然后同样需要结合`pg_inherits`来获取子表) 注意:在PostgreSQL 10及以上版本,推荐使用声明式分区,但旧的分区方法(通过继承)仍然存在。所以上述查询可以覆盖两种方法吗? 实际上,声明式分区在系统目录中也是使用继承来实现的,所以`pg_inherits`对于两种方法都适用。 但是,用户可能只关心声明式分区的子表(即分区表),或者也可能包括旧式继承的子表。根据问题,我们假设用户指的是声明式分区,因为这是现在推荐的方式。 另外,用户可能想知道如何列出所有分区表(子表)而不指定父表?但问题中是指定表(即父表)的所有分区表。 因此,我们给出针对指定父表的分区子表查询。 另外,在PostgreSQL 12及以上,支持分区表的分区层次(即子分区),所以一个分区子表本身也可能是另一个分区表的父表。但问题中通常是指直接分区子表。如果需要递归所有分区,则要使用递归查询。 但用户问题没有明确是否递归,我们默认先查询直接分区子表。 如果用户需要递归所有层级的分区表,我们可以使用递归CTE。 因此,我们提供两种方案: 1. 直接子表(非递归) 2. 所有子表(递归) 根据用户问题“查询指定表的所有分区表”,可以理解为包括所有层级的分区表(即递归),但通常也可能只需要直接子表。我们在这里可以都提供。 另外,注意表名和模式名的大小写:PostgreSQL默认情况下是不区分大小写的,除非创建时使用了双引号。在查询时,如果表名是大小写敏感的,我们需要使用双引号,但通常我们使用小写。 为了安全,我们使用模式名和表名时,可以按照用户输入的方式处理,但这里我们假设用户输入的是小写,并且没有使用双引号创建。 如果用户输入的表名是大小写敏感的,我们需要在查询条件中使用双引号,例如:p.relname = 'MyTable' 会变成 p.relname = 'mytable'(因为系统默认转成小写),所以如果表名是大写,我们需要写双引号:p.relname = '"MyTable"'。但这样很麻烦。因此,我们建议用户使用小写表名,或者在查询时使用正确的引号。 为了简化,我们假设用户输入的表名和模式名都是小写,且没有使用双引号。 因此,我们给出以下查询: 方案1:直接子表(非递归) ```sql SELECT nmsp_child.nspname AS child_schema, child.relname AS child_table FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace WHERE parent.relname = 'parent_table' AND nmsp_parent.nspname = 'schema_name'; ``` 方案2:递归所有子表(包括子分区) 使用递归CTE,因为分区表可能还有子分区。 ```sql WITH RECURSIVE partitions AS ( SELECT c.oid AS root_oid, c.oid, c.relname, c.relnamespace, 0 AS level FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 'parent_table' AND n.nspname = 'schema_name' UNION ALL SELECT p.root_oid, c.oid, c.relname, c.relnamespace, p.level + 1 FROM pg_inherits i JOIN partitions p ON i.inhparent = p.oid JOIN pg_class c ON c.oid = i.inhrelid ) SELECT nsp.nspname AS child_schema, partitions.relname AS child_table FROM partitions JOIN pg_namespace nsp ON nsp.oid = partitions.relnamespace WHERE oid <> root_oid; -- 排除根节点(即父表本身) ``` 注意:在递归CTE中,我们从父表开始,然后逐级向下查找子表。最后我们排除根节点(即父表本身),因为父表不是分区表(子表)。这样我们就得到了所有层级的分区表(子表)。 但是,请注意:在递归CTE中,我们只获取了分区子表,没有区分直接和间接,而是全部。 另外,我们也可以选择不排除根节点,但根节点是父表,不是分区子表,所以排除。 因此,我们提供了两种查询。 根据用户的需求,如果只需要直接分区子表,使用第一种;如果需要所有层级的分区子表(包括子分区),使用第二种。 另外,在PostgreSQL 12及以上,我们还可以使用`pg_partition_tree`函数,但这个函数是系统管理函数,可能不适合所有用户,并且它返回的信息需要解析。 例如: ```sql SELECT relid::regclass AS partition FROM pg_partition_tree('schema_name.parent_table'); ``` 这个函数会返回包括父表在内的所有分区表(父表作为第一个,然后子表)。如果我们不想要父表,可以过滤掉第一行(根据`is_leaf`和`level`),但注意这个函数返回的列包括:parentrelid, relid, isleaf, level。 具体: ```sql SELECT relid::regclass AS partition FROM pg_partition_tree('schema_name.parent_table') WHERE parentrelid IS NOT NULL; -- 这样会排除根节点(根节点的parentrelid为null) ``` 或者根据level过滤(根节点的level为0,子节点为1,孙节点为2...),如果我们只想要分区子表(不管层级),那么排除level=0的即可。 但是,`pg_partition_tree`函数在PG12及以上版本可用。 因此,如果用户使用的是PG12+,推荐使用这个函数,因为它更简单。 总结: 根据用户使用的PostgreSQL版本: 1. 如果版本>=12,可以使用`pg_partition_tree`函数。 2. 否则,使用递归CTE(适用于所有版本,包括旧版本,但需要递归支持,PG8.4以上支持递归CTE)。 但是,用户可能不知道版本,所以我们可以提供两个版本,并说明适用条件。 因此,我们给出以下建议: 如果您的PostgreSQL版本是12及以上,可以使用: ```sql SELECT relid::regclass AS partition_name FROM pg_partition_tree('schema_name.parent_table') WHERE parentrelid IS NOT NULL; ``` 或者(排除根节点的另一种方式): ```sql SELECT relid::regclass AS partition_name FROM pg_partition_tree('schema_name.parent_table') WHERE is_leaf = false; -- 注意:这里is_leaf=false表示非叶子节点?不对,is_leaf=true才是叶子节点。但是我们要所有分区子表,包括中间分区和叶子分区。所以不能根据is_leaf过滤。 实际上,我们只需要排除根节点(即level=0),所以: SELECT relid::regclass AS partition_name FROM pg_partition_tree('schema_name.parent_table') WHERE level > 0; ``` 但是,注意:`pg_partition_tree`函数返回的结果包括所有子分区(递归的),并且它返回的每一行代表一个分区表,其中根节点(父表)的level为0,直接子分区为1,孙分区为2,以此类推。 所以,如果我们只想要直接子分区(第一级),可以添加条件`level=1`。 因此,根据用户需求,我们可以灵活调整。 现在,回到用户问题:查询指定表的所有分区表(子表)。我们假设用户需要所有层级的分区表(即递归所有子表),所以使用`level>0`。 但是,如果用户只需要直接分区表,则使用`level=1`(在`pg_partition_tree`中)或者在第一种方法(非递归)中使用`pg_inherits`。 因此,我们提供多种方法供用户选择。 最后,我们给出一个综合的示例: 假设模式名为`public`,表名为`parent_table`,并且我们想要所有层级的分区子表。 方法1(适用于PG12+): ```sql SELECT relid::regclass AS partition_table_name FROM pg_partition_tree('public.parent_table') WHERE level > 0; ``` 方法2(适用于所有支持递归CTE的版本,PG8.4+): ```sql WITH RECURSIVE partitions AS ( SELECT c.oid AS root_oid, c.oid, c.relnamespace, c.relname, 0 AS level FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 'parent_table' AND n.nspname = 'public' UNION ALL SELECT p.root_oid, c.oid, c.relnamespace, c.relname, p.level + 1 FROM pg_inherits i JOIN partitions p ON i.inhparent = p.oid JOIN pg_class c ON c.oid = i.inhrelid ) SELECT nsp.nspname AS child_schema, partitions.relname AS child_table FROM partitions JOIN pg_namespace nsp ON nsp.oid = partitions.relnamespace WHERE partitions.oid <> partitions.root_oid; ``` 注意:在递归CTE中,我们使用`oid <> root_oid`来排除根节点(即父表)。 另外,我们也可以不存储root_oid,而是通过递归开始时固定根节点,然后在结果中排除根节点(即level>0),但这样写也可以。 最后,我们还要考虑性能:对于非常大的分区表,递归查询可能效率不高,但通常分区数量不会太多。 因此,我们根据用户的需求和版本提供不同的查询。 另外,用户可能会问如何查询分区表的结构(比如分区键等),但当前问题只要求列出分区表名。 所以,我们回答: 在PostgreSQL中,要查询指定父表的所有分区表(子表),可以使用以下方法之一: 1. 对于较新的PostgreSQL版本(12及以上),使用内置函数`pg_partition_tree`,并过滤掉根节点(使用`level>0`)。 2. 对于较旧的版本,使用递归CTE来遍历继承层次。 示例查询(假设父表在模式`public`下,表名为`parent_table`): **方法1(PG12+):** ```sql SELECT relid::regclass AS partition_table_name FROM pg_partition_tree('public.parent_table') WHERE level > 0; ``` **方法2(通用):** ```sql WITH RECURSIVE partitions AS ( SELECT c.oid AS root_oid, c.oid, c.relnamespace, c.relname, 0 AS level FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 'parent_table' AND n.nspname = 'public' UNION ALL SELECT p.root_oid, c.oid, c.relnamespace, c.relname, p.level + 1 FROM pg_inherits i JOIN partitions p ON i.inhparent = p.oid JOIN pg_class c ON c.oid = i.inhrelid ) SELECT nsp.nspname AS child_schema, partitions.relname AS child_table FROM partitions JOIN pg_namespace nsp ON nsp.oid = partitions.relnamespace WHERE partitions.oid <> partitions.root_oid; ``` 注意:请将`public`和`parent_table`替换为实际的模式名和表名。 如果只需要直接分区子表(第一级),则: 对于方法1(PG12+): ```sql SELECT relid::regclass AS partition_table_name FROM pg_partition_tree('public.parent_table') WHERE level = 1; ``` 对于方法2(通用),可以修改递归CTE使其不递归(即只查一级),或者使用非递归查询: ```sql SELECT c.relname AS child_table, ns.nspname AS child_schema FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid JOIN pg_class p ON p.oid = i.inhparent JOIN pg_namespace ns ON ns.oid = c.relnamespace WHERE p.relname = 'parent_table' AND p.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public'); ``` 以上。 最后,我们生成相关问题。</think>### PostgreSQL 查询指定表的所有分区表PostgreSQL 中查询指定父表的所有分区表(子表),可通过系统目录表实现。以下是两种常用方法: #### 方法 1:使用 `pg_inherits` 系统表(通用方法) ```sql SELECT c.relnamespace::regnamespace AS schema_name, c.relname AS partition_table FROM pg_inherits i JOIN pg_class c ON i.inhrelid = c.oid -- 子表 OID JOIN pg_class p ON i.inhparent = p.oid -- 父表 OID WHERE p.relname = 'your_parent_table' -- 替换为父表名 AND p.relnamespace = 'your_schema'::regnamespace; -- 替换为父表所在模式 ``` #### 方法 2:使用 `pg_partition_tree` 函数(PG 12+ 推荐) ```sql SELECT relid::regclass AS partition_table FROM pg_partition_tree('your_schema.your_parent_table') -- 替换为完整父表名 WHERE parentrelid IS NOT NULL; -- 排除根节点(父表自身) ``` #### 关键说明: 1. **替换参数**: - `your_schema` → 父表所在的模式(如 `public`) - `your_parent_table` → 父表的名称 2. **递归包含**: 以上查询会返回**所有层级的分区子表**(包括子分区)。 3. **版本兼容性**: - `pg_inherits` 适用于所有支持分区的 PostgreSQL 版本(≥ 10.0) - `pg_partition_tree` 仅限 PostgreSQL 12.0 及以上[^1] 4. **结果示例**: ``` partition_table ------------------ sales_2023_q1 sales_2023_q2 sales_2024_q1 ``` #### 注意事项: - 确保父表是**分区主表**(`pg_class.relkind = 'p'`) - 若表名大小写敏感,需用双引号包裹:`"MyTable"` - 权限要求:用户需具有 `pg_class` 的查询权限 --- ### 相关问题 1. **如何判断某张表是否为分区父表?** ```sql SELECT relname FROM pg_class WHERE relkind = 'p' AND relname = 'your_table'; ``` 2. **PostgreSQL 中如何列出所有分区表及其父表?** ```sql SELECT p.relname AS parent_table, c.relname AS partition_table FROM pg_inherits i JOIN pg_class p ON i.inhparent = p.oid JOIN pg_class c ON i.inhrelid = c.oid; ``` 3. **分区表与普通表在系统目录中有何区别?** 分区子表的 `pg_class.relispartition` 为 `t`,普通表为 `f`[^1]。 4. **如何查询分区表的分区键定义?** ```sql SELECT partattrs FROM pg_partitioned_table WHERE partrelid = 'parent_table'::regclass; ``` [^1]: 系统目录字段说明参考 PostgreSQL 官方文档:`pg_class.relispartition` 标识分区表,`pg_partitioned_table` 存储分区键信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值