代码之家  ›  专栏  ›  技术社区  ›  Tom van Enckevort

使用“不存在”检查更新查询会导致主键冲突

  •  0
  • Tom van Enckevort  · 技术社区  · 16 年前

    涉及的表格如下:

    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列设置为合并的_uProduct_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表包含多个具有相同合并产品id的行,因此它将成功执行第一个产品的更改,但当转到具有相同合并产品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
    

    [编辑2]

    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 回复  |  直到 14 年前
        1
  •  2
  •   Bob Jarvis - Слава Україні    16 年前

    1. 非空
    2. 不变的

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

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

    分享和享受。

        2
  •  1
  •   Remus Rusanu    16 年前

    你可以用 MERGE 如果您至少在SQL 2008上。

    否则,您将不得不选择要建立的标准 合并的\u产品\u 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
    

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

        3
  •  1
  •   T.J. Crowder    16 年前

    我不能 Company_Product 以及设置一个(新的) product_id 产品标识 ; e、 例如,将行从一个产品更改为另一个产品。这似乎是一个奇怪的用例,我希望您插入一个新的唯一行。所以我想我错过了什么。

    如果你是 公司产品 要使用一组新的产品id而不是旧的产品id(“merged_product_id”这个名称让我猜测),您确定新旧产品之间没有重叠吗? 那个 会引起像你描述的那样的问题。

        4
  •  0
  •   Cade Roux    16 年前

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

    我认为您需要更精确地定义关于根据合并的产品id将多个产品更改为同一产品的规则,然后在查询中明确这些规则。例如,您可以排除属于该类别的任何产品,并使用适当的查询进一步排除不存在的产品。

        5
  •  0
  •   Ray    16 年前

    关于更新失败的原因,我认为你是正确的。若要解决此问题,请在公司产品表上运行删除查询,以删除将应用相同合并产品id的额外产品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
      )
    

    --作为对评论的回应,增加了解释--

    product_id  company_id
    1           A
    2           A
    3           A
    

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

    product_id  company_id
    10          A
    20          A
    
        6
  •  0
  •   HLGEM    16 年前

    试试这个:

    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