Унікальне обмеження та значення NULL у багатоколонці PostgreSQL


93

У мене така таблиця:

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

І я хочу (id_A, id_B, id_C)бути різним у будь-якій ситуації. Отже, наступні дві вставки повинні призвести до помилки:

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

Але він не веде себе так, як очікувалося, оскільки згідно з документацією два NULLзначення не порівнюються між собою, тому обидві вставки проходять без помилок.

Як я можу гарантувати своє унікальне обмеження, навіть якщо це id_Cможе бути NULLв цьому випадку? Насправді, справжнє питання: чи можу я гарантувати такий унікальність у "чистому sql" чи мені доведеться реалізувати його на більш високому рівні (у моєму випадку java)?


Отже, скажіть, у вас є значення (1,2,1)і (1,2,2)в (A,B,C)стовпцях. Потрібно (1,2,NULL)додати чи ні?
ypercubeᵀᴹ

A і B не можуть бути нульовими, але C може бути нульовим або будь-яким додатним цілим числом. Отже (1,2,3) і (2,4, null) є дійсними, але (null, 2,3) або (1, null, 4) є недійсними. І [(1,2, null), (1,2,3)] не порушує унікальне обмеження, але [(1,2, null), (1,2, null)] повинен його порушити.
Мануель Ледук

2
Чи є якісь значення, які ніколи не з'являтимуться в цих стовпцях (наприклад, негативні значення?)
a_horse_with_no_name

Не потрібно мітити свої обмеження в pg. Це автоматично генерує ім’я. Просто FYI.
Еван Керролл

Відповіді:


93

Це можна зробити в чистому SQL . Створіть частковий унікальний індекс на додаток до того, який у вас є:

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

Таким чином ви можете ввести (a, b, c)у свою таблицю:

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

Але нічого з цього вдруге.

Або використовувати два часткові UNIQUEіндекси та відсутність повного індексу (або обмеження). Найкраще рішення залежить від деталей ваших вимог. Порівняйте:

Хоча це елегантно і ефективно для одного нульового стовпчика в UNIQUEіндексі, він швидко виходить з-під руки для отримання додаткового. Обговорюючи це - і як використовувати UPSERT з частковими індексами:

Убік

Не потрібно використовувати змішані ідентифікатори випадків без подвійних лапок у PostgreSQL.

Ви могли б розглянути serialстовпець в якості первинного ключа або IDENTITYстовпець в Postgres 10 або пізнішої версії. Пов'язані:

Тому:

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

Якщо ви не очікуєте більше 2 мільярдів рядків (> 2147483647) протягом життя вашої таблиці (включаючи відходи та видалені рядки), врахуйте integer(4 байти) замість bigint(8 байт).


1
Документи обстоюють цей метод. Додавання унікального обмеження автоматично створить унікальний індекс B-дерева на стовпчику або групі стовпців, перелічених у обмеженні. Унікальне обмеження, що охоплює лише деякі рядки, не може бути записане як унікальне обмеження, але можливо застосувати таке обмеження, створивши унікальний частковий індекс.
Еван Керролл

12

У мене була така ж проблема, і я знайшов інший спосіб включити унікальний NULL в таблицю.

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

У моєму випадку поле foreign_key_fieldє натуральним числом і ніколи не буде -1.

Отже, відповісти на посібник Ледук, може бути інше рішення

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

Я припускаю, що ідентифікатор не буде -1.

Яка перевага у створенні часткового індексу?
У випадку, якщо у вас немає пункту NOT NULL id_a, id_bі ви id_cможете бути NULL разом лише один раз.
З частковим індексом 3 поля можуть бути NULL не один раз.


3
> Яка перевага у створенні часткового індексу? Те, як ви це зробили, COALESCEможе бути ефективним для обмеження дублікатів, але індекс не буде дуже корисним для запитів, оскільки його індекс вираження, який, ймовірно, не відповідає виразам запитів. Тобто, якщо SELECT COALESCE(col, -1) ...б ви не потрапили в індекс.
Bo Jeanes

@BoJeanes Індекс не створений для проблеми ефективності. Він створений для повного виконання вимог бізнесу.
Люк М

8

Null може означати, що значення для цього рядка наразі невідоме, але воно буде додане, коли воно буде відоме в майбутньому (приклад FinishDateдля запущеного Project) або що для цього рядка не може бути застосовано жодне значення (наприклад, EscapeVelocityдля чорної діри Star).

На мою думку, зазвичай краще нормалізувати таблиці, усунувши всі Нулі.

У вашому випадку ви хочете дозволити NULLsу своєму стовпці, але ви хочете, NULLщоб його було дозволено. Чому? Який зв’язок це між двома таблицями?

Можливо, ви можете просто змінити стовпець NOT NULLі зберегти замість NULLнього спеціальне значення (як -1), яке, як відомо, ніколи не з’являється. Це вирішить проблему обмеження унікальності (але може мати й інші, можливо, небажані побічні ефекти. Наприклад, використання -1знака "невідомо / не застосовується" буде перекосити будь-яку суму чи середні розрахунки на стовпці. Або всі такі розрахунки доведеться взяти враховуйте спеціальне значення і ігноруйте його.)


2
У моєму випадку NULL - це дійсно NULL (id_C - це зовнішній ключ до table_c for exampleple, тому він не може мати значення -1), це означає, що вони не мають зв'язку між "my_table" та "table_c". Так воно має функціональне означення. До речі [(1, 1,1, null), (2, 1,2, null), (3,2,4, null)] - це дійсний список вставлених даних.
Мануель Ледук

1
Це насправді не Null, як використовується в SQL, тому що ви хочете лише один у всіх рядках. Ви можете змінити схему бази даних або додавши -1 до table_c, або додавши іншу таблицю (що буде супертипом до підтипу table_c).
ypercubeᵀᴹ

3
Я просто хотів би зазначити @Manuel, що думка про нулі у цій відповіді не є загальновиробничою та є багато дискусійною. Багато хто, як я, вважають, що нуль можна використовувати для будь-яких бажаних цілей (але має означати лише одне для кожного поля та бути задокументованим, можливо, в назві поля чи коментарі стовпця)
Джек Дуглас

1
Ви не можете використовувати фіктивне значення, коли ваш стовпець - ЗАМЕЧНИЙ КЛЮЧ.
Люк М

1
+1 Я з вами: якщо ми хочемо, щоб якась комбінація стовпців була унікальною, тоді вам слід врахувати сутність, у якій ця комбінація стовпців є ПК. Схема баз даних ОП повинна, ймовірно, змінюватися на батьківську таблицю та дочірню.
АК
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.