在 Oracle 中生成动态 SQL

Generating Dynamic SQL in Oracle

我的 plsql 代码有问题,尝试了几乎所有的方法。现在我失去了解决我的问题的想法和力量:)

案例是我想在所有表架构中搜索分配给变量 v_ss 的特定字符串并将其打印到 DBMS_OUTPUT。我知道这种情况有现成的解决方案,但我想自己编写代码。下面的代码给我一个错误,在我的 v_stmt 中 "table does not exist"。我假设这个 select 不识别 rec.column_name,但为什么?

这是我的代码:

DECLARE
 v_stmt VARCHAR2(1000);
 v_ss VARCHAR2(30) := 'Argentina';

 v_own ALL_TAB_COLUMNS.OWNER%TYPE;
 v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
 v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;

 CURSOR cur_asc IS
   SELECT t.owner, t.table_name, t.column_name
   FROM SYS.ALL_TAB_COLUMNS t
   WHERE t.OWNER LIKE 'HR'
     AND t.DATA_TYPE LIKE 'VARCHAR2';


BEGIN
  FOR rec IN cur_asc LOOP
    v_stmt := 'SELECT rec.owner, rec.table_name, rec.column_name FROM rec.table_name WHERE rec.column_name LIKE :1';
    EXECUTE IMMEDIATE v_stmt INTO v_own, v_tab_nam, v_col_nam USING v_ss;
    DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
  END LOOP;


END;
/

Error report -
ORA-00942: table or view does not exist
ORA-06512: at line 19
00942. 00000 -  "table or view does not exist"
*Cause:    
*Action:

你能给我解释一下我的错误以及如何修复它吗?提前致谢

动态语句在看不到你的PL/SQL变量的上下文中执行,所以当它运行s rec.table_name等被解释为SQL级对象 - 不存在。

您必须将变量值连接到 fromwhere 子句的动态语句中;你可以在 select 列表中做同样的事情(尽管它们需要用转义的单引号括起来,因为它们是字符串),或者在那里使用绑定变量:

v_stmt := 'SELECT :owner, :table_name, :column_name FROM '
  || rec.table_name || ' WHERE ' || rec.column_name || ' LIKE :ss';
EXECUTE IMMEDIATE v_stmt INTO v_own, v_tab_nam, v_col_nam
USING rec.owner, rec.table_name, rec.column_name, v_ss;

您不能将绑定变量用于对象标识符,因此需要对这些部分进行串联。

除非您 运行 作为 HR 用户,在这种情况下您可以使用 user_tables 而不是 all_tables,您还需要在查询中指定架构(如托尼和拉利特提到);硬编码 HR 或使用查询的所有者:

v_stmt := 'SELECT :owner, :table_name, :column_name FROM '
  || rec.owner || '.' || rec.table_name
  || ' WHERE ' || rec.column_name || ' LIKE :ss';

对于所有不完全包含一个匹配值的表,这将出错 - 如果动态 select 获得零行或多于一行。但这是一个单独的问题。

这里有 2 个问题:

  1. 您需要将 table 和列名连接到动态 SQL 中,因为 'SELECT rec.owner...' 正在尝试 select 来自一个名为 owner 的列table 别名 rec,它不引用您的 for 循环记录。
  2. 由于 table 中可能没有匹配行或有很多匹配行,您不能使用 select into,您需要使用游标。

试试这个:

DECLARE
 v_stmt VARCHAR2(1000);
 v_ss VARCHAR2(30) := 'Argentina';

 v_own ALL_TAB_COLUMNS.OWNER%TYPE;
 v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
 v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;

 CURSOR cur_asc IS
   SELECT t.owner, t.table_name, t.column_name
   FROM ALL_TAB_COLUMNS t
   WHERE t.DATA_TYPE LIKE 'VARCHAR2'
   AND ROWNUM < 10;

 c SYS_REFCURSOR;

BEGIN
  FOR rec IN cur_asc LOOP
    v_stmt := 'SELECT ''' || rec.owner || ''',''' || rec.table_name || ''', ''' || rec.column_name || ''' FROM ' || rec.table_name || ' WHERE ' || rec.column_name || ' LIKE :1';
    OPEN c FOR v_stmt USING v_ss;
    LOOP
      FETCH c INTO v_own, v_tab_nam, v_col_nam;
      EXIT WHEN c%NOTFOUND;
      DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
    END LOOP;
    CLOSE c;
  END LOOP;
END;
/

v_stmt := 'SELECT rec.owner, rec.table_name, rec.column_name FROM rec.table_name WHERE rec.column_name LIKE :1';

  1. 您的动态 sql 语句格式不正确。如果您将变量括在单引号之间,那么它们将被视为文字而不是变量。
  2. 您必须在 table_name 之前为 架构 添加前缀,否则当您作为 HR 用户连接时,您必须 运行 脚本。
v_stmt := 'SELECT '''||rec.owner||''', '''|| rec.table_name||''', '
                     ||rec.column_name||' FROM '||rec.owner||'.'
                     ||rec.table_name||' WHERE '||rec.column_name||' LIKE :1';

永远记住,每当使用动态语句时,总是使用 DBMS_OUTPUT 来首先验证实际生成的 SQL。这是调试动态查询的最佳方式。

  1. 您需要处理 NO_DATA_FOUND 异常,因为它会为所有与您正在使用的过滤器不匹配的表抛出错误。
SQL> DECLARE
  2    v_stmt VARCHAR2(1000);
  3    v_ss   VARCHAR2(30) := 'Argentina';
  4    v_own ALL_TAB_COLUMNS.OWNER%TYPE;
  5    v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
  6    v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
  7    CURSOR cur_asc
  8    IS
  9      SELECT t.owner,
 10        t.table_name,
 11        t.column_name
 12      FROM SYS.ALL_TAB_COLUMNS t
 13      WHERE t.OWNER LIKE 'HR'
 14      AND t.DATA_TYPE LIKE 'VARCHAR2';
 15  BEGIN
 16    FOR rec IN cur_asc
 17    LOOP
 18      v_stmt := 'SELECT '''||rec.owner||''', '''|| rec.table_name||''', '
 19                           || rec.column_name||' FROM '||rec.owner||'.'
 20                           || rec.table_name||' WHERE '||rec.column_name||' LIKE :1';
 21      BEGIN
 22        EXECUTE IMMEDIATE v_stmt INTO v_own,
 23        v_tab_nam,
 24        v_col_nam USING v_ss;
 25        DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
 26      EXCEPTION
 27      WHEN NO_DATA_FOUND THEN
 28        NULL;
 29      END;
 30      END LOOP;
 31    END;
 32    /
HR:COUNTRIES:Argentina

PL/SQL procedure successfully completed.