在现代数据库系统中,如SQL Server和Oracle,计算列(Computed Columns)是一个常见的特性。它们允许数据库表中某些列的值基于其他列动态计算得出。然而,直到PostgreSQL12版本之前,PostgreSQL并不原生支持计算列,这使得从其他数据库迁移到PostgreSQL变得相对困难。本文旨在探讨在不同版本的PostgreSQL中实现类似计算列功能的多种方法。
PostgreSQL12是这个世界上最受欢迎且功能丰富的开源数据库的下一个主要版本。计划于2019年底发布,它支持生成列。在PostgreSQL中,使用GENERATED ALWAYS AS
子句来创建生成列。用于定义生成列的表达式被称为生成表达式。
在PostgreSQL中,创建带有生成列的表的语法如下:
CREATE TABLE employee (
...,
dob timestamp,
age integer GENERATED ALWAYS AS (
(date_part('year', CURRENT_TIME) - date_part('year', dob)) STORED
);
与SQL Server的语法相比,SQL Server并没有特殊的关键字来声明计算列。只需要在AS
子句后面指定计算列的表达式。
CREATE TABLE Employee (
...,
dob datetime,
age AS DATEDIFF(year, dob, GETDATE()) PERSISTED
);
注意PERSISTED
子句,它等同于PostgreSQL的STORED
子句。SQL Server还支持非持久化计算列,只需要不指定PERSISTED
子句即可。但PostgreSQL目前仅实现了存储生成列。
当行首次插入且未提供其他值时,列默认值仅评估一次;生成列则在行发生变化时更新,且不能被覆盖。带有DEFAULT
约束的列可以在INSERT
或UPDATE
语句中赋予值,而生成列则不能赋予值,它们总是被计算得出。
带有DEFAULT
约束的列不能引用表中的其他列,而生成列正是为了实现这一点。带有DEFAULT
约束的列可以使用易变函数,例如random()
或current_time
,而生成列则不能。
定义生成列和包含生成列的表时,有一些限制条件:
tableoid
。外键表可以有生成列。生成列的访问权限与其底层基础列是分开维护的。因此,可以授予角色对生成列的读取权限,但不是对底层基础列的权限。
从概念上讲,生成列在BEFORE
触发器运行后更新。因此,在BEFORE
触发器中对基础列所做的更改将反映在生成列中。但生成列本身不能在BEFORE
触发器中被访问。
截至本文撰写之时,PostgreSQL 12的稳定版本尚未发布,运行在旧版本的工作负载可能仍然需要此功能。在PostgreSQL 11.x及更早版本中,有两种方法可以实现:
在这种方法中,表没有'age'列。需要'age'列时使用视图。
CREATE VIEW v_employee AS
SELECT dob, date_part('year', CURRENT_TIME) - date_part('year', dob) as age
FROM employee;
在这种方法中,'age'被声明为常规整数列,触发器在插入和更新时填充该列。
CREATE TABLE employee (
...,
dob timestamp,
age integer
);
CREATE OR REPLACE FUNCTION calc_age()
RETURNS TRIGGER AS $body$
BEGIN
NEW.age := date_part('year', CURRENT_TIME) - date_part('year', dob);
RETURN NEW;
END;
$body$
LANGUAGE plpgsql;