it-swarm.com.de

PostgreSQL mehrspaltige eindeutige Einschränkung und NULL-Werte

Ich habe eine Tabelle wie die folgende:

create table my_table (
    id   int8 not null,
    id_A int8 not null,
    id_B int8 not null,
    id_C int8 null,
    constraint pk_my_table primary key (id),
    constraint u_constrainte unique (id_A, id_B, id_C)
);

Und ich will (id_A, id_B, id_C) in jeder Situation unterschiedlich sein. Die folgenden zwei Einfügungen müssen also zu einem Fehler führen:

INSERT INTO my_table VALUES (1, 1, 2, NULL);
INSERT INTO my_table VALUES (2, 1, 2, NULL);

Es verhält sich jedoch nicht wie erwartet, da laut Dokumentation zwei NULL -Werte nicht miteinander verglichen werden, sodass beide Einfügungen fehlerfrei bestanden werden.

Wie kann ich meine eindeutige Einschränkung garantieren, auch wenn id_C kann in diesem Fall NULL sein? Die eigentliche Frage ist: Kann ich diese Art von Einzigartigkeit in "pure sql" garantieren oder muss ich sie auf einer höheren Ebene implementieren (in meinem Fall Java)?

102
Manuel Leduc

Sie können dies in reinem SQL tun. Erstellen Sie einen teilweise eindeutigen Index zusätzlich zu dem, den Sie haben:

CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;

Auf diese Weise können Sie für (a, b, c) in deiner Tabelle:

(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)

Aber keines davon ein zweites Mal.

Oder verwenden Sie zwei partielle UNIQUE -Indizes und keinen vollständigen Index (oder eine vollständige Einschränkung). Die beste Lösung hängt von den Details Ihrer Anforderungen ab. Vergleichen Sie:

Dies ist zwar elegant und effizient für eine einzelne nullfähige Spalte im UNIQUE -Index, für weitere jedoch schnell außer Kontrolle geraten. Besprechen Sie dies - und wie Sie UPSERT mit Teilindizes verwenden:

Nebenbei

Keine Verwendung für gemischte Fallbezeichner ohne doppelte Anführungszeichen in PostgreSQL.

Sie könnte betrachten eine serial Spalte als Primärschlüssel oder eine IDENTITY Spalte in Postgres 10 oder später. Verbunden:

Damit:

CREATE TABLE my_table (
   my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY  -- for pg 10+
-- my_table_id bigserial PRIMARY KEY  -- for pg 9.6 or older
 , id_a int8 NOT NULL
 , id_b int8 NOT NULL
 , id_c int8
 , CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);

Wenn Sie über die Lebensdauer Ihrer Tabelle nicht mehr als 2 Milliarden Zeilen (> 2147483647) erwarten (einschließlich Abfall und gelöschter Zeilen), ziehen Sie integer (4 Byte) anstelle von bigint (8) in Betracht Bytes).

102

Ich hatte das gleiche Problem und fand einen anderen Weg, um NULL eindeutig in die Tabelle aufzunehmen.

CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )

In meinem Fall ist das Feld foreign_key_field ist eine positive ganze Zahl und wird niemals -1 sein.

Um auf Manual Leduc zu antworten, könnte eine andere Lösung sein

CREATE UNIQUE INDEX  u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )

Ich gehe davon aus, dass IDs nicht -1 sein werden.

Was ist der Vorteil beim Erstellen eines Teilindex?
Falls Sie die NOT NULL-Klausel nicht haben, id_a, id_b und id_c kann nur einmal zusammen NULL sein.
Bei einem Teilindex können die 3 Felder mehr als einmal NULL sein.

12
Luc M

Eine Null kann bedeuten, dass der Wert für diese Zeile derzeit nicht bekannt ist, aber, falls bekannt, in Zukunft hinzugefügt wird (Beispiel FinishDate für ein laufendes Project) oder dass kein Wert vorhanden sein kann für diese Zeile angewendet (Beispiel EscapeVelocity für ein Schwarzes Loch Star).

Meiner Meinung nach ist es normalerweise besser, die Tabellen zu normalisieren, indem alle Nullen entfernt werden.

In Ihrem Fall möchten Sie NULLs in Ihrer Spalte zulassen, möchten jedoch, dass nur ein NULL zulässig ist. Warum? Welche Beziehung besteht zwischen den beiden Tabellen?

Vielleicht können Sie die Spalte einfach in NOT NULL Ändern und anstelle von NULL einen speziellen Wert (wie -1) Speichern, von dem bekannt ist, dass er niemals erscheint. Dies löst das Problem der Eindeutigkeitsbeschränkung (kann jedoch andere möglicherweise unerwünschte Nebenwirkungen haben. Wenn Sie beispielsweise -1 Verwenden, um "nicht bekannt/nicht zutreffend" zu verwenden, werden Summen- oder Durchschnittsberechnungen in der Spalte verzerrt. Oder alle Bei solchen Berechnungen muss der spezielle Wert berücksichtigt und ignoriert werden.)

8
ypercubeᵀᴹ