在运行时访问 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-4 - :超级适合初学者的入门级笔记:plsql,基本语法,记录类型,循环,游标,异常处理,存储过程,存储函数,触发器
在 Oracle 内存缓存中长时间存储 PLSQL 存储过程值