更新查询在FROM子句中使用了错误的表

编程语言 2026-07-11

我在生产环境中误执行了一条错误且不完整的查询,如下所示。

#TempDiff 是一个包含两列的临时表,如下所示。对于每个产品,存在一个或多个 [Pallet Contents]。每个SKU在 #TempDiff 中都是唯一的,在 products 表中存在,并且恰好存在一条 Pallet Content 满足 P2CLocation = 1。还有一些其他产品不在 #TempDiff 范围内。

我的问题:有没有办法看到这条查询更新了哪些行?

#TempDiff 里只有69行,但实际更新了60,000行……

根据我的观察,所有非空的 [pallet contents] 在执行完这条查询后似乎都被减去了3。3确实是 #tempDiff 中的一个 diff 值,但它是第二条(插入的)行,而不是第一条。

这台服务器的版本是SQL Server 2017,兼容性级别为100。

我尝试创建一个最小示例,请看这个 db fiddle,但它的行为有所不同(尽管结果是一致的)。也许结果是不可确定的?

以下是原始查询:

CREATE TABLE #TempDiff 
(
    SKU VARCHAR(50),
    Diff INT
);

/* Here is the logic that add rows into #temp diff */

with productPickLocation as 
(
    select [Pallet Contents].*
    from [Pallet Contents]
    left join pallets on [Pallet Contents].[Pallet ID] = pallets.[Pallet ID]
    where P2CPullLocation = 1 and pallets.WarehouseID = 3
)
update [Pallet Contents]
set [Pallet Contents].Qty = [Pallet Contents].Qty - diff
from #TempDiff 
inner join products on #TempDiff.SKU = products.ItemNumber
left join productPickLocation on products.[PRODUCT KEY] = productPickLocation.[Product key]

解决方案

问题基本在于 update [Pallet Contents] 指向对 [Pallet Contents] 表的新引用。它看不到 productPickLocation 中的引用,因为它处于不同的作用域。

所以你先有:

with productPickLocation as (
    select [Pallet Contents].*
    from [Pallet Contents]
    left join pallets on [Pallet Contents].[Pallet ID] = pallets.[Pallet ID]
    where P2CPullLocation = 1 and pallets.WarehouseID = 3
)

这实际上因为 where 子句而构成一个内连接,而不是左连接。

你随后在主段中执行

from #TempDiff 
inner join products on #TempDiff.SKU = products.ItemNumber
left join productPickLocation on products.[PRODUCT KEY] = productPickLocation.[Product key]

这又带有左连接,导致选出所有 products,并忽略了 productPickLocation,因为它在逻辑上未被使用。

接着你得到

update [Pallet Contents]

它在自身的作用域中查找引用,找不到,因此再次对整张表进行笛卡尔积连接。这个隐式笛卡尔积没有其他连接条件,因此所有行都被更新。

更有意思的是:这看起来像是在试图让每一行被多次更新。其实不会发生。SQL Server检测到 update 是非确定性的,于是只选择一个值来更新。该行在一条语句中最多只会被更新一次。

并且因为 set 引用了一个可为空的列

set [Pallet Contents].Qty = [Pallet Contents].Qty - diff

null - value 的结果是 null,因此它保持不变,只有其他行受到影响。


你的查询本来应该长成这样:

with productPickLocation as (
    select pc.*
    from [Pallet Contents] pc
    inner join pallets pt on pc.[Pallet ID] = pt.[Pallet ID]
    where pc.P2CPullLocation = 1
      and pt.WarehouseID = 3
)
update ppl   -- NOTE THIS LINE
set Qty -= t.diff
from productPickLocation ppl
inner join products p on p.[PRODUCT KEY] = ppl.[Product key]
inner join #TempDiff t on t.SKU = p.ItemNumber;

或者更简单地:

update pc   -- NOTE THIS LINE
set Qty -= t.diff
from [Pallet Contents] pc
inner join products p on p.[PRODUCT KEY] = pc.[Product key]
inner join pallets pt on pc.[Pallet ID] = pt.[Pallet ID]
inner join #TempDiff t on t.SKU = p.ItemNumber
where pc.P2CPullLocation = 1
  and pt.WarehouseID = 3;

如果临时表实际上并不唯一,那么你需要先对它进行聚合

with AggDiff as (
    select
        t.SKU,
        TotalDiff = SUM(t.diff)
    from #TempDiff t
    group by
        t.SKU
)
update pc   -- NOTE THIS LINE
set Qty -= t.TotalDiff
from [Pallet Contents] pc
inner join products p on p.[PRODUCT KEY] = pc.[Product key]
inner join pallets pt on pc.[Pallet ID] = pt.[Pallet ID]
inner join AggDiff t on t.SKU = products.ItemNumber
where pc.P2CPullLocation = 1
  and pt.WarehouseID = 3;

TL;DR;

在编写带连接的更新时,

  • 始终为每个表使用唯一的别名,第一行的 update 指的是别名而不是表名。
  • 除非你确定逻辑正确,否则不要使用左连接。
  • 为了清晰起见,将你要更新的表放在 from 行中,然后确保每个连接返回一个唯一的行(要么通过连接条件,要么通过子聚合)。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章