Null oder Eins bis Null oder Eins

9

Wie modelliere ich die Null-oder-Eins-zu-Null-oder-Eins-Beziehung in SQL Server auf natürlichste Weise?

Es gibt eine 'Hazard'-Tabelle, in der die Gefahren auf einer Site aufgelistet sind. Es gibt eine Aufgabentabelle für Arbeiten, die an einer Site ausgeführt werden müssen. Einige Aufgaben dienen dazu, eine Gefahr zu beheben. Keine Aufgabe kann mehrere Gefahren bewältigen. Einige Gefahren haben die Aufgabe, sie zu beheben. Mit keiner Gefahr können zwei Aufgaben verbunden sein.

Das Folgende ist das Beste, was ich mir vorstellen kann:

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

Würden Sie das anders machen? Der Grund, warum ich mit dieser Einrichtung nicht zufrieden bin, ist, dass es eine Anwendungslogik geben muss, um sicherzustellen, dass Aufgaben und Gefahren aufeinander und nicht auf andere Aufgaben und Gefahren verweisen und dass keine Aufgabe / Gefahr auf dieselbe Gefahr / Aufgabe verweist eine andere Aufgabe / Gefahr weist auf.

Gibt es einen besseren Weg?

Geben Sie hier die Bildbeschreibung ein

Andrew Savinykh
quelle
Gibt es einen Grund, warum Sie keinen eindeutigen Index für TaskID in der Hazard-Tabelle und keinen eindeutigen Index für HazardID in der Task-Tabelle erstellen konnten? Das würde es so machen, dass Sie nur einen von ihnen in der Tabelle haben könnten, was meiner Meinung nach das ist, was Sie erreichen wollen.
mskinner
@mskinner, aber sie sind nicht einzigartig, viele von ihnen können sein null.
Andrew Savinykh
Ah, Gotcha. In diesem Fall hat Herr Ben-Gan eine großartige Beschreibung, wie diese Einschränkung erstellt werden kann, um hier mehrere Nullen zuzulassen . Sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Ich denke, das wird für dich funktionieren. Lass es mich wissen, wenn nicht.
mskinner
Als Randnotiz dazu gibt es eine Reihe von Problemen bei der Verwendung gefilterter Indizes. Wenn Sie nicht vertraut sind, sollten Sie diese wahrscheinlich nachlesen. Hier ist ein guter Blog dazu. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… , aber für dieses spezielle Szenario könnte es für Sie gut funktionieren, vorausgesetzt, die anderen Probleme verursachen Ihnen nicht zu viel Kummer.
mskinner
FWIW ein eindeutiger gefilterter Index kann die Eindeutigkeit nur auf die Nicht-Null-Zeilen anwenden, z. B.CREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Antworten:

9

Sie können Ihrer eigenen Vorstellung von einem asymmetrischen Schema folgen, indem Sie einen der Fremdschlüssel aus dem aktuellen Setup entfernen. Um die Symmetrie zu gewährleisten, können Sie beide Fremdschlüssel entfernen und eine Junction-Tabelle mit einer eindeutigen Einschränkung für jede Referenz einführen .

Es wäre also so:

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

Sie können zusätzlich (HazardId, TaskId)den Primärschlüssel deklarieren , wenn Sie diese Kombinationen aus einer anderen Tabelle referenzieren müssen. Um die Paare eindeutig zu halten, ist der Primärschlüssel jedoch nicht erforderlich. Es reicht aus, dass jede ID eindeutig ist.

Andriy M.
quelle
1
+1 Ich denke, dies ist der natürlichste Weg, eine solche Beziehung zu implementieren. Normalerweise wählt man ein Tupel nur dann als primär aus, wenn es minimal ist. Das Tupel (HazardId, TaskId)hat die Tupel (HazardId)und (TaskId)beide identifizieren eine Zeile dieser Tabelle eindeutig. Einer von ihnen sollte als Primärschlüssel ausgewählt werden.
Wunder173
2

Um zusammenzufassen:

  • Gefahren haben eine oder keine Aufgaben
  • Aufgaben haben eine oder keine Gefahren

Wenn die Aufgaben- und Gefahrentabellen für etwas anderes verwendet werden (dh Aufgaben und / oder Gefahren sind mit anderen Daten verknüpft, und das Modell, das Sie uns gezeigt haben, wird vereinfacht, um nur die relevanten Felder anzuzeigen), würde ich sagen, dass Ihre Lösung korrekt ist.

Andernfalls benötigen Sie keine zwei Tabellen, wenn Aufgaben und Gefahren nur vorhanden sind, um miteinander gekoppelt zu werden. Sie können eine einzelne Tabelle für ihre Beziehung mit den folgenden Feldern erstellen:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
DR_
quelle
1
Ich habe mir erlaubt zu klären, dass es sich jeweils um einen gefilterten Index handeln sollte (obwohl dies aus der Beschreibung des Problems erraten werden kann, sieht es möglicherweise nicht ganz offensichtlich aus). Sie können diese gerne weiter bearbeiten, wenn Sie dies für unnötig halten oder die Idee auf andere Weise vermitteln möchten.
Andriy M
Ich muss sagen, dass ich mit dieser Idee immer noch nicht wirklich zufrieden bin. Das Problem ist, dass ich TaskID und HazardID manuell generieren muss. Wenn dies 2012 wäre, wäre es möglich, Sequenzen dafür zu verwenden, aber selbst dann würde es mir nicht richtig erscheinen. Ich denke, das liegt daran, dass die beiden Dinge, Aufgaben und Gefahren, völlig unterschiedliche Einheiten sind und es daher schwierig ist, sie mit der Idee zu vereinbaren, sie in einer einzigen Tabelle zu speichern.
Andriy M
Wenn dieses Design gewählt wird, warum brauchen wir das TaskIDund HazardID? Sie könnten 2 BIT Spalten haben, IsTask, IsHazardund eine Einschränkung , die nicht beide falsch sind. Dann ist die HazardTabelle einfach eine Ansicht: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;bzw. Aufgabe.
Ypercubeᵀᴹ
@ypercube Sie würden ein formloses Objekt in einer Tabelle speichern und dann anhand eines Binärfelds feststellen, ob es sich um eine Aufgabe oder eine Gefahr handelt. Es ist eine hässliche Datenbankmodellierung.
dr_
@AndriyM Ich verstehe und stimme zu, dass wir in den meisten Fällen nicht zwei Arten unterschiedlicher Entitäten in derselben Tabelle speichern sollten. Lesen Sie jedoch meine Einschränkung: Wenn Aufgaben und Gefahren in einer 1: 1-Beziehung stehen und nur für diese existieren , ist es akzeptabel, eine einzelne Tabelle zu verwenden.
dr_
1

Ein anderer Ansatz, der noch nicht erwähnt worden zu sein scheint, besteht darin, dass Gefahren und Aufgaben denselben ID-Bereich verwenden. Wenn eine Gefahr eine Aufgabe hat, hat sie dieselbe ID. Wenn eine Aufgabe für eine Gefahr bestimmt ist, hat sie dieselbe ID.

Sie würden eine Sequenz anstelle von Identitätsspalten verwenden, um diese IDs zu füllen.

Abfragen für diese Art von Datenmodell würden (vollständige) äußere Verknüpfungen verwenden, um ihre Ergebnisse abzurufen.

Dieser Ansatz ist der Antwort von @ AndriyM sehr ähnlich, außer dass seine Antwort zulässt, dass die IDs unterschiedlich sind, und eine Tabelle zum Speichern dieser Beziehung.

Ich bin nicht sicher, ob Sie diesen Ansatz für ein Szenario mit zwei Tabellen verwenden möchten, aber er funktioniert gut, wenn die Anzahl der beteiligten Tabellen zunimmt.

Colin 't Hart
quelle
Vielen Dank für Ihren Beitrag. Ich habe diesen Ansatz ebenfalls berücksichtigt. Ich habe mich am Ende dagegen entschieden, weil die Implementierung der Sequenz in sql2008 umständlich ist und mehr Probleme verursacht, als ich ertragen wollte.
Andrew Savinykh