Могут ли столбцы таблицы с внешним ключом быть NULL?

235

У меня есть таблица, которая имеет несколько столбцов ID для других таблиц.

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

(Вероятно, это зависит от сервера базы данных, я использую тип таблицы 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
Родительская таблица также может быть объявлена ​​с идентификатором INT NOT NULL.
Будет
@CJDennis Если вы сделаете так, чтобы только одна строка могла иметь нулевой идентификатор, она могла бы использоваться в качестве запасных значений для других строк. (Хотя это может сработать лучше для БД, если вы просто используете больше столбцов.) Ограничение по умолчанию кажется проблемой, если вы хотите узнать позже, было ли значение изначально установлено как «значение по умолчанию» (с использованием нуля) или установлено на значение, которое оказывается таким же, как «по умолчанию». Имея строку с нулевым идентификатором, вы можете четко указать, что эта строка не должна использоваться как обычная строка, и можете использовать строку как способ предоставления своего рода динамического значения по умолчанию для других строк.
Уроборус
1
я думаю, что parent_id INT NULLчасть (многословно) равнаparent_id int default null
Примечание для пользователей java: если вы используете ibatis или другой ORM и используете примитив intвместо членов Integerвашего класса, то по умолчанию никогда не будет нулевым, но будет 0, и вы нарушите ограничение.
Джим Форд
32

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

отступник
источник
8
Не могли бы вы установить значение по умолчанию NULL для столбца, чтобы разрешить это?
Кевин Куломб
Да, в большинстве языков NULL отличается от пустой строки. Возможно, тонкий, когда начало, но важно помнить.
Гэри
Привет, Backslider, вы говорите «(в отличие от пустой строки)», но я не думаю, что вы имели в виду, что вы ВСТУПИЛИ значение пустой строки, а скорее, что вы не указываете значение at для значения в все? то есть ты даже не упоминаешь колонку в своем INSERT INTO {table} {list_of_columns}? Потому что это правда для меня; Пропуск упоминания столбца приводит к ошибке, но включение и явное значение NULL исправляет ошибку. Если я прав, я думаю, что комментарий @ Gary не применим (потому что вы не имели в виду пустую строку), но @Kevin Coulombe может быть полезным ...
The Red Pea
Да, предложение @ KevinCoulombe работает, я описал, как этого добиться с помощью скриптов миграции Entity Framework Core, здесь
The Red Pea
Важно отметить, что обоснование явного указания при обновлении записи, содержащей внешние ключи NULL, применимо только к строковым типам (varchar и т. Д.), Поскольку в противном случае пустая строка может передаваться по умолчанию. Это в случае с MySQL и приводит к ошибке целостности при обновлении.
CodeMantle
4

Да, это будет работать так, как вы ожидаете. К сожалению, у меня, похоже, возникают проблемы с нахождением явного утверждения об этом в руководстве по MySQL .

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

davidtbernal
источник
По внешнему виду внешний ключ должен ссылаться на некоторый ключ (первичный), который не равен NULL, но на этапе разработки, когда нам нужно сначала вставить несколько данных в дочернюю таблицу, к которой мы не знаем, к кому они будут обращаться (родительская таблица) , Вот почему мы допустим значение NULL. В производстве, имеющем NULL, будет процесс проектирования, что можно грубо сказать.
Вимал Кришна
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)
MrFabulous
источник
4
Что вы подразумеваете под «вышеупомянутым»? Обратите внимание, что если вы ссылаетесь на другой ответ, порядок может измениться.
d219
2

Да, значение может быть NULL, но вы должны быть явным. Я уже сталкивался с такой же ситуацией и раньше, и легко забыть ПОЧЕМУ это происходит, и поэтому нужно немного вспомнить, что нужно сделать.

Если представленные данные преобразуются или интерпретируются как пустая строка, произойдет сбой. Тем не менее, явно установив значение NULL при вставке или обновлении, все готово.

Но ведь это удовольствие от программирования, не так ли? Создание собственных проблем, а затем их исправление! Ура!

Тоби Крейн
источник
1

Другим способом было бы вставить элемент DEFAULT в другую таблицу. Например, любая ссылка на uuid = 00000000-0000-0000-0000-000000000000 в другой таблице будет указывать на отсутствие действий. Вам также нужно установить все значения для этого идентификатора как «нейтральные», например, 0, пустая строка, ноль, чтобы не влиять на логику кода.

Wildhammer
источник
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;
Шамс Реза
источник