Das ist „einfach“, weil PostgreSQL so erweiterbar ist. Sie können Ihren eigenen Typ, Vergleichsoperatoren für den Typ und eine Operatorklasse zur Verwendung mit einem btree
definieren indexieren, damit PostgreSQL weiß, wie sie zu vergleichen sind.
Der Trick besteht darin, „gleich“ so zu definieren, dass widersprüchliche Werte gleich sind.
Zuerst definieren wir unseren Typ:
CREATE TYPE tod AS ENUM ('morning', 'afternoon', 'anytime');
Dann definieren wir eine Indexunterstützungsroutine damit der btree
index weiß, wie man die Werte vergleicht:
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$$;
Basierend auf dieser Vergleichsfunktion definieren wir Funktionen, die die Vergleichsoperatoren implementieren:
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';
Jetzt können wir Operatoren für unseren Typ definieren:
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 = ~<~
);
Jetzt muss nur noch eine Operatorklasse definiert werden die verwendet werden kann, um einen Index zu definieren (dies erfordert Superuser-Privilegien):
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);
Jetzt können wir eine Tabelle definieren, die den neuen Datentyp verwendet.
Da wir tod_ops
definiert haben als Standardoperatorklasse für den Typ tod
, können wir eine einfache eindeutige Einschränkung erstellen, und der zugrunde liegende Index verwendet unsere Operatorklasse.
CREATE TABLE schedule (
id integer PRIMARY KEY,
day date NOT NULL,
time_of_day tod NOT NULL,
UNIQUE (day, time_of_day)
);
Testen wir es:
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.
Ist PostgreSQL nicht cool?