zéro ou un à zéro ou un

9

Comment modéliser la relation zéro ou un à zéro ou un dans Sql Server de la manière la plus naturelle?

Il existe un tableau des «dangers» qui répertorie les dangers sur un site. Il existe une table «Tâche» pour le travail qui doit être effectué sur un site. Certaines tâches consistent à corriger un danger, aucune tâche ne peut faire face à plusieurs dangers. Certains dangers ont pour tâche de les résoudre. Aucun danger ne peut être associé à deux tâches.

Ce qui suit est le meilleur auquel j'ai pu penser:

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

Le feriez-vous différemment? La raison pour laquelle je ne suis pas satisfait de cette configuration est qu'il doit y avoir une logique d'application pour s'assurer que les tâches et les dangers pointent les uns vers les autres et non vers d'autres tâches et dangers et qu'aucune tâche / danger ne pointe vers le même danger / tâche. une autre tâche / un danger indique.

Y a-t-il une meilleure façon?

entrez la description de l'image ici

Andrew Savinykh
la source
Y a-t-il une raison pour laquelle vous n'avez pas pu créer un index unique sur TaskID sur la table des dangers et un index unique de HazardID sur la table des tâches? Cela ferait en sorte que vous ne pourriez en avoir qu'un seul dans le tableau, ce qui est, je pense, ce que vous essayez d'accomplir.
mskinner
@mskinner mais ils ne sont pas uniques, beaucoup d'entre eux peuvent l'être null.
Andrew Savinykh
ah, gotcha. Dans ce cas, M. Ben-Gan a une grande écriture sur la façon de créer cette contrainte pour autoriser plusieurs null ici sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Je pense que cela fonctionnera pour vous. Faites-moi savoir sinon.
mskinner
En guise de remarque, il existe un certain nombre de problèmes à l'aide des index filtrés, il serait donc probablement utile de les lire si vous n'êtes pas familier. Voici un bon blog à ce sujet. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… , mais pour ce scénario spécifique, cela pourrait bien fonctionner pour vous, en supposant que les autres problèmes ne vous causent pas trop de chagrin.
mskinner
FWIW un index filtré unique ne peut appliquer l'unicité qu'aux lignes non nulles, par exempleCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Réponses:

9

Vous pouvez utiliser votre propre idée d'un schéma asymétrique en supprimant l'une des clés étrangères de la configuration actuelle, ou, pour garder la symétrie, vous pouvez supprimer les deux clés étrangères et introduire une table de jonction avec une contrainte unique sur chaque référence. .

Donc, ce serait comme ça:

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

Vous pouvez également déclarer (HazardId, TaskId)être la clé primaire si vous devez référencer ces combinaisons à partir d'une autre table. Dans le but de garder les paires uniques, cependant, la clé primaire n'est pas nécessaire, il suffit que chaque ID soit unique.

Andriy M
la source
1
+1 Je pense que c'est la façon la plus naturelle de mettre en œuvre une telle relation. mais d'habitude on ne sélectionne un tuple comme primaire que s'il est minimal. Le tuple (HazardId, TaskId)a les tuples (HazardId)et (TaskId)que les deux identifient une ligne de ce tableau de façon unique. L'un d'eux doit être sélectionné comme clé primaire.
miracle173
2

Pour résumer:

  • Les dangers ont une ou zéro tâche
  • Les tâches comportent un ou zéro danger

Si les tableaux des tâches et des dangers sont utilisés pour autre chose (c'est-à-dire que les tâches et / ou les dangers ont d'autres données associées et que le modèle que vous nous avez montré est simplifié pour ne montrer que les champs pertinents), je dirais que votre solution est correcte.

Sinon, si les tâches et les dangers n'existent que pour être couplés les uns aux autres, vous n'avez pas besoin de deux tables; vous pouvez créer une seule table pour leur relation, avec les champs suivants:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
dr_
la source
1
J'ai pris la liberté de préciser que ce devrait être un index filtré dans chaque cas (bien qu'il puisse être deviné à partir de la description du problème, il peut ne pas sembler tout à fait évident). N'hésitez pas à modifier davantage si vous pensez que cela n'est pas nécessaire ou si vous souhaitez transmettre l'idée d'une manière différente.
Andriy M
Je dois dire que je ne suis toujours pas vraiment satisfait de cette idée. Le problème est que je devrais générer manuellement TaskID et HazardID. Si c'était 2012, il serait possible d'utiliser des séquences pour cela, mais même alors cela ne me semblerait pas juste. Je suppose que c'est parce que les deux choses, tâches et dangers, sont des entités complètement différentes et qu'il est donc difficile de se réconcilier avec l'idée de les stocker dans une seule table.
Andriy M
Si cette conception est choisie, pourquoi avons-nous besoin du TaskIDet HazardID? Vous pouvez avoir 2 colonnes BIT IsTask, IsHazardet une contrainte selon laquelle les deux ne sont pas fausses. Le Hazardtableau est alors simplement une vue: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;et Task respectivement.
ypercubeᵀᴹ
@ypercube Vous stockeriez une chose informe dans une table, puis vous utiliseriez un champ binaire pour dire s'il s'agit d'une tâche ou d'un danger. C'est une modélisation de base de données laide.
dr_
@AndriyM Je comprends et je conviens que, dans la plupart des cas , nous ne devons pas stocker deux types d'entités différentes dans la même table. Cependant, lisez ma mise en garde: si les tâches et les dangers sont dans une relation 1 à 1 et n'existent que pour cela , alors il est acceptable d'utiliser une seule table.
dr_
1

Une autre approche qui ne semble pas encore être mentionnée consiste à faire en sorte que les dangers et les tâches utilisent le même espace d'identification. Si un danger a une tâche, il aura le même ID. Si une tâche concerne un danger, elle aura le même ID.

Vous utiliseriez une séquence plutôt que des colonnes d'identité pour remplir ces ID.

Les requêtes sur ce type de modèle de données utiliseraient des jointures externes (complètes) pour récupérer leurs résultats.

Cette approche est très similaire à la réponse de @ AndriyM, sauf que sa réponse permet que les ID soient différents et une table pour stocker cette relation.

Je ne suis pas sûr que vous souhaitiez utiliser cette approche pour un scénario à deux tables, mais cela fonctionne bien lorsque le nombre de tables impliquées augmente.

Colin 't Hart
la source
Merci d'avoir contribué, j'ai également envisagé cette approche. J'ai finalement décidé de ne pas le faire, car la séquence est difficile à mettre en œuvre dans sql2008 et plus de problèmes que je ne le souhaiterais.
Andrew Savinykh