使用“not exists”检查更新查询会导致主键冲突

时间:2010-01-13 16:39:25

标签: sql sql-server primary-key sql-update

涉及以下表格:

Table Product:
product_id
merged_product_id
product_name

Table Company_Product:
product_id
company_id

(Company_Product在product_id和company_id列上都有一个主键)

我现在想要在Company_Product上运行更新,将product_id列设置为merged_ product_id。此更新可能会导致重复操作,从而触发主键冲突,因此我在where子句中添加了“not exists”检查,我的查询如下所示:

update cp
set cp.product_id = p.merged_product_id
from Company_Product cp
join Product p on p.product_id = cp.product_id
where p.merged_product_id is not null
and not exists 
 (select * from Company_Product cp2 
  where cp2.company_id = cp.company_id and 
  cp2.product_id = p.merged_product_id)

但是这个查询因主键违规而失败。

我认为可能会发生因为Product表包含多个具有相同merged_product_id的行,它将成功用于第一个产品,但是当使用相同的merged_product_id转到下一个产品时,它将失败,因为'不存在'子查询没有看到第一个更改,因为查询尚未完成并已提交。

我是否正确地思考这个问题,以及如何更改查询以使其正常工作?

[编辑] 一些数据示例:

Product:

product_id merged_product_id    
   23            35    
   24            35    
   25            12    
   26            35    
   27           NULL

Company_Product:

product_id company_id    
   23          2    
   24          2    
   25          2    
   26          3    
   27          4

[编辑2] 最后我使用了此解决方案,该解决方案使用临时表进行更新,然后将更新的数据插入到原始的Company_Product表中:

create table #Company_Product
(product_id int, company_id int)

insert #Company_Product select * from Company_Product

update cp
set cp.product_id = p.merged_product_id
from #Company_Product cp
join Product p on p.product_id = cp.product_id
where p.merged_product_id is not null

delete from Company_Product

insert Company_Product select distinct * from #Company_Product

drop table #Company_Product

6 个答案:

答案 0 :(得分:2)

主键应该是三件事:

  1. 非空
  2. 唯一
  3. 不变的
  4. 通过更改部分主键,您违反了要求#3。

    我认为你最好创建一个新表,填充它,然后删除约束,删除原始表,并将新表重命名为所需的名称(当然,重新应用原始约束) 。根据我的经验,这使您有机会在“新”数据生效前检查它们。

    分享并享受。

答案 1 :(得分:1)

如果您至少使用SQL 2008,则可以使用MERGE

否则,您将不得不选择一个标准来建立您想要的 merged_product_id以及您遗漏哪一个:

update cp
set cp.product_id = p.merged_product_id
from Company_Product cp
cross apply (
  select top(1) merged_product_id
  from Product 
  where product_id = cp.product_id
  and p.merged_product_id is not null
  and not exists (
    select * from Company_Product cp2 
    where cp2.company_id = cp.company_id and 
    cp2.product_id = merged_product_id)
  order by <insert diferentiating criteria here>) as p

请注意,如果多个并发请求正在运行合并逻辑,则这不安全。

答案 2 :(得分:1)

我不能完全看看你的结构是如何工作的,或者这个更新试图实现的目标。您似乎正在更新Company_Product并在显然具有不同product_id的现有行上设置(新)product_id;例如,将行从一个产品更改为另一个产品。这似乎是一个奇怪的用例,我希望你能插入一个新的独特行。所以我想我错过了什么。

如果你转换 Company_Product使用一组新的产品ID而不是旧的产品ID(名称“merged_product_id”让我推测这个),你确定那里吗?新旧之间没有重叠? 会导致像你所描述的那样的问题。

答案 3 :(得分:0)

如果没有看到您的数据,我相信您的分析是正确的 - 整个集合已更新,然后提交失败,因为它会导致违反约束。在某些UPDATE的“部分提交”之后,永远不会重新评估EXISTS。

我认为您需要更准确地定义有关尝试根据merged_product_id将多个产品更改为同一产品的规则,然后在查询中明确说明这些规则。例如,您可以排除任何属于该类别的产品,并使用适当的查询进一步提供NOT EXISTS。

答案 4 :(得分:0)

我认为你对更新失败的原因是正确的。要解决此问题,请在company_product表上运行删除查询,以删除将应用相同merged_prduct_id的额外product_ids。

这里是查询可能是什么

delete company_product
  where product_id not in (
    select min(product_id)
      from product
      group by merged_product_id
  )
  and product_id not in (
    select product_id
      from product
      where merged_product_id is null
  )

- 在评论中添加了解释 -

这样做的目的是删除更新后将重复的行。由于您的产品具有多个合并ID,因此在完成后您实际上只需要表中的一个产品(针对每个公司)。因此,我的查询(如果有效......)将保留每个合并产品ID的最小原始产品ID - 然后您的更新将有效。

所以,假设您有3个产品ID,它们将映射到2个合并的ID:1 - &gt; 10,2 - &gt; 20,3 - &gt; 20.您有以下company_product数据:

product_id  company_id
1           A
2           A
3           A

如果针对此运行更新,它将尝试将第二行和第三行都更改为产品ID 20,它将失败。如果你运行我建议的删除,它将删除第三行。删除和更新后,表格如下所示:

product_id  company_id
10          A
20          A

答案 5 :(得分:0)

试试这个:

create table #Company_Product
(product_id int, company_id int)
create table #Product (product_id int,merged_product_id int)
insert into #Company_Product
select           23, 2     
union all select 24, 2     
union all select 25, 2     
union all select 26, 3     
union all select 27, 4
insert into #product 
Select              23, 35     
union all select    24, 35     
union all select    25, 12     
union all select    26, 35     
union all select   27, NULL 

update cp 
set product_id = merged_product_id
from #company_product cp
join
  ( 
    select min(product_id) as product_id, merged_product_id  
      from #product where merged_product_id is not null
      group by merged_product_id 
  ) a on a.product_id = cp.product_id

delete cp 
--select *
from #company_product cp
join #product p on cp.product_id = p.product_id
where cp.product_id <> p.merged_product_id
and p.merged_product_id is not null