【问题标题】:Update query with 'not exists' check causes primary key violation使用“不存在”检查更新查询会导致主键违规
【发布时间】:2012-05-03 18:16:36
【问题描述】:

涉及以下表格:

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 列设置为 merge_product_id。此更新可能会导致重复,从而触发主键违规,因此我在 where 子句中添加了“不存在”检查,我的查询如下所示:

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 表包含具有相同 merge_product_id 的多行,因此它将成功用于第一个产品,但是当转到具有相同 merge_product_id 的下一个产品时,它将失败,因为 ' not exists' 子查询没有看到第一次更改,因为查询尚未完成并提交。

我的想法是否正确,我将如何更改查询以使其正常工作?

[编辑]一些数据示例:

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

[EDIT 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

【问题讨论】:

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


    【解决方案1】:

    一个主键应该是三个东西:

    1. 非空
    2. 独特
    3. 不变

    通过更改部分主键,您违反了要求 #3。

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

    分享和享受。

    【讨论】:

    • 您最接近提出解决方案的建议,因此您得到了答案。如上所述,我使用了一个临时表。
    【解决方案2】:

    如果您至少使用 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
    

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

    【讨论】:

    • 抱歉,不能使用 SQL 2008,因为它也必须在 SQL 2005 上运行。
    【解决方案3】:

    我无法完全了解您的结构是如何工作的,或者此更新试图实现什么。您似乎正在更新Company_Product 并在显然具有不同product_id 的现有行上设置(新)product_id;例如,将行从一种产品更改为另一种产品。这似乎......一个奇怪的用例,我希望您插入一个新的唯一行。所以我觉得我错过了一些东西。

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

    【讨论】:

    • 是的,你说得对,我正在将 Company_Product 中的旧产品 ID 转换为新的集合,在 merge_product_id 中指定。是的,可能存在重叠,因为可能已经有 product_id 和 company_id 的条目。问题是如何防止查询尝试插入重复项(这会导致 PK 违规)。
    • @tomlog:我认为您无法就地进行更新,因为(例如)将旧产品 ID 23 替换为新 ID 35 时,可能与表中已有的旧产品 ID 35 冲突。您必须创建一个新表,通过旧表的连接填充它,然后将其换入。我还注意到,您添加的示例数据对于 merge_product_id 有一个 NULL,这意味着您将离开旧的 ID 27 躺在桌子上,如果还有一个新的 ID 27,这将是一件坏事。
    • @T.J.:并非所有产品都被合并,所以 ID 27 可以保留。此外,所有产品 ID 都是唯一的,因此不会有 ID 为 27 的新产品。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      这里是查询可能是什么的刺

      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 -> 10、2 -> 20、3 -> 20。并且您有以下 company_product 数据:

      product_id  company_id
      1           A
      2           A
      3           A
      

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

      product_id  company_id
      10          A
      20          A
      

      【讨论】:

      • 但是通过删除行肯定会丢失我的公司-产品链接?当我尝试添加 merge_product_id 时,我如何知道产品与哪个公司相关联?
      • 我现在可以看到您的目标,但您的查询并不完全正确,因为它还会删除没有 merge_product_id 的行(=NULL,它只会选择第一个 product_id 并删除其余部分)。
      • 我没有考虑空值 - 我修复了删除查询来处理它们。除非我看错了,否则删除只会删除会在更新中导致重复记录(密钥违规)的产品 id - 你不能保留这些
      • 啊哈 - 我明白你的意思 - 我的删除查询不起作用。很抱歉浪费您的时间。我认为你最好的选择是运行一个连接两个表的选择,以将正确的 ids 放入临时表中,清空 c_p 表,然后使用 select distinct 从临时表中插入 c_p。
      【解决方案6】:

      试试这个:

      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
      

      【讨论】:

      • 此查询仅在 Company_Product 表没有主键时有效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-17
      • 1970-01-01
      相关资源
      最近更新 更多