Postgres constraint

▼魔方 西西 提交于 2020-01-03 03:34:11

问题


Is there a way in postgres to create a constraint that works like so:

I have an entity that has a value "time_of_day". This value can either be morning, afternoon, evening, day, night or anytime.

SO I am trying to figure out how to allow the following combinations:

  1. Anytime (cannot have anything else) i.e. there can only be one row if anytime is chosen
  2. Morning, or Afternoon - can be many rows, but none can contain 'Anytime'. Also cannot be two rows of the same type e.g. two 'morning' rows.

(2) has been done, as it is just a standard unique constraint on time_of_day. How do I achieve (1). Is it possible?


回答1:


That is “easy” because PostgreSQL is so extensible. You can define your own type, comparison operators for the type and an operator class to use with a btree index so that PostgreSQL knows how to compare them.

The trick is to define “equal” in such a way that conflicting values are equal.

First, we define our type:

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

Then we define an index support routine so that the btree index knows how to compare the values:

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$$;

Based on this comparison function, we define functions that implement the comparison operators:

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';

Now we can define operators on our type:

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 = ~<~
);

Now all that is left is to define an operator class that can be used to define an index (this requires superuser privileges):

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);

Now we can define a table that uses the new data type.

Since we defined tod_ops as the default operator class for type tod, we can create a simple unique constraint, and the underlying index will use our operator class.

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

Let's test it:

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.

Isn't PostgreSQL cool?



来源:https://stackoverflow.com/questions/50226679/postgres-constraint

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!