【问题标题】:T-SQL IF condition is not working as expectedT-SQL IF 条件未按预期工作
【发布时间】:2010-10-02 10:50:56
【问题描述】:

我有一个将记录插入到表中的存储过程。我在表上创建了唯一键约束以避免重复记录。

不管定义了唯一键约束这一事实,我都使用 IF() 条件(在 INSERT 语句之前)来检查可能已经存在的重复记录。

但是,我用来检查重复记录的条件语句似乎对是否执行 INSERT 没有影响。 - 即当向存储过程提交重复记录时,会抛出“违反 UNIQUE KEY 约束...”异常。

这是我的存储过程的示例:

BEGIN       
    if
        (SELECT Count(f1)
        FROM table
        WHERE f1 = @f1
        AND f2= @f2) 
    <= 0
    BEGIN
        INSERT INTO table
        (f1,f2)
        VALUES
        (@f1, @f2)

        RETURN @@IDENTITY
    END
END

我的语法有问题吗?或者,也许,我做错了?

【问题讨论】:

  • 这是在高负载下吗?两个并发事务都可以读取该行不存在,然后进入插入阶段。 Related Question 也与您的问题无关,但如果目标是返回最近插入的行的 id,您应该使用 scope_identity

标签: sql sql-server tsql sql-server-2008 if-statement


【解决方案1】:

如果您使用的是 SQL Server 2008 或更新版本,那么您应该使用 MERGE 语句:

MERGE INTO table AS t
USING (VALUES (@f1, @f2)) AS s (f1, f2)
    ON s.f1 = t.f1
        AND s.f2 = t.f2
WHEN NOT MATCHED BY t THEN
    INSERT (f1, f2) VALUES (@f1, @f2)

【讨论】:

  • Emtucifor:MERGE 有什么并发问题?
【解决方案2】:

Count(field) 计算非空值。
这可能与计算行数的count(*) 不同。

但我不鼓励在这里使用count
看看this question

【讨论】:

    【解决方案3】:

    这更安全:

    INSERT INTO table
    SELECT @f1, @f2, @f3
      FROM TABLE WITH (UPDLOCK, HOLDLOCK)
     WHERE NOT EXISTS(SELECT NULL
                        FROM table
                       WHERE f1 = @f1
                         AND f2 = @f2
                         AND f3 = @f3) 
    

    或者,如果您想保持决策逻辑(但并发性较低),请使用 NOT EXISTS:

    IF NOT EXISTS(SELECT NULL
                    FROM table
                   WHERE f1 = @f1
                     AND f2 = @f2
                     AND f3 = @f3) 
    BEGIN
    
        INSERT INTO table
          (f1,f2, f3)
        VALUES
          (@f1, @f2, @f3)
    
        RETURN @@SCOPE_IDENTITY
    
    END
    

    【讨论】:

    • 您应该使用 SCOPE_IDENTITY() 而不是 @@IDENTITY,表上的触发器可能会更改 @@IDENTITY
    • 它仍然是 NOT 并发安全的。您必须在选择中使用WITH (UPDLOCK, HOLDLOCK),或者从失败的插入中恢复并执行更新。
    【解决方案4】:

    感谢您的输入 OMG/Martin,但事实证明我的问题的根源是由于参数值为空。

    具体来说:

    --sproc params
    @f1 int,
    @f2 nvarchar(30),
    @f3 datetime = null --<<a null value will screw up the IF() condition
    
    IF(SELECT COUNT(f1)
    FROM table
    WHERE f1 = @f1
    AND f2 = @f2
    AND f3 = @f3)
    <1
    
    BEGIN
      INSERT INTO....
    END
    

    注意@f3 参数的默认值为空。因此,在 sproc 的调用者没有传递 @f3 参数的情况下,即使存在匹配的 @f1、@f2 和空 @f3,IF() 条件也会返回 0(零)。

    例如:假设表已经有一条记录

    f1   f2   f3
    --------------
    45   foo  NULL
    

    现在调用者通过发送它来触发存储过程:

    @f1=45

    @f2=foo

    (注意调用者没有指定@f3)

    如果触发 IF() 条件,它将返回 0(零)。奇怪,嗯?直观地说,我认为条件会返回 1,因为参数与表中的现有值完全匹配。

    为了让事情变得更加混乱(无论如何对我来说),即使 IF() 成功(返回零),我也会收到“违反 UNIQUE KEY 约束..”异常。实际上,我对抛出异常并不感到惊讶,因为我认为参数值与表中的现有记录完美匹配 - 对我来说,令人困惑的部分是为什么 IF() 条件与违规异常相矛盾.

    顺便说一句,OMGPonies,我尝试使用您的建议 (EXISTS),并且出现了相同的症状。显然,NULL 因素让事情变得很时髦。

    【讨论】:

    • 如果您向我们展示了您的实际代码,我会猜到这是一个选项!使用MergeScope_Identity 仍然比您当前使用的更安全。
    • 郑重声明,MERGE 解决方案不能解决 NULL 问题。换句话说,在我的 MERGE 语句的 ON 连接中,如果一个(或多个)字段具有 NULL 值,则始终执行 WHEN NOT MATCHED。
    • 当然不会,因为这是一个逻辑错误。没有语言功能可以解决这个问题。在并发条件下合并会更安全。
    • 你是说不能搜索空记录?
    【解决方案5】:
    SET NOCOUNT ON  
    SELECT * FROM table  
    WHERE f1 = @f1
            AND f2= @f2  
    
    IF @@ROWCOUNT = 0  
    BEGIN  
     INSERT INTO table  
            (f1,f2)  
            VALUES  
            (@f1, @f2)  
    
    
        RETURN @@IDENTITY  
    END  
    

    【讨论】:

      猜你喜欢
      • 2015-04-21
      • 1970-01-01
      • 2021-06-02
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-29
      相关资源
      最近更新 更多