Обеспечение связи «ноль или один к одному» в базе данных SQL? - PullRequest
3 голосов
/ 14 ноября 2011

У меня есть Post сущность и FbPost сущность.

Post.FbPost равно нулю или FbPost, и никакие два объекта Post не могут ссылаться на один и тот же объект FbPost. Другими словами, zero-or-one to one.

Как правильно применять это как zero-or-one to one вместо many to one в SQL Server (в идеале)?

Если это невозможно, как я могу применить это на уровне EF?

Ответы [ 4 ]

5 голосов
/ 14 ноября 2011

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

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

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

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

Однако это не дает вам требования «только одна исходная строка может ссылаться на целевую строку». Это можно добавить с помощью триггера before- (insert / update), который будет проверять каждую вторую строку в исходной таблице, чтобы убедиться, что никакая другая строка уже не ссылается на целевую строку.

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


(a) Следующий текст, перефразированный с здесь , показывает различную поддержку обнуляемых уникальных столбцов в нескольких продуктах СУБД:

Стандарт:

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

Столбец или набор столбцов, на которые распространяется ограничение UNIQUE, также должны быть подвержены ограничению NOT NULL, если только СУБД не реализует необязательную функцию «NULLs позволен» (идентификатор функции 591). Дополнительная функция добавляет некоторые дополнительные характеристики к ограничению UNIQUE:

Во-первых, столбцы, включенные в ограничение UNIQUE, могут также иметь ограничения NOT NULL, но они не обязаны это делать. Во-вторых, если столбцы с ограничениями UNIQUE также не имеют ограничений NOT NULL, то столбцы могут содержать любое количество значений NULL (логическое следствие того факта, что NULL не равен NULL).

PostgreSQL:

Соответствует стандарту, включая дополнительную разрешенную функцию NULL.

DB2:

Следует за необязательными частями UNIQUE-ограничения. Не реализует дополнительную разрешенную функцию NULL.

MSSQL:

Следует стандарту с изюминкой.

MSSQL предлагает функцию разрешенных значений NULL, но допускает не более одного экземпляра значения NULL, если допустимы NULL. Другими словами, он нарушает характеристику 2 в приведенном выше описании стандарта.

MySQL:

Соответствует стандарту, включая дополнительную разрешенную функцию NULL.

Oracle:

Следует стандарту с изюминкой относительно многоколоночных UNIQUE-ограничений.

Реализована необязательная функция разрешенных значений NULL: если UNIQUE-ограничение накладывается на один столбец, то этот столбец может содержать любое количество NULL (как ожидается из характеристики 2 в приведенном выше описании стандарта). Однако если UNIQUE-ограничение указано для нескольких столбцов, то Oracle видит ограничение как нарушенное, если любые две строки

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

(b) Другим выходом, конечно, является выбор СУБД, реализующей эту функцию, например PostgreSQL или MySQL.

Это может оказаться невозможным в вашем конкретном случаено это должно быть, по крайней мере, . Например, я держусь подальше от Oracle из-за его неспособности различать NULL из пустых строк в определенных символьных столбцах, хотя другие, вероятно, не как "пурист" (моя женасказал бы "анальный retentive"), как я: -)

4 голосов
/ 14 ноября 2011

Создайте уникальный отфильтрованный индекс:

CREATE UNIQUE INDEX Post_Unq_FbPost ON dbo.Post(FbPost) WHERE FbPost IS NOT NULL;

Также, конечно, создайте внешний ключ.

1 голос
/ 27 декабря 2012

Суперкласс It!

Я думаю, что лучшее решение - сделать таблицу Post суперклассом таблицы FBPost. (Примечание: я собираюсь использовать PostID и FBPostID здесь, чтобы было абсолютно ясно, о чем я говорю.) То есть полностью удалите столбец FBPostID из таблицы Post после обновления FBPost.FBPostID столбец, соответствующий соответствующему PostID. Вместо каждого FBPostID, имеющего свое уникальное значение, оно будет иметь то же значение, что и PostID. По моему профессиональному мнению, это правильный способ моделировать отношения «один к нулю или один». Он обладает огромным преимуществом: он надежен и не требует никаких дополнительных индексов, триггеров или ограничений, помимо того, что уже обеспечивает простой FK.

Примечание: я предполагаю, что мы можем просто обновить столбец FBPostID в FBPost, как только мы уроним (предположительно) PK на него. Если это столбец identity, то потребуется дополнительная работа - просто добавьте новый столбец, который станет новым PK, и переименуйте исходный столбец. Если порядок столбцов имеет значение, данные должны быть перемещены в новую таблицу, чтобы новый столбец появился в нужном месте.

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

Почему вы выбрали бы это

Когда вы моделируете отношение is-a вместо отношения has-a, и эти два объекта участвуют как ноль-ноль-ноль или один, тогда они должны использовать один и тот же суррогатный ключ, потому что на самом деле они ' одна и та же сущность (на самом деле вы моделируете отношения is-sometimes-a).

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

  • Вы бы никогда не написали триггер для принудительного применения отношения внешнего ключа, поскольку базы данных предлагают явные ограничения внешнего ключа, которые выполняют работу со 100% надежностью.
  • Вы никогда бы не изменили системные таблицы напрямую, чтобы создать новую таблицу, вы бы выполнили команду CREATE TABLE.
  • И так далее, и тому подобное.

Точно так же вы использовали бы комбинацию внешнего ключа / первичного ключа для моделирования отношения is-sometimes-a, не выполняя странных разрешающих NULL уникальных ограничений или отфильтрованных индексов для достижения этой цели.

Пример сценария изменения

Это даже не так уж плохо! Вот некоторый скрипт, который должен довольно хорошо начать:

BEGIN TRAN;
ALTER TABLE dbo.Post DROP CONSTRAINT FK_Post_FBPostID; -- referencing FBPost
-- Also remove all FKs from any other tables referencing FBPostID

ALTER TABLE dbo.FBPost DROP CONSTRAINT PK_FBPost; -- FBPostID column not PK
ALTER TABLE dbo.FBPost ADD OriginalFBPostID int;
UPDATE dbo.FBPost WITH (HOLDLOCK, UPDLOCK) SET OriginalFBPostID = FBPostID;

UPDATE F
SET F.FBPostID = P.PostID
FROM
   dbo.FBPost F
   INNER JOIN dbo.Post P
      ON F.OriginalFBPostID = P.FBPostID;

-- Perform a similar update on all other tables referencing FBPostID

-- Now, the two most important changes
ALTER TABLE dbo.FBPost ADD CONSTRAINT PK_FBPost PRIMARY KEY CLUSTERED (FBPostID);
ALTER TABLE dbo.FBPost ADD CONSTRAINT FK_FBPost_PostID FOREIGN KEY
   REFERENCES dbo.Post (PostID); -- This is where the magic happens!

ALTER TABLE dbo.SomeTable ADD CONSTRAINT FK_SomeTable_FBPostID FOREIGN KEY
   REFERENCES dbo.FBPost (FBPostID); -- and all other tables referencing FBPostID

EXEC sp_rename 'dbo.Post.FBPostID', 'OriginalFBPostID'; -- should stop using it
ALTER TABLE dbo.Post DROP COLUMN FBPostID; -- Or even better, remove it.
COMMIT TRAN;
ALTER TABLE dbo.FBPost DROP COLUMN OriginalFBPostID; -- Meaningless now
-- If you keep OriginalFBPostID and it is identity, please copy the values
-- to a new non-identity column and drop it so you don't keep generating more

Наконец, измените ваш код вставки Post / FBPost, чтобы использовать PostID в качестве FBPostID.

Появление нового запроса

Просто чтобы понять суть, ваши объединения между таблицами раньше выглядели так:

SELECT
   P.Something,
   F.SomethingElse
FROM
   dbo.Post P
   INNER JOIN dbo.FBPost F
      ON P.FBPostID = F.FBPostID

Но теперь они будут выглядеть так:

SELECT
   P.Something,
   F.SomethingElse
FROM
   dbo.Post P
   INNER JOIN dbo.FBPost F
      ON P.PostID = F.FBPostID -- the important part

Теперь проблема полностью решена! Ваши таблицы даже занимают меньше места (потеря столбца FBPostID из Post). Вам не нужно возиться с FK, который допускает несколько NULL. С PK на FBPostID в таблице FBPost очевидно, что вы можете иметь только одну FBPost строку на FBPostID.

0 голосов
/ 14 ноября 2011

NULL способный дает вам 0 ... УНИКАЛЬНОЕ СОДЕРЖАНИЕ на вашей ссылке FK даст вам 1.

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