zero-or-one to zero-or-one


9

Come modellare la relazione da zero a uno a zero o uno in SQL Server nel modo più naturale?

Esiste una tabella "Hazard" che elenca i pericoli in un sito. Esiste una tabella "Attività" per il lavoro che deve essere svolto su un sito. Alcune attività consistono nel correggere un pericolo, nessuna attività può gestire più pericoli. Alcuni pericoli hanno il compito di risolverli. A nessun pericolo possono essere associati due compiti.

Di seguito è il migliore che mi viene in mente:

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

Lo faresti diversamente? Il motivo per cui non sono soddisfatto di questa configurazione è che è necessario disporre di una logica applicativa per assicurarsi che le attività e i pericoli puntino l'una verso l'altra e non verso altre attività e pericoli e che nessuna attività / pericolosità indichi la stessa pericolosità / attività un'altra attività / pericolo indica.

Esiste un modo migliore?

inserisci qui la descrizione dell'immagine


C'è qualche motivo per cui non è stato possibile creare un indice univoco su TaskID nella tabella Hazard e un indice univoco di HazardID nella tabella Task? Ciò farebbe in modo che tu possa averne solo uno nel tavolo, che è quello che penso che stai cercando di realizzare.
mskinner,

@mskinner ma non sono unici, molti di loro possono esserlo null.
Andrew Savinykh,

ah, gotcha. In tal caso, Ben-Gan ha una bella descrizione di come creare quel vincolo per consentire più null qui sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Penso che funzionerà per te. Fammi sapere se no.
mskinner,

Come nota a margine, ci sono una serie di problemi con l'utilizzo di indici filtrati, quindi probabilmente varrebbe la pena leggerli se non si ha familiarità. Ecco un buon blog su questo. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/… , Ma per questo scenario specifico, potrebbe funzionare bene per te, supponendo che gli altri problemi non ti causino troppo dolore.
mskinner,

FWIW un indice filtrato univoco può applicare l'unicità solo alle righe non nulle, ad esempioCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Risposte:


9

Potresti andare con la tua idea di uno schema asimmetrico rimuovendo una delle chiavi esterne dall'impostazione corrente oppure, per mantenere le cose simmetriche, puoi rimuovere entrambe le chiavi esterne e introdurre una tabella di giunzione con un vincolo univoco su ciascun riferimento .

Quindi, sarebbe così:

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

È inoltre possibile dichiarare (HazardId, TaskId)di essere la chiave primaria se è necessario fare riferimento a queste combinazioni da un'altra tabella. Allo scopo di mantenere uniche le coppie, tuttavia, la chiave primaria non è necessaria, è sufficiente che ogni ID sia univoco.


1
+1 Penso che questo sia il modo più naturale per attuare una simile relazione. ma di solito si seleziona una tupla come primaria solo se è minima La tupla (HazardId, TaskId)ha le tuple (HazardId)e (TaskId)entrambe identificano in modo univoco una riga di questa tabella. Uno di questi dovrebbe essere selezionato come chiave primaria.
miracle173,

2

Per riassumere:

  • I pericoli hanno uno o zero compiti
  • Le attività hanno uno o zero pericoli

Se le tabelle Task e Hazard vengono utilizzate per qualcos'altro (ad es. Task e / o hazard hanno altri dati associati e il modello che ci hai mostrato è semplificato per mostrare solo i campi pertinenti) direi che la tua soluzione è corretta.

Altrimenti, se Compiti e pericoli esistono solo per essere accoppiati tra loro, non sono necessarie due tabelle; puoi creare una singola tabella per la loro relazione, con i seguenti campi:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar

1
Mi sono preso la libertà di chiarire che dovrebbe essere un indice filtrato in ogni caso, (anche se potrebbe essere indovinato dalla descrizione del problema, potrebbe non sembrare del tutto ovvio). Sentiti libero di modificare ulteriormente se ritieni che non sia necessario o desideri trasmettere l'idea in modo diverso.
Andriy M,

Devo dire che non sono ancora molto contento di questa idea. Il problema è che dovrei generare TaskID e HazardID manualmente. Se fosse il 2012, sarebbe possibile usare sequenze per quello, ma anche allora non mi sembrerebbe giusto. Immagino sia perché le due cose, i compiti e i pericoli, sono entità completamente diverse e quindi è difficile riconciliarsi con l'idea di memorizzarle in una singola tabella.
Andriy M,

Se viene scelto questo disegno, perché abbiamo bisogno del TaskIDe HazardID? Si potrebbe avere 2 colonne BIT, IsTask, IsHazarde un vincolo che non sia di loro sono falsi. Quindi la Hazardtabella è semplicemente una vista: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;e Task rispettivamente.
ypercubeᵀᴹ

@ypercube Dovresti archiviare una cosa informe in una tabella e quindi utilizzare un campo binario per dire se si tratta di un'attività o di un pericolo. È una brutta modellazione di database.
dr_

@AndriyM Capisco e concordo sul fatto che, nella maggior parte dei casi , non dovremmo archiviare due tipi di entità diverse nella stessa tabella. Tuttavia, leggi il mio avvertimento: se Attività e Rischi sono in una relazione 1 a 1 ed esistono solo per questo , allora è accettabile usare una sola tabella.
dr_

1

Un altro approccio che sembra non essere ancora stato menzionato è quello di fare in modo che Hazards and Tasks utilizzino lo stesso spazio ID. Se un Hazard ha un'attività, avrà lo stesso ID. Se un'attività è relativa a un pericolo, avrà lo stesso ID.

Utilizzerai una sequenza anziché le colonne identità per popolare questi ID.

Le query su questo tipo di modello di dati utilizzerebbero join (completi) esterni per recuperare i loro risultati.

Questo approccio è molto simile alla risposta di @ AndriyM, tranne per il fatto che la sua risposta consente di differenziare gli ID e una tabella per memorizzare questa relazione.

Non sono sicuro che tu voglia utilizzare questo approccio per uno scenario a due tabelle, ma funziona bene quando aumenta il numero di tabelle coinvolte.


Grazie per aver contribuito, ho considerato anche questo approccio. Alla fine ho deciso di non farlo perché la sequenza è imbarazzante da implementare in sql2008 e più problemi di quanti sarei disposto a sopportare.
Andrew Savinykh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.