域类型
域(domain)是基于现有数据类型的可复用类型。它保留基础类型的运算和索引能力,同时集中声明 NOT NULL、默认值或 CHECK 约束,适合表达“所有地方都必须遵守”的业务概念。
创建域
CREATE DOMAIN email_address AS text
CHECK (VALUE = lower(VALUE))
CHECK (position('@' IN VALUE) > 1);
CREATE TABLE users (
user_id bigint PRIMARY KEY,
email email_address NOT NULL
);
在域约束中,VALUE 代表待检查的域值。域可以基于 text、numeric、数组等类型,但不能直接基于组合类型字段语义替代完整表结构。
约束和 NULL
域约束默认不会阻止 NULL;CHECK 对 NULL 的结果可能为未知,而未知不等于失败。需要禁止空值时显式写 NOT NULL:
CREATE DOMAIN positive_amount AS numeric(18, 2)
NOT NULL
CHECK (VALUE >= 0);
域上的默认值只在列没有其他默认值且插入未提供值时发挥作用。表列可以覆盖域默认值,但不能绕过域约束。
修改域
ALTER DOMAIN email_address
ADD CONSTRAINT email_length CHECK (length(VALUE) <= 320);
ALTER DOMAIN positive_amount DROP CONSTRAINT positive_amount_check;
新增约束时,已有数据可能不满足要求,迁移会失败或需要先清理数据。域被多个表、函数和视图依赖,修改前应检查锁、依赖和回滚方案。
域与 CHECK、类型别名的区别
CREATE DOMAIN是真正的数据库类型,列、参数和返回值可以复用它。- 每个表单独写
CHECK容易出现规则漂移。 - SQL 中的类型别名或应用层类型不会由数据库强制执行。
- 域不能替代外键、跨行约束和复杂业务状态机。
域约束应保持稳定、局部且可推理。需要随着业务配置动态变化的规则,通常应使用引用表和外键,而不是频繁修改域定义。
转换和客户端
域值通常可转换为其基础类型,但类型解析、参数绑定和 ORM 映射可能把它当成自定义类型。驱动或 ORM 升级后应测试参数类型、返回值和迁移脚本,必要时显式 CAST:
SELECT email::text FROM users WHERE email = $1::email_address;
常见误区
- 以为域上的
CHECK会自动拒绝NULL。 - 把跨表唯一性、外键或动态配置规则放进域。
- 只修改域而不检查所有依赖表的现有数据。
- 让域承载展示格式、语言或时区等不属于基础值的语义。
- 忽略 ORM 对自定义数据库类型的参数绑定支持。