sql - 如何强制执行 "ALL-TO-ALL"关系?

标签 sql sql-server database many-to-many

假设我有以下结构(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 中插入几行,所有行都在 A 中进行新引用在 B 中,反之亦然,在 B 中插入的情况下。

问题是,在 SQL Server 中,表 C 必须具有 A 和 B 的所有组合,您如何确保这种关系的完整性?或者,如果您认为这样的结构是一种不好的做法,您推荐哪些替代表不会增加必须执行 DISTINCT 选择等的麻烦?

谢谢!

fiddle 链接: Fiddle

最佳答案

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

关于sql - 如何强制执行 "ALL-TO-ALL"关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15536529/

相关文章:

sql - 使用嵌套表时如何避免在触发器中改变表,并更新到另一个表?

python - Django:解压参数列表以在聚合查询中使用

database - Django 南 : Creating schemamigration for more than one app

mysql - 如何比较两个表并显示每个差异

sql-server - 两个 dacpac 文件之间的详细差异

mysql - 如果我只知道 paret 类型 ID,如何选择合适的子类型?

mysql - 在 if 语句中插入的 SQL 问题

sql - Oracle 联接(左外、右等。:S )

按两列分组的Mysql问题

sql - 如何使用相同的约束映射不同的多对多