跳到主要内容

域类型

域(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 对自定义数据库类型的参数绑定支持。