3

假设我有以下结构(SQL Server 语法):

CREATE TABLE A (
   key_A int NOT NULL PRIMARY KEY CLUSTERED,
   info_A nvarchar(50) NULL
);

CREATE TABLE B(
   key_B int NOT NULL PRIMARY KEY CLUSTERED,
   info_B nvarchar(50) NULL
);

CREATE TABLE C(
   key_C int NOT NULL PRIMARY KEY CLUSTERED,
   key_A int NOT NULL,
   key_B int NOT NULL,
   info1 nvarchar(50) NULL,
   info2 nvarchar(50) NULL,
   info3 nvarchar(50) NULL
);
ALTER TABLE C WITH CHECK ADD  CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A);
ALTER TABLE C WITH CHECK ADD  CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B);

所以,表 C 有两个表 A 和表 B 的主键。表 C 必须有表 A 和表 B 的笛卡尔积。这意味着所有组合,所以当在表 A 中插入新记录时,我们必须插入在表 C 中的几行与 B 中的所有行在 A 中的新引用。反之亦然,在 B 中插入的情况下。

问题是,如何在 SQL Server 中强制这种关系的完整性,其中表 C 必须具有 A 和 B 的所有组合?或者,如果您认为这样的结构是一种不好的做法,您建议使用哪些替代表来避免增加必须进行 DISTINCT 选择等的麻烦?

谢谢!

链接到小提琴: 小提琴

4

1 回答 1

2

您需要先插入到表 A / B 中,然后才能在表 C 中引用这个新条目。我知道的唯一方法是在表 A 和 B 上创建触发器,以在对表 C 进行新条目时填充表 C那些表。问题是你在其他领域投入了什么?由于这些是可空的,我假设您很高兴将它们默认为空?如果不是(即您希望用户输入有效值),那么执行此操作的唯一方法是在您的应用程序的逻辑中,而不是在数据库级别(或通过使用存储过程来填充这些 procs 具有合适逻辑的表除了 A / B) 之外,在 C 中创建适当的条目。

触发代码示例:

use StackOverflowDemos
go
if OBJECT_ID('TRG_C_DELETE','TR') is not null drop trigger TRG_C_DELETE
if OBJECT_ID('TRG_A_INSERT','TR') is not null drop trigger TRG_A_INSERT
if OBJECT_ID('TRG_B_INSERT','TR') is not null drop trigger TRG_B_INSERT
if OBJECT_ID('C','U') is not null drop table C
if OBJECT_ID('A','U') is not null drop table A
if OBJECT_ID('B','U') is not null drop table B
go

CREATE TABLE A 
(
   key_A int NOT NULL IDENTITY(1,1) CONSTRAINT PK_A PRIMARY KEY CLUSTERED,
   info_A nvarchar(50) NULL
);
go

CREATE TABLE B
(
   key_B int NOT NULL IDENTITY(1,1) CONSTRAINT PK_B PRIMARY KEY CLUSTERED,
   info_B nvarchar(50) NULL
);
go

CREATE TABLE C
(
   key_C int NOT NULL IDENTITY(1,1) CONSTRAINT PK_C PRIMARY KEY CLUSTERED,
   key_A int NOT NULL CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A),
   key_B int NOT NULL CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B),
   info1 nvarchar(50) NULL,
   info2 nvarchar(50) NULL,
   info3 nvarchar(50) NULL
);
go

CREATE TRIGGER TRG_A_INSERT 
ON A 
AFTER INSERT
AS
BEGIN

    --add new As to C
    insert C (key_A, key_B)
    select key_A, key_B
    from inserted
    cross join B

END;
go


CREATE TRIGGER TRG_B_INSERT 
ON B 
AFTER INSERT
AS
BEGIN

    --add new As to C
    insert C (key_A, key_B)
    select key_A, key_B
    from inserted
    cross join A

END;
go

CREATE TRIGGER TRG_C_DELETE
ON C 
AFTER DELETE
AS
BEGIN

    DELETE 
    FROM B
    WHERE key_B IN
    (
        SELECT key_B
        FROM DELETED d
        --ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of B in this table
        WHERE key_B NOT IN 
        (
            SELECT key_B 
            FROM C
            WHERE C.key_C NOT IN
            (
                SELECT key_C 
                FROM deleted 
            )
        )
    )

    DELETE 
    FROM A
    WHERE key_A IN
    (
        SELECT key_A
        FROM DELETED d
        --ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of A in this table
        WHERE key_A NOT IN 
        (
            SELECT key_A 
            FROM C
            WHERE C.key_C NOT IN
            (
                SELECT key_C 
                FROM deleted 
            )
        )
    )

END;
go
insert A select 'X'
select * from C --no results as no Bs yet
insert A select 'Y'
select * from C --no results as no Bs yet
insert B select '1'
select * from C --2 results; (X,1) and (Y,1)
insert A select 'Z'
select * from C --3 results; the above and (Z,1)
delete from A where info_A = 'Y'
select * from C --3 results; as above since the previous statement should fail due to enforced referential integrity
insert C (key_A, key_B, info1) 
select a.key_A, b.key_B, 'second entry for (Y,1)'
from A
cross join B
where a.info_A = 'Y'
and b.info_B = '1'
select * from C --4 results; as above but with a second (Y,1), this time with data in info1
delete from C where info1 = 'second entry for (Y,1)'
select * from C --3 results; as above but without the new(Y,1)
select * from A --3 results
select * from B --1 result
delete from C where key_A in (select key_A from A where info_A = 'Y')
select * from C --2 results; (X,1) and (Z,1)
select * from A --2 results; X and Z
select * from B --1 result
delete from C where key_B in (select key_B from B where info_B = '1')
select * from C --0 results
select * from A --0 results
select * from B --0 result

此处的 SQL Fiddle 演示(注意:只有 SQL Fiddle 仅显示表 C 的输出;错误演示也已被注释掉,因为这会导致整个错误,而不仅仅是一个错误行)。 http://sqlfiddle.com/#!3/34d2f/4

于 2013-03-20T23:26:24.477 回答