【问题标题】:IF EXISTS, THEN SELECT ELSE INSERT AND THEN SELECT如果存在,则选择 ELSE INSERT 然后选择
【发布时间】:2009-09-28 17:38:05
【问题描述】:

您在 Microsoft SQL Server 2005 中怎么说:

IF EXISTS (SELECT * FROM Table WHERE FieldValue='') THEN
   SELECT TableID FROM Table WHERE FieldValue=''
ELSE
   INSERT INTO TABLE(FieldValue) VALUES('')
   SELECT TableID FROM Table WHERE TableID=SCOPE_IDENTITY()
END IF

我要做的是查看是否已经有一个空白字段值,如果有则返回该 TableID,否则插入一个空白字段值并返回相应的主键。

【问题讨论】:

  • 您使用什么编程语言?这可能会在事务中更好地完成。
  • 这是较老的问题; “仅插入一行”问题应作为此问题的副本关闭,而不是相反。

标签: sql sql-server sql-server-2005 tsql


【解决方案1】:

您需要在事务中执行此操作,以确保两个同时的客户端不会插入相同的 fieldValue 两次:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
    DECLARE @id AS INT
    SELECT @id = tableId FROM table WHERE fieldValue=@newValue
    IF @id IS NULL
    BEGIN
       INSERT INTO table (fieldValue) VALUES (@newValue)
       SELECT @id = SCOPE_IDENTITY()
    END
    SELECT @id
COMMIT TRANSACTION

您也可以使用Double-checked locking 来减少锁定开销

DECLARE @id AS INT
SELECT @id = tableID FROM table (NOLOCK) WHERE fieldValue=@newValue
IF @id IS NULL
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION
        SELECT @id = tableID FROM table WHERE fieldValue=@newValue
        IF @id IS NULL
        BEGIN
           INSERT INTO table (fieldValue) VALUES (@newValue)
           SELECT @id = SCOPE_IDENTITY()
        END
    COMMIT TRANSACTION
END
SELECT @id

至于为什么需要 ISOLATION LEVEL SERIALIZABLE ,当你在一个可序列化的事务中时,第一个命中表的 SELECT 会创建一个范围锁,覆盖记录应该在的位置,所以在此之前没有人可以插入相同的记录交易结束。

如果没有 ISOLATION LEVEL SERIALIZABLE,默认隔离级别(READ COMMITTED)不会在读取时锁定表,因此在 SELECT 和 UPDATE 之间,仍然可以有人插入。具有 READ COMMITTED 隔离级别的事务不会导致 SELECT 锁定。具有 REPEATABLE READS 的事务会锁定记录(如果找到),但不会锁定间隙。

【讨论】:

  • +1 我不明白为什么考虑竞争条件和并发性的唯一答案在零票上表现不佳。
  • 请您扩展解释为什么ISOLATION LEVEL SERIALIZABLE 是必要的,如果您不设置会发生什么?
  • @binki,在可序列化事务中,第一个 SELECT 命中表,创建一个 范围锁 覆盖记录应该在的位置,因此其他人无法插入相同的记录,直到此事务结束。如果没有 ISOLATION LEVEL SERIALIZABLE,默认隔离级别(READ COMMITTED)不会在读取时锁定表,因此在 select 和 update 之间,仍然有人能够插入。具有 READ COMMITTED 隔离级别的事务,不会导致 SELECT 锁定。具有 REPEATABLE READS 的事务,锁定记录(如果找到)但不锁定间隙。
【解决方案2】:
IF EXISTS (SELECT 1 FROM Table WHERE FieldValue='') 
BEGIN
   SELECT TableID FROM Table WHERE FieldValue=''
END
ELSE
BEGIN
   INSERT INTO TABLE(FieldValue) VALUES('')
   SELECT SCOPE_IDENTITY() AS TableID
END

有关 IF ELSE 的更多信息,请参阅 here

注意:在没有安装 SQL Server 的情况下编写,方便仔细检查,但我认为它是正确的

另外,我已将 EXISTS 位更改为 SELECT 1 而不是 SELECT * 因为您不关心 EXISTS 中返回的内容,只要某些内容是 假设 TableID 是标识列,我还更改了 SCOPE_IDENTITY() 位以仅返回标识

【讨论】:

  • 'SELECT 1' 无关紧要。你改变它只是为了指出你不关心细节吗?这对性能没有帮助。
  • 我更喜欢在我的代码中避免使用 SELECT * - 这感觉不是一个好习惯,所以我通常在执行存在时执行 SELECT 1
  • @zvolkov 的解决方案比这略好,因为它使用了一个变量,并且只需 SELECT FROM Table 一次而不是两次。
【解决方案3】:

你很亲密:

IF EXISTS (SELECT * FROM Table WHERE FieldValue='')
   SELECT TableID FROM Table WHERE FieldValue=''
ELSE
BEGIN
   INSERT INTO TABLE (FieldValue) VALUES ('')
   SELECT TableID FROM Table WHERE TableID=SCOPE_IDENTITY()
END

【讨论】:

    【解决方案4】:

    你只需要稍微改变if...else..endif的结构:

    if exists(select * from Table where FieldValue='') then begin
      select TableID from Table where FieldValue=''
    end else begin
      insert into Table (FieldValue) values ('')
      select TableID from Table where TableID = scope_identity()
    end
    

    你也可以这样做:

    if not exists(select * from Table where FieldValue='') then begin
      insert into Table (FieldValue) values ('')
    end
    select TableID from Table where FieldValue=''
    

    或者:

    if exists(select * from Table where FieldValue='') then begin
      select TableID from Table where FieldValue=''
    end else begin
      insert into Table (FieldValue) values ('')
      select scope_identity() as TableID
    end
    

    【讨论】:

      【解决方案5】:

      听起来你的桌子没有钥匙。您应该可以简单地尝试INSERT:如果它是重复的,那么键约束将被咬住,INSERT 将失败。不用担心:您只需要确保应用程序不会看到/忽略错误。当您说“主键”时,您可能是指IDENTITY 值。这一切都很好,但您还需要对自然键进行键约束(例如 UNIQUE)。

      另外,我想知道您的程序是否做得太多。考虑分别为“创建”和“读取”操作设置单独的程序。

      【讨论】:

        【解决方案6】:
        DECLARE @t1 TABLE (
            TableID     int         IDENTITY,
            FieldValue  varchar(20)
        )
        
        --<< No empty string
        IF EXISTS (
            SELECT *
            FROM @t1
            WHERE FieldValue = ''
        ) BEGIN
            SELECT TableID
            FROM @t1
            WHERE FieldValue=''
        END
        ELSE BEGIN
            INSERT INTO @t1 (FieldValue) VALUES ('')
            SELECT SCOPE_IDENTITY() AS TableID
        END
        
        --<< A record with an empty string already exists
        IF EXISTS (
            SELECT *
            FROM @t1
            WHERE FieldValue = ''
        ) BEGIN
            SELECT TableID
            FROM @t1
            WHERE FieldValue=''
        END
        ELSE BEGIN
            INSERT INTO @t1 (FieldValue) VALUES ('')
            SELECT SCOPE_IDENTITY() AS TableID
        END
        

        【讨论】:

          【解决方案7】:
          create schema tableName authorization dbo
          go
          IF OBJECT_ID ('tableName.put_fieldValue', 'P' ) IS NOT NULL 
          drop proc tableName.put_fieldValue
          go
          create proc tableName.put_fieldValue(@fieldValue int) as
          declare @tableid int = 0
          select @tableid = tableid from table where fieldValue=''
          if @tableid = 0 begin
             insert into table(fieldValue) values('')
             select @tableid = scope_identity()
          end
          return @tableid
          go
          declare @tablid int = 0
          exec @tableid = tableName.put_fieldValue('')
          

          【讨论】:

            猜你喜欢
            • 2022-10-13
            • 2021-07-13
            • 2022-01-17
            • 2014-10-01
            • 2014-02-21
            • 2020-01-01
            • 1970-01-01
            • 2014-11-24
            • 1970-01-01
            相关资源
            最近更新 更多