Что означает / должно означать NULL вместе с отношениями FK - База данных - PullRequest
16 голосов
/ 18 марта 2009

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

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

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

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

Ответы [ 12 ]

9 голосов
/ 18 марта 2009

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

Представьте себе таблицу Транспортных средств и Двигателей, и Двигатели еще не установлены в Транспортном средстве (поэтому VehicleID равен нулю). Или таблица Employee с колонкой Supervisor и генеральным директором компании.

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

CREATE TABLE [dbo].[EngineTable](
    [EngineID] [int] IDENTITY(1,1) NOT NULL,
    [EngineCylinders] smallint NOT NULL,
 CONSTRAINT [EngineTbl_PK] PRIMARY KEY NONCLUSTERED 
(
    [EngineID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[CarTable](
    [CarID] [int] IDENTITY(1,1) NOT NULL,
    [Model] [varchar](32) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [EngineID] [int] NULL
 CONSTRAINT [PK_UnitList] PRIMARY KEY CLUSTERED 
(
    [CarID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[CarTable]  WITH CHECK ADD CONSTRAINT [FK_Engine_Car] FOREIGN KEY([EngineID])
REFERENCES [dbo].[EngineTable] ([EngineID])


Insert Into EngineTable (EngineCylinders) Values (4);
Insert Into EngineTable (EngineCylinders) Values (6);
Insert Into EngineTable (EngineCylinders) Values (6);
Insert Into EngineTable (EngineCylinders) Values (8);

- Теперь некоторые тесты:

Insert Into CarTable (Model, EngineID) Values ('G35x', 3);  -- References the third engine

Insert Into CarTable (Model, EngineID) Values ('Sienna', 13);  -- Invalid FK reference - throws an error

Insert Into CarTable (Model) Values ('M');  -- Leaves null in the engine id field & does NOT throw an error 
8 голосов
/ 18 марта 2009

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

Варианты использования для "еще не установленных" отношений действительны, но с нулевыми FK некоторые считают, что это усложняет их запросы, вводя более сложные функции SQL, в частности, LEFT JOINs.

Одним из распространенных альтернативных решений, которое я видел, является введение «нулевой строки» или «строки часового» в каждую таблицу с pk = 0 или pk = 1 (в зависимости от того, что поддерживается вашей RDBMS). Это позволяет вам создавать доменный слой с «еще не установленными» отношениями, но также избегать введения ЛЕВЫХ СОЕДИНЕНИЙ, поскольку вы гарантируете, что всегда будет к чему присоединиться.

Конечно, этот подход также требует усердия, потому что вы в основном торгуете с ЛЕВЫМИ СОЕДИНЕНИЯМИ за необходимость проверять наличие вашей строки дозорного в запросах, чтобы вы не обновляли / не удаляли ее и т. Д. оправданы это другое дело. Я склонен согласиться с тем, что изобретать null просто для того, чтобы избежать более изящного объединения, кажется немного глупым, но я также работал в среде, где разработчики приложений не выигрывают дебаты с администраторами баз данных.

редактирует

Я удалил некоторые формулировки «по сути» и попытался уточнить, что я имел в виду под «неудачными» объединениями. Пример @ wcoenen - причина, по которой я лично чаще всего слышал, чтобы избегать нулевых ФК. Дело не в том, что они терпят неудачу, как в «сломанном», а в том, что некоторые терпят неудачу - придерживаться принципа наименьшего удивления.

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

7 голосов
/ 18 марта 2009

Я настоятельно поддерживаю аргументы для NULL во внешних ключах для указания отсутствия родителя в системе OLTP, но в системе поддержки принятия решений это редко работает хорошо. Там наиболее целесообразно использовать специальное значение «Не применимо» (или аналогичное) в качестве родительского (в таблице измерения), к которому могут ссылаться дочерние записи (в таблице фактов).

Причина этого заключается в том, что исследовательский характер детализации / поперечной обработки и т. Д. Может привести к тому, что пользователи не поймут, как метрика может измениться, когда они просто запросили дополнительную информацию о ней. Например, если витрина финансовых данных включает в себя сочетание продаж продукта и других источников дохода, при детализации к «Типу продукта» следует классифицировать данные, относящиеся к продажам, не относящимся к продаже, как таковые, вместо того, чтобы эти цифры исключались из отчета, поскольку нет соединения таблицы фактов с таблицей измерений продукта.

6 голосов
/ 18 марта 2009

Проблема с пустыми значениями в столбцах внешнего ключа возникает, когда внешний ключ является составным. Что это значит, если один из двух столбцов равен нулю? Должен ли другой столбец соответствовать чему-либо в указанной таблице? С простыми (одностолбцовыми) ограничениями внешнего ключа вы можете обойтись нулевыми значениями.

С другой стороны, если взаимосвязь между двумя таблицами является условной (обе сущности могут существовать сами по себе, но могут быть почти случайно связаны), то лучше всего смоделировать это с помощью «соединяющей таблицы» - таблицы который содержит FK для ссылочной таблицы и другой для ссылочной таблицы и который имеет свой собственный первичный ключ в виде комбинации двух FK.

В качестве примера таблицы присоединения, предположим, что в вашей базе данных есть таблицы клубов и людей. Некоторые люди принадлежат к некоторым клубам. Таблица присоединения будет club_members и будет содержать FK для лица, ссылающегося на таблицу «people», и будет содержать другой FK для клуба, к которому принадлежит человек, и комбинация идентификаторов для person и club будет первичным ключом соединительный стол (Другое название присоединяющейся таблицы - «ассоциативная» или «ассоциативная» таблица.)

4 голосов
/ 18 марта 2009

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

3 голосов
/ 18 марта 2009

Если вы назначаете NULL для бизнес-причины, вы по существу переопределяете, что означает NULL в вашем домене, и должны документировать это для пользователей и будущих разработчиков. Если есть бизнес-причина для использования NULL в качестве внешнего ключа, я бы посоветовал вам сделать это, как уже упоминали другие, и добавить запись о присоединении, которая имеет значение чего-то вроде «N / A» или «Not Assigned».

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

3 голосов
/ 18 марта 2009
CREATE TABLE [tree]
{
    [id] int NOT NULL,
    [parent_id] int NULL
};

ALTER TABLE [tree] ADD CONSTRAINT [FK_tree_tree] FOREIGN KEY([parent_id])
REFERENCES [tree] ([id]);

В этом нет ничего плохого! Корневой узел всегда будет иметь родительский NULL, и это не случай «еще не установленных» отношений. Здесь также нет проблем с объединениями.

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

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

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

3 голосов
/ 18 марта 2009

Предположим, вам нужно создать отчет обо всех клиентах. Каждый клиент имеет свой FK для страны, и данные о стране должны быть включены в отчет. Теперь предположим, что вы разрешаете FK быть null и выполняете следующий запрос:

SELECT * FROM customer, country WHERE customer.countryID = country.ID

Любой клиент, для которого FK страны null, будет молча исключен из отчета (вместо этого нужно использовать LEFT JOIN вместо этого) Я нахожу это неинтуитивным и удивительным, поэтому мне не нравятся NULL FK, и я избегаю их в схемах моей базы данных. Вместо этого я использую дозорные значения, например особая «неизвестная страна».

2 голосов
/ 02 марта 2011

Я бы сказал, что, хотя это вполне возможно, в чем проблема использования соединительного стола в соответствии с хорошо сформулированным замечанием Джонатона Леффлера?

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

Затем, чтобы упростить свои запросы, я просто сделал СЛЕДУЮЩЕЕ СОЕДИНЕНИЕ двух таблиц, которое создает видимость необязательного объединения, но на самом деле использует более четкую структуру базы данных. Кроме того, используя ISNULL (MyField, «None»), на мой взгляд, я могу обеспечить преимущества «не присутствующего» дополнительного дизайна строк, но без боли.

С учетом упомянутых здесь моментов у меня есть вопросы администратора баз данных - зачем иметь пустой столбец, когда вы можете иметь более "прочные" отношения, которые проще использовать с представлением? И без особых дополнительных усилий.

2 голосов
/ 18 марта 2009

Вы правильно поняли. Для FK NULL означает отсутствие значения (то есть отсутствие отношения). Если в FK есть значение, оно должно точно соответствовать одному значению в PK, на которое оно ссылается.

Это не обязательно плохой дизайн, чтобы разрешить это. Если отношение «один ко многим» и является необязательным, то вполне нормально добавить FK в таблицу на одной стороне, ссылаясь на PK на другой стороне.

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

Некоторые люди проектируют так, чтобы избежать необходимости разрешения NULLS. Эти люди будут использовать соединительную таблицу для связи «многие-к-одному» и пропускают строку, как указано выше, когда связь отсутствует.

Я сам не следую этой практике, но она имеет определенные преимущества.

...