【问题标题】:Oracle stored procedure: FOR LOOP over a column from a variableOracle 存储过程:对变量中的列进行 FOR LOOP
【发布时间】:2021-06-29 21:13:31
【问题描述】:

我想将列值列表保存到变量 input_cols 中,然后循环遍历这些值

CREATE OR REPLACE PROCEDURE map_values (mapping_table VARCHAR2(64)) AS
TYPE col_names IS TABLE OF VARCHAR2(64);
input_cols col_names;
BEGIN
    EXECUTE IMMEDIATE
        'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS 
        WHERE TABLE_NAME = '' ' || mapping_table || ' '' '
    BULK COLLECT INTO (input_cols);
    FOR in_col IN input_cols
    LOOP 
        dbms_output.put_line ('test');
    END LOOP;
END;

我收到了错误

PLS-00103:当期望以下之一时遇到符号“LOOP”:* & - + / at mod remaining rem .. ||

【问题讨论】:

标签: sql oracle stored-procedures dynamic-sql


【解决方案1】:

忠告:使用SQL Developer 之类的工具来创建您的程序。它们以更清晰的方式显示编译错误。如果您是 PL/SQL 新手,请从最基本的(空过程)开始,编译、修复错误(如果有)并添加代码。您的代码中有 3 个阻塞问题 - 调试非常困难。

我为每个错误添加了注释

create or replace PROCEDURE map_values 
(mapping_table VARCHAR /* just define the datatype, not the precision */
)
AS
TYPE col_names IS TABLE OF VARCHAR2(64) INDEX BY BINARY_INTEGER;
input_cols col_names;
BEGIN
    EXECUTE IMMEDIATE
        'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS 
        WHERE TABLE_NAME = ''' ||mapping_table|| ''' '
    BULK COLLECT INTO input_cols; /* no brackets needed */
        dbms_output.put_line ('test:');

    FOR in_col IN 1 .. input_cols.COUNT /* this is not a implicit cursor but a collection - you need to iterate over it.*/
    LOOP 
        dbms_output.put_line ('test:'||input_cols(in_col));
    END LOOP;
END;
/

【讨论】:

    【解决方案2】:

    虽然您可以通过连接值来构造动态查询,但通常最好尽可能使用绑定变量,例如:

    execute immediate
        'select column_name from user_tab_columns where table_name = :b1'
        bulk collect into input_cols
        using p_table;
    

    我建议养成将代码中的类型锚定到相应数据库对象(如果有的话)的习惯。例如,这个:

    mapping_table dba_tab_columns.table_name%type
    

    指示编译器查找dba_tab_columns.table_name 的类型并使用它。但是,我通常会避免在这样的过程中使用dba_ 视图并坚持使用user_ 视图,例如user_tab_columns,将它们限制为您拥有的对象。如果必须使用dba_ 视图,还应包括表所有者,因为可能有多个同名表。

    我也更喜欢以将参数与列名等分开的方式命名参数。有各种约定(camelCase,前缀为i_ 用于inp_ 用于参数,前缀为过程名称,例如map_values.mapping_table),所以选择一个你喜欢的。

    把它们放在一起,你会得到这样的东西:

    create or replace procedure map_values
        ( p_table user_tab_columns.table_name%type )
    as
        type col_names is table of user_tab_columns.column_name%type;
        input_cols col_names;
    begin
        execute immediate
            'select column_name from user_tab_columns where table_name = :b1 order by column_id'
            bulk collect into input_cols
            using p_table;
    
        for i in 1..input_cols.count loop 
            dbms_output.put_line(input_cols(i));
        end loop;
    end map_values;
    

    或者,如果您并不特别需要一个集合,而只是想遍历一个结果集:

    create or replace procedure map_values
        ( p_table user_tab_columns.column_name%type )
    as
        columns_cur sys_refcursor;
        colname user_tab_columns.column_name%type;
    begin
        open columns_cur for
            'select column_name from user_tab_columns where table_name = :b1 order by column_id'
            using p_table;
    
        loop 
            fetch columns_cur into colname;
            exit when columns_cur%notfound;
            dbms_output.put_line(colname);
        end loop;
        
        close columns_cur;
    end;
    

    正如 Koen 在 cmets 中指出的那样,在此示例中不需要动态 SQL,因此更简单的版本可能只是:

    create or replace procedure map_values
        ( p_table user_tab_columns.column_name%type )
    as
    begin
        for r in (
            select column_name from user_tab_columns
            where  table_name = p_table
            order by column_id
        )
        loop
            dbms_output.put_line(r.column_name);
        end loop;
    end map_values;
    

    【讨论】:

    • 就我个人而言,我更喜欢带有 select 语句的游标 for 循环,以支持最后一个示例。不必获取、存在或关闭。
    • 这是一个很好的观点 - 这根本不需要使用动态 SQL。我有点假设 OP 需要动态 SQL 语法的帮助,这只是一个简化的例子,但你是对的,也许他们只是没有想到。
    【解决方案3】:

    此用例请不要使用动态 SQL,静态 SQL 就足够了,效率更高。

    对于循环,您必须使用索引并按索引从集合中获取元素。没有任何功能可以让您直接将集合元素分配给变量(就像您正在尝试的那样)。

    create or replace procedure map_values(mapping_table sys.dba_tab_columns%table_name)
    as
      type col_names is table of sys.dba_tab_columns.column_name%type;
      input_cols col_names;
    begin
      select column_name
      bulk collect into input_cols
      from sys.dba_tab_columns
      where table_name = mapping_table;
      
      for i in 1 .. input_cols.count
      loop
        dbms_output.put_line(input_cols(i));
      end loop;
    end;
    

    【讨论】:

    • 新的 for x in values of y iterator syntax in 21c 在某种程度上简化了收藏导航,可能更接近 OP 的梦想。
    • @WilliamRobertson 你是对的,就是这样,感谢您指出这个新功能!也许我会在 10 到 15 年内使用它(当我们最终升级时):)
    • 是的,这里也一样。我只是在玩一个 21c Free Cloud 实例来检查新功能并保存我的旧笔记本电脑在本地运行 19c 的工作。可悲的是,他们不可能在我的工作中使用它几年。
    猜你喜欢
    • 2013-08-01
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多