您将如何在 SQL 脚本中强制执行 DRY(不要重复自己)?

Posted

技术标签:

【中文标题】您将如何在 SQL 脚本中强制执行 DRY(不要重复自己)?【英文标题】:How would you enforce DRY (Don't Repeat Yourself) in a SQL script? 【发布时间】:2009-03-26 12:16:04 【问题描述】:

我正在使用包含一些更新的脚本更改数据库(oracle),如下所示:

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...

在这种情况下,您将如何执行 DRY(不要重复自己)?

我对解决以下两个反复出现的问题特别感兴趣:

定义一个函数,仅在此脚本中可用,以提取子查询SELECT id FROM category WHERE code = 'ABC'

创建一组替换规则(在流行的编程语言中可能类似于 "X_Y": "yx", "X_Z": "xz", ...),然后在其上迭代单个更新查询。

谢谢!

【问题讨论】:

【参考方案1】:

我会将其简化为单个查询:

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】:

首先,请记住,脚本编写与编程不同,您不必必须遵守 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;

【讨论】:

“脚本”和“编程”之间的界限在哪里?为什么在“脚本”中重复自己可以,但在“程序”中却不行? 正如我所说,像这样的脚本通常是一次性的 - 即只在生产中运行一次,以修复或升级某些数据。一旦运行它们就会被丢弃,那么谁在乎是否使用了 DRY 原则?构成系统永久组成部分的脚本是另一回事。 哦,我设法忽略了“喜欢这个”。再次阅读您的句子,这很有意义。很抱歉打扰了。 ;-)【参考方案3】:

非常简单,除非我遗漏了什么。

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');

【讨论】:

当状态像 %X_Y% 和 %X_Z% 时,您不会处理 %X_Z% 的替换。【参考方案4】:

编写一个带参数的脚本并多次调用它。 (我假设您使用 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

【讨论】:

【参考方案5】:

好的,这里从臀部的角度来看,放轻松我的语法:-)

这样的方法会有帮助吗:

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 上还有一些工作要做,这是您的第一个要求;-)

编辑

当!忘了提到您需要小心 WHERE 子句中的替换字符串 - 下划线是 Oracle 中的单个字符匹配通配符...

【讨论】:

【参考方案6】:

根据脚本的重要性,我会:

    只需复制粘贴和修改,或者 用另一种可以更好地解决重复问题的编程语言编写脚本。

对于替换规则,您可以创建一个临时表并用这些替换规则填充它,然后与该表连接。

如果子查询始终相同,则您也通过使用连接解决了第一个问题。

【讨论】:

【参考方案7】:

我已经看到了一些解决方法:

    使用字符串缓冲区通过 PL/SQL 或您的编程语言动态组装 sql。 使用 IBATIS 等框架,让您可以重用和扩展存储在 XML 文件中的 SQL 片段。 使用 ORM 框架通过使用对象而不是直接使用 SQL 来规避此问题。

根据您的语言和手头的问题,使用框架可能是最好的方法,然后将其扩展为您希望它执行的操作。

【讨论】:

【参考方案8】:

soulmerge 建议的解决方案是最简单的,因此也是最好的解决方案 - 您只需嵌套调用“替换”即可。我只想添加条件

status like '%tagada%'

没用。如果未找到搜索的字符串,replace() 不会改变状态,因此您可以安全地将其应用于所有行。而且由于您搜索丢失在另一个字符串中间的字符串的条件不能使用您拥有的任何索引,因此它作为过滤条件是无用的。 您唯一的过滤条件是 category_id ... 这带来了一个观点,证明了为什么 Soulmerge 的解决方案是最好的:迭代所有更改是一个坏主意。假设 category_id 上的过滤器具有中等选择性,Oracle 很可能会选择扫描表。当您可以一次完成所有更改时,您真的要每次都扫描表吗?

【讨论】:

以上是关于您将如何在 SQL 脚本中强制执行 DRY(不要重复自己)?的主要内容,如果未能解决你的问题,请参考以下文章

设计原则之DRY原则

oracle如何批量执行某个文件夹下面的多个.sql脚本

informix上如何执行*.sql脚本

MySQL中如何执行SQL语句?

在shell脚本执行sql语句为啥会报错

检查 NAnt 脚本中的 NAnt 版本