在运行时访问 Oracle PLSQL 记录类型的元素

Posted

技术标签:

【中文标题】在运行时访问 Oracle PLSQL 记录类型的元素【英文标题】:Accessing elements of Oracle PLSQL record type on runtime 【发布时间】:2019-05-01 16:54:42 【问题描述】:

我正在使用动态 SQL,其中我动态地使用列名的值来绑定以及要绑定的值

旧代码

<Outer Loop>
FOR i IN lvaDBOBJDTLRecTab.FIRST .. lvaDBOBJDTLRecTab.LAST
LOOP
  DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT2VC100', 
  lvaDBOBJDTLRecTab(i).DBONAME  );

  DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId, ':RTTEXT3VC100', 
  lvaDBOBJDTLRecTab(i).DBOTYPE  );
    3.
   .
  .
  .
  100

END LOOP;

我不想写 BIND_VARIABLE 100 次,而是想动态访问集合的值。我能够动态获取需要绑定的列的值(lvsColForBinding),但是 lvsColValForBind 的值 以 'lvrCurDBOBJDTL(i).DBONAME' 、 'lvrCurDBOBJDTL(i).DBOTYPE' 的形式出现 其余 98 列也一样,

       <Inner Loop>
    FOR j IN lvaMappingTab.FIRST..lvaMappingTab.LAST
    LOOP
    lvsColForBinding := ':'||lvaMappingTab(j).MstRptColCds;

        lvsColValForBind :=  'lvrCurDBOBJDTL(i).'||lvaMappingTab(j).RptColCd;

DBMS_SQL.BIND_VARIABLE ( lvnInsertCursorId,lvsColForBinding, lvsColValForBind);

  END LOOP; 

当为每一行运行 DBMS_SQL.BIND_VARIABLE 时,如前所述,要绑定的列正确,但要绑定的值,而不是作为 'XYZ' 的值 = lvrCurDBOBJDTL(i).DBONAME 它在单引号 'lvrCurDBOBJDTL(i).DBONAME' 中对所有列都相同。 我们如何提取内部循环中每个元素的值。我们需要执行什么步骤来获取 lvsColValForBind 的值?

通过 SQLDEveloper Watches 调试时,我可以看到元素名称、值和类型,当添加并双击 plsql 记录变量时, 这背后的SQL是什么,我们可以在编码中使用它吗?

【问题讨论】:

【参考方案1】:

当您调用bind_variable 时,您将实际值绑定到占位符。因此,如果您提供一个作为变量名称的字符串,那么该字符串就是绑定到占位符的值。

如果数组包含这些值,则只需引用数组元素而不是该元素的名称,如下所示:

DBMS_SQL.BIND_VARIABLE ( 
   lvnInsertCursorId,
   lvaMappingTab(j).MstRptColCds, 
   lvrCurDBOBJDTL(i).lvaMappingTab(j).RptColCd);

但我很确定这不是你所拥有的。希望这会有所帮助!

【讨论】:

我也试过了,它给出了一个错误:PLS-00302:必须声明组件'lvaMappingTab'。作为下一步,我在包级别声明了“lvaMappingTab”。同样的错误 PLS-00302。这就是我尝试使用 SQLDEVELOPER 在“WATCHES”窗口中生成集合元素值时使用的功能/ SQL 的原因。它显示元素的名称、元素的值和元素的类型(+更多详细信息)与此类似,如果我可以动态访问元素,我应该能够比较和匹配绑定变量的值。【参考方案2】:

我的第一个建议是使用动态 SQL 来生成大量愚蠢的代码,而不是使用少量的智能 PL/SQL。如果代码生成不起作用,您可以使用 ANYDATA 和 ANYTYPE 创建 PL/SQL 反射,以便在运行时动态迭代记录的元素。

代码生成

不要编写 BIND_VARIABLE 100 次,而是创建一个小程序来生成 100 行代码。如果数据最终来自一个表并进入另一个表,则可以根据 DBA_TAB_COLUMNS 等数据字典视图来预测输入和输出。

希望这样的小查询可以帮助生成单个表的所有代码:

--Generate PL/SQL statements for binds.
select
    'DBMS_SQL.BIND_VARIABLE(lvnInsertCursorId, '':RTTEXT'||column_id||'VC100'', lvaDBOBJDTLRecTab(i).'||column_name||');'
from dba_tab_columns
where owner = 'SOME_OWNER'
    and table_name = 'SOME_TABLE'
order by 1;

然后您可以将输出复制并粘贴到 PL/SQL 块中。您可能还需要一个警告,例如“请勿修改,此代码由过程 CODE_TRON_2000 自动生成”。

这种方法只有在 PL/SQL 代码是可预测的情况下才有效,基于数据字典或其他一些元数据。

PL/SQL 反射

对于 PL/SQL 类型*没有纯 PL/SQL 反射,但如果您愿意将记录类型创建为 SQL 对象,则有一个简单的解决方法。如果所有 PL/SQL 记录都基于对象类型,那么 ANYDATA 和 ANYTYPE 可用于动态访问属性。对象类型和 PL/SQL 记录类型非常相似,将一种转换为另一种应该相对容易。

例如,如果您创建一个包含数字和字符串的对象类型:

create or replace type v_type is object(a number, b varchar2(1));

这个(痛苦的)PL/SQL 块展示了如何遍历集合的所有记录,然后遍历每个记录中的所有属性。 (代码打印值,您必须自己添加绑定部分。)

declare
    type v_nt_type is table of v_type;
    v_values v_nt_type := v_nt_type(v_type(1, 'A'), v_type(2, 'B'));
begin
    --For each record:
    for i in 1 .. v_values.count loop
        declare
            v_anydata anydata := anydata.ConvertObject(v_values(i));
            v_number number;
            v_varchar2 varchar2(4000);
            v_result pls_integer;
            v_anytype anytype;
            v_dummy_num  pls_integer;
            v_dummy_char varchar2(4000);
            v_dummy_anytype anytype;
            v_number_of_elements number;
        begin
            --Get the ANYTYPE and the number of elements.
            v_result := v_anydata.getType(v_anytype);
            v_result := v_anytype.getInfo
            (
               prec        => v_dummy_num,
               scale       => v_dummy_num,
               len         => v_dummy_num,
               csid        => v_dummy_num,
               csfrm       => v_dummy_num,
               schema_name => v_dummy_char,
               type_name   => v_dummy_char,
               version     => v_dummy_char,
               numelems    => v_number_of_elements
            );

            --For each element in the record:
            for i in 1 .. v_number_of_elements loop
                --Find the type of the element:
                v_anydata.piecewise;
                v_result := v_anytype.getAttrElemInfo(
                pos            => i,
                prec           => v_dummy_num,
                scale          => v_dummy_num,
                len            => v_dummy_num,
                csid           => v_dummy_num,
                csfrm          => v_dummy_num,
                attr_elt_type  => v_dummy_anytype,
                aname          => v_dummy_char);

                --This is where you do something interesting with the values.
                --(The same code merely prints the values.)
                if v_result = dbms_types.typecode_number then
                    v_result := v_anydata.getNumber(num => v_number);
                    dbms_output.put_line(v_number);
                elsif v_result = dbms_types.typecode_varchar2 then
                    v_result := v_anydata.getVarchar2(c => v_varchar2);
                    dbms_output.put_line(v_varchar2);
                --TODO: Add other potential types here.
                end if;
            end loop;
        end;
    end loop;
end;
/

结果:

1
A
2
B

* 你是对的,如果调试器得到它,必须有 some 方法来查找此运行时信息。但据我所知,PL/SQL 无法检索该调试信息。也许它只适用于 OCI(?) 接口?

【讨论】:

【参考方案3】:

@乔恩 感谢您的投入,这有帮助。我也能够在不使用 DBMS_SQL.DESCRIBE_COLUMNS 创建 OBJECT 的情况下迭代 cols。

**下面的代码仍然需要一点点微调,但大部分都可以工作:)

   BEGIN
        COLS_TRAVERSE('SELECT * FROM ALL_OBJECTS WHERE ROWNUM<=100');
   END;



   create or replace PROCEDURE COLS_TRAVERSE ( p_query in varchar2 )
   AS
            v_curid    NUMBER;
            v_desctab  DBMS_SQL.DESC_TAB;
            v_colcnt   NUMBER;
            v_RowNumcnt   NUMBER := 1;
            v_Colname_var  VARCHAR2(10000);
            v_name_var  VARCHAR2(10000);
            v_num_var   NUMBER;
            v_date_var  DATE;
            v_row_num    NUMBER;
            p_sql_stmt VARCHAR2(1000);

   BEGIN
        v_curid := DBMS_SQL.OPEN_CURSOR;
        DBMS_SQL.PARSE(v_curid, p_query, DBMS_SQL.NATIVE);
        DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);

       -- Define columns:
       FOR i IN 1 .. v_colcnt LOOP
        IF v_desctab(i).col_type = 2 THEN
            DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
            ELSIF v_desctab(i).col_type = 12 THEN
            DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
            ELSE
            DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
            END IF;
        END LOOP;
        v_row_num := dbms_sql.execute(v_curid);
        -- Fetch rows with DBMS_SQL package:
        WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP

          FOR i IN 1 .. v_colcnt 

            LOOP
                v_Colname_var := v_desctab(i).col_name;
                dbms_output.put_line( 'Name:' ||v_Colname_var );
                IF (v_desctab(i).col_type = 1) THEN
                    DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
                    dbms_output.put_line( 'String Value:' || v_name_var );
                ELSIF (v_desctab(i).col_type = 2) THEN
                    DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
                    dbms_output.put_line( 'Number Value:' || v_num_var);
                ELSIF (v_desctab(i).col_type = 12) THEN
                    DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
                    dbms_output.put_line( 'Date Value:' || v_date_var );
                END IF;
            END LOOP;



            dbms_output.put_line( 'End of Row Number # ' ||v_RowNumcnt );

            v_RowNumcnt := v_RowNumcnt+1;

        END LOOP;

        DBMS_SQL.CLOSE_CURSOR(v_curid);
     END;
     /


    DBMS_OUT PUT 

    Name:OWNER
    String Value:SYS
    Name:OBJECT_NAME
    String Value:ORA$BASE
    Name:SUBOBJECT_NAME
    String Value:
    Name:OBJECT_ID
    Number Value:134
    Name:DATA_OBJECT_ID
    Number Value:
    Name:OBJECT_TYPE
    String Value:EDITION
    Name:CREATED
    Date Value:30-03-18
    Name:LAST_DDL_TIME
    Date Value:30-03-18
    Name:TIMESTAMP
    String Value:2018-03-30:21:37:22
    Name:STATUS
    String Value:VALID
    Name:TEMPORARY
    String Value:N
    Name:GENERATED
    String Value:N
    Name:SECONDARY
    String Value:N
    Name:NAMESPACE
    Number Value:64
    Name:EDITION_NAME
    String Value:
    Name:SHARING
    String Value:NONE
    Name:EDITIONABLE
    String Value:
    Name:ORACLE_MAINTAINED
    String Value:Y
    Name:APPLICATION
    String Value:N
    Name:DEFAULT_COLLATION
    String Value:
    Name:DUPLICATED
    String Value:N
    Name:SHARDED
    String Value:N
    Name:CREATED_APPID
    Number Value:
    Name:CREATED_VSNID
    Number Value:
    Name:MODIFIED_APPID
    Number Value:
    Name:MODIFIED_VSNID
    Number Value:
    End of Row Number # 1

    Name:OWNER
    String Value:SYS
    Name:OBJECT_NAME
    String Value:DUAL
    Name:SUBOBJECT_NAME
    String Value:
    Name:OBJECT_ID
    Number Value:143
    Name:DATA_OBJECT_ID
    Number Value:143
    Name:OBJECT_TYPE
    String Value:TABLE
    Name:CREATED
    Date Value:30-03-18
    Name:LAST_DDL_TIME
    Date Value:31-03-18
    Name:TIMESTAMP
    String Value:2018-03-30:21:37:22
    Name:STATUS
    String Value:VALID
    Name:TEMPORARY
    String Value:N
    Name:GENERATED
    String Value:N
    Name:SECONDARY
    String Value:N
    Name:NAMESPACE
    Number Value:1
    Name:EDITION_NAME
    String Value:
    Name:SHARING
    String Value:METADATA LINK
    Name:EDITIONABLE
    String Value:
    Name:ORACLE_MAINTAINED
    String Value:Y
    Name:APPLICATION
    String Value:N
    Name:DEFAULT_COLLATION
    String Value:USING_NLS_COMP
    Name:DUPLICATED
    String Value:N
    Name:SHARDED
    String Value:N
    Name:CREATED_APPID
    Number Value:
    Name:CREATED_VSNID
    Number Value:
    Name:MODIFIED_APPID
    Number Value:
    Name:MODIFIED_VSNID
    Number Value:
    End of Row Number # 2   

【讨论】:

以上是关于在运行时访问 Oracle PLSQL 记录类型的元素的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 运行总计

PLSQL 之类型变量和结构

Oracle-4 - :超级适合初学者的入门级笔记:plsql,基本语法,记录类型,循环,游标,异常处理,存储过程,存储函数,触发器

在 Oracle 内存缓存中长时间存储 PLSQL 存储过程值

MyEclipse+Weblogic+Oracle+PLSQL配置注意事项

SQL记录-PLSQL集合