我有一个bankcustomer
表,看起来像这样:
create table bankcustomer
(
cpr char(10) primary key,
name varchar(30) not null
)
还有一个如下所示的帐户表:
create table account
(
accountnr int identity(1001,1) primary key,
accountowner char(10) foreign key references bankcustomer,
created date not null,
balance decimal(14,2) not null
)
我想在SQL Server中编写一个限制bankcustomer
的触发器,以便一个bankcustomer
在account
表中最多可以有3个帐户。
我通过在帐户表的accountowner
列中插入与cpr
表中的bankcustomer
相匹配的值来创建特定银行客户的帐户(将记录插入account
表)。
到目前为止,我有此代码:
create trigger mytrigger4
on account
for insert
as
if exists (select count(*)
from inserted
join bankcustomer on inserted.accountowner = bankcustomer.cpr
where cpr = inserted.accountowner
having count(*) > 3)
begin
rollback tran
raiserror('A customer must have a maximum of 3 accounts', 16, 1)
end
go
问题是,即使客户已经有3个帐户,我仍可以继续为其创建帐户(在帐户表中插入记录)。这意味着触发器中的代码根本不起作用。
任何帮助将不胜感激!
答案 0 :(得分:1)
让我们考虑一下您的代码。首先,很明显,您仅使用单行插入进行测试。通常,这可能会延续到您的sql代码中。不好,因为插入(或更新,删除或合并)会影响任意数量的行。尽管可以预期应用程序中的大多数插入可能是单行,但总有一些情况会影响多行。这是通常在编写sql代码时应始终记住的一个假设-并专门触发。
您的测试基于存在。那就是测试存在子句中查询生成的行是否存在。因此-请仔细查看您的查询。首先,您计数但不分组。因此,您正在计算查询生成的所有行。由于前面提到的假设,这是不正确的。但是让我们暂时忽略它,仅检查select语句。
您的选择语句联接被插入到上级银行客户。联接是正确的,但是为什么会有where子句?让我们回避最佳实践。始终-始终-为每个表提供有用的别名,并在引用列时使用该别名。为什么?因为这样可以使其他人更容易阅读和理解您的查询。顺便说一句-有用的别名不是单个字符。是的-编写代码可能会有点麻烦。
让我们继续。您的查询计数结果集中的所有行。如果您插入一行,那么加入的结果是什么?我们知道一个帐户与一个银行客户相关联。因此,当您在帐户中插入一行时,联接将产生“单行”。计算该单行结果集将始终产生值为-tadah-1的单行。现在,有一种方法可以使您的触发器生成错误。用一条语句插入4行或更多行。该错误可能并不准确,但是它将终止该事务并显示一条消息。
所以您看到您的逻辑有缺陷。您需要计算实际表(帐户)中未插入的行。但不是所有的行-因为那样效率低下。您只需要考虑所有“共享”插入的帐户所有者值的行。注意复数的“值”。这是单行假设失败的地方。那么,该怎么做呢?这是编写触发器的一种方法。请注意,包含了第一个查询,以使您“查看”计数查询产生的内容-这仅用于调试。生产触发器不应以任何方式返回结果集。
alter trigger mytrigger4
on account
for insert
as begin
select cust.cpr, count(*)
from bankcustomer as cust join account as acc on cust.cpr = acc.accountowner
where exists (select * from inserted as ins where ins.accountowner = cust.cpr)
group by cust.cpr;
if exists (select cust.cpr, count(*)
from bankcustomer as cust join account as acc on cust.cpr = acc.accountowner
where exists (select * from inserted as ins where ins.accountowner = cust.cpr)
group by cust.cpr
having count(*) > 3)
begin
rollback tran
raiserror('A customer must have a maximum of 3 accounts', 16, 1)
end
end;
go
我将留给您进行实际测试,以证明彻底-其中包括使用插入多行插入语句。而且,当然,您将更改测试数据,以包括没有帐户,少于3个帐户,恰好3个帐户和3个以上帐户的客户(因为尽管您尽了最大努力有时会发生某些事情,并且会添加额外的帐户)。