Kunci Asing ke kunci non-primer

143

Saya memiliki tabel yang menyimpan data, dan salah satu baris tersebut harus ada di tabel lain. Jadi, saya ingin kunci asing untuk menjaga integritas referensial.

CREATE TABLE table1
(
   ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
   AnotherID INT NOT NULL,
   SomeData VARCHAR(100) NOT NULL
)

CREATE TABLE table2
(
   ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
   AnotherID INT NOT NULL,
   MoreData VARCHAR(30) NOT NULL,

   CONSTRAINT fk_table2_table1 FOREIGN KEY (AnotherID) REFERENCES table1 (AnotherID)
)

Namun, seperti yang Anda lihat, tabel I foreign key, kolomnya bukan PK. Apakah ada cara untuk membuat kunci asing ini, atau mungkin cara yang lebih baik untuk menjaga integritas referensial ini?

Craig
sumber
Tidak masuk akal untuk melakukan itu. Mengapa tidak mengacu pada table1.ID?
zerkms
sudah pasti bahwa jika AnothidID Anda bukan kunci utama, ia harus menjadi ForeignKey, jadi sebagai ForeignKey, tabel2 Anda harus menunjuk ke tabel yang sama (kemungkinan tabel3)
Roger Barreto

Jawaban:

193

Jika Anda benar-benar ingin membuat kunci asing ke kunci non-primer, itu HARUS berupa kolom yang memiliki batasan unik.

Dari Buku Online :

Batasan FOREIGN KEY tidak harus ditautkan hanya ke batasan PRIMARY KEY di tabel lain; itu juga dapat didefinisikan untuk mereferensikan kolom kendala UNIK di tabel lain.

Jadi dalam kasus Anda jika Anda membuatnya AnotherIDunik, itu akan diizinkan. Jika Anda tidak dapat menerapkan batasan unik, Anda kurang beruntung, tetapi ini benar-benar masuk akal jika Anda memikirkannya.

Meskipun, seperti yang telah disebutkan, jika Anda memiliki kunci utama yang sangat bagus sebagai kunci kandidat, mengapa tidak menggunakannya?

Ian Preston
sumber
2
Terkait dengan pertanyaan terakhir Anda ... Saya memiliki situasi di mana saya ingin kunci kandidat komposit menjadi kunci utama hanya karena secara semantik lebih penting dan paling baik menggambarkan model saya. Saya juga ingin memiliki referensi kunci asing sebagai kunci pengganti yang baru dibuat demi kinerja (seperti yang disebutkan di atas). Apakah ada yang meramalkan adanya masalah dengan pengaturan seperti itu?
Daniel Macias
Pak bisakah anda ceritakan apa logika dibalik bahwa foreign key selalu mereferensikan atribut dengan unique constraint?
Shivangi Gupta
Bagaimana melakukan ini di asp net MVC 5
irfandar
Bisakah bilangan bulat non primary key dinyatakan sebagai foreign key di tabel lain? Seperti yang ini. apakah ini mungkin? CREATE TABLE Project (PSLNO Numeric (8,0) Not Null, PrMan Numeric (8,0), StEng Numeric (8,0), CONSTRAINT PK_Project PRIMARY KEY (PSLNO), CONSTRAINT FK_Project1 FOREIGN KEY (PrMan) REFERENSI Karyawan (EmpID) , CONSTRAINT FK_Project2 FOREIGN KEY (StEng) REFERENSI Karyawan (EmpID),)
Nabid
20

Seperti yang ditunjukkan orang lain, idealnya, kunci asing akan dibuat sebagai referensi ke kunci utama (biasanya kolom IDENTITAS). Namun, kita tidak hidup di dunia yang ideal, dan terkadang bahkan perubahan "kecil" pada skema dapat memiliki efek riak yang signifikan pada logika aplikasi.

Pertimbangkan kasus tabel Pelanggan dengan kolom SSN (dan kunci utama bodoh), dan tabel Klaim yang juga berisi kolom SSN (diisi oleh logika bisnis dari data Pelanggan, tetapi tidak ada FK). Desainnya cacat, tetapi telah digunakan selama beberapa tahun, dan tiga aplikasi berbeda telah dibangun di atas skema tersebut. Jelas bahwa merobek Claim.SSN dan membuat hubungan PK-FK yang sebenarnya akan ideal, tetapi juga akan menjadi perbaikan yang signifikan . Di sisi lain, menempatkan batasan UNIK pada Customer.SSN, dan menambahkan FK pada Claim.SSN, dapat memberikan integritas referensial, dengan sedikit atau tidak ada dampak pada aplikasi.

Jangan salah paham, saya mendukung normalisasi, tetapi terkadang pragmatisme menang atas idealisme. Jika desain yang biasa-biasa saja dapat dibantu dengan perban, pembedahan dapat dihindari.

EJSawyer
sumber
18

Necromancing.
Saya berasumsi ketika seseorang mendarat di sini, dia membutuhkan kunci asing ke kolom dalam tabel yang berisi kunci non-unik.

Masalahnya adalah, jika Anda memiliki masalah itu, skema database didenormalisasi.

Anda misalnya menyimpan ruangan dalam sebuah meja, dengan kunci utama kamar-uid, bidang DateFrom dan DateTo, dan uid lain, di sini RM_ApertureID untuk melacak ruangan yang sama, dan bidang hapus lembut, seperti RM_Status, dimana 99 berarti 'dihapus', dan <> 99 berarti 'aktif'.

Jadi saat Anda membuat ruang pertama, Anda memasukkan RM_UID dan RM_ApertureID sebagai nilai yang sama dengan RM_UID. Kemudian, saat Anda menghentikan ruangan ke tanggal, dan membuatnya kembali dengan rentang tanggal baru, RM_UID adalah newid (), dan RM_ApertureID dari entri sebelumnya menjadi RM_ApertureID baru.

Jadi, jika demikian, RM_ApertureID adalah bidang non-unik, sehingga Anda tidak dapat menyetel kunci asing di tabel lain.

Dan tidak ada cara untuk menyetel kunci asing ke kolom / indeks non-unik, misalnya di T_ZO_REM_AP_Raum_Reinigung (DI MANA RM_UID sebenarnya adalah RM_ApertureID).
Tetapi untuk melarang nilai yang tidak valid, Anda perlu menyetel kunci asing, jika tidak, data-sampah adalah hasilnya lebih cepat daripada nanti ...

Sekarang yang dapat Anda lakukan dalam kasus ini (singkatnya menulis ulang seluruh aplikasi) adalah memasukkan batasan PERIKSA, dengan fungsi skalar memeriksa keberadaan kunci:

IF  EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]'))
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung DROP CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_Constaint_ValidRmApertureId]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[fu_Constaint_ValidRmApertureId]
GO




CREATE FUNCTION [dbo].[fu_Constaint_ValidRmApertureId](
     @in_RM_ApertureID uniqueidentifier 
    ,@in_DatumVon AS datetime 
    ,@in_DatumBis AS datetime 
    ,@in_Status AS integer 
) 
    RETURNS bit 
AS 
BEGIN   
    DECLARE @bNoCheckForThisCustomer AS bit 
    DECLARE @bIsInvalidValue AS bit 
    SET @bNoCheckForThisCustomer = 'false' 
    SET @bIsInvalidValue = 'false' 

    IF @in_Status = 99 
        RETURN 'false' 


    IF @in_DatumVon > @in_DatumBis 
    BEGIN 
        RETURN 'true' 
    END 


    IF @bNoCheckForThisCustomer = 'true'
        RETURN @bIsInvalidValue 


    IF NOT EXISTS
    ( 
        SELECT 
             T_Raum.RM_UID 
            ,T_Raum.RM_Status 
            ,T_Raum.RM_DatumVon 
            ,T_Raum.RM_DatumBis 
            ,T_Raum.RM_ApertureID 
        FROM T_Raum 
        WHERE (1=1) 
        AND T_Raum.RM_ApertureID = @in_RM_ApertureID 
        AND @in_DatumVon >= T_Raum.RM_DatumVon 
        AND @in_DatumBis <= T_Raum.RM_DatumBis 
        AND T_Raum.RM_Status <> 99  
    ) 
        SET @bIsInvalidValue = 'true' -- IF ! 

    RETURN @bIsInvalidValue 
END 



GO



IF  EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]'))
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung DROP CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]
GO


-- ALTER TABLE dbo.T_AP_Kontakte WITH CHECK ADD CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]  
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung WITH NOCHECK ADD CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
CHECK 
( 
    NOT 
    ( 
        dbo.fu_Constaint_ValidRmApertureId(ZO_RMREM_RM_UID, ZO_RMREM_GueltigVon, ZO_RMREM_GueltigBis, ZO_RMREM_Status) = 1 
    ) 
) 
GO


IF  EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]')) 
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung CHECK CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
GO
Stefan Steiger
sumber
Selalu terlambat ke pesta ... Tapi terima kasih atas saran dunia nyata ini - saya punya persis seperti itu - data di tabel sekunder berversi (memiliki rentang tanggal selain kunci), dan saya hanya ingin menautkan versi terbaru dari meja utama saya ...
Ian
1
Nasihat dunia nyata yang bagus! Saya dapat membayangkan banyak skenario dengan aplikasi lama di mana "praktik terbaik" tidak dimungkinkan karena satu atau lain alasan, dan batasan pemeriksaan akan bekerja dengan baik.
ryanwc
3

Kunci utama selalu harus unik, kunci asing harus mengizinkan nilai non-unik jika tabel adalah hubungan satu ke banyak. Tidak masalah menggunakan kunci asing sebagai kunci utama jika tabel dihubungkan dengan hubungan satu-ke-satu, bukan hubungan satu-ke-banyak.

Batasan FOREIGN KEY tidak harus ditautkan hanya ke batasan PRIMARY KEY di tabel lain; itu juga dapat didefinisikan untuk mereferensikan kolom kendala UNIK di tabel lain.

Anzeem SN
sumber