生成列 是一个特殊列,它总是从其他列计算得来。
生成列在 PostgreSQL 12 中引入。
功能
从 PostgreSQL 13 开始,可以使用 ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION 命令将生成列转换为普通列。
从 PostgreSQL 17 开始,可以更改用于创建生成列的表达式。
从 PostgreSQL 18 开始,生成列也可以是 VIRTUAL 类型。请注意,这将成为默认的虚拟列类型。
实现
已将列 attgenerated 添加到 pg_attribute 中,以指示该列是否为生成列;s 表示该列为 STORED 类型,否则该值为 "" 且该列不是生成列。
外部数据包装器 (FDW) 支持
外部表 可以定义生成列,生成的值将提供给底层的 外部数据包装器。FDW 应确保该值已存储在外部服务器上并且可以后续检索,但不能保证检索到的值未在外部服务器上被更改。
变更历史
- PostgreSQL 18
- PostgreSQL 17
- 可以更改生成列的生成表达式 (提交 5d06e99a)
- PostgreSQL 13
- 生成列可以转换为普通列 (提交 f595117e)
- PostgreSQL 12
- 已添加 (提交 fc22b662)
示例
带有生成列的表的简单示例,这里用于预先计算一个古老的温度转换
postgres=# CREATE TABLE temperature (
celsius SMALLINT NOT NULL,
fahrenheit SMALLINT NOT NULL GENERATED ALWAYS AS ((celsius * 9/5) + 32) STORED
);
CREATE TABLE
postgres=# \d temperature
Table "public.temperature"
Column | Type | Collation | Nullable | Default
------------+----------+-----------+----------+-----------------------------------------------------
celsius | smallint | | not null |
fahrenheit | smallint | | not null | generated always as ((celsius * 9 / 5 + 32)) stored
插入数据
postgres=# INSERT INTO temperature VALUES (0), (100);
INSERT 0 2
postgres=# SELECT * FROM temperature;
celsius | fahrenheit
---------+------------
0 | 32
100 | 212
(2 rows)
无法直接向生成列插入值
postgres=# INSERT INTO temperature VALUES (0, 32); ERROR: cannot insert a non-DEFAULT value into column "fahrenheit" DETAIL: Column "fahrenheit" is a generated column.
但是 DEFAULT 关键字可以用作占位符值
postgres=# INSERT INTO temperature VALUES (0, DEFAULT); INSERT 0 1
将 fahrenheit 列转换为普通列 (PostgreSQL 13 及更高版本)
postgres=# ALTER TABLE temperature ALTER COLUMN fahrenheit DROP EXPRESSION;
ALTER TABLE
postgres=# \d temperature
Table "public.temperature"
Column | Type | Collation | Nullable | Default
------------+----------+-----------+----------+---------
celsius | smallint | | not null |
fahrenheit | smallint | | not null |
尝试将非生成列转换为普通列
postgres=# ALTER TABLE temperature ALTER COLUMN celsius DROP EXPRESSION; ERROR: column "celsius" of relation "temperature" is not a stored generated column
上面定义的 temperature 表在 pg_attribute 中的条目部分
postgres=# SELECT attname, attnum, attgenerated
FROM pg_attribute
WHERE attrelid='temperature'::regclass
AND attnum > 0;
attname | attnum | attgenerated
------------+--------+--------------
celsius | 1 |
fahrenheit | 2 | s
(2 rows)
生成列表达式存储在 pg_attrdef 中
postgres=# SELECT pg_get_expr(adbin, adrelid, true) FROM pg_attrdef WHERE adrelid = 'temperature'::regclass; pg_get_expr ------------------------ (celsius * 9 / 5 + 32) (1 row)
参考资料
- PostgreSQL 文档: 生成列
