KDV, www.ibase.ru
Иногда возникает необходимость глобально обновить столбец одной таблицы значениями из этой же таблицы или из другой таблицы. Обычно для таких целей используют запрос вида:
update TABLE1
set FIELD1 = (select PRIMARYKEY
from TABLE1 T1
where T1.FIELD1=FIELD2)
Это
единственный способ обновить таблицу таким образом, поскольку синтаксис "update TABLE1, TABLE2..." в IB не поддерживается.
Вероятность, что вышеприведенный запрос будет работать медленно, весьма высока. Намного выше чем у запроса, обновляющего записи данными из другой таблицы. Посмотрите план такого запроса, и если увидите там два цикла с перебором записей NATURAL, то запрос надо менять. Ускорить его можно путем "переворачивания"
update и
select местами внутри хранимой процедуры.
...
for select T1.PK, T2.PK
from TABLE1 T1, TABLE1 T2
where T1.FIELD1 = T2.FIELD1
into :TARGET, :SOURCE
do
begin
update TABLE1
set FIELD1 = :SOURCE
where PK = :TARGET;
end
Такая конструкция осуществит обновление всего за один "проход" по записям TABLE1, но в любом случае стоит проверить план оператора select и план оператора update отдельно.
Есть
еще один способ, который аналогичен приведенному for select, но использует номер записи IB – RDB$DB_KEY:
create procedure TESTUPD
as
declare variable db_key CHAR(8);
begin
for select RDB$DB_KEY, ...
from TAB
into :db_key
do
update TAB
set ...
where RDB$DB_KEY = :db_key;
end
Причем по таблице TAB может не быть индекса совсем, но по скорости выполнения такая конструкция практически равна скорости с оптимизацией по индексам. Например, если таблица TAB не имеет ни одного индекса, то без rdb$db_key время обновления 3-х тысяч записей 1500 секунд, а с rdb$db_key – 10 секунд.
Замечание. Обратите внимание, что длина db_key равна 8 байт. Если таблица TAB на самом деле является view, состоящим из двух таблиц, то длина db_key должна быть 16 байт, и так далее.
Неожиданный способ на основе ключевых слов AS CURSOR и WHERE CURRENT OF:
declare variable counter integer;
declare variable x integer;
begin
counter = 1;
for select c1 from t2 into :x
as cursor FOO
do
begin
update t2
set c2 = c1 / :counter, c1 = :counter
where current of foo;
counter = :counter + 1;
end
end
Здесь таблица T2 рассматривается как курсор FOO, что в принципе эквивалентно предыдущему примеру с RDB$DB_KEY. Переменные
counter,
x,
c2 и
c1 – просто пример применения.
Выгода этого способа перед предыдущим – отсутствие необходимости объявлять переменную для хранения RDB$DB_KEY. Как упоминалось выше, размер db_key для view зависит от количества таблиц, на котором это view построено. При использовании as cursor и current of не нужно задумываться, какой объект используется для сканирования и обновления – таблица или view.