【问题标题】:Trigger compare inserted to deleted data issue触发比较插入到删除的数据问题
【发布时间】:2017-11-16 09:11:12
【问题描述】:

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

null '值'

在触发器中不起作用。 为了解决问题,我不得不使用 isnull 函数

isnull(null,'') isnull('value','')

下面要测试的所有代码:

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

【问题讨论】:

  • NULL 是一个有点不标准的值。您无法使用= 进行检查,但可以使用WHERE value IS NULL,或者在您的情况下使用WHERE I.NAME IS NOT NULL
  • 我知道这一点,但在我的情况下,我想在主表中的数据发生更改时插入数据,因此值不为空的地方不适用
  • 非常感谢您输入的小伙子们,我将支持您的答案。我稍后决定应该接受哪个答案。

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


【解决方案1】:

问题是您无法使用= 检查NULL 值。 NULL 很特殊,但仍然可以使用 'IS NULL' 或 'IS NOT NULL' 进行检查

试试下面的代码:

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

【讨论】:

  • 正如我所说,如果发生任何更改,我想插入记录,如果我将数据从非空值更改为空值,这将不起作用...
  • 也没有用,请仔细阅读我的问题。我有解决问题的办法,我的问题是为什么它会这样,为什么触发器的第一个版本不起作用?
  • @PawelCz 本质上仍然无法将某些东西与NULL 进行比较。您甚至无法将 NULLNULL 进行比较(尝试:SELECT CASE WHEN NULL = NULL THEN 1 ELSE 0 END 将产生 0)。如果您希望代码也进行NULL 比较,您将明确需要将其定义为这样(如在我的更新中)。这是整个 SQL 的限制(不是特定于 SQL Server)。另请参阅:stackoverflow.com/questions/15929269/…
  • 谢谢,我知道这一点。 ISNULL 不仅是一种方式,而且是最简单的一种方式。
  • @PawelCz 不用担心。但是,是的,NULLS 是/可能是一个真正的痛苦。由于 NULL,我经常把头撞在桌子上:D
【解决方案2】:

这是因为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 作为回报,
所以SELECT D.NAME + ',' + I.NAME 将返回 null 如果名称在更新之前为 null 或正在更新为 null。
为避免这种情况,您可以使用此技术:

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

只有当两个值都为空时才会返回 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
)

这样,如果其中任何一个为空,您的 where 子句将返回 true,如果它们都不为空,但它们持有不同的值。

当然,使用ISNULL 将提供更短、更易于维护的代码,但您是编写您希望避免它的人...

【讨论】:

  • thnx,但在这里连接不是问题。我知道 null + 'value' 会给我 null。我遇到了 WHERE D.NAME I.NAME 的问题,我不得不用昂贵的 WHERE ISNULL(D.NAME,'') ISNULL(I.NAME,'') 替换它。只是想知道为什么第一个版本不起作用。
  • 嗯,我的回答的第一部分告诉你确切的原因。您不能将未知值与任何东西进行比较,甚至不能与另一个未知值进行比较。这就是为什么任何尝试使用带有 null 的 =&lt;&gt;&lt;&gt; 将始终返回 false。
  • 请阅读我回答的最后一部分,它指的是您对另一个答案的评论。
  • 感谢您的输入,如果将空值更新为不希望的空值,则会创建记录。解决方案是使用我在问题中指出的 ISNULL 函数。
  • 然后只需添加另一个条件...查看我编辑的答案。
【解决方案3】:

你不能使用正常的 null 比较

这些都行不通:

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, '')

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

【讨论】:

  • WHERE I.NAME IS NOT NULL 将无法解决问题,如果我将从非 null 更新为 null 值将无法工作。我有解决问题的方法,但只是想知道为什么即使 I.NAME value = null 我的 forst 触发器也不起作用
  • I.NAME can never = null 它只能是 I.NAME is null
  • 连接不是问题,它只是示例数据,WHERE isnull(D.NAME, '') isnull(I.NAME, '') 已经在我的答案中作为解决方案问题。我的问题是为什么触发器会这样?
  • 因为这就是 sql 的工作方式。你不能比较空值。每个数据库都是这样的。这是正常行为
  • 是的,我知道这一点。看起来我无法避免昂贵的 ISNULL 函数。
【解决方案4】:

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在创建时使用它

【讨论】:

    猜你喜欢
    • 2020-10-27
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多