Skip to content

创建与使用继承表 ​

在理解了继承的基本语义后,本节进入实操:如何完整地创建一张父表与多张子表,向父子表插入数据时行为有何差异,如何查询、如何用 ONLY 收窄范围、如何借助 tableoid 追溯数据来源,以及如何用 ALTER TABLE ... INHERIT / NO INHERIT 动态调整继承关系。

这些操作组合起来就是老式分区(legacy partitioning)的完整工具箱。掌握它们,既能正确使用继承,也能读懂现存系统中大量基于继承的历史设计。

前置知识

阅读本节前,建议先了解:表继承概述、创建表(CREATE TABLE)

基础概念 ​

建表时的继承声明。 子表在 CREATE TABLE 中通过 INHERITS (parent) 声明,可继承一个或多个父表。父表必须已存在,且子表不能重复声明与父表同名的列(除非类型兼容,会被合并)。

sql
CREATE TABLE measurements (
  city_id   int,
  logdate   date,
  peaktemp  int
);

CREATE TABLE measurements_y2025 (
  note text
) INHERITS (measurements);

插入语义:绝不路由。

语句行为是否影响其他表
INSERT INTO parent只写父表否
INSERT INTO child只写子表否
INSERT INTO parent 带子表专有列直接报错(列不存在)—
COPY parent FROM ...只写父表否
text
-- 试图把带 note 的行插进父表
INSERT INTO measurements (city_id, logdate, peaktemp, note) VALUES (1, '2025-01-01', 20, 'x');
ERROR:  column "note" of relation "measurements" does not exist

即使把 note 去掉、插入成功,数据也留在 measurements,不会「落到」拥有 note 列的 measurements_y2025。

查询语义:默认向下递归。

写法作用范围
SELECT * FROM measurements父表 + 全部后代表
SELECT * FROM ONLY measurements仅父表
SELECT * FROM measurements *显式含后代(与默认等价)

行来源识别。 每条行都有一个隐含的系统列 tableoid,可直接与 pg_class.oid 连接,或用 ::regclass 转换显示表名。

语法与用法 ​

继承表的完整语法涉及建表、改写关系、查询三部分:

text
-- 1. 建父表
CREATE TABLE 父表 ( 公共列定义…, 公共约束… );

-- 2. 建子表(继承父表列 + 追加专有列)
CREATE TABLE 子表 (
    专有列定义…,
    [ 子表级 CHECK 约束… ]
) INHERITS (父表);

-- 3. 动态增删父子关系(表结构必须兼容)
ALTER TABLE 子表 INHERIT    父表;
ALTER TABLE 子表 NO INHERIT 父表;

-- 4. 已有表要变成子表时,先用 LIKE 复制结构
CREATE TABLE 新子表 (LIKE 父表 INCLUDING CONSTRAINTS);

-- 5. 查询范围控制
SELECT … FROM 父表;             -- 递归
SELECT … FROM ONLY 父表;        -- 不递归
SELECT … FROM 父表 *;           -- 显式递归

-- 6. 行来源
SELECT tableoid, tableoid::regclass, … FROM 父表;

最简可运行示例——父子表建好、插入、验证默认递归:

sql
DROP TABLE IF EXISTS measurements_y2025, measurements;

CREATE TABLE measurements (
  city_id  int,
  logdate  date,
  peaktemp int
);

CREATE TABLE measurements_y2025 () INHERITS (measurements);

INSERT INTO measurements       VALUES (1, '2025-01-01', 20);
INSERT INTO measurements_y2025 VALUES (2, '2025-06-15', 31);

SELECT tableoid::regclass AS src, city_id, logdate, peaktemp
FROM measurements
ORDER BY logdate;
text
          src           | city_id |  logdate   | peaktemp
------------------------+---------+------------+----------
 measurements           |       1 | 2025-01-01 |       20
 measurements_y2025     |       2 | 2025-06-15 |       31
(2 rows)

详细说明 ​

完整建表示例:父表 + 多子表 ​

一个贴近生产的例子:按年切分的测量表。父表放公共列与公共约束,子表只加自己的元信息:

sql
DROP TABLE IF EXISTS meas_y2026, meas_y2025, meas_y2024, meas CASCADE;

CREATE TABLE meas (
  city_id  int          NOT NULL,
  logdate  date         NOT NULL,
  peaktemp int,
  CONSTRAINT ck_peaktemp_range CHECK (peaktemp IS NULL OR peaktemp BETWEEN -100 AND 100)
);

CREATE TABLE meas_y2024 () INHERITS (meas);
CREATE TABLE meas_y2025 () INHERITS (meas);
CREATE TABLE meas_y2026 () INHERITS (meas);

-- 每个子表加自己的「数据范围」检查约束,用于老式分区的约束排除
ALTER TABLE meas_y2024 ADD CONSTRAINT ck_y2024
  CHECK (logdate >= DATE '2024-01-01' AND logdate < DATE '2025-01-01');
ALTER TABLE meas_y2025 ADD CONSTRAINT ck_y2025
  CHECK (logdate >= DATE '2025-01-01' AND logdate < DATE '2026-01-01');
ALTER TABLE meas_y2026 ADD CONSTRAINT ck_y2026
  CHECK (logdate >= DATE '2026-01-01' AND logdate < DATE '2027-01-01');

观察结构:

sql
\d meas
text
                 Table "public.meas"
  Column  |  Type   | Collation | Nullable | Default
----------+---------+-----------+----------+---------
 city_id  | integer |           | not null |
 logdate  | date    |           | not null |
 peaktemp | integer |           |          |
Check constraints:
    "ck_peaktemp_range" CHECK (peaktemp IS NULL OR peaktemp >= '-100'::integer AND peaktemp <= 100)
Number of child tables: 3 (Use \d+ to list them.)

\d 的输出末尾会明确列出子表数量——这是确认继承关系是否建立成功最直观的方式。每个子表的 CHECK 约束独立存在,互不影响。

向父表与子表插入的语义 ​

插入永远只作用于指定表,这是继承最容易误解的一点:

sql
-- 插入父表:数据留在父表
INSERT INTO meas (city_id, logdate, peaktemp) VALUES (10, '2023-12-31', 5);

-- 插入子表:数据留在子表
INSERT INTO meas_y2025 (city_id, logdate, peaktemp) VALUES (20, '2025-03-01', 18);
sql
SELECT tableoid::regclass AS src, city_id, logdate
FROM meas ORDER BY logdate;
text
      src       | city_id |  logdate
----------------+---------+------------
 meas           |      10 | 2023-12-31
 meas_y2025     |      20 | 2025-03-01
(2 rows)

注意 2023-12-31 这行没有被路由到任何一年的子表,因为它本来就该进父表(父表才是那张「没有 CHECK 约束」的兜底表)。父表的 CHECK 只约束 peaktemp,不约束 logdate,所以任何日期都能进父表。

子表的 CHECK 约束会真的拦截越界数据:

sql
INSERT INTO meas_y2025 (city_id, logdate, peaktemp) VALUES (30, '2024-05-01', 22);
text
ERROR:  new row for relation "meas_y2025" violates check constraint "ck_y2025"
DETAIL:  Failing row contains (30, 2024-05-01, 22).

这个约束保护在直接插子表时有效;但通过父表插入不会被任何子表约束检查——这正是继承式分区的最大隐患,也是为什么需要规则或触发器来重定向。

查询父表返回所有子表数据 ​

默认递归是继承查询的核心能力,它让「一张父表 = 整个表族」:

sql
SELECT count(*) FROM meas;          -- 全部
SELECT count(*) FROM ONLY meas;     -- 仅父表
text
 count
-------
     2
(1 row)

 count
-------
     1
(1 row)

看执行计划可以验证规划器如何展开:

sql
EXPLAIN (COSTS OFF) SELECT count(*) FROM meas;
text
 Aggregate
   ->  Append
         ->  Seq Scan on meas meas_1
         ->  Seq Scan on meas_y2024 meas_2
         ->  Seq Scan on meas_y2025 meas_3
         ->  Seq Scan on meas_y2026 meas_4
(6 rows)

四个分支说明「父表 + 三个子表」都被纳入。这是继承查询的实际代价:即使子表为空,也要访问它们(空表扫描很便宜,但仍是规划与执行开销)。

ONLY 关键字:收窄到父表 ​

SELECT / UPDATE / DELETE / 多数 ALTER TABLE 变体都支持 ONLY:

sql
-- 只读父表
SELECT * FROM ONLY meas;

-- 只更新父表
UPDATE ONLY meas SET peaktemp = 6 WHERE city_id = 10;

-- 只删父表
DELETE FROM ONLY meas WHERE city_id = 10;

-- 只改父表结构(不传播给子表)
ALTER TABLE ONLY meas ADD COLUMN quality text;
text
-- 注意:ALTER TABLE ONLY meas ADD COLUMN quality text;
-- 之后子表并没有 quality 列!

这一点与「不加 ONLY 的 ALTER TABLE」形成鲜明对比——后者会把新列加到整棵子树。加 ONLY 后子表缺列,会导致父表查询报错:

sql
SELECT quality FROM meas;
text
ERROR:  column "quality" does not exist

原因:父表有这个列,子表没有,展开成 Append 时无法统一输出列集合。这解释了为什么 ALTER TABLE ONLY ... ADD COLUMN 在继承结构上几乎总是坏主意。

子表 CHECK 约束区分条件(老式分区) ​

老式分区的性能依赖规划器的约束排除(constraint exclusion):如果查询条件与子表的 CHECK 约束矛盾,该子表可被跳过。

sql
SET constraint_exclusion = on;   -- 让规划器利用 CHECK 约束排除

EXPLAIN (COSTS OFF)
SELECT * FROM meas WHERE logdate >= '2025-01-01' AND logdate < '2026-01-01';
text
 Append
   ->  Seq Scan on meas meas_1
         Filter: ((logdate >= '2025-01-01'::date) AND (logdate < '2026-01-01'::date))
   ->  Seq Scan on meas_y2025 meas_3
         Filter: ((logdate >= '2025-01-01'::date) AND (logdate < '2026-01-01'::date))
(4 rows)

meas_y2024 与 meas_y2026 被跳过,因为它们各自的 CHECK 与查询条件矛盾。但 meas(父表)无法被排除——它没有 logdate 范围的约束,可能含任意日期。这就是老式分区必须准备「兜底表」引发的代价:兜底表永远要扫。

sql
-- 查看约束排除的配置
SHOW constraint_exclusion;
text
 constraint_exclusion
----------------------
 partition
(1 row)

父表 DEFAULT 的继承行为 ​

父表列的 DEFAULT 会被子表继承(作为列的默认值复制到子表定义中):

sql
DROP TABLE IF EXISTS d_parent, d_child CASCADE;
CREATE TABLE d_parent (
  id        int,
  created_at timestamptz NOT NULL DEFAULT now(),
  status     text DEFAULT 'new'
);
CREATE TABLE d_child () INHERITS (d_parent);

\d d_child
text
                 Table "public.d_child"
   Column   |           Type           | Nullable | Default
------------+--------------------------+----------+---------
 id         | integer                  |          |
 created_at | timestamp with time zone | not null | now()
 status     | text                     |          | 'new'::text

子表拿到了 now() 与 'new' 两个默认值。若之后修改父表的默认值,只有新加的列或之后显式同步的操作才会传播——ALTER TABLE parent ALTER COLUMN ... SET DEFAULT 会向下传播到子表:

sql
ALTER TABLE d_parent ALTER COLUMN status SET DEFAULT 'draft';

SELECT table_name, column_default
FROM information_schema.columns
WHERE table_name IN ('d_parent', 'd_child') AND column_name = 'status'
ORDER BY table_name;
text
 table_name | column_default
------------+----------------
 d_child    | 'draft'::text
 d_parent   | 'draft'::text
(2 rows)

同名同类型列的 DEFAULT 合并规则与列合并一致:子表若自己定义了不同默认值,以子表定义为准;父表后续的 ALTER 会覆盖子表的默认值。

INSERT ... RETURNING 与 tableoid ​

RETURNING 可以返回 tableoid,从而在「不知道数据进了哪张表」时立刻得知结果:

sql
INSERT INTO meas (city_id, logdate, peaktemp)
VALUES (99, '2025-07-07', 25)
RETURNING tableoid::regclass AS inserted_into, city_id, logdate;
text
 inserted_into | city_id |  logdate
---------------+---------+------------
 meas          |      99 | 2025-07-07
(1 row)

如果你期望它进 meas_y2025,这个输出就是「没路由」的直接证据。批量写入时可以用 tableoid 做审计:

sql
WITH ins AS (
  INSERT INTO meas (city_id, logdate, peaktemp)
  SELECT city_id, logdate, peaktemp FROM staging_meas
  RETURNING tableoid::regclass AS tgt
)
SELECT tgt, count(*) FROM ins GROUP BY tgt;

如果只有一行(meas),说明全部数据都堆进了父表——这是老式分区最常见的生产事故。

ALTER TABLE ... INHERIT / NO INHERIT ​

动态调整继承关系是继承式分区的「加/卸分区」手段。要求新子表结构兼容:同名同类型的列、同名同表达式的 CHECK 约束。

sql
-- 准备一张兼容表
CREATE TABLE meas_y2027 (LIKE meas INCLUDING CONSTRAINTS);
ALTER TABLE meas_y2027 ADD CONSTRAINT ck_y2027
  CHECK (logdate >= DATE '2027-01-01' AND logdate < DATE '2028-01-01');

-- 挂入继承树
ALTER TABLE meas_y2027 INHERIT meas;

-- 摘出继承树(数据与表都保留)
ALTER TABLE meas_y2027 NO INHERIT meas;

若结构不兼容会报错:

sql
CREATE TABLE bad_child (city_id bigint, logdate date, peaktemp int);
ALTER TABLE bad_child INHERIT meas;
text
ERROR:  child table is missing column "city_id"

INHERIT 会对父子表加锁并扫描子表验证约束兼容性,大表上要安排在维护窗口。

sql
-- 用系统目录确认 INHERIT / NO INHERIT 的结果
SELECT child.relname
FROM pg_inherits i
JOIN pg_class child ON child.oid = i.inhrelid
WHERE i.inhparent = 'meas'::regclass
ORDER BY child.relname;

UPDATE / DELETE 的影响范围 ​

这是继承最危险的部分:不加 ONLY 时,UPDATE / DELETE 会作用于整棵子树。

sql
-- 危险:会改到所有子表
UPDATE meas SET peaktemp = 0 WHERE city_id > 0;
-- 只改父表
UPDATE ONLY meas SET peaktemp = 0 WHERE city_id > 0;

-- 危险:会删掉所有子表的行
DELETE FROM meas WHERE logdate < '2025-01-01';
-- 只删父表
DELETE FROM ONLY meas WHERE logdate < '2025-01-01';

看 DELETE 的计划会更清楚:

sql
EXPLAIN (COSTS OFF) DELETE FROM meas WHERE logdate < '2024-01-01';
text
 Delete
   ->  Append
         ->  Seq Scan on meas meas_1
               Filter: (logdate < '2024-01-01'::date)
         ->  Seq Scan on meas_y2024 meas_2
               Filter: (logdate < '2024-01-01'::date)
         ->  Seq Scan on meas_y2025 meas_3
               Filter: ...
         ->  Seq Scan on meas_y2026 meas_4
               Filter: ...
(9 rows)

注意 Delete 在 Append 之上——一次 DELETE 语句会删除多张物理表中的行。这在生产环境需要极其小心,尤其是「清理历史数据」类脚本。

子表上直接操作与继承列的只读性。 子表中继承来的列不能在子表层面删除或改类型:

sql
ALTER TABLE meas_y2025 DROP COLUMN city_id;
text
ERROR:  cannot drop inherited column "city_id"
sql
ALTER TABLE meas_y2025 ALTER COLUMN city_id TYPE bigint;
text
ERROR:  cannot alter inherited column "city_id"

必须在父表上操作,由 ALTER TABLE parent 向下传播:

sql
-- 正确:在父表上改,子表跟着变
ALTER TABLE meas ALTER COLUMN peaktemp TYPE bigint;   -- 会传播到所有子表

子表可以删除自己新增的列:

sql
-- 子表自己加的列可以自行删除
ALTER TABLE meas_y2025 ADD COLUMN src_system text;
ALTER TABLE meas_y2025 DROP COLUMN src_system;        -- 成功

用 pg_inherits 遍历与巡检 ​

pg_inherits 记录直接父子关系,需要递归才能得到整棵树。下面是生产巡检常用的三个查询:

sql
-- 1) 某父表的直接子表
SELECT child.relname AS child, i.inhseqno
FROM pg_inherits i
JOIN pg_class child ON child.oid = i.inhrelid
WHERE i.inhparent = 'meas'::regclass
ORDER BY i.inhseqno;

-- 2) 某子表的父表(回溯)
SELECT parent.relname AS parent
FROM pg_inherits i
JOIN pg_class parent ON parent.oid = i.inhparent
WHERE i.inhrelid = 'meas_y2025'::regclass;

-- 3) 递归列出整棵子树(含每一层的行数,便于发现「数据插错表」)
WITH RECURSIVE tree AS (
  SELECT 'meas'::regclass AS rel, 0 AS depth
  UNION ALL
  SELECT i.inhrelid::regclass, t.depth + 1
  FROM tree t JOIN pg_inherits i ON i.inhparent = t.rel
)
SELECT rel::text AS tbl, depth FROM tree ORDER BY depth, tbl;
text
     tbl        | depth
----------------+-------
 meas           |     0
 meas_y2024     |     1
 meas_y2025     |     1
 meas_y2026     |     1
(4 rows)
sql
-- 巡检:哪些父表实际上没有任何子表(继承声明可能写错了)
SELECT p.relname AS parent
FROM pg_class p
WHERE p.relkind = 'r' AND p.relhassubclass = false
  AND EXISTS (
    SELECT 1 FROM pg_attribute a
    WHERE a.attrelid = p.oid AND a.attname = 'id'
  )
ORDER BY 1;

实战示例 ​

场景:为多租户系统建立「按租户归档」的继承结构,并安全地只查/只改指定租户的数据。

第一步,建父表与两个租户子表:

sql
DROP TABLE IF EXISTS tenant_b_events, tenant_a_events, all_events CASCADE;

CREATE TABLE all_events (
  id         bigint,
  tenant_id  text        NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  payload    jsonb
);

CREATE TABLE tenant_a_events () INHERITS (all_events);
CREATE TABLE tenant_b_events () INHERITS (all_events);

ALTER TABLE tenant_a_events ADD CONSTRAINT ck_tenant_a CHECK (tenant_id = 'tenant_a');
ALTER TABLE tenant_b_events ADD CONSTRAINT ck_tenant_b CHECK (tenant_id = 'tenant_b');

INSERT INTO tenant_a_events (id, tenant_id, payload) VALUES
  (1, 'tenant_a', '{"action":"login"}'),
  (2, 'tenant_a', '{"action":"purchase"}');
INSERT INTO tenant_b_events (id, tenant_id, payload) VALUES
  (3, 'tenant_b', '{"action":"login"}');

第二步,验证全局视图与单租户视图:

sql
SELECT tableoid::regclass AS src, tenant_id, payload
FROM all_events ORDER BY id;

SELECT 'global' AS scope, count(*) FROM all_events
UNION ALL
SELECT 'tenant_a only', count(*) FROM ONLY tenant_a_events;
text
         src         | tenant_id |        payload
---------------------+-----------+------------------------
 tenant_a_events     | tenant_a  | {"action": "login"}
 tenant_a_events     | tenant_a  | {"action": "purchase"}
 tenant_b_events     | tenant_b  | {"action": "login"}
(3 rows)

    scope     | count
--------------+-------
 global       |     3
 tenant_a only |    2
(2 rows)

第三步,验证约束排除与安全删除:

sql
SET constraint_exclusion = on;

EXPLAIN (COSTS OFF)
SELECT * FROM all_events WHERE tenant_id = 'tenant_b';
text
 Append
   ->  Seq Scan on all_events all_events_1
         Filter: (tenant_id = 'tenant_b'::text)
   ->  Seq Scan on tenant_b_events all_events_3
         Filter: (tenant_id = 'tenant_b'::text)
(4 rows)

tenant_a_events 因 ck_tenant_a 与 tenant_id = 'tenant_b' 矛盾被排除,但父表 all_events 仍被扫描(父表无 tenant_id 约束)。

sql
-- 安全删除:只删指定租户子表的过期数据
DELETE FROM ONLY tenant_b_events WHERE created_at < now() - interval '30 days';
-- 危险:会扫到所有租户
-- DELETE FROM all_events WHERE created_at < now() - interval '30 days';

结果分析:

  • 继承把「全局视图」与「租户隔离」统一在一套结构里,报表走 all_events,隔离操作走 ONLY tenant_x_events。
  • 约束排除减少了无关子表的扫描,但父表无法被排除,这是继承式隔离相比声明式分区最明显的性能损失。
  • tableoid::regclass 是验证「数据是否落在正确租户子表」的唯一可靠手段;仅凭 tenant_id 列不足以说明问题,因为父表也可能存有该租户的数据。

场景二:通过 INHERIT / NO INHERIT 模拟「归档一个租户」。

sql
-- 摘出:仍在层次外独立存在,数据保留
ALTER TABLE tenant_b_events NO INHERIT all_events;
SELECT count(*) FROM all_events;             -- 只剩父表 + tenant_a
SELECT count(*) FROM tenant_b_events;        -- 数据仍在

-- 挂回
ALTER TABLE tenant_b_events INHERIT all_events;
SELECT count(*) FROM all_events;             -- 恢复
text
 count
-------
     2
(1 row)

 count
-------
     1
(1 row)

 count
-------
     3
(1 row)

摘出与挂回都不搬数据、不加数据量,只改 pg_inherits。这就是「归档 = 卸下子表 + 移动到冷存储」的实现基础。

注意事项 ​

  • 错误!以为 INSERT INTO parent 会按条件自动进入子表。 它永远写父表,且不报错。

    sql
    -- 不推荐:期望这行落到 meas_y2025
    INSERT INTO meas (city_id, logdate, peaktemp) VALUES (1, '2025-06-01', 20);
  • 错误!在继承结构上使用 ALTER TABLE ONLY ... ADD COLUMN。 会造成父子列不一致,父表查询直接报错。

    sql
    -- 错误!子表不会有 quality 列
    ALTER TABLE ONLY meas ADD COLUMN quality text;
  • 错误!用不带 ONLY 的 DELETE / UPDATE 做数据清理。 会作用于整棵子树。

    sql
    -- 危险:一次删除多张物理表的数据
    DELETE FROM meas WHERE logdate < '2024-01-01';
  • 错误!直接插子表却不加范围 CHECK 约束。 少了这道闸门,越界数据会静默进入错误的年份。

    sql
    -- 推荐:每个子表都有 logdate 范围 CHECK
    ALTER TABLE meas_y2025 ADD CONSTRAINT ck_y2025
      CHECK (logdate >= DATE '2025-01-01' AND logdate < DATE '2026-01-01');
  • 注意 INHERIT 会加锁并校验全表。 大表上执行会长时间持锁,应在维护窗口进行,并先 ANALYZE 相关表。

  • 注意 ALTER TABLE parent ... ALTER COLUMN ... SET DEFAULT 会覆盖子表默认值。 若子表有意使用不同默认值,改动父表后需复核。

  • 注意继承来的列不能在子表层面 DROP COLUMN / 改类型,必须从父表操作;只有子表自增的列可以自行修改。

  • 注意 TRUNCATE ONLY child 与 TRUNCATE child 的差别:后者在继承结构下默认会连子孙表一起清空,务必写 ONLY。

最佳实践 ​

  • 老式分区场景下,每个子表都加「范围 CHECK 约束」,并开启 constraint_exclusion,让规划器能排除无关子表:

    sql
    ALTER TABLE meas_y2025 ADD CONSTRAINT ck_y2025
      CHECK (logdate >= DATE '2025-01-01' AND logdate < DATE '2026-01-01');
  • 准备一张「兜底」父表或专门的 default 子表,并明确约定「越界数据进哪里」;老式分区必须解决这个问题,否则总有数据落在父表。

  • 所有清理类语句强制写 ONLY,并在代码评审中检查:DELETE FROM ONLY / UPDATE ONLY / TRUNCATE ONLY。

  • 用 tableoid::regclass 做写入后审计,尤其是批量导入和 ETL:

    sql
    SELECT tableoid::regclass AS tgt, count(*) FROM meas GROUP BY 1;
  • 建立写入路由约定并在应用层落实(或使用规则/触发器),因为继承本身不做路由;文档中写清「哪张表由谁写」。

  • ALTER TABLE ONLY child DROP COLUMN 只用于子表自增列;继承列一律从父表统一变更。

  • 定期跑继承树巡检 SQL,确认子表集合与预期一致、没有「孤儿父表」或漏挂的子表:

    sql
    SELECT p.relname AS parent, count(i.inhrelid) AS children
    FROM pg_class p
    LEFT JOIN pg_inherits i ON i.inhparent = p.oid
    WHERE p.relkind = 'r' AND p.relnamespace = 'public'::regnamespace
    GROUP BY p.relname
    HAVING count(i.inhrelid) > 0
    ORDER BY 1;
  • 新项目优先使用声明式分区,只在确有「非分区型」层次建模需求时才用继承;把继承当作「表族的统一查询入口」而非分区手段。

下一节

继续学习:继承与约束

参考链接 ​