it-swarm-eu.dev

PostgreSQL multi-sloupec jedinečné omezení a hodnoty NULL

Mám tabulku jako je tato:

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

A já chci (id_A, id_B, id_C) být odlišný v každé situaci. Následující dvě přílohy musí tedy vést k chybě:

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

Ale nechová se tak, jak se očekávalo, protože podle dokumentace nejsou dvě hodnoty NULL vzájemně porovnávány, takže obě vložky procházejí bez chyby.

Jak mohu zaručit své jedinečné omezení, i když id_C může být v tomto případě NULL? Skutečná otázka zní: mohu zaručit tento druh jedinečnosti v „čistém sql“ nebo musím implementovat na vyšší úrovni (v mém případě Java)?

102
Manuel Leduc

Můžete to udělat v čistém SQL . Vytvořte částečný jedinečný index navíc k tomu, který máte:

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

Tímto způsobem můžete zadat (a, b, c) v tabulce:

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

Ale nic z toho podruhé.

Nebo použijte dva částečné UNIQUE indexy a žádný úplný index (nebo omezení). Nejlepší řešení závisí na detailech vašich požadavků. Porovnat:

I když je to elegantní a účinný pro jeden sloupec s nulovatelným nulovým číslem v indexu UNIQUE, rychle se vymkne z ruky pro další. Diskutujte o tom - a jak používat UPSERT s částečnými indexy:

Kromě

Žádné použití pro smíšené případy bez dvojitých uvozovek v PostgreSQL.

Můžete považovat serial sloupec za primární klíč nebo IDENTITY sloupec in Postgres 10 nebo novější. Příbuzný:

Tak:

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

Pokud neočekáváte více než 2 miliardy řádků (> 2147483647) po celou dobu životnosti vaší tabulky (včetně řádků s odpadem a odstraněním), zvažte místo integer (8 bajtů) místo bigint (8 bajtů).

102
Erwin Brandstetter

Měl jsem stejný problém a našel jsem jiný způsob, jak mít jedinečný NULL do stolu.

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

V mém případě pole foreign_key_field je kladné celé číslo a nikdy nebude -1.

Takže na odpověď na Manual Leduc by mohlo být jiné řešení

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

Předpokládám, že idy nebudou -1.

Jaká je výhoda při vytváření částečného indexu?
V případě, že nemáte klauzuli NOT NULL, id_a, id_b a id_c může být NULL společně pouze jednou.
S částečným indexem by mohla být 3 pole NULL více než jednou.

12
Luc M

Null může znamenat, že hodnota není pro daný řádek v tuto chvíli známa, ale bude přidána, pokud je známa, v budoucnosti (příklad FinishDate pro běžící Project) nebo že žádná hodnota nemůže být použito pro tento řádek (příklad EscapeVelocity pro černou díru Star).

Podle mého názoru je obvykle lepší normalizovat tabulky odstraněním všech hodnot Null.

Ve vašem případě chcete povolit ve vašem sloupci NULLs, ale přesto chcete povolit pouze jeden NULL. Proč? Jaký je to vztah mezi oběma tabulkami?

Možná můžete jednoduše změnit sloupec na NOT NULL a místo NULL uložte speciální hodnotu (jako -1) je známo, že se nikdy neobjeví. Tím se vyřeší problém omezení jedinečnosti (ale může to mít další nežádoucí vedlejší účinky. Například použití -1 znamená, že „není známo/nevztahuje se“, bude ve sloupci zkosena jakákoli suma nebo průměrné výpočty. Nebo všechny takové výpočty budou muset vzít v úvahu speciální hodnotu a ignorovat ji.)

8
ypercubeᵀᴹ