sql atomic exists / insert - 多个唯一字段

时间:2013-09-26 21:59:40

标签: sql sql-server

好的,所以我从其他帖子/网站上了解到,以下样式的查询将以原子方式执行:

INSERT INTO Foo(field1, field2) 
SELECT @field1, @field2 
WHERE NOT EXISTS (SELECT * FROM Foo 
                  WHERE Field1 = @field1 
                     OR Field2 = @field2)

这将用于满足field1和field2都是唯一的设计要求。到目前为止,一切都很好,花花公子。

但是,如果这样的记录已经存在(查询插入0行),如果我想知道WHICH字段已经有一个具有相同值的记录。 (即,以下哪项陈述属实?

  • 已存在Field1 = @ field1
  • 的现有记录
  • 已经存在Field2 = @ field2
  • 的现有记录
  • 以上两个

您需要知道哪个字段有问题才能显示错误消息,告知用户需要将哪个字段更改为唯一值。我总是可以检查第二个查询,但那不是原子的。尝试插入和第二个查询之间的数据可能已更改,以确定哪个字段出错,并且错误消息可能无法反映数据库的实际状态。

有关如何处理此案的任何想法?

2 个答案:

答案 0 :(得分:0)

假设:

  1. 您希望大多数INSERT都能成功。
  2. 您有一个独特的约束,以致错误的 INSERT将失败。
  3. 您不关心匹配NULL。

    set transaction isolation level serializable;
    begin transaction;
    begin try
      insert into Foo ( Field1, Field2 ) values ( @Field1, @Field2 );
    end try
    begin catch
    -- Really ought to confirm that the error was the expected one here.
    select
        @Field1Match = case when Field1 = @Field1 then Cast( 1 as Bit ) else Cast( 0 as Bit ) end,
        @Field2Match = case when Field2 = @Field2 then Cast( 1 as Bit ) else Cast( 0 as Bit ) end
        from Foo
        where Field1 = @Field1 or Field2 = @Field2;
    end catch;
    commit transaction;
    

答案 1 :(得分:0)

根据我的研究(here& here),除非您非常有信心超过95%的插入成功,否则我会使用相反的方法作为HABO。借用相同的假设,你不关心匹配NULL:

DECLARE @Col1Match VARCHAR(20), @Col2Match VARCHAR(20), @msg NVARCHAR(4000);

SET ANSI_WARNINGS OFF;

BEGIN TRANSACTION;

SELECT @Col1Match = MAX(CASE Field1 WHEN @Field1 THEN 'Field1 exists:' END),
       @Col2Match = MAX(CASE Field2 WHEN @Field2 THEN 'Field2 exists:' END)
FROM dbo.Foo WITH (HOLDLOCK)
WHERE Field1 = @Field1 OR Field2 = @Field2;

IF @Col1Match IS NULL AND @Col2Match IS NULL
BEGIN
  INSERT dbo.Foo(Field1, Field2) SELECT @Field1, @Field2;
END
ELSE
BEGIN
  SET @msg = LTRIM(COALESCE(' ' + @Col1Match + ' (' + @Field1 + ')', '')
           + COALESCE(' ' + @Col2Match + ' (' + @Field2 + ')', ''));
END

COMMIT TRANSACTION;

IF @msg IS NOT NULL
BEGIN
  RAISERROR(@msg, 11, 1);
END