nol-atau-satu ke nol atau satu

9

Bagaimana cara saya memodelkan hubungan nol atau satu ke nol atau satu di Sql Server dengan cara yang paling alami?

Ada tabel 'Bahaya' yang mencantumkan bahaya di situs. Ada tabel 'Tugas' untuk pekerjaan yang perlu dilakukan di situs. Beberapa Tugas adalah untuk memperbaiki bahaya, tidak ada tugas yang dapat menangani beberapa bahaya. Beberapa Bahaya memiliki tugas untuk memperbaikinya. Tidak ada bahaya yang dapat memiliki dua tugas yang terkait dengannya.

Di bawah ini adalah yang terbaik yang bisa saya pikirkan:

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

Apakah Anda akan melakukannya secara berbeda? Alasan saya tidak senang dengan pengaturan ini adalah bahwa harus ada logika aplikasi untuk memastikan bahwa tugas dan bahaya menunjuk satu sama lain dan tidak ke tugas dan bahaya lain dan bahwa tidak ada tugas / bahaya menunjuk ke bahaya / tugas yang sama tugas / bahaya lain menunjuk ke.

Apakah ada cara yang lebih baik?

masukkan deskripsi gambar di sini

Andrew Savinykh
sumber
Apakah ada alasan Anda tidak bisa membuat indeks unik pada TaskID di tabel Bahaya dan indeks unik HazardID di tabel Tugas? Itu akan membuatnya sehingga Anda hanya bisa memiliki salah satu dari mereka di meja, yang saya pikir apa yang Anda coba capai.
mskinner
@ mskinner tetapi mereka tidak unik, banyak dari mereka bisa saja null.
Andrew Savinykh
ah, mengerti. Dalam hal ini, Tuan Ben-Gan memiliki tulisan yang bagus tentang cara membuat batasan itu untuk memungkinkan beberapa null di sini sqlmag.com/sql-server-2008/unique-constraint-multiple-nulls . Saya pikir itu akan berhasil untuk Anda. Beri tahu saya jika tidak.
mskinner
Sebagai catatan tambahan untuk itu, ada sejumlah masalah menggunakan indeks yang difilter, jadi mungkin ada baiknya membaca jika mereka tidak terbiasa. Ini blog yang bagus. blogs.msdn.com/b/sqlprogrammability/archive/2009/06/29/... , Tetapi untuk skenario khusus ini, mungkin akan berhasil untuk Anda, dengan asumsi masalah lain tidak menyebabkan Anda terlalu banyak bersedih.
mskinner
FWIW indeks disaring unik dapat menerapkan keunikan hanya untuk baris non-nol, misalnyaCREATE UNIQUE INDEX x ON dbo.Hazards(TaskID) WHERE TaskID IS NOT NULL;
Aaron Bertrand

Jawaban:

9

Anda dapat menggunakan ide Anda sendiri tentang skema asimetris dengan menghapus salah satu kunci asing dari pengaturan saat ini, atau, untuk menjaga agar semuanya simetris, Anda dapat menghapus kedua kunci asing dan memperkenalkan tabel persimpangan dengan kendala unik pada setiap referensi .

Jadi, akan seperti ini:

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

Anda juga bisa mendeklarasikan (HazardId, TaskId)sebagai kunci utama jika Anda perlu referensi kombinasi ini dari tabel lain. Untuk tujuan menjaga pasangan tetap unik, kunci primer tidak diperlukan, cukup setiap ID unik.

Andriy M
sumber
1
+1 Saya pikir ini adalah cara paling alami untuk menerapkan hubungan seperti itu. tetapi biasanya satu memilih tuple sebagai primer hanya jika tuple minimal. Tuple (HazardId, TaskId)memiliki tuple (HazardId)dan (TaskId)keduanya mengidentifikasi deretan tabel ini secara unik. Salah satunya harus dipilih sebagai kunci utama.
miracle173
2

Untuk menyimpulkan:

  • Bahaya memiliki satu atau nol Tugas
  • Tugas memiliki satu atau nol Bahaya

Jika tabel Tugas dan Bahaya digunakan untuk hal lain (yaitu tugas dan / atau bahaya memiliki data lain yang terkait, dan model yang Anda perlihatkan kepada kami disederhanakan untuk hanya menampilkan bidang yang relevan) Saya akan mengatakan bahwa solusi Anda sudah benar.

Jika tidak, jika Tugas dan Bahaya hanya ada untuk digabungkan satu sama lain, Anda tidak perlu dua tabel; Anda bisa membuat tabel tunggal untuk hubungan mereka, dengan bidang-bidang berikut:

ID            int, PK
TaskID        int, (filtered) unique index  
TaskDetails   varchar
HazardID      int, (filtered) unique index
HazardDetails varchar
dr_
sumber
1
Saya mengambil kebebasan untuk mengklarifikasi bahwa itu harus menjadi indeks yang disaring dalam setiap kasus, (meskipun bisa ditebak dari deskripsi masalah, mungkin tidak terlihat sepenuhnya jelas). Silakan mengedit lebih lanjut jika Anda merasa itu tidak perlu atau ingin menyampaikan gagasan dengan cara yang berbeda.
Andriy M
Saya harus mengatakan saya masih tidak terlalu senang dengan ide ini. Masalahnya adalah saya harus membuat TaskID dan HazardID secara manual. Jika ini adalah 2012, akan mungkin untuk menggunakan urutan untuk itu, tetapi bahkan kemudian itu tampaknya tidak tepat untuk saya. Saya kira itu karena dua hal, tugas dan bahaya, adalah entitas yang benar-benar berbeda sehingga sulit untuk berdamai dengan ide untuk menyimpannya dalam satu tabel.
Andriy M
Jika desain ini dipilih, mengapa kita perlu TaskIDdan HazardID? Anda bisa memiliki 2 kolom BIT IsTask,, IsHazarddan batasan yang tidak keduanya salah. Maka Hazardtabel hanyalah sebuah tampilan: CRAETE VIEW Hazard SELECT HazardID = ID, HazardDetails, ... FROM ThisTable WHERE IsHazard = 1;dan Tugas masing-masing.
ypercubeᵀᴹ
@ ypercube Anda akan menyimpan benda tak berbentuk dalam tabel dan kemudian menggunakan bidang biner untuk mengetahui apakah itu Tugas atau Bahaya. Ini pemodelan basis data yang jelek.
dr_
@AndriyM Saya mengerti, dan saya setuju bahwa, dalam banyak kasus , kita seharusnya tidak menyimpan dua jenis entitas yang berbeda dalam tabel yang sama. Namun, baca peringatan saya: jika Tugas dan Bahaya berada dalam hubungan 1-ke-1 dan hanya ada untuk ini , maka dapat diterima untuk menggunakan satu tabel.
dr_
1

Pendekatan lain yang tampaknya belum disebutkan adalah memiliki Bahaya dan Tugas menggunakan ruang ID yang sama. Jika Hazard memiliki Tugas, ia akan memiliki ID yang sama. Jika suatu Tugas adalah untuk suatu Bahaya itu akan memiliki ID yang sama.

Anda akan menggunakan urutan daripada kolom identitas untuk mengisi ID ini.

Kueri pada model data jenis ini akan menggunakan gabungan luar (penuh) untuk mengambil hasilnya.

Pendekatan ini sangat mirip dengan jawaban @ AndriyM kecuali jawabannya memungkinkan ID berbeda, dan sebuah tabel untuk menyimpan hubungan ini.

Saya tidak yakin Anda ingin menggunakan pendekatan ini untuk skenario dua-tabel, tetapi itu bekerja dengan baik ketika jumlah tabel yang terlibat meningkat.

Colin 't Hart
sumber
Terima kasih telah berkontribusi, saya telah mempertimbangkan pendekatan itu juga. Saya memutuskan menentangnya pada akhirnya karena urutannya canggung untuk diimplementasikan dalam sql2008 dan lebih banyak masalah daripada yang saya rela tahan.
Andrew Savinykh