我必须修复由EntityFramework4自动创建的旧MS SQL数据库中的一些问题.让我们以下表为例:

CREATE TABLE a (
 id int NOT NULL PRIMARY KEY,
 date datetime NOT NULL,
 client_id int NOT NULL //references to clients table
 -- other properties related to entity a
);

这是一种抽象的实体,实际上我有8个子表来定义类型,其中包括一些其他属性.让我们以此为例:

CREATE TABLE b (
 id int NOT NULL PRIMARY FOREIGN KEY REFERENCES a (id)
 -- other properties related to entity b
);

CREATE TABLE c (
 id int NOT NULL PRIMARY FOREIGN KEY REFERENCES a (id)
 -- other properties related to entity c
);

也就是说,一个客户每天只能有一个b或c(或两者),理想的约束是UNIQUE,但由于属性位于不同的表中,这是不可能的(根据我的知识).我采用的解决方案是创建触发器,判断每个子类型的唯一性:

CREATE TRIGGER trigger_b_upsert
ON b
AFTER INSERT, UPDATE
AS  
    IF EXISTS (
        SELECT 1
        FROM a a
        JOIN b b ON a.id = b.id
        WHERE a.client_id IN (
            SELECT client_id
            FROM a a
            JOIN inserted i ON a.id = i.id
        ) AND date IN (
            SELECT date
            FROM a a
            JOIN inserted i ON a.id = i.id
        )
        GROUP BY date, client_id
        HAVING count(*) > 1
    
    )
   BEGIN
      RAISERROR ('not allowed.', 10, 1)
      ROLLBACK TRANSACTION
   END

c个也一样.

INSERT INTO a (id, date, client_id) VALUES (1, '2050-01-01', 1);
INSERT INTO b (id) VALUES (1);
INSERT INTO a (id, date, client_id) VALUES (2, '2050-01-01', 1);
INSERT INTO b (id) VALUES (2); -- should raise error
INSERT INTO a (id, date, client_id) VALUES (1, '2050-01-01', 1);
INSERT INTO b (id) VALUES (1);
INSERT INTO a (id, date, client_id) VALUES (2, '2050-01-01', 1);
INSERT INTO c (id) VALUES (2); -- should work
INSERT INTO a (id, date, client_id) VALUES (1, '2050-01-01', 1);
INSERT INTO a (id, date, client_id) VALUES (2, '2050-01-02', 1);
INSERT INTO a (id, date, client_id) VALUES (3, '2050-01-01', 1);
INSERT INTO a (id, date, client_id) VALUES (4, '2050-01-02', 1);
INSERT INTO b (id) VALUES (1);
INSERT INTO b (id) VALUES (2); -- should work
INSERT INTO c (id) VALUES (3);
INSERT INTO c (id) VALUES (4); -- should work

因为我对MS SQL不是很熟悉,所以我想知道这种方法是否正确,以及是否存在一些性能问题/改进.

Edit:
@GarethD has shown a brillant solution to my problem, but I have another case of trigger I need to improve, if possible. Let's take the same table a and define two new ones:

-- another table, different from a, which as well references the client
CREATE TABLE e (
 id int NOT NULL PRIMARY KEY,
 client_id int NOT NULL //references to clients table
 -- other properties related to entity e
);

CREATE TABLE d (
 id int NOT NULL PRIMARY FOREIGN KEY REFERENCES a (id)
 e_id int NOT NULL PRIMARY FOREIGN KEY REFERENCES e (id)
 -- other properties related to entity d
);

如您所见,该 struct 允许在由e连接的ad中插入一条记录,其中a.client_id可能不同于e.client_id.必须否认这种情况.我定义了一个简单的触发器:

CREATE TRIGGER trigger_d_upsert
ON d
AFTER INSERT, UPDATE
AS     
    IF EXISTS (
        SELECT 1
        FROM a a
        JOIN inserted i ON a.id = i.id
        JOIN e e on e.id = i.e_id
        WHERE e.client_id != a.client_id
    
    )
   BEGIN
      RAISERROR ('Conflict client_id.', 10, 1)
      ROLLBACK TRANSACTION
   END 

有什么更好的解决方案的建议吗?

推荐答案

我从来不喜欢在可以避免的地方使用触发器,所以我个人会在这里使用索引视图来管理唯一性,每个子表一个:

CREATE VIEW dbo.UniqueB
WITH SCHEMABINDING
AS
SELECT a.client_id, a.date
FROM dbo.a 
INNER JOIN dbo.b ON b.id = a.id;
GO
CREATE UNIQUE CLUSTERED INDEX UQ_UniqueB__clientid_date ON dbo.UniqueB (client_id, date);

这将约束您的记录以与触发器相同的方式唯一,但在我看来,这是一个更好的解决方案,虽然没有经过测试,但我预计它也会更高效.try 插入副本将生成如下消息:

Msg 2601 Level 14 State 1 Line 1
Cannot insert duplicate key row in object 'dbo.UniqueB' with unique index 'UQ_UniqueB__clientid_date'. The duplicate key value is (1, Jan 1 2050 12:00AM).

100

增编

首先,请不要编辑您的问题来问第二个问题,只需问一个全新的问题,如有必要,请链接到第一个问题以提供额外的上下文.

但是,当我努力想出一个真正的用例时,为了实际回答您的问题,我认为我处理这种情况的方法是在表d中实际包括client_id列.这确实带来了冗余,但这是我个人愿意做出的权衡.要使其完全工作,您还需要对表a和表e进行额外的唯一约束:

CREATE TABLE dbo.a (
 id int NOT NULL PRIMARY KEY,
 date datetime NOT NULL,
 client_id int NOT NULL ,
  UNIQUE(id, client_id)
);

CREATE TABLE e (
 id int NOT NULL PRIMARY KEY,
 client_id int NOT NULL,
  UNIQUE(id, client_id)
);

有了这些,您现在可以在表d中引用这些内容:

CREATE TABLE d (
 id int NOT NULL PRIMARY KEY FOREIGN KEY REFERENCES dbo.a (id),
 e_id int NOT NULL FOREIGN KEY REFERENCES dbo.e (id),
 client_id INT NOT NULL,
  FOREIGN KEY (id,client_id) REFERENCES dbo.a (id, client_id),
  FOREIGN KEY (e_id,client_id) REFERENCES dbo.e (id, client_id)
);

这意味着将不匹配的CLIENT_ID添加到表d的try 将导致表a或表d的外键错误.

100

与触发器相比,这种方法的好处是它可以捕获所有更改.如果您只将触发器放在表d上,则不会捕获其他更改,例如,即使触发器已就位,以下查询也运行正常:

UPDATE a
SET client_id =2
WHERE id =1;

这就留下了不匹配的记录.对表e的更新也是如此.

100

Database相关问答推荐

如何在维护数据库模型的同时从Firestore中删除文档?

如何使一个IP允许在运行GitHub操作到RDS数据库时,您不允许从外部访问数据库?

Prisma - 将属性的类型设置为枚举数组

第一次请求后数据库连接关闭

UML 类图中关联的复杂规则

使用 golan 查询 mongodb 中的集合并返回 id 作为字符串

需要未提供的参数@ID?

Select 正确的数据库:MySQL 与Everything 其它数据库

使用 cloud-spanner 进行本地开发

如何允许多个用户同时连接到我的 H2 数据库?

一个 5MB 的 SQL 数据库可以存储多少数据?

免费的便携式数据库有哪些?

更新列的子字符串

如何在一行中显示 redis 中的所有键?

在 MySQL 存储过程中使用if和else

在 MySQL 中 Select 浮点数

如何在我的 SQL Server 代理作业(job)中创建一个将运行我的 SSIS 包的步骤?

如何在实体框架中使用字符串属性作为主键

防止 PostgreSQL 有时 Select 错误的查询计划

最小覆盖和功能依赖