Det er "let", fordi PostgreSQL er så udvidelsesvenligt. Du kan definere din egen type, sammenligningsoperatorer for typen og en operatorklasse til brug med en btree
indeks, så PostgreSQL ved, hvordan man sammenligner dem.
Tricket er at definere "lige" på en sådan måde, at modstridende værdier er ens.
Først definerer vi vores type:
CREATE TYPE tod AS ENUM ('morning', 'afternoon', 'anytime');
Derefter definerer vi en indeksunderstøttelsesrutine så btree
indeks ved, hvordan man sammenligner værdierne:
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$$;
Baseret på denne sammenligningsfunktion definerer vi funktioner, der implementerer sammenligningsoperatorerne:
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';
Nu kan vi definere operatorer på vores 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 = ~<~
);
Nu er der kun tilbage at definere en operatørklasse der kan bruges til at definere et indeks (dette kræver superbrugerrettigheder):
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);
Nu kan vi definere en tabel, der bruger den nye datatype.
Siden vi definerede tod_ops
som standard operatørklasse for typen tod
, kan vi oprette en simpel unik begrænsning, og det underliggende indeks vil bruge vores operatørklasse.
CREATE TABLE schedule (
id integer PRIMARY KEY,
day date NOT NULL,
time_of_day tod NOT NULL,
UNIQUE (day, time_of_day)
);
Lad os teste det:
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.
Er PostgreSQL ikke sejt?