ноль или один в ноль или один

9

Как мне естественным образом смоделировать отношение «ноль или один к нулю или один» в Sql Server?

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

Ниже это лучшее, что я мог придумать:

CREATE TABLE [dbo].[Hazard](
  [HazardId] [int] IDENTITY(1,1) NOT NULL,
  [TaskId] [int] NULL,
  [Details] [varchar](max) NULL,
 CONSTRAINT [PK_Hazard] PRIMARY KEY CLUSTERED 
(
  [HazardId] ASC
))

GO

ALTER TABLE [dbo].[Hazard]  WITH CHECK ADD  CONSTRAINT [FK_Hazard_Task] FOREIGN KEY([TaskId])
REFERENCES [dbo].[Task] ([TaskId])
GO


CREATE TABLE [dbo].[Task](
  [TaskId] [int] IDENTITY(1,1) NOT NULL,
  [HazardId] [int] NULL,
  [Details] [varchar](max) NULL,
 CONSTRAINT [PK_Task] PRIMARY KEY CLUSTERED 
(
  [TaskId] ASC
))

GO

ALTER TABLE [dbo].[Task]  WITH CHECK ADD  CONSTRAINT [FK_Task_Hazard] FOREIGN KEY([HazardId])
REFERENCES [dbo].[Hazard] ([HazardId])
GO

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

Есть ли способ лучше?

введите описание изображения здесь

Андрей Савиных
источник
Есть ли какая-то причина, по которой вы не смогли создать уникальный индекс TaskID для таблицы Hazard и уникальный индекс HazardID для таблицы Task? Это сделало бы так, чтобы вы могли иметь только один из них в таблице, что я думаю, что вы пытаетесь достичь.
mskinner
@mskinner, но они не уникальны, многие из них могут быть null.
Андрей Савиных
ах, понял В этом случае г-н Бен-Ган отлично написал, как создать это ограничение, чтобы разрешить множественные нули здесь sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Я думаю, что это будет работать для вас. Дайте мне знать, если нет.
mskinner
Как примечание к этому, есть ряд проблем, связанных с использованием отфильтрованных индексов, поэтому, вероятно, стоит их прочитать, если вы не знакомы. Вот хороший блог об этом. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… Но для этого конкретного сценария он может хорошо сработать для вас, если другие проблемы не доставят вам слишком много горя.
mskinner
FWIW уникальный отфильтрованный индекс может применять уникальность только к ненулевым строкам, напримерCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Аарон Бертран

Ответы:

9

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

Итак, это было бы так:

CREATE TABLE dbo.Hazard
(
  HazardId int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Hazard PRIMARY KEY CLUSTERED,
  Details varchar(max) NULL
);

CREATE TABLE dbo.Task
(
  TaskId int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Task PRIMARY KEY CLUSTERED,
  Details varchar(max) NULL,
);

CREATE TABLE dbo.HazardTask
(
  HazardId int NOT NULL
    CONSTRAINT FK_HazardTask_Hazard FOREIGN KEY REFERENCES dbo.Hazard (HazardId)
    CONSTRAINT UQ_HazardTask_Hazard UNIQUE,
  TaskId int NOT NULL
    CONSTRAINT FK_HazardTask_Task FOREIGN KEY REFERENCES dbo.Task (TaskId)
    CONSTRAINT UQ_HazardTask_Task UNIQUE
);

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

Андрей М
источник
1
+1 Я думаю, что это самый естественный способ реализовать такое отношение. но обычно выбирают кортеж в качестве основного, только если он минимален. Кортеж (HazardId, TaskId)имеет кортежи, (HazardId)и (TaskId)оба идентифицируют строку этой таблицы однозначно. Один из них должен быть выбран в качестве первичного ключа.
чудо173
2

Подводить итоги:

  • У опасности есть одна или ноль задач
  • Задачи имеют один или ноль рисков

Если таблицы «Задача» и «Опасность» используются для чего-то другого (например, задачи и / или опасности связаны с другими данными, а модель, которую вы нам показали, упрощена и отображает только соответствующие поля), я бы сказал, что ваше решение верное.

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

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
dr_
источник
1
Я позволил себе уточнить, что в каждом случае это должен быть отфильтрованный индекс (хотя его можно догадаться из описания проблемы, он может показаться не совсем очевидным). Пожалуйста, не стесняйтесь редактировать дальше, если вы считаете это ненужным или хотите донести идею по-другому.
Андрей М
Я должен сказать, что я все еще не очень доволен этой идеей. Проблема в том, что мне придется генерировать TaskID и HazardID вручную. Если бы это был 2012 год, можно было бы использовать последовательности для этого, но даже тогда мне это не показалось бы правильным. Я полагаю, это потому, что две вещи, задачи и опасности, являются совершенно разными сущностями, и поэтому трудно смириться с идеей хранить их в одной таблице.
Андрей М
Если этот дизайн выбран, зачем нам TaskIDи HazardID? Вы можете иметь 2 BIT колонки, IsTask, IsHazardи ограничение, но не оба из них являются ложными. Тогда Hazardтаблица - это просто представление: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;и Задача соответственно.
ypercubeᵀᴹ
@ypercube Вы будете хранить бесформенную вещь в таблице, а затем использовать двоичное поле, чтобы определить, является ли это Задачей или Опасностью. Это уродливое моделирование базы данных.
Др_
@AndriyM Я понимаю и согласен с тем, что в большинстве случаев мы не должны хранить два вида разных сущностей в одной таблице. Тем не менее, прочитайте мое предупреждение: если задачи и опасности находятся в соотношении 1: 1 и существуют только для этого , то допустимо использовать одну таблицу.
Др_
1

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

Для заполнения этих идентификаторов вы должны использовать последовательность, а не столбцы идентификаторов.

Запросы к этому типу модели данных будут использовать (полные) внешние объединения для получения своих результатов.

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

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

Колин т Харт
источник
Спасибо за помощь, я тоже обдумал этот подход. В конце концов, я отказался от этого, потому что последовательность неудобна в реализации в sql2008 и доставляет больше хлопот, чем я бы смирился.
Андрей Савиных