cero o uno a cero o uno

9

¿Cómo modelo la relación de cero o uno a cero o uno en Sql Server de la manera más natural?

Hay una tabla de 'Peligro' que enumera los peligros en un sitio. Hay una tabla de "Tareas" para el trabajo que debe hacerse en un sitio. Algunas tareas son para solucionar un peligro, ninguna tarea puede hacer frente a múltiples peligros. Algunos peligros tienen la tarea de solucionarlos. Ningún peligro puede tener dos tareas asociadas con ellos.

Lo siguiente es lo mejor que se me ocurre:

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

¿Harías eso de manera diferente? La razón por la que no estoy contento con esta configuración es que debe existir una lógica de aplicación para asegurarse de que las tareas y los peligros se señalen entre sí y no a otras tareas y peligros, y que ninguna tarea / peligro señale al mismo peligro / tarea otra tarea / puntos de peligro a.

¿Hay una mejor manera?

ingrese la descripción de la imagen aquí

Andrew Savinykh
fuente
¿Hay alguna razón por la que no pueda crear un índice único en TaskID en la tabla Hazard y un índice único de HazardID en la tabla Task? Eso haría que solo pudieras tener uno de ellos en la mesa, que creo que es lo que estás tratando de lograr.
mskinner
@mskinner pero no son únicos, muchos de ellos pueden serlo null.
Andrew Savinykh
Ah, te tengo. En ese caso, el Sr. Ben-Gan tiene una excelente redacción sobre cómo crear esa restricción para permitir múltiples nulos aquí sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Creo que eso funcionará para ti. Avísame si no.
mskinner
Como nota al margen de eso, hay una serie de problemas al usar índices filtrados, por lo que probablemente valga la pena leerlos si no está familiarizado. Aquí hay un buen blog sobre eso. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… , Pero para este escenario específico, podría funcionar bien para usted, suponiendo que los otros problemas no le causen demasiada pena.
mskinner
FWIW, un índice filtrado único puede aplicar unicidad solo a las filas no nulas, por ejemploCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Respuestas:

9

Puede seguir su propia idea de un esquema asimétrico eliminando una de las claves externas de la configuración actual o, para mantener las cosas simétricas, puede eliminar ambas claves externas e introducir una tabla de unión con una restricción única en cada referencia .

Entonces, sería así:

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
);

También puede declarar (HazardId, TaskId)que es la clave principal si necesita hacer referencia a estas combinaciones desde otra tabla. Sin embargo, con el fin de mantener los pares únicos, la clave primaria es innecesaria, es suficiente que cada ID sea única.

Andriy M
fuente
1
+1 Creo que esta es la forma más natural de implementar tal relación. pero generalmente uno selecciona una tupla como primaria solo si es mínima. La tupla (HazardId, TaskId)tiene las tuplas (HazardId)y (TaskId)ambas identifican una fila de esta tabla de manera única. Uno de ellos debe seleccionarse como clave principal.
miracle173
2

Para resumir:

  • Los peligros tienen una o cero tareas
  • Las tareas tienen uno o cero peligros

Si las tablas de tareas y riesgos se usan para otra cosa (es decir, las tareas y / o los riesgos tienen otros datos asociados, y el modelo que nos mostró está simplificado para mostrar solo los campos relevantes), diría que su solución es correcta.

De lo contrario, si las Tareas y los Peligros solo existen para combinarse entre sí, no necesita dos tablas; Puede crear una sola tabla para su relación, con los siguientes campos:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
Dr_
fuente
1
Me tomé la libertad de aclarar que debería ser un índice filtrado en cada caso (aunque podría adivinarse por la descripción del problema, puede que no parezca del todo obvio). Siéntase libre de editar más si lo considera innecesario o si desea transmitir la idea de una manera diferente.
Andriy M
Debo decir que todavía no estoy muy contento con esta idea. El problema es que tendría que generar TaskID y HazardID manualmente. Si esto fuera 2012, sería posible usar secuencias para eso, pero incluso así no me parecería correcto. Supongo que es porque las dos cosas, tareas y riesgos, son entidades completamente diferentes y, por lo tanto, es difícil conciliar con la idea de almacenarlos en una sola tabla.
Andriy M
Si se elige este diseño, ¿por qué necesitamos el TaskIDy HazardID? Podría tener 2 columnas BIT IsTask, IsHazardy una restricción de que no ambas son falsas. Entonces la Hazardtabla es simplemente una vista: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;y Tarea respectivamente.
ypercubeᵀᴹ
@ypercube Estaría almacenando una cosa sin forma en una tabla y luego usaría un campo binario para saber si es una Tarea o un Peligro. Es feo modelado de bases de datos.
dr_
@AndriyM Entiendo, y estoy de acuerdo en que, en la mayoría de los casos , no deberíamos almacenar dos tipos de entidades diferentes en la misma tabla. Sin embargo, lea mi advertencia: si Tareas y Peligros están en una relación de 1 a 1 y existen solo para esto , entonces es aceptable usar una sola tabla.
dr_
1

Otro enfoque que parece no haber sido mencionado todavía es hacer que Hazards and Tasks use el mismo espacio de ID. Si un peligro tiene una tarea, tendrá la misma identificación. Si una tarea es para un peligro, tendrá la misma identificación.

Usaría una secuencia en lugar de columnas de identidad para completar estos ID.

Las consultas sobre este tipo de modelo de datos utilizarían combinaciones externas (completas) para recuperar sus resultados.

Este enfoque es muy similar a la respuesta de @ AndriyM, excepto que su respuesta permite que los ID sean diferentes y una tabla para almacenar esta relación.

No estoy seguro de que desee utilizar este enfoque para un escenario de dos tablas, pero funciona bien cuando aumenta el número de tablas involucradas.

Colin 't Hart
fuente
Gracias por contribuir, también he considerado ese enfoque. Al final decidí no hacerlo porque la secuencia es difícil de implementar en sql2008 y tiene más problemas de los que estaría dispuesto a soportar.
Andrew Savinykh