od zera lub jednego do zera lub jednego

9

Jak w najbardziej naturalny sposób modelować relację zero-jedynka do zera jeden lub jeden w Sql Server?

Istnieje tabela „Zagrożenia”, która zawiera listę zagrożeń w witrynie. Istnieje tabela „Zadań” do pracy, którą należy wykonać na stronie. Niektóre zadania mają naprawić zagrożenie, żadne zadanie nie może poradzić sobie z wieloma zagrożeniami. Niektóre zagrożenia mają za zadanie je naprawić. Żadnemu zagrożeniu nie można przypisać dwóch zadań.

Poniżej jest najlepsze, o czym mogłem myśleć:

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

Czy zrobiłbyś to inaczej? Powodem, dla którego nie jestem zadowolony z tej konfiguracji jest to, że musi istnieć logika aplikacji, aby upewnić się, że zadania i zagrożenia wskazują na siebie nawzajem, a nie na inne zadania i zagrożenia, i że żadne zadanie / zagrożenie nie wskazuje na to samo zagrożenie / zadanie inne zadanie / zagrożenie wskazuje na.

Czy jest lepszy sposób?

wprowadź opis zdjęcia tutaj

Andrew Savinykh
źródło
Czy istnieje powód, dla którego nie można utworzyć unikalnego indeksu dla TaskID w tabeli zagrożeń i unikalnego indeksu HazardID w tabeli zadań? To sprawiłoby, że mógłbyś mieć tylko jednego z nich na stole, co myślę, co próbujesz osiągnąć.
mskinner,
@mskinner, ale nie są unikalne, wiele z nich może być null.
Andrew Savinykh,
ach, mam cię. W takim przypadku pan Ben-Gan ma świetny opis, jak utworzyć to ograniczenie, aby zezwolić na wiele wartości null tutaj sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Myślę, że to zadziała dla ciebie. Daj mi znać, jeśli nie.
mskinner,
Na marginesie należy zauważyć, że istnieje wiele problemów z używaniem filtrowanych indeksów, więc prawdopodobnie warto je przeczytać, jeśli nie jesteś zaznajomiony. Oto dobry blog na ten temat. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/... , Ale w tym konkretnym scenariuszu może on działać dobrze, zakładając, że inne problemy nie powodują zbyt dużego smutku.
mskinner,
FWIW unikalny filtrowany indeks może zastosować unikalność tylko do wierszy niepustych, np.CREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Odpowiedzi:

9

Możesz pójść z własnym pomysłem na schemat asymetryczny, usuwając jeden z kluczy obcych z bieżącej konfiguracji, lub, aby zachować symetryczność, możesz usunąć oba klucze obce i wprowadzić tabelę połączeń z unikalnym ograniczeniem dla każdego odwołania .

Tak by wyglądało to tak:

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

Możesz dodatkowo zadeklarować, (HazardId, TaskId)że jesteś kluczem podstawowym, jeśli chcesz odwoływać się do tych kombinacji z innej tabeli. Jednak aby zachować unikalność par, klucz podstawowy jest niepotrzebny, wystarczy, aby każdy identyfikator był unikalny.

Andriy M.
źródło
1
+1 Myślę, że jest to najbardziej naturalny sposób na wdrożenie takiej relacji. ale zwykle wybiera się krotkę jako podstawową tylko wtedy, gdy jest minimalna Krotka (HazardId, TaskId)ma krotki (HazardId)i (TaskId)oba jednoznacznie identyfikują rząd tej tabeli. Jeden z nich należy wybrać jako klucz podstawowy.
miracle173,
2

Podsumowując:

  • Zagrożenia mają jedno lub zero zadań
  • Zadania mają jedno lub zero zagrożeń

Jeśli tabele zadań i zagrożeń są używane do czegoś innego (tj. Zadania i / lub zagrożenia mają inne powiązane dane, a model, który nam pokazałeś, jest uproszczony, aby pokazać tylko odpowiednie pola), powiedziałbym, że twoje rozwiązanie jest poprawne.

W przeciwnym razie, jeśli Zadania i Zagrożenia istnieją tylko w celu ich połączenia, nie potrzebujesz dwóch tabel; możesz utworzyć jedną tabelę dla ich relacji, z następującymi polami:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
dr_
źródło
1
Pozwoliłem sobie wyjaśnić, że powinien to być filtrowany indeks w każdym przypadku (chociaż można go zgadnąć z opisu problemu, może nie wyglądać to całkowicie oczywiste). Prosimy o dalszą edycję, jeśli uważasz, że jest to niepotrzebne lub chcesz przekazać pomysł w inny sposób.
Andriy M,
Muszę powiedzieć, że wciąż nie jestem zadowolony z tego pomysłu. Problem polega na tym, że musiałbym ręcznie wygenerować TaskID i HazardID. Gdyby to był rok 2012, można by do tego użyć sekwencji, ale nawet wtedy nie wydawałoby mi się to właściwe. Wydaje mi się, że to dlatego, że dwie rzeczy, zadania i zagrożenia są zupełnie różnymi bytami, dlatego trudno jest pogodzić się z pomysłem przechowywania ich w jednym stole.
Andriy M,
Jeśli ten projekt zostanie wybrany, dlaczego potrzebujemy TaskIDi HazardID? Można mieć 2 kolumny bit, IsTask, IsHazardi ograniczenie, które nie oba z nich są fałszywe. Zatem Hazardtabela jest po prostu odpowiednio widokiem: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;i zadaniem.
ypercubeᵀᴹ
@ypercube Będziesz przechowywać bezkształtną rzecz w tabeli, a następnie użyj pola binarnego, aby stwierdzić, czy jest to zadanie, czy zagrożenie. To brzydkie modelowanie bazy danych.
dr_
@AndriyM Rozumiem i zgadzam się, że w większości przypadków nie powinniśmy przechowywać dwóch rodzajów różnych podmiotów w tej samej tabeli. Przeczytaj jednak moje zastrzeżenie: jeśli Zadania i Zagrożenia są w relacji 1-do-1 i istnieją tylko w tym celu , to dopuszczalne jest użycie pojedynczej tabeli.
dr_
1

Innym podejściem, które wydaje się jeszcze nie wspomniane, jest używanie zagrożeń i zadań w tej samej przestrzeni ID. Jeśli zagrożenie ma zadanie, będzie miało ten sam identyfikator. Jeśli zadanie dotyczy zagrożenia, będzie miało ten sam identyfikator.

Aby wypełnić te identyfikatory, należy użyć sekwencji zamiast kolumn tożsamości.

Kwerendy dotyczące tego typu modelu danych wykorzystywałyby (pełne) sprzężenia zewnętrzne do pobierania wyników.

To podejście jest bardzo podobne do odpowiedzi @ AndriyM, tyle że jego odpowiedź pozwala na różne identyfikatory i tabelę do przechowywania tej relacji.

Nie jestem pewien, czy chcesz zastosować to podejście do scenariusza z dwiema tabelami, ale działa ono dobrze, gdy wzrasta liczba zaangażowanych tabel.

Colin 't Hart
źródło
Dziękuję za wkład, rozważałem również to podejście. Ostatecznie zdecydowałem się tego nie robić, ponieważ sekwencja jest trudna do wdrożenia w sql2008 i więcej kłopotów, niż chciałbym znieść.
Andrew Savinykh