【问题标题】:Using a if condition in an insert SQL Server在插入 SQL Server 中使用 if 条件
【发布时间】:2018-10-12 13:03:30
【问题描述】:

我的代码中有以下语句

INSERT INTO #TProductSales (ProductID, StockQTY, ETA1) 
VALUES (@ProductID, @StockQTY, @ETA1)

我想做这样的事情:

IF @ProductID exists THEN 
   UPDATE #TProductSales 
ELSE 
   INSERT INTO #TProductSales

有什么办法可以做到吗?

【问题讨论】:

标签: sql-server tsql upsert


【解决方案1】:

模式是(没有错误处理):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
  WHERE ProductID = @ProductID;

IF @@ROWCOUNT = 0
BEGIN
  INSERT #TProductSales(ProductID, StockQTY, ETA1) 
    VALUES(@ProductID, @StockQTY, @ETA1);
END

COMMIT TRANSACTION;

您无需在此处额外读取#temp 表。您已经通过尝试更新来做到这一点。为了防止竞争条件,您可以执行与保护要隔离的两个或多个语句的任何块相同的操作:您将其包装在具有适当隔离级别的事务中(可能在此处可序列化,尽管仅当我们不是在谈论 #temp 表时才有意义,因为根据定义,它是序列化的)。

通过添加IF EXISTS 检查(和you would need to add locking hints to make that safe / serializable anyway),您不会进一步领先,但您可能会落后,具体取决于您更新现有行与插入新行的次数。这可能会增加很多额外的 I/O。

人们可能会告诉你使用MERGE(这实际上是在幕后的多个操作,以及also needs to be protected with serializable),我劝你不要。我和其他人在这里阐述了原因:

对于多行模式(如 TVP),我会以完全相同的方式处理此问题,但没有一种实用的方法可以像使用单行情况一样避免第二次读取。不,MERGE 也没有避免它。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE t SET t.col = tvp.col
  FROM dbo.TargetTable AS t
  INNER JOIN @TVP AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols)
  SELECT ProductID, othercols
  FROM @TVP AS tvp
  WHERE NOT EXISTS
  (
    SELECT 1 FROM dbo.TargetTable
    WHERE ProductID = tvp.ProductID
  );

COMMIT TRANSACTION;

好吧,我想有办法做到这一点,但我还没有彻底测试过:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

DECLARE @exist TABLE(ProductID int PRIMARY KEY);

UPDATE t SET t.col = tvp.col
  OUTPUT deleted.ProductID INTO @exist
  FROM dbo.TargetTable AS t
  INNER JOIN @tvp AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols) 
  SELECT ProductID, othercols 
  FROM @tvp AS t 
  WHERE NOT EXISTS 
  (
    SELECT 1 FROM @exist 
    WHERE ProductID = t.ProductID
  );

COMMIT TRANSACTION;

无论哪种情况,你都要先执行更新,否则你会更新你刚刚插入的所有行,这会很浪费。

【讨论】:

  • “人们可能会告诉你使用 MERGE,我劝你不要” - 智慧之言
  • Merge 让我吃惊的是它很难修复。我的意思是其他 SQL 服务器 TSQL 构造有这么多问题?
【解决方案2】:

我个人喜欢创建一个表变量或临时表来存储值,然后进行更新/插入,但我通常会进行大量插入/更新。这种模式的好处在于它适用于多条记录,而不会在插入/更新中产生冗余。

DECLARE @Tbl TABLE (
    StockQty INT,
    ETA1 DATETIME,
    ProductID INT
)

INSERT INTO @Tbl (StockQty,ETA1,ProductID)
    SELECT @StockQty AS StockQty ,@ETA1 AS ETA1,@ProductID AS ProductID

UPDATE tps
SET   StockQty = tmp.StockQty
    , tmp.ETA1 = tmp.ETA1
FROM #TProductSales tps
INNER JOIN @Tbl tmp ON tmp.ProductID=tps.ProductID

INSERT INTO #TProductSales(StockQty,ETA1,ProductID)
    SELECT
        tmp.StockQty,tmp.ETA1,tmp.ProductID
    FROM @Tbl tmp
    LEFT JOIN #TProductSales tps ON tps.ProductID=tmp.ProductID
    WHERE tps.ProductID IS NULL

【讨论】:

    【解决方案3】:

    你可以使用类似的东西:

    IF EXISTS( SELECT NULL FROM  #TProductSales WHERE ProductID = @ProductID)
         UPDATE #TProductSales SET StockQTY = @StockQTY, ETA1 = @ETA1 WHERE ProductID = @ProductID
    ELSE
         INSERT INTO #TProductSales(ProductID,StockQTY,ETA1) VALUES(@ProductID,@StockQTY,@ETA1)
    

    【讨论】:

    • IF EXISTS 检查是不必要的,并且作为一般模式(显然不适用于#temp 表)实际上会导致并发问题/死锁。
    • 我同意你的观点,它会导致额外的读取,但在一个看起来只有 1 条记录的临时表上,它不应该对性能造成太大影响。我通常仍然使用它,因为它似乎比尝试更新然后检查行数更具可读性和更容易理解。
    • 问题是人们会从中吸取教训,并在确实重要的地方使用这种方法,因为它可能会对性能造成重大影响,并且可能导致并发问题或两者兼而有之。另外,cmets generally don't cause performance issues,所以如果你有一段代码你知道更好,但你发现它的工作方式不太可读或难以记住,请添加评论。
    • (我已经承认这无关紧要这里但是,由于这个网站的运作方式,这些答案将会被很多未来有同样问题的用户阅读,但是谁可能没有使用#temp 表。请始终尝试在这些未来读者的脑海中回答问题。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    相关资源
    最近更新 更多