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#)

INSTEAD OF与AFTER触发器

MySQL 触发器中的“INSTEAD OF”,从 SQL Server 转换而来

T-SQL INSTEAD OF DELETE 触发器在没有“挑衅”的情况下触发

为啥在 PL/SQL Oracle 中尝试创建 INSTEAD OF 触发器时出现“错误的绑定变量”错误?