【问题标题】:Why does Execute Immediate throw this PLS-00201 error?为什么立即执行会引发此 PLS-00201 错误?
【发布时间】:2017-03-09 16:07:11
【问题描述】:

我试图运行以下代码,但在立即执行块中失败。那么,我的语法有问题吗?

DECLARE
  l_data long; 
  emp_rec EMP%rowtype;
begin 

  select *  INTO emp_rec  from EMP A WHERE A.EMP_NO = '001322';

 for x in ( select column_name, data_type 
            from user_tab_columns 
             where table_name = 'EMP' ) 
 loop 
   execute immediate 
      'begin 
        :x := emp_rec.' || x.column_name || '; 
      end;' using OUT l_data; 

     dbms_output.put_line( x.column_name || ' = ' || l_data ); 

  end loop; 

end; 

我收到这个错误

PLS-00201:必须声明标识符 EMP_REC.EMP_NO

【问题讨论】:

    标签: sql oracle plsql dynamic-sql


    【解决方案1】:

    您的 emp_rec 变量是本地 PL/SQL 记录。当您这样做时,即使使用静态字段名称引用:

     execute immediate 'begin :x := emp_rec.emp_no; end;' 
    

    动态 SQL 在调用它的块的单独上下文中运行。然后在该上下文中运行一个新的匿名 PL/SQL 块。

    外部匿名块中的任何变量,特别是这里的emp_rec,都超出了动态 SQL 上下文的范围。它们只是不存在于试图将值分配给 :x 的代码中。

    您可以使用dbms_sql 做一些事情来使这个动态化,但是如果您知道表格列,这样做会更容易:

    declare
      l_data varchar2(4000); -- long is deprecated; how big does this really need to be?
      emp_rec EMP%rowtype;
    begin 
      select *  INTO emp_rec  from EMP A WHERE A.EMP_NO = '001322';
    
      for x in (
        select column_name, data_type 
        from user_tab_columns 
        where table_name = 'EMP'
      ) 
      loop
        case x.column_name
          when 'EMP_NO' then
            l_data := emp_rec.emp_no;
          -- when clauses for each column in your real table
          when 'FIRST_NAME' then
            l_data := emp_rec.first_name;
          when 'LAST_NAME' then
            l_data := emp_rec.last_name;
          -- list other columns and assignments
          -- else ...
        end case;
    
        dbms_output.put_line( x.column_name || ' = ' || l_data ); 
      end loop; 
    end; 
    /
    

    尽管正如@APC 指出的那样,循环现在有点毫无意义,因为你可以这样做:

    declare
      emp_rec EMP%rowtype;
    begin 
      select *  INTO emp_rec  from EMP A WHERE A.EMP_NO = '001322';
    
      dbms_output.put_line( 'EMP_NO = ' || emp_rec.emp_no ); 
      dbms_output.put_line( 'FIRST_NAME = ' || emp_rec.first_anme ); 
      dbms_output.put_line( 'LAST_NAME = ' || emp_rec.last_name ); 
      -- ... any other columns you want to show
    end; 
    /
    

    【讨论】:

    • 如果我们要对循环体中的列名进行硬编码,根本不值得查询 USER_TAB_COLUMNS
    【解决方案2】:

    EXECUTE IMMEDIATE 语句中的emp_rec 与调用代码中的emp_rec 存在于不同的命名空间中。

    不确定您想要实现的具体目标,但可能是这样的:

    DECLARE
         l_data long; 
         emp_rec EMP%rowtype;
    begin 
          select *  INTO emp_rec  from EMP A WHERE A.EMP_NO = '001322';
         for x in ( select column_name, data_type 
                from user_tab_columns 
                where table_name = 'EMP' ) 
         loop 
             execute immediate 
             'declare
               lrec EMP%rowtype;
             begin 
                lrec := :emp_rec;
                 :x := lrec.' || x.column_name || '; 
             end;' using  emp_rec, OUT l_data; 
    
             dbms_output.put_line( x.column_name || ' = ' || l_data ); 
    
         end loop; 
    end; 
    

    注意:我在 12C 中测试了此代码的一个版本,它确实在那里工作。唉,它在 11gR2 中不起作用(可能还有更早的版本);它投掷PLS-00457。尽管如此,11gR2 几乎不受支持,除了那些财力雄厚的人,现在每个人都应该使用 12c:)

    【讨论】:

    • 你能用这样的绑定变量记录吗?这是 12c 中的新功能吗?
    【解决方案3】:

    我的猜测是您的 emp 表中有一个隐藏列。这些是已标记为未使用但尚未删除的列,因此无法从中进行选择。

    您可以更新光标以使用:

     select column_name, data_type 
     from   user_tab_cols 
     where  table_name = 'EMP'
     and    hidden_column != 'NO'
    

    或:

     select column_name, data_type 
     from   user_tab_columns 
     where  table_name = 'EMP';
    

    注意两个查询中使用的不同视图名称 - user_tab_columns 不会输出隐藏列的行,而 user_tab_cols 会这样做,因此如果您不想看到它们,则必须显式过滤掉它们。

    【讨论】:

    • 错误来自动态SQL;并引用emp_no,它也在游标中使用 - 所以会出现编译错误,我认为来自游标的 ORA-00904 而不是来自动态 SQL 的 PLS-00201,如果它被隐藏了?
    • @AlexPoole 是的,我看到了其他答案(看起来确实是对的!)但我想我还是会留下我的答案,因为无论如何都需要正确处理隐藏的列。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-07
    相关资源
    最近更新 更多