Запрос на обновление с проверкой «не существует» вызывает нарушение первичного ключа - PullRequest
0 голосов
/ 13 января 2010

Используются следующие таблицы:

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, и мой запрос выглядит так:

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 произойдет сбой, потому что не существует 'подзапрос не видит первое изменение, так как запрос еще не завершен и не зафиксирован.

Правильно ли я думаю об этом, и как бы я изменил запрос, чтобы он работал?

[EDIT] Некоторые примеры данных:

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 ]

2 голосов
/ 13 января 2010

Предполагается, что первичным ключом являются три вещи:

  1. ненулевой
  2. Уникальная
  3. Неизменное

Изменяя часть первичного ключа, вы нарушаете требование № 3.

Я думаю, вам лучше создать новую таблицу, заполнить ее, затем удалить ограничения, удалить исходную таблицу и переименовать новую таблицу с нужным именем (затем, конечно, повторно применить исходные ограничения) , По моему опыту, это дает вам возможность проверить «новые» данные, прежде чем они будут «живыми».

Делись и наслаждайся.

1 голос
/ 13 января 2010

Я не могу вполне увидеть, как ваша структура должна работать или чего пытается достичь это обновление. Вы, кажется, обновляете Company_Product и устанавливаете (новый) product_id в существующей строке, которая, очевидно, имеет другой product_id; например, изменение строки с одного продукта на другой. Это кажется ... странным вариантом использования, я ожидаю, что вы вставите новую уникальную строку. Поэтому я думаю, что что-то упустил.

Если вы конвертируете Company_Product в новый набор идентификаторов продуктов вместо старого (имя «merged_product_id» заставляет меня это размышлять), вы уверены, что нет совпадений? между старым и новым? Это вызовет проблему, подобную тому, что вы описываете.

1 голос
/ 13 января 2010

Вы можете использовать MERGE , если вы используете SQL 2008 как минимум.

В противном случае вам нужно будет выбрать критерий для установки , какой 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

Обратите внимание, что это не безопасно, если несколько параллельных запросов выполняют логику слияния.

0 голосов
/ 13 января 2010

Попробуйте это:

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
0 голосов
/ 13 января 2010

Я думаю, что вы правы в том, почему обновление не удается. Чтобы это исправить, запустите запрос на удаление в вашей таблице company_product, чтобы удалить дополнительные product_ids, к которым будет применен тот же merged_prduct_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
  )

- Добавлено пояснение к комментарию -

Что он пытается сделать, это удалить строки, которые будут дублироваться после обновления. Поскольку у вас есть продукты с несколькими объединенными идентификаторами, вам действительно нужен только один из этих продуктов (для каждой компании) в таблице, когда вы закончите. Итак, мой запрос (если он работает ...) будет содержать минимальный исходный идентификатор продукта для каждого объединенного идентификатора продукта - тогда ваше обновление будет работать.

Итак, предположим, у вас есть 3 идентификатора продукта, которые будут сопоставлены с двумя объединенными идентификаторами: 1 -> 10, 2 -> 20, 3 -> 20. И у вас есть следующие данные company_product:

product_id  company_id
1           A
2           A
3           A

Если вы запустите свое обновление для этого, оно попытается изменить и вторую, и в третью строки на идентификатор продукта 20, и это не удастся. Если вы запустите удаление, которое я предлагаю, оно удалит третью строку. После удаления и обновления таблица будет выглядеть так:

product_id  company_id
10          A
20          A
0 голосов
/ 13 января 2010

Не видя ваших данных, я полагаю, что ваш анализ верен - весь набор обновляется, а затем происходит сбой фиксации, поскольку это приводит к нарушению ограничения. EXISTS никогда не переоценивается после «частичной фиксации» некоторых обновлений.

Я думаю, что вам нужно более точно определить ваши правила, касающиеся попытки изменить несколько продуктов на один и тот же продукт в соответствии с merged_product_id, а затем сделать это явным образом в вашем запросе. Например, вы можете исключить любые товары, которые попадают в эту категорию, с дополнительным НЕ СУЩЕСТВУЕТ с соответствующим запросом.

...