UPDATE данными из других таблиц


Иногда возникает необходимость глобально обновить столбец одной таблицы значениями из этой же таблицы или из другой таблицы. Обычно для таких целей используют запрос вида:
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.


(c) KDV, www.ibase.ru