Как бы вы применили DRY (не повторять себя) в сценарии SQL? - PullRequest
4 голосов
/ 26 марта 2009

Я изменяю базу данных (оракул) с помощью скрипта, содержащего несколько обновлений, похожих на:

UPDATE customer
SET status = REPLACE(status,   'X_Y',   'xy')
WHERE status LIKE '%X_Y%'
 AND category_id IN
  (SELECT id
   FROM category
   WHERE code = 'ABC');

UPDATE customer
SET status = REPLACE(status,   'X_Z',   'xz')
WHERE status LIKE '%X_Z%'
 AND category_id IN
  (SELECT id
   FROM category
   WHERE code = 'ABC');

-- More updates looking the same...

В этом случае, как бы вы применили СУХОЙ (не повторяйте себя)?

Я бы особенно заинтересовался решением двух следующих повторяющихся проблем:

  • Определите функцию, доступную только из этого скрипта, для извлечения подзапроса SELECT id FROM category WHERE code = 'ABC'

  • Создайте набор правил замены (которые могут выглядеть как {"X_Y": "yx", "X_Z": "xz", ...} в популярном языке программирования), а затем выполните один и тот же запрос на обновление.

Спасибо!

Ответы [ 8 ]

5 голосов
/ 26 марта 2009

Я бы сократил его до одного запроса:

UPDATE customer
SET status = REPLACE(REPLACE(status, 'X_Y', 'xy'), 'X_Z', 'xz')
WHERE status REGEXP_LIKE 'X_[YZ]'
 AND category_id IN
  (SELECT id
   FROM category
   WHERE code = 'ABC');
2 голосов
/ 26 марта 2009

Довольно просто, если я что-то упустил.

UPDATE customer
SET status = REPLACE(REPLACE(status,'X_Y','xy'),'X_Z','xz')
WHERE ( status LIKE '%X_Y%' Or status LIKE '%X_Z%')
  AND category_id IN
     (SELECT id
      FROM category
      WHERE code = 'ABC');
2 голосов
/ 26 марта 2009

Прежде всего, помните, что сценарии - это не то же самое, что программирование, и вам не нужно придерживаться принципов DRY. Сценарии, подобные этому, обычно одноразовые, а не программа, которую нужно поддерживать в течение длительного времени.

Но вы можете использовать PL / SQL для этого:

declare
   type str_tab is table of varchar2(30) index by binary_integer;
   from_tab str_tab;
   to_tab str_tab;
begin
   from_tab(1) := 'X_Y';
   from_tab(2) := 'X_Z';
   to_tab(1) := 'xy';
   to_tab(2) := 'xz';

   for i in 1..from_tab.count loop

      UPDATE customer
      SET status = REPLACE(status,   from_tab(i),   to_tab(i))
      WHERE status LIKE '%' || from_tab(i) || '%'
       AND category_id IN
        (SELECT id
         FROM category
         WHERE code = 'ABC');

   end loop;
end;
1 голос
/ 26 марта 2009

Хорошо, снимок с бедра, успокойся в моем синтаксисе: -)

Подойдет ли такой подход:

DECLARE
  v_sql1   VARCHAR2(1000);
  v_sql2   VARCHAR2(2000);
  TYPE T_Rules IS RECORD (srch  VARCHAR2(100),  repl(VARCHAR2(100));
  TYPE T_RuleTab IS TABLE OF T_Rules INDEX BY BINARY_INTEGER;
  v_rules T_RuleTab;

  FUNCTION get_subquery RETURN VARCHAR2 IS
  BEGIN
    RETURN '(SELECT id FROM category WHERE code = ''ABC'')';
  END;

BEGIN
  v_sql1 := 'UPDATE customer SET status = REPLACE('':1'','':2'') WHERE status LIKE ''%:1%'' AND category_id IN ';
  v_rules(1).srch := ('X_Y'); v_rules(1).repl := 'yx';
  v_rules(2).srch := ('X_Z'); v_rules(2).repl := 'xz';

  FOR i IN 1..v_rules.COUNT LOOP
    v_sql2 := v_sql1||get_subquery();
    EXECUTE IMMEDIATE v_sql2 USING v_rules(i).srch, v_rules(i).repl;
  END LOOP;
END;

Вы можете заменить таблицу PL / SQL реальной таблицей и навести на нее курсор, но это отвечает вашему второму требованию.

Очевидно, что в get_subquery, вашем первом требовании, осталось немного работы; -)

EDIT

Dang! забыл упомянуть, что вы должны быть осторожны с этой строкой замены в предложении WHERE - подчеркивания - это подстановочный знак, соответствующий одиночному символу в Oracle ...

1 голос
/ 26 марта 2009

Напишите скрипт, который принимает параметры и вызывает его несколько раз. (Я предполагаю, что вы используете SQLPlus для запуска скрипта.)

replace_in_status.sql:

UPDATE customer
SET status = REPLACE(status,   UPPER('&1'),   '&2')
WHERE status LIKE '%' ||UPPER('&1')|| '%'
 AND category_id IN
  (SELECT id
   FROM category
   WHERE code = 'ABC');

Сценарий вызова:

@replace_in_status X_Y xy
@replace_in_status X_Z xz
0 голосов
/ 26 марта 2009

Решение, предложенное soulmerge, является самым простым и, следовательно, лучшим - вам просто нужно вложить призывы «заменить». Я просто хочу добавить, что условие

status like '%tagada%'

бесполезно. replace () ничего не изменит в статус, если искомая строка не найдена, поэтому вы можете смело применять ее ко всем строкам. А поскольку условие, в котором вы ищете строку, потерянную в середине другой строки, не может использовать какой-либо индекс, который у вас есть, он бесполезен в качестве условия фильтрации. Ваше единственное условие фильтрации - это условие для category_id ... Что приводит к одному пункту, который оправдывает, почему решение soulmerge является лучшим: повторять все изменения - плохая идея. Предположим, что фильтр для category_id является умеренно избирательным, и есть вероятность, что Oracle выберет для сканирования таблицы. Вы действительно хотите сканировать таблицу каждый раз, когда вы можете сделать все изменения за один проход?

0 голосов
/ 26 марта 2009

Я видел несколько подходов к этому:

  1. Используйте строковые буферы для динамической сборки sql с использованием PL / SQL или на вашем языке программирования.
  2. Используйте такую ​​среду, как IBATIS, которая позволяет повторно использовать и расширять фрагменты SQL, хранящиеся в файлах XML.
  3. Использование платформы ORM позволяет обойти эту проблему, работая с объектами, а не напрямую с SQL.

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

0 голосов
/ 26 марта 2009

В зависимости от того, насколько важен сценарий, я бы:

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

Для правил замены вы можете создать временную таблицу и заполнить ее этими правилами замены, а затем объединить с этой таблицей.

Если подзапрос всегда один и тот же, вы решили первую проблему также с помощью объединения.

...