sql - Postgres 约束

标签 sql postgresql unique-constraint

在 postgres 中有没有办法创建一个像这样工作的约束:

我有一个值为“time_of_day”的实体。该值可以是早上、下午、晚上、白天、晚上或任何时间。

所以我想弄清楚如何允许以下组合:

  1. 任何时间(不能有任何其他)即如果选择任何时间,则只能有一行
  2. Morning 或 Afternoon - 可以有很多行,但不能包含“任何时间”。也不能是同一类型的两行,例如两排“早上”。

(2) 已经完成,因为它只是 time_of_day 的标准唯一约束。我如何实现(1)。可能吗?

最佳答案

这很“简单”,因为 PostgreSQL 的可扩展性很强。您可以定义自己的类型、类型的比较运算符和运算符类以与 btree 索引一起使用,以便 PostgreSQL 知道如何比较它们。

诀窍是以冲突值相等的方式定义“相等”。

首先,我们定义我们的类型:

CREATE TYPE tod AS ENUM ('morning', 'afternoon', 'anytime');

然后我们定义一个索引支持例程,以便btree 索引知道如何比较值:

CREATE FUNCTION tod_compare(tod, tod) RETURNS integer
   IMMUTABLE LANGUAGE sql AS
$$SELECT CASE WHEN $1 = 'morning' AND $2 = 'afternoon' THEN -1
            WHEN $1 = 'afternoon' AND $2 = 'morning' THEN 1
            ELSE 0
       END$$;

基于这个比较函数,我们定义了实现比较运算符的函数:

CREATE FUNCTION tod_eq(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) = 0';

CREATE FUNCTION tod_lt(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) = -1';

CREATE FUNCTION tod_le(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) <= 0';

CREATE FUNCTION tod_ge(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) >= 0';

CREATE FUNCTION tod_gt(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) = 1';

CREATE FUNCTION tod_ne(tod, tod) RETURNS boolean IMMUTABLE LANGUAGE sql
   AS 'SELECT tod_compare($1, $2) <> 0';

现在我们可以在我们的类型上定义运算符了:

CREATE OPERATOR ~=~ (
   PROCEDURE = tod_eq,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~=~,
   NEGATOR = ~<>~
);

CREATE OPERATOR ~<>~ (
   PROCEDURE = tod_ne,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~<>~,
   NEGATOR = ~=~
);

CREATE OPERATOR ~<=~ (
   PROCEDURE = tod_le,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~>=~,
   NEGATOR = ~>~
); 

CREATE OPERATOR ~<~ (
   PROCEDURE = tod_lt,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~>~,
   NEGATOR = ~>=~
);

CREATE OPERATOR ~>~ (
   PROCEDURE = tod_gt,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~<~,
   NEGATOR = ~<=~
);

CREATE OPERATOR ~>=~ (
   PROCEDURE = tod_ge,
   LEFTARG = tod,
   RIGHTARG = tod,
   COMMUTATOR = ~<=~,
   NEGATOR = ~<~
);

现在剩下的就是定义一个可用于定义索引的运算符类(这需要 super 用户权限):

CREATE OPERATOR CLASS tod_ops DEFAULT FOR TYPE tod USING btree AS
   OPERATOR 1 ~<~(tod,tod),
   OPERATOR 2 ~<=~(tod,tod),
   OPERATOR 3 ~=~(tod,tod),
   OPERATOR 4 ~>=~(tod,tod),
   OPERATOR 5 ~>~(tod,tod),
   FUNCTION 1 tod_compare(tod,tod);

现在我们可以定义一个使用新数据类型的表。

由于我们将 tod_ops 定义为类型 tod 的默认运算符类,我们可以创建一个简单的唯一约束,并且底层索引将使用我们的运算符类。

CREATE TABLE schedule (
   id integer PRIMARY KEY,
   day date NOT NULL,
   time_of_day tod NOT NULL,
   UNIQUE (day, time_of_day)
);

让我们测试一下:

INSERT INTO schedule VALUES (1, '2018-05-01', 'morning');

INSERT INTO schedule VALUES (2, '2018-05-01', 'afternoon');

INSERT INTO schedule VALUES (3, '2018-05-02', 'anytime');

INSERT INTO schedule VALUES (4, '2018-05-02', 'morning');
ERROR:  duplicate key value violates unique constraint "schedule_day_time_of_day_key"
DETAIL:  Key (day, time_of_day)=(2018-05-02, morning) already exists.

PostgreSQL 不是很酷吗?

关于sql - Postgres 约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50226679/

相关文章:

java - 使用 JPA Criteria API 生成正确和/或条件

sql - 如何加速创建大型字母数字代码表的 SQL 脚本

sql - Postgres - 有时只想在小数分隔符左侧显示一个数字

postgresql - Odoo (OpenERP8) 与 Postgre 的连接失败

postgresql - SSL 系统调用错误 : Operation timed out | pgAdmin 4, Postgres

mysql - Laravel 验证和更新记录

sql - 如何从表列中删除唯一约束?

sql - 如何在 Google BigQuery 中执行 "safe"SPLIT?

postgresql - SELECT CURRVAL (pg_get_serial_sequence 我做错了什么?

SQL Oracle 约束值在多个列中唯一