Почему и как работает этот неоднозначный оператор UPDATE?

Допустим, вы выполняете оператор UPDATE для таблицы, но информация, которую вы помещаете в эту базовую таблицу, взята из какой-то другой вспомогательной таблицы. Обычно вы должны СОЕДИНИТЬ данные и не ожидать, что строки в предложении FROM оператора UPDATE будут умножаться, сохраняя при этом, что одна новая строка отображается в одну старую строку в базовой таблице.

Но мне было интересно, что произойдет, если ваша таблица JOIN будет каким-то образом неоднозначной, например, вы не сможете учесть сопоставление каждого базового объекта только с одним объединенным объектом. Или если вы сделали что-то бессмысленное, например, присоединились к базовой таблице к таблице ее дочерних элементов и обновили базовую таблицу, используя эту информацию. Как бы оно выбрало? Теперь на одну строку базовой таблицы приходится несколько строк.

Я запускал подобный оператор в SQL Server 2005, и казалось, что он выбирает первую строку в каждом наборе. Но мне это кажется неправильным. Не должно ли вылетать ошибка? Почему это желаемое поведение?

Пример кода

-- normal
-- categories are one-to-many bundles

update bundles_denormalized set category = c.description

from bundles_denormalized b
left join categories c
on b.category_id = c.id

-- ambiguous
-- bundles are one-to-many products

update bundles_denormalized set category = p.description

from bundles_denormalized b
left join products p
on b.id = p.bundle_id

person Mark Canlas    schedule 06.02.2009    source источник


Ответы (3)


Из BOL при ОБНОВЛЕНИИ

Использование UPDATE с предложением FROM

Результаты оператора UPDATE не определены, если оператор включает предложение FROM, которое не указано таким образом, что для каждого экземпляра обновляемого столбца доступно только одно значение, т. е. если оператор UPDATE не является детерминированным. Например, в операторе UPDATE в следующем скрипте обе строки таблицы Table1 соответствуют условиям предложения FROM в операторе UPDATE; но не определено, какая строка из таблицы 1 используется для обновления строки в таблице 2.

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.Table1', 'U') IS NOT NULL
    DROP TABLE dbo.Table1;
GO
IF OBJECT_ID ('dbo.Table2', 'U') IS NOT NULL
    DROP TABLE dbo.Table2;
GO
CREATE TABLE dbo.Table1 
    (ColA int NOT NULL, ColB decimal(10,3) NOT NULL);
GO
CREATE TABLE dbo.Table2 
    (ColA int PRIMARY KEY NOT NULL, ColB decimal(10,3) NOT NULL);
GO
INSERT INTO dbo.Table1 VALUES(1, 10.0);
INSERT INTO dbo.Table1 VALUES(1, 20.0);
INSERT INTO dbo.Table2 VALUES(1, 0.0);
GO
UPDATE dbo.Table2 
SET dbo.Table2.ColB = dbo.Table2.ColB + dbo.Table1.ColB
FROM dbo.Table2 
    INNER JOIN dbo.Table1 
    ON (dbo.Table2.ColA = dbo.Table1.ColA);
GO
SELECT ColA, ColB 
FROM dbo.Table2;

Другими словами, это допустимый синтаксис, и он не вызовет ошибки или исключения.

Но в то же время вы не можете быть уверены, что значение обновления будет первой или последней записью из вашего предложения FROM, поскольку оно не определено.

person kristof    schedule 06.02.2009
comment
Важно отметить часть, в которой говорится, что результаты неизвестны. Хотя в примере с OP всегда использовался первый результат, это не гарантируется. SQL Server может выполнять обновления в любом порядке. - person Tom H; 06.02.2009
comment
Dag nabbit, и у меня тоже была открыта эта статья. Не проверял раздел «Примечания». Победитель! - person Mark Canlas; 06.02.2009
comment
Следует отметить, что разные версии SQL будут реагировать по-разному. В то время как SQL 2005 обычно использует первую строку, более старые версии SQL обычно используют последнюю строку. - person mrdenny; 07.02.2009

На самом деле, если я правильно понимаю вопрос, это поле обновляется несколько раз, просто поскольку существует только одна запись, поэтому в итоге получается только одно значение. Почему не выдает ошибку? Потому что синтаксис правильный, и база данных не может узнать, каково было ваше намерение. Хотели бы вы сделать это? Обычно нет, поэтому вы должны выбрать свое обновление перед его запуском, чтобы убедиться, что записи corret получают правильные значения.

Я обычно пишу обновление с объединением таким образом:

update b    
set category = p.description
--select b.category, p.description
from bundles_denormalized b
left join products p on b.id = p.bundle_id

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

person HLGEM    schedule 06.02.2009

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

Я почти уверен, что Firebird выдает исключение, если предпринимается попытка сделать что-то столь неоднозначное. Но тогда Firebird вообще не поддерживает (нестандартное?) обновление sntax X из X join Y...

person Community    schedule 05.10.2009