Oracle PL/SQL - 迭代本地定义表的列名
Posted
技术标签:
【中文标题】Oracle PL/SQL - 迭代本地定义表的列名【英文标题】:Oracle PL/SQL - Iterate on column names of locally defined table 【发布时间】:2020-11-19 10:31:11 【问题描述】:我想迭代本地定义的表的名称,但它没有按预期工作:
declare
type books is record
(title varchar(50) := 'First Book'
,author varchar(50) := 'Me'
,subject varchar(100) := 'Simple ones'
,book_id number := 94321
);
type table_of_books is
table of books;
list_of_books table_of_books;
CURSOR c IS
SELECT COLUMN_NAME FROM ALL_COL_COMMENTS WHERE table_name='list_of_books';
begin
dbms_output.enable;
list_of_books := table_of_books();
list_of_books.extend(1);
list_of_books(1) := books();
FOR current_field IN c LOOP -- Is not iterating
dbms_output.put_line(current_field.column_name); -- I would expect output here
END LOOP;
end;
/
这是 Oracle 19c。
【问题讨论】:
list_of_books 不是表格,在 all_col_cmets 视图中没有条目 获取登录用户可访问的表名的数据字典最好是 ALL_TABLES。 @rainu 正在尝试SELECT COLUMN_NAME FROM ALL_TABLES WHERE table_name='list_of_books';
我得到ORA-06550: line 13, column 13: PL/SQL: ORA-00904: "COLUMN_NAME": invalid identifier
试一试 ALL_TAB_COLUMNS 请在检查表名时使用大写。 docs.oracle.com/en/database/oracle/oracle-database/19/refrn/…
@rainu 使用ALL_TAB_COLUMNS
对我也不起作用。如果有工作演示,请提供。
【参考方案1】:
如果您使用在 SQL 范围内创建的对象而不是在 PL/SQL 范围内创建的记录,那么您可以使用 ANYDATA
找出属性及其值。
例如,如果您的类型是:
CREATE TYPE book is OBJECT(
title varchar(50),
author varchar(50),
subject varchar(100),
book_id number,
first_published DATE
);
CREATE TYPE table_of_books IS TABLE OF book;
然后就可以创建包了:
CREATE PACKAGE reflection IS
TYPE type_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
schema_name VARCHAR2(30),
type_name VARCHAR2(30),
version VARCHAR2(100),
count PLS_INTEGER
);
TYPE attr_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
attr_elt_type ANYTYPE,
aname VARCHAR2(30)
);
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
END;
/
与包体:
CREATE PACKAGE BODY reflection IS
DEBUG BOOLEAN := FALSE;
FUNCTION get_type(
p_anydata IN ANYDATA
) RETURN ANYTYPE
IS
v_typeid PLS_INTEGER;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
BEGIN
v_typeid := p_anydata.GetType( typ => v_anytype );
RETURN v_anytype;
END;
FUNCTION get_info(
p_anytype IN ANYTYPE
) RETURN type_info
IS
v_typeid PLS_INTEGER;
v_type_info REFLECTION.TYPE_INFO;
BEGIN
v_typeid := p_anytype.GetInfo (
v_type_info.prec,
v_type_info.scale,
v_type_info.len,
v_type_info.csid,
v_type_info.csfrm,
v_type_info.schema_name,
v_type_info.type_name,
v_type_info.version,
v_type_info.count
);
IF v_typeid <> DBMS_TYPES.TYPECODE_OBJECT THEN
RAISE_APPLICATION_ERROR( -20000, 'Not an object.' );
END IF;
RETURN v_type_info;
END;
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER
IS
BEGIN
RETURN Get_Info( Get_Type( p_anydata ) ).COUNT;
END;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
BEGIN
v_anytype := Get_Type( v_anydata );
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => p_index,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
RETURN v_attr_info.aname;
END;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
BEGIN
v_anytype := Get_Type( v_anydata );
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
FOR i IN 1 .. p_index LOOP
DECLARE
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
v_result_code PLS_INTEGER;
BEGIN
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => i,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
IF DEBUG THEN
DBMS_OUTPUT.PUT_LINE(
'Attribute ' || i || ': '
|| v_attr_info.aname
|| ' (type ' || v_attr_typeid || ')'
);
END IF;
CASE v_attr_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
DECLARE
v_value NUMBER;
BEGIN
v_result_code := v_anydata.GetNumber( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value );
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DECLARE
v_value VARCHAR2(4000);
BEGIN
v_result_code := v_anydata.GetVarchar2( v_value );
IF i = p_index THEN
RETURN v_value;
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
DECLARE
v_value DATE;
BEGIN
v_result_code := v_anydata.GetDate( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
END IF;
END;
ELSE
NULL;
END CASE;
END;
END LOOP;
RETURN NULL;
END;
END;
/
那么您获取值的代码可能是:
DECLARE
list_of_books table_of_books;
idx PLS_INTEGER := 1;
p_anydata ANYDATA;
p_attr_name VARCHAR2(30);
p_attr_value VARCHAR2(4000);
BEGIN
dbms_output.enable;
list_of_books := table_of_books(
book(
'First book',
'Me',
'Simple Ones',
94321,
DATE '1970-01-01'
),
book(
'Second book',
'You',
'Intermediate Ones',
55555,
DATE '2020-01-01'
),
book(
'Third book',
NULL,
'Advanced Ones',
77777,
DATE '2099-12-31' + INTERVAL '0 23:59:59' DAY TO SECOND
)
);
FOR book_no IN 1 .. list_of_books.COUNT LOOP
p_anydata := ANYDATA.ConvertObject( list_of_books(book_no) );
DBMS_OUTPUT.PUT_LINE( 'Book ' || book_no || ':' );
FOR attr_no IN 1 .. REFLECTION.get_size( p_anydata ) LOOP
p_attr_name := REFLECTION.get_attr_name_at( p_anydata, attr_no );
p_attr_value := REFLECTION.get_attr_value_at( p_anydata, attr_no );
DBMS_OUTPUT.PUT_LINE( ' ' || p_attr_name || ': ' || p_attr_value );
END LOOP;
END LOOP;
END;
/
哪些输出:
Book 1: TITLE: First book AUTHOR: Me SUBJECT: Simple Ones BOOK_ID: 94321 FIRST_PUBLISHED: 1970-01-01 00:00:00 Book 2: TITLE: Second book AUTHOR: You SUBJECT: Intermediate Ones BOOK_ID: 55555 FIRST_PUBLISHED: 2020-01-01 00:00:00 Book 3: TITLE: Third book AUTHOR: SUBJECT: Advanced Ones BOOK_ID: 77777 FIRST_PUBLISHED: 2099-12-31 23:59:59
db小提琴here
【讨论】:
感谢您的回答,但它太复杂了。我正在寻找简单的东西。 PL/SQL 应该提供一些方法来做到这一点。 @user5507535 “PL/SQL 应该提供一些方法来做到这一点。”但是,它并没有提供这样做的本地方式,我们不能只是希望某些东西成为这样。一旦你创建了包,那么几乎没有比你的问题更多的代码(最后一个 PL/SQL 块的大部分是创建示例数据,获取属性是一个简单的FOR ... LOOP
调用包)。跨度>
【参考方案2】:
简短的回答:你不能。在 PL/SQL 中没有这样的工具来遍历记录属性。
长答案:您可以使用table
运算符通过 SQL 读取数组。因此,您可以unpivot
结构,将列名作为行返回。
为此,您需要定义记录和数组:
在包规范中 作为 SQL 对象类型坚持使用 PL/SQL 类型会得到类似的结果:
create or replace package pkg as
type books is record
(title varchar(50) := 'First Book'
,author varchar(50) := 'Me'
,subject varchar(100) := 'Simple ones'
,book_id number := 94321
);
type table_of_books is
table of books
index by pls_integer;
end;
/
declare
list_of_books pkg.table_of_books;
cursor c is
select * from table ( list_of_books )
unpivot (
val for c in (
title, author, subject
)
);
begin
list_of_books := pkg.table_of_books (
1 => pkg.books (
'First book', 'me', 'this', 100
)
);
for current_field in c loop
dbms_output.put_line(current_field.c);
end loop;
end;
/
TITLE
AUTHOR
SUBJECT
一些注意事项:
您需要知道要在unpivot
子句中成为行的属性的名称
对于unpivot
列,它们需要具有相同的数据类型,因此您需要对to_char
Everything 进行子查询
如果您使用 19c,还可以查看 converting the objects to JSON
【讨论】:
是的,它是 Oracla 19c。我将查看 JSON 链接,看看它是否会更简单。 我看不出如何使用 JSON。如果有人帮助老年人,那就太好了。 可以将文档转成json_object_t
,并在此调用get_keys()
方法【参考方案3】:
在 PL/SQL 范围内声明的类型的属性不作为元数据存储在数据字典中。
一种可能的解决方案是使用TABLE
运算符作为取消嵌套查询 的结果获取列名。 SQL 解析器必须知道集合,即至少在包中声明。在这种情况下不需要集合中的数据,只需用空集合打开游标即可:
create or replace package libapi as
type book is record (
title varchar(50),
author varchar(50),
subject varchar(100),
book_id number);
type books is table of book index by pls_integer;
end;
/
declare
lib libapi.books;
nc number;
cols number;
ds dbms_sql.desc_tab;
rc sys_refcursor;
begin
open rc for select * from table (lib);
nc := dbms_sql.to_cursor_number (rc);
dbms_sql.describe_columns (nc, cols, ds);
dbms_sql.close_cursor (nc);
for i in 1..cols loop dbms_output.put_line (ds(i).col_name);
end loop;
end;
/
结果:
TITLE
AUTHOR
SUBJECT
BOOK_ID
【讨论】:
【参考方案4】:使用 JSON_OBJECT_T 的解决方案:
DECLARE
books CONSTANT JSON_OBJECT_T := JSON_OBJECT_T.parse(
'
"title": "First Book",
"author": "Me",
"subject": "Simple ones",
"book_id": 94321
');
book_keys JSON_KEY_LIST := books.get_keys;
BEGIN
FOR i IN 1..book_keys.COUNT LOOP
DBMS_OUTPUT.put_line( book_keys(i) || ': ' || books.get_string(book_keys(i)) );
END LOOP;
END;
/
【讨论】:
以上是关于Oracle PL/SQL - 迭代本地定义表的列名的主要内容,如果未能解决你的问题,请参考以下文章