插入触发器比较已删除的数据问题

时间:2017-06-14 09:40:00

标签: sql sql-server triggers sql-server-2012

我上次遇到过以下问题,只是想知道为什么

  

null<> '值'

在触发器中不起作用。 要解决问题,我必须使用isnull函数

  

isnull(null,'')<> ISNULL( '值', '')

下面要测试的所有代码:

-- create main table
CREATE TABLE T_SAMPLE
(
    ID INT,
    NAME NVARCHAR(20)
)
GO

-- populate data in main table
INSERT INTO T_SAMPLE
VALUES (1, 'ONE'),
    (2,'TWO'),
    (3,'THREE')
GO

-- create table to store changes
CREATE TABLE T_SMAPLE_TEST
(
    NAME NVARCHAR(40)
)
GO

-- create trigger on main table
CREATE TRIGGER [dbo].[TRG_SAMPLE]
   ON  [dbo].[T_SAMPLE]
   AFTER UPDATE
AS
BEGIN
    INSERT INTO T_SMAPLE_TEST
    SELECT D.NAME + ',' + I.NAME
    FROM INSERTED I
        INNER JOIN DELETED D
        ON I.ID = D.ID 
    WHERE D.NAME <> I.NAME
END
GO

-- ######### test ######### 
-- below works fine
UPDATE T_SAMPLE
SET NAME = 'ONE2'
WHERE ID = 1
GO

-- test data by running below selects
SELECT * FROM T_SMAPLE_TEST
SELECT * FROM T_SAMPLE

-- but when try to update to null value from not null or vice versa, it doesn't work
UPDATE T_SAMPLE
SET NAME = NULL
WHERE ID = 1
GO

-- test data by running below selects
SELECT * FROM T_SMAPLE_TEST
SELECT * FROM T_SAMPLE

UPDATE T_SAMPLE
SET NAME = 'AGAIN'
WHERE ID = 1
GO

-- test data by running below selects
SELECT * FROM T_SMAPLE_TEST
SELECT * FROM T_SAMPLE

-- solution for this is to alter trigger as below
ALTER TRIGGER [dbo].[TRG_SAMPLE]
   ON  [dbo].[T_SAMPLE]
   AFTER UPDATE
AS
BEGIN
    INSERT INTO T_SMAPLE_TEST
    SELECT D.NAME + ',' + I.NAME
    FROM INSERTED I
        INNER JOIN DELETED D
        ON I.ID = D.ID 
    WHERE ISNULL(D.NAME,'') <> ISNULL(I.NAME,'')
END
GO

/*
DROP TABLE T_SMAPLE_TEST
DROP TABLE T_SAMPLE
DROP TRIGGER TRG_SAMPLE
*/

4 个答案:

答案 0 :(得分:1)

问题是您无法使用NULL检查=值。 NULL很特别,但仍然可以使用&#39; IS NULL&#39;来检查或者&#39;不是空的&#39;

请尝试以下代码:

ALTER TRIGGER [dbo].[TRG_SAMPLE]
   ON  [dbo].[T_SAMPLE]
   AFTER UPDATE
AS
BEGIN
    INSERT INTO T_SMAPLE_TEST
    SELECT D.NAME + ',' + I.NAME
    FROM INSERTED I
        INNER JOIN DELETED D
        ON I.ID = D.ID 
    WHERE I.NAME IS NOT NULL
END
GO

更新:我刚刚看到您对我评论的回复。这会诀窍吗?

ALTER TRIGGER [dbo].[TRG_SAMPLE]
   ON  [dbo].[T_SAMPLE]
   AFTER UPDATE
AS
BEGIN
    INSERT INTO T_SMAPLE_TEST
    SELECT D.NAME + ',' + I.NAME
    FROM INSERTED I
        INNER JOIN DELETED D
        ON I.ID = D.ID 
    WHERE I.NAME <> D.NAME AND D.NAME IS NOT NULL
END
GO

答案 1 :(得分:1)

那是因为NULL被定义为未知值,这意味着它无法与之比较 以下所有语句都将产生一个空记录集:

SELECT 1 WHERE NULL = 1
SELECT 1 WHERE NULL <> 1
SELECT 1 WHERE NULL = NULL
SELECT 1 WHERE NULL <> NULL

虽然这些陈述将返回1:

SELECT 1 WHERE ISNULL(NULL, 1) = 1
SELECT 1 WHERE ISNULL(NULL, 0) <> 1
SELECT 1 WHERE ISNULL(NULL, 0) = ISNULL(NULL, 0)
SELECT 1 WHERE NULL IS NULL

顺便说一句,如果你将一个字符串与NULL连接起来,你将得到NULL,作为回报 因此,如果在更新之前名称为null或者更新为null,则SELECT D.NAME + ',' + I.NAME将返回null 为避免这种情况,您可以使用此技术:

SELECT  STUFF(
        ISNULL(','+ D.NAME, '') + 
        ISNULL(',' + I.NAME, '')
        , 1, 1, '')

只有当两个值都为null时才返回NULL,但如果它们中的任何一个不为null,它将仅返回它。 STUFF用于删除第一个逗号,以防其中一个值为空。

如果您正在寻找避免使用ISNULL的方法,您可以这样做:

WHERE 
(
    I.NAME <> D.NAME
    OR I.NAME IS NULL
    OR D.NAME IS NULL
) 
AND NOT 
(
I.NAME IS NULL 
AND D.NAME IS NULL
)

这样,如果其中任何一个为null,则where子句将返回true,以及如果它们都不为null但它们具有不同的值。

当然,使用ISNULL会提供更短,更易于维护的代码,但是您就是那个想要避免使用它的人......

答案 2 :(得分:1)

您不能使用与

的正常比较

这些不会起作用:

if null <> value
if null = value

对null的测试必须是这样的:

if value is not null
if value is null

或者像这样

if isnull(value, '') = ''
if isnull(value, '') <> ''

在您的情况下替换

WHERE D.NAME <> I.NAME

用这个

WHERE isnull(D.NAME, '') <> isnull(I.NAME, '')

那应该解决你的问题

同时更改此

SELECT D.NAME + ',' + I.NAME

到这个

SELECT isnull(D.NAME, '') + ',' + isnull(I.NAME, '')

因为当其中一个名称为null时,整个连接字符串也将为null

答案 3 :(得分:1)

null比较的行为取决于ANSI_NULLS选项。 试试这段代码:

set ansi_nulls off
go
if 'jjj' <> null print 'it works with ansi_nulls off'
go
set ansi_nulls on
go
if 'jjj' <> null print 'it works ansi_nulls on'

所以你看,ansi_nulls的关闭行为与预期的一样,但它是遗留选项而且

  

在SQL Server的未来版本中,ANSI_NULLS将始终为ON和   任何明确将选项设置为OFF的应用程序都将生成   错误。

所以我认为由于某种原因,你可以使用此选项设置为关闭,但你的触发器是在ansi_nulls打开的情况下创建的,所以当你的其余代码可以正常工作时,当触发器触发它时使用ansi_nulls保存在创建时使用它