Khóa ngoại đến khóa không chính


136

Tôi có một bảng chứa dữ liệu và một trong những hàng đó cần tồn tại trong một bảng khác. Vì vậy, tôi muốn một khóa ngoại để duy trì tính toàn vẹn tham chiếu.

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

Tuy nhiên, như bạn có thể thấy, bảng tôi khóa ngoại, cột không phải là PK. Có cách nào để tạo khóa ngoại này, hoặc có thể là cách tốt hơn để duy trì tính toàn vẹn tham chiếu này?


Nó không có nhiều ý nghĩa để làm điều đó. Tại sao không tham khảo table1.ID?
zerkms

điều chắc chắn là nếu AnothidID của bạn không phải là khóa chính thì nó phải là ForeignKey, do đó, là ForeignKey, bảng2 của bạn phải trỏ đến cùng một bảng (có thể là bảng 3)
Roger Barreto

Câu trả lời:


182

Nếu bạn thực sự muốn tạo khóa ngoại thành khóa không chính, thì PHẢI là một cột có một ràng buộc duy nhất đối với nó.

Từ sách trực tuyến :

Một ràng buộc KEY NGOẠI TỆ không nhất thiết phải được liên kết với một ràng buộc KEY PRIMARY KEY trong một bảng khác; nó cũng có thể được định nghĩa để tham chiếu các cột của một ràng buộc ĐỘC ĐÁO trong một bảng khác.

Vì vậy, trong trường hợp của bạn nếu bạn thực hiện AnotherID duy nhất, nó sẽ được cho phép. Nếu bạn không thể áp dụng một ràng buộc duy nhất, bạn sẽ không gặp may, nhưng điều này thực sự có ý nghĩa nếu bạn nghĩ về nó.

Mặc dù, như đã được đề cập, nếu bạn có khóa chính hoàn toàn tốt làm khóa ứng viên, tại sao không sử dụng khóa đó?


1
Liên quan đến câu hỏi cuối cùng của bạn ... Tôi có một tình huống tôi muốn các khóa ứng viên tổng hợp trở thành khóa chính chỉ vì về mặt ngữ nghĩa có tầm quan trọng hơn và mô tả tốt nhất mô hình của tôi. Tôi cũng muốn có một tham chiếu khóa ngoài một khóa thay thế mới được tạo ra vì mục đích hiệu suất (như đã lưu ý ở trên). Có ai thấy trước bất kỳ vấn đề với một thiết lập như vậy?
Daniel Macias

Thưa ông, ông có thể vui lòng cho biết logic đằng sau khóa ngoại đó luôn tham chiếu thuộc tính với ràng buộc duy nhất không?
Shivangi Gupta

Cách thực hiện điều này trong asp net MVC 5
irfandar

Số nguyên khóa chính không bình thường có thể được khai báo khóa ngoại trong bảng khác không? Giống như cái này. điều này có thể không Dự án CREATE TABLE (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) , CONSTRAINT FK_Project2 FOREIGN KEY (StEng) TÀI LIỆU THAM KHẢO Nhân viên (EmpID),)
Nabid

19

Như những người khác đã chỉ ra, lý tưởng nhất, khóa ngoại sẽ được tạo như một tham chiếu đến khóa chính (thường là cột IDENTITY). Tuy nhiên, chúng ta không sống trong một thế giới lý tưởng và đôi khi ngay cả một thay đổi "nhỏ" đối với một lược đồ cũng có thể có các hiệu ứng gợn đáng kể đối với logic ứng dụng.

Hãy xem xét trường hợp của bảng Khách hàng có cột SSN (và khóa chính câm) và bảng Khiếu nại cũng chứa cột SSN (được điền bởi logic nghiệp vụ từ dữ liệu Khách hàng, nhưng không tồn tại FK). Thiết kế còn thiếu sót, nhưng đã được sử dụng trong vài năm và ba ứng dụng khác nhau đã được xây dựng trên lược đồ. Rõ ràng là việc trích xuất Claim.SSN và đưa vào mối quan hệ PK-FK thực sự sẽ là lý tưởng, nhưng cũng sẽ là một đại tu đáng kể . Mặt khác, đặt một ràng buộc ĐỘC ĐÁO đối với Khách hàng.SSN và thêm FK trên Claim.SSN, có thể cung cấp tính toàn vẹn tham chiếu, ít hoặc không ảnh hưởng đến các ứng dụng.

Đừng hiểu lầm tôi, tôi là tất cả để bình thường hóa, nhưng đôi khi chủ nghĩa thực dụng chiến thắng chủ nghĩa duy tâm. Nếu một thiết kế tầm thường có thể được hỗ trợ với sự hỗ trợ của ban nhạc, phẫu thuật có thể tránh được.


18

Necromance.
Tôi giả sử khi ai đó hạ cánh ở đây, anh ta cần một khóa ngoại để cột trong một bảng có chứa các khóa không duy nhất.

Vấn đề là, nếu bạn gặp vấn đề đó, lược đồ cơ sở dữ liệu bị không chuẩn hóa.

Ví dụ, bạn đang giữ các phòng trong một bảng, với khóa chính là phòng, một trường DateFrom và DateTo, và một uid khác, ở đây là RM_ApertureID để theo dõi cùng một phòng và một trường xóa mềm, như RM_Status, trong đó 99 có nghĩa là 'đã xóa' và <> 99 có nghĩa là 'hoạt động'.

Vì vậy, khi bạn tạo phòng đầu tiên, bạn chèn RM_UID và RM_ApertureID có cùng giá trị với RM_UID. Sau đó, khi bạn chấm dứt phòng đến một ngày và thiết lập lại nó với một phạm vi ngày mới, RM_UID là newid () và RM_ApertureID từ mục trước đó trở thành RM_ApertureID mới.

Vì vậy, nếu đó là trường hợp, RM_ApertureID là một trường không phải là duy nhất và vì vậy bạn không thể đặt khóa ngoại trong một bảng khác.

Và không có cách nào để đặt khóa ngoại thành một cột / chỉ mục không duy nhất, ví dụ như trong T_ZO_REM_AP_Raum_Reinigung (WHERE RM_UID thực sự là RM_ApertureID).
Nhưng để cấm các giá trị không hợp lệ, bạn cần đặt khóa ngoại, nếu không, rác dữ liệu là kết quả sớm hơn là sau này ...

Bây giờ, những gì bạn có thể làm trong trường hợp này (viết tắt toàn bộ ứng dụng) là chèn một ràng buộc CHECK, với hàm vô hướng kiểm tra sự hiện diện của khóa:

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

Luôn luôn trễ bữa tiệc ... Nhưng cảm ơn vì lời khuyên trong thế giới thực này - tôi có chính xác điều đó - dữ liệu trong bảng phụ được phiên bản (có phạm vi ngày ngoài khóa) và tôi chỉ muốn liên kết phiên bản mới nhất từ bảng chính của tôi ...
Ian

1
Lời khuyên thực tế tốt đẹp! Tôi có thể tưởng tượng nhiều kịch bản với các ứng dụng cũ, trong đó "thực tiễn tốt nhất" không thể thực hiện được vì lý do này hay lý do khác và ràng buộc kiểm tra sẽ hoạt động tốt.
ryanwc

Giải pháp này không đáng tin cậy. Xem: dba.stackexchange.com / Trực / how
stomy

2

Khóa chính luôn cần phải là duy nhất, khóa ngoại cần cho phép các giá trị không duy nhất nếu bảng là mối quan hệ một-nhiều. Hoàn toàn ổn khi sử dụng khóa ngoại làm khóa chính nếu bảng được kết nối bằng mối quan hệ một-một, chứ không phải mối quan hệ một-nhiều.

Một ràng buộc KEY NGOẠI TỆ không nhất thiết phải được liên kết với một ràng buộc KEY PRIMARY KEY trong một bảng khác; nó cũng có thể được định nghĩa để tham chiếu các cột của một ràng buộc ĐỘC ĐÁO trong một bảng khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.