sql - INSTEAD OF INSERT 触发器 - 插入前清除值
Posted
技术标签:
【中文标题】sql - INSTEAD OF INSERT 触发器 - 插入前清除值【英文标题】:sql - INSTEAD OF INSERT trigger - clean value before insert 【发布时间】:2021-12-24 04:02:52 【问题描述】:我想在插入表格之前触发以清除非数字字符中的电话号码。
电话桌是这样的:
rowId PhoneNumber DepartmentId ...
1 12345678 4 ...
2 23456789 5 ...
3 34255467 6 ...
我创建了这个触发器:
CREATE TRIGGER tr_insertPhone ON [Phone]
instead of INSERT as
begin
declare @Phone nvarchar(50)
declare @DepartmentId int
...
select @Phone = (select PhoneNumber from inserted)
select @DepartmentId = (select DepartmentId from inserted)
...
WHILE PATINDEX('%[^0-9]%',@Phone)>0
set @Phone=STUFF(@Phone,PATINDEX('%[^0-9]%',@Phone),1,'')
if (len(@Phone)>7) and (len(@Phone)<14)
INSERT INTO [Phone](PhoneNumber,DepartmentId,..) values (@Phone,@DepartmentId,..)
end
但在插入时出现错误:
Msg 512, Level 16, State 1, Procedure tr_insertPhone, Line 10 [Batch Start Line 1]
SQL Server Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as as expression.
我做错了什么?
【问题讨论】:
您假设,在您的触发器中,INSERT
只影响一行。这是不正确的;它可以影响 0+ 行。此外,TRIGGER
的影响应该尽可能小,而WHILE
将与此远离。我建议你在这里完全重新考虑你的方法。您实际上试图解决的问题是什么?
SQL Server 有语句触发器,而不是行触发器。您必须假设 inserted
有多行。您可以将清理逻辑分解为标量 UDF 并执行基于集合的 INSERT,或者在 inserted
上打开游标并对每一行进行操作。
我的 gole 非常简单,制作一个触发器,在插入之前检查和清理每个电话号码(从非数字字符中清除它,并且只有在它有 7-14 个数字时才插入)。
CHECK CONSTRAINT
阻止错误输入会更好吗,@rotem ...?尽管假设电话号码仅由数字组成是错误的。您将如何存储国际号码、区号(某些国家/地区将它们放在括号中 (()
))或所需的分机号码?
再次,CHECK CONSTRAINT
。如果您想变得聪明,请在您的表示层中添加进一步的验证。
【参考方案1】:
不建议使用trigger
来编辑或格式化任何字段。也许,您经常在insert
命令中看到这些命令。例如:insert into table1 (field1, field2) values (trim(@f1), @f2)
。这就是为什么建议为 do clean phone 创建一个函数并在insert
命令中使用它。
例子:
Create function clear_phone (@str varchar(200))
returns varchar(200)
begin
declare
@Phone varchar(200)
set @Phone = @str
WHILE PATINDEX('%[^0-9]%',@Phone)>0
set @Phone = STUFF(@Phone,PATINDEX('%[^0-9]%',@Phone),1,'')
return @Phone
end
insert
命令中使用该函数的示例:
insert into table
(
departmentId,
phoneNumber
)
values (
@depId,
clear_phone(@phoneNum)
)
【讨论】:
我实际上对分离函数做了同样的事情,并在触发器中使用它。但我不想清理和检查每个插入句子中的长度。为什么我不能制作触发器来为我检查和清理它? @rotem IMO 您正在实施一个有风险的设计。当进程将“x”插入表中时,如果没有发生错误,它希望将“x”添加到表中。在这里,应用程序在插入后找不到“x”,因为您“清理”了它。可能看起来像一场微不足道的辩论,但您应该只验证数据是否正确,而不是在数据从应用程序传递到表时对其进行更改。也许应用程序会在插入后打印确认信息。现在确认不正确,因为“x”不存在 - 您将其“清理”为不同的值。 但这是我需要的,在插入之前清理/更改/检查数据。所以我不想在每一个插入句子中都这样做,我只想构建一个触发器来为我做这件事。【参考方案2】:我强烈建议您为此使用 CHECK
约束,并在应用程序代码中进行验证和清理
例如
ALTER TABLE Phone
ADD CONSTRAINT ValidPhone
CHECK (PhoneNumber NOT LIKE '%[^0-9]%');
如果您真的想要使用INSTEAD OF
触发器来强制执行此操作,请确保它可以处理多行(或零行)。
此外,内联计数表比 WHILE
循环快得多,因此我们可以拆分每个字符,检查它,然后汇总备份。
CREATE TRIGGER tr_insertPhone ON [Phone]
instead of INSERT as
INSERT INTO [Phone]
(PhoneNumber, DepartmentId,..)
SELECT v.Phone, i.DepartmentId,..)
FROM inserted i
CROSS APPLY (
SELECT Phone = STRING_AGG(SUBSTRING(i.Phone, v.indx, 1), '') WITHIN GROUP (ORDER BY v.indx
FROM (VALUES -- Recommended max phone length by ITU-T is 15
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)
) v(indx)
WHERE v.indx <= LEN(i.Phone)
AND SUBSTRING(i.Phone, v.indx, 1) LIKE '[0-9]'
) v
WHERE LEN(i.Phone) BETWEEN 7 AND 15;
GO
【讨论】:
以上是关于sql - INSTEAD OF INSERT 触发器 - 插入前清除值的主要内容,如果未能解决你的问题,请参考以下文章
INSTEAD OF INSERT 触发器未触发视图 - SQL Server
INSTEAD OF INSERT 设置的主键在实体框架中抛出错误(ASP.NET 框架 c#)
MySQL 触发器中的“INSTEAD OF”,从 SQL Server 转换而来