Trigger compare inserted to deleted data issue

不打扰是莪最后的温柔 提交于 2019-12-12 04:14:27

问题


I have faced below issue last time and just wondering why

null <> 'value'

doesn't work in trigger. To resolve issue I had to use isnull function

isnull(null,'') <> isnull('value','')

All code to test below:

-- 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
*/

回答1:


You cannot use normal comparision with null

these wont work:

if null <> value
if null = value

testing on null has to be like this :

if value is not null
if value is null

Or like this

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

in your case replace

WHERE D.NAME <> I.NAME

with this

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

That should fix your problem

Also change this

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

to this

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

because when one of the names is null the whole concatinated string will also be null




回答2:


The issue is that you cannot check for a NULL value using =. NULL is special, but can still be checked using 'IS NULL' or 'IS NOT NULL'

Try the following code:

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

Update: I just saw your reply to my comment. Would this do the trick?

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



回答3:


The behaviour off null comparison depends on ANSI_NULLS option. Try this code:

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'

So you see, with ansi_nulls off the behaviour is as you expected but it's the legacy option and

In a future version of SQL Server, ANSI_NULLS will always be ON and any applications that explicitly set the option to OFF will generate an error.

So I think for one reason or another, you get to use this option set to off, but your trigger was created with ansi_nulls on, so when the rest of your code can work fine, when the trigger fires it uses ansi_nulls saved with it when it was created




回答4:


That's because NULL is defined as an unknown value, meaning it can't be compared to.
All of the following statements will result in an empty recordset:

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

While these statements will return 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

By the way, and you are concatenating a string with NULL, you'll get NULL in return,
so SELECT D.NAME + ',' + I.NAME will return null if the name was null before the update or is being updated to null.
To avoid that you can use this technique:

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

That will return NULL only if both values are null, but if any of them is not null it will return only it. The STUFF is used to remove the first comma in case one of the values is not null.

If you are looking for a way to avoid the use of ISNULL, you can simply do this:

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
)

This way, if any one of them is null your where clause will return true, as well as if none of them is null but they hold different values.

Of course, using ISNULL will provide a shorter, easier to maintain code, but you are the one that wrote you wish to avoid it...



来源:https://stackoverflow.com/questions/44540993/trigger-compare-inserted-to-deleted-data-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!