0

我创建了一个触发器,确保电话号码以默认格式插入到数据库中。它是为 Oracle 数据库创建的。现在我在 SQL Server 2008 R2 中工作并且在转换这个触发器时遇到了麻烦。任何人都可以帮我将以下触发器转换为 T-SQL 吗?

create or replace trigger before_insert_update_locations  
BEFORE insert or update  
ON locations  
for each row  
BEGIN  
    if length(:new.phone) = 10 then  
       :new.phone := '('||substr(:new.phone,1,3)||') '||substr(:new.phone,4,3)||'-'||substr(:new.phone,7,4);  
    elsif length(:new.phone) = 7 then  
       :new.phone := '(907) '||substr(:new.phone,1,3)+'-'||substr(:new.phone,4,4);  
    end if;  
END;  
4

2 回答 2

2

这应该这样做。请记住,每个语句都会触发 T-SQL 触发器。没有“FOR EACH ROW”等价物。因此,下面的触发器在更新和插入后触发,以调整新更新/插入的行的值。如果没有更新电话号码,“IF EXISTS”位会阻止它执行任何操作。

CREATE TRIGGER UPDPH
ON locations
AFTER UPDATE, INSERT
AS
BEGIN
    IF EXISTS
        (
            SELECT NULL
            FROM
                inserted i
            LEFT JOIN
                locations l
                ON i.locationsID = l.locationsID
                AND 
                    (
                        i.phone <> l.phone
                        OR 
                        (   l.phone IS NULL
                            AND 
                            i.phone IS NOT NULL
                        )
                        OR
                        (   i.phone IS NULL
                            AND 
                            l.phone IS NOT NULL
                        )
                    )                   
        )
    UPDATE locations
    SET phone = 
        CASE 
            WHEN LEN(i.phone) = 10
                THEN '(' + left(i.phone, 3)
                    + ') '
                    + SUBSTRING(i.phone, 4, 3)
                    + '-'
                    + RIGHT(i.phone, 4)
            WHEN LEN(i.phone) = 7
                THEN '(907) '
                    + LEFT(i.phone, 3)
                    + '-'
                    + RIGHT(i.phone, 4)
            ELSE    
                i.phone
        END
    FROM
        inserted i
    JOIN
        locations 
        ON locations.locationsID = i.locationsID;
END
于 2013-03-11T16:52:38.460 回答
1

就像@Jason Quinones 所说,Transact-SQL 不支持触​​发器FOR EACH ROW。它也不支持BEFORE那些,这就是为什么他和我的回答都提供了一个AFTER触发器。尽管事实上,INSTEAD OFT-SQL 中有触发器,有时可以用作BEFORE触发器的替代品,但两者还是完全不同的(正如名称所暗示的那样)。

无论如何,AFTER触发器应该可以完美地解决这个问题,所以,这里有另一个 Transact-SQL 版本供您使用:

CREATE TRIGGER locations_format_phone
ON locations
AFTER INSERT, UPDATE
AS
BEGIN
  UPDATE locations
  SET phone = '(' + STUFF(STUFF(RIGHT('907' + RTRIM(i.phone), 10), 7, 0, '-'), 4, 0, ') ')
  FROM inserted AS i
  WHERE i.location_id = locations.location_id
    AND LEN(i.phone) IN (7, 10)
END

如您所见,仅当 的长度为phone7 或 10 个字符时,触发器才会执行更新。(其他长度的值因此保持不变。)

如果该值是指定长度之一,则首先确保其长度为 10:907在开头添加默认前缀,然后取结果的最后 10 个字符。因此,为了说明该方法的工作原理,如果原始长度为 7,您将得到:

'907' + '1234567'  ->  '9071234567'            ->  '9071234567'
                        ^^^^^^^^^^ (10 chars)      (same as before)

如果是 10,则会发生以下情况:

'907' + '1234567890'  ->  '9071234567890'            ->  '1234567890'
                              ^^^^^^^^^^ (10 chars)      (original value)

接下来,STUFF调用该函数两次以在结果数字的中间插入 a-和 a 。因为插入位置与未更改的 10 位数字相关,所以-先插入 ,然后插入(否则需要考虑位置的移动):

       #4  #7
        |  |
        v  v
0)  'xxxxxxxxxx'

1)  'xxxxxx-xxxx'

2)  'xxx) xxx-xxxx'

最后,(在开头简单地连接了 a。


与此问题相关的另一件值得牢记的事情是,AFTER UPDATE更新同一个表的触发器可能会导致自身再次触发,这是一种称为触发器递归的现象。默认情况下禁用触发递归,但如果您不确定相应选项是否从未更改,您可能需要验证其当前状态。查询sys.databases是一种方法:

SELECT is_recursive_triggers_on
FROM sys.databases
WHERE name = 'your database name'

作为is_recursive_triggers_onbit列,0 代表off,1 代表on

上述触发器的设计方式不会阻止其无限递归:即使嵌套调用会在某个时候停止更新任何行(因为LEN(i.phone) IN (7, 10)条件false最终会变为),触发器仍将继续被调用。要解决此问题,您只需在开头添加此检查:

IF NOT EXISTS (SELECT * FROM inserted)
  RETURN
;

在此处阅读有关递归触发器的更多信息:

于 2013-03-13T08:48:13.947 回答