Чи можуть стовпці таблиць із зовнішнім ключем бути NULL?


235

У мене є таблиця, яка містить кілька стовпців ідентифікаторів до інших таблиць.

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

(Можливо, це залежить від сервера баз даних, я використовую тип таблиці MySQL та InnoDB)

Я вважаю, що це розумне очікування, але виправте мене, якщо я помиляюся.


6
Я не знаю про MySQL, але MS SQL Server дозволяє зовнішні ключі зводити нанівець із потрібною семантикою. Я очікую, що це стандартна поведінка.
Джефрі Л Уітлідж

1
Іноземний ключ, за замовчуванням у mySQL не може бути нульовим, причина проста, якщо ви посилаєтесь на щось, і дозволите це зробити нульовим, ви втратите цілісність даних. коли ви створюєте набір таблиць, дозвольте NULL NOT, а потім застосуйте обмеження зовнішнього ключа. Ви не можете встановити null під час оновлення, воно повинно надіслати вам помилку, але ви можете (потрібно) просто не оновлювати цей стовпець і оновлювати лише поля, які потрібно змінити.
JoelBonetR

Відповіді:


245

Так, ви можете застосувати обмеження лише тоді, коли значення не NULL. Це можна легко перевірити за допомогою наступного прикладу:

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                     PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                    parent_id INT NULL,
                    FOREIGN KEY (parent_id) REFERENCES parent(id)
) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)


INSERT INTO child (id, parent_id) VALUES (2, 1);

-- ERROR 1452 (23000): Cannot add or update a child row: a foreign key 
-- constraint fails (`t/child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY
-- (`parent_id`) REFERENCES `parent` (`id`))

Перша вставка пройде, тому що ми вставимо NULL у parent_id. Друга вставка не вдається через обмеження зовнішнього ключа, оскільки ми намагалися вставити значення, яке не існує в parentтаблиці.


16
Батьківська таблиця також може бути оголошена за допомогою id INT NOT NULL.
Буде

@CJDennis Якщо ви зробите так, що лише один рядок може мати нульовий ідентифікатор, він може використовуватися як резервні значення для інших рядків. (Хоча це може бути краще для БД, якщо ви просто використовуєте більше стовпців.) Обмеження за замовчуванням здається проблемою, якщо ви хочете дізнатися пізніше, чи було значення спочатку встановлено як "за замовчуванням" (за допомогою null) або встановлено значення a значення, яке трапляється таким самим, як "за замовчуванням". Маючи рядок з нульовим ідентифікатором, ви можете чітко вказати, що цей рядок не повинен використовуватися як звичайний рядок і може використовувати рядок як спосіб надання свого роду динамічного значення за замовчуванням для інших рядків.
Ouroborus

1
Я думаю, що parent_id INT NULLчастина (дослівно) дорівнюєparent_id int default null

Бічна примітка для користувачів Java, якщо ви використовуєте ibatis або інший ORM та користувач примітив intзамість членів Integerвашого класу, за замовчуванням ніколи не буде нульовим, але буде 0, і ви втратите обмеження.
Джим Форд

32

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


8
Ви не можете встановити значення стовпця NULL у стовпці, щоб дозволити це?
Кевін Куломбе

Так, у більшості мов NULL відрізняється від порожнього рядка. Можливо, тонкий на початку, але критично пам'ятати.
Гері

Здравствуйте, Backslider, ви говорите "(на відміну від порожнього рядка)", але я не думаю, що ви мали на увазі, що ви ВСТАВИТЕ значення порожнього рядка, а, скоріше, не вказуєте значення для значення " все? тобто ви навіть не згадуєте стовпець у своєму INSERT INTO {table} {list_of_columns}? Бо це правда для мене; пропускання згадки про стовпчик викликає помилку, але включає і явно встановлює помилку виправлень NULL. Якщо я маю рацію, я думаю, що коментар @ Gary не застосовується (тому що ви не мали на увазі порожній рядок), але @Kevin Coulombe може бути корисним ...
The Red Pea

Так, @ Пропозиція KevinCoulombe працює, я описав, як цього досягти за допомогою сценаріїв міграції Entity Framework Core, тут
Red Pea

Важливо зазначити, що обґрунтування того, що вони є явними при оновленні запису, що містить зовнішні ключі NULL, стосується лише типів рядків (varchar тощо), тому що в іншому випадку порожній рядок може бути переданий за замовчуванням. Це випадок з MySQL і призводить до помилки цілісності при оновленні.
CodeMantle

4

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

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


За задумом Foreign Key повинен посилатися на якийсь ключ (Primary), який не є NULL, але на етапі розробки, коли нам потрібно вперше вставити кілька даних у дочірню таблицю, на яку ми не знаємо, на кого вона буде посилатися (батьківська таблиця) . Ось чому у нас дозволено значення NULL. У виробництві, що має NULL, буде проектний потік, про що можна приблизно сказати.
vimal krishna

2

Вищезазначене працює, але це не робить. Зверніть увагу на ВИМКНЕННЯ КАСКАД

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                 PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                parent_id INT NULL,
                FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE

) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)

4
Що ви маєте на увазі під «вищезазначеним»? Зауважте, що якщо ви посилаєтесь на іншу відповідь, порядок може змінитися.
d219

2

Так, значення може бути NULL, але ви повинні бути явним. Я переживав цю саму ситуацію і раніше, і легко забути, ЧОМУ це відбувається, і тому потрібно трохи згадати, що потрібно зробити.

Якщо подані дані передаються або інтерпретуються як порожній рядок, він не вдасться. Однак, чітко встановивши значення NULL під час ВСТАВЛЕННЯ або ОНОВЛЕННЯ, ви готові йти.

Але це весело програмування, чи не так? Створення власних проблем, а потім їх вирішення! Ура!


1

Іншим способом цього було б вставити елемент DEFAULT в іншу таблицю. Наприклад, будь-яке посилання на uuid = 00000000-0000-0000-0000-000000000000 в іншій таблиці не означало б жодних дій. Вам також потрібно встановити всі значення для цього id, щоб вони були "нейтральними", наприклад 0, порожній рядок, null, щоб не впливати на вашу логіку коду.


2
Це не одне і те ж. Значення за замовчуванням або "нейтральне" не те саме, що NULL, відсутність значення. Не обговорюючи достовірності значення за замовчуванням над NULL, ваше фразування є змішаною літтою. "Іншим способом цього було б вставити нульовий елемент в іншу таблицю", слід сказати щось на кшталт "Іншим способом цього було б вставити елемент DEFAULT в іншу таблицю"
blindguy

0

Я також зупинився на цьому питанні. Але я вирішив просто, визначивши зовнішній ключ як unsigned integer. Знайдіть наведений нижче приклад-

CREATE TABLE parent (
   id int(10) UNSIGNED NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (
    id int(10) UNSIGNED NOT NULL,
    parent_id int(10) UNSIGNED DEFAULT NULL,
    FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
) ENGINE=INNODB;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.