【问题标题】:Insert statement is executing before delete SQL Server在删除 SQL Server 之前执行插入语句
【发布时间】:2021-08-16 16:26:52
【问题描述】:

我的存储过程中有如下语句:

IF (  
    SELECT COUNT(1)  
    FROM dbo.t_user_suggested
    WHERE ListId = @pListId  
    ) > 0  
  BEGIN  
   DELETE  
   FROM dbo.t_user_suggested
   WHERE ListId = @pListId  
  END

 INSERT INTO dbo.t_user_suggested (  
   Id  
   ,InId  
   ,InName  
   ,UserId  
   ,ListId
   )  
  SELECT DISTINCT Id  
   ,InId  
   ,InName  
   ,@pUserId  
   ,@pListId
  FROM CTE123  

我收到错误违反主键约束“PK_t_user_suggested”。无法在对象“dbo.t_user_suggested”中插入重复键。在我插入之前,我有一个明确的检查来删除所有记录,这个错误是随机出现的。我手动执行了存储过程 10-15 次,没有出现错误。如何确保在控制转移到存储过程中的 Insert 之前删除所有记录。

【问题讨论】:

  • SQL 自上而下顺序执行,INSERT DELETE之后执行。我怀疑您的 DELETE 没有删除您认为它是 的行,您在尝试插入的行中有重复值。花点时间创建一个minimal reproducible example,您可能会发现现有尝试的问题(这是您在提问时应该尝试创建一个的原因之一)。
  • @Larnu:这正是我造成问题的代码。我有一张包含数千行的表格,因此无法提供任何数据。我知道它很难重现它,因为我也有重现它的问题。我只是想知道在存储过程中在 T-SQL 中执行语句是否存在一些问题,这是一个非常棘手的问题。
  • @HoneyBadger:感谢您指出这一点。这是一个错字.. 不,我在表 t_user_suggested 上没有任何触发器
  • 那么你表的主键定义是什么?
  • 这张表的PK是什么?我建议它是Id,而不是ListId。由于我们不知道CTE123 是什么,因此很难为您提供帮助

标签: sql-server tsql stored-procedures transactions


【解决方案1】:

如果两个事务同时运行此代码,它们可能都尝试插入相同的密钥。在默认锁定/隔离级别下,没有什么可以阻止它们,而且您似乎并没有使用事务。

您最好的选择是使用带有正确提示的交易。

顺便说一句,IF (SELECT COUNT(1)... 不需要,因为DELETE 只会删除存在的行。另外,如果你真的需要这个,那么你应该改用IF(EXISTS

SET XACT_ABORT, NOCOUNT ON;

BEGIN TRAN;

DELETE
FROM dbo.t_user_suggested WITH (HOLDLOCK)
WHERE ListId = @pListId;
 
INSERT INTO dbo.t_user_suggested (  
   Id  
   ,InId  
   ,InName  
   ,UserId  
   ,ListId
   )  
  SELECT DISTINCT Id  
   ,InId  
   ,InName  
   ,@pUserId  
   ,@pListId
  FROM CTE123;

COMMIT TRAN;

或者,如果@pListId 实际上是主键,那么最好发出UPDATE

UPDATE 
SET Id = CTE123.InId
   ,InId  = CTE123.InId  
   ,InName = CTE123.InName 
   ,UserId = CTE123.UserId 
FROM dbo.t_user_suggested t
JOIN (
  SELECT DISTINCT Id  
   ,InId  
   ,InName  
   ,@pUserId AS pUserId
   ,@pListId AS pListId
  FROM CTE123
) CTE123 ON CTE123.pListId = t.pListId
WHERE t.ListId = @pListId;

【讨论】:

  • 非常感谢,(Id,InId,ListId) 组合是主键。
  • 那么第二个版本可能不适合你
【解决方案2】:

我怀疑下面的查询返回了不止一行。所以每行都得到相同的 ListID。

SELECT DISTINCT Id  
   ,InId  
   ,InName  
   ,@pUserId  
   ,@pListId
  FROM CTE123  

请尝试执行以下查询:

IF (  
    SELECT COUNT(1)  
    FROM dbo.t_user_suggested
    WHERE ListId = @pListId  
    ) > 0  
  BEGIN  
   DELETE  
   FROM dbo.t_user_suggested
   WHERE ListId = @pListId  
  END

 INSERT INTO dbo.t_user_suggested (  
   Id  
   ,InId  
   ,InName  
   ,UserId  
   ,ListId
   )  
  SELECT top 1 DISTINCT Id  
   ,InId  
   ,InName  
   ,@pUserId  
   ,@pListId
  FROM CTE123  

我已尝试创建您的情况,您的查询运行良好。您可以查看以下示例。 ListId 不是您的主键列,或者您的选择查询返回多行。

DB-Fiddle:

表定义和插入语句:

 create table t_user_suggested (Id  int ,InId  int,InName  varchar(50),UserId  INT,ListId INT);
 INSERT INTO t_user_suggested values(1,1,'A',2,3);

选择查询:

 select* from t_user_suggested

表格的当前输出:

Id InId InName UserId ListId
1 1 A 2 3

您的查询:

删除部分

 IF (  
     SELECT COUNT(1)  
     FROM dbo.t_user_suggested
     WHERE ListId = 3
     )  0  
   BEGIN  
    DELETE  
    FROM dbo.t_user_suggested
    WHERE ListId = 3  
   END

删除后选择查询:

 select * from t_user_suggested;

输出:

Id InId InName UserId ListId

行已成功删除。

插入语句:

  INSERT INTO dbo.t_user_suggested (  
    Id  
    ,InId  
    ,InName  
    ,UserId  
    ,ListId
    )  
   values(1,1,'A',2,3);  

插入后选择查询:

 select * from t_user_suggested;

输出:

Id InId InName UserId ListId
1 1 A 2 3

dbhere

【讨论】:

  • 嗯,好点,总是先检查明显的
【解决方案3】:

我不确定到底是什么问题,但似乎是竞争条件或事务锁定/隔离级别的问题。我暂时使用了 MERGE 语句而不是 Insert 和 delete 来解决问题。所以下面是我的查询。

MERGE t_user_suggested AS tar
USING CTE123  AS cte
    ON tar.Id = cte.Id 
        AND tar.InId  = cte.InId  
        AND tar.ListId = @pListId
WHEN NOT MATCHED BY Target
    THEN
        INSERT (
            Id 
            ,InId  
            ,InName
            ,UserId
            ,ListId
            )
        VALUES (
            cte.Id
            ,cte.IngrId
            ,cte.InName
            ,@pUserId
            ,@pListId
            )
     -- For Updates
    WHEN MATCHED THEN UPDATE SET
        tar.IName       = cte.InName,
        tar.datetimelastseen = null,
        tar.seenlast24hours=null
        -- For Deletes
    WHEN NOT MATCHED BY SOURCE THEN
        DELETE;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
  • 1970-01-01
  • 2012-01-11
  • 2012-03-19
  • 1970-01-01
  • 2022-06-11
相关资源
最近更新 更多