PL/SQL 创建包含结果集连接的过程
PL/SQL Creating a procedure that contains result set joins
我想在 PL/SQL 中创建一个包含 5 个步骤的程序。第 1 步和第 2 步首先执行 return 和 ID
。在第 3 步中,我们有一个 SELECT
语句,它的条件是 returned ID
。然后我想获取该 SELECT
语句的所有结果,并在另一个 SELECT
语句的 JOIN
中使用它们,并在第三个 SELECT
语句中再次使用这些结果JOIN
。据我所知,我不能在 JOIN
语句中使用 CURSOR
。我的一些同事建议我将结果保存在 CURSOR
中,然后使用循环遍历每一行并将该数据用于下一个 SELECT
。然而,由于我要进行 2 次选择,这将创建一个巨大的内部循环分支,而这正是我要避免的。
另一个建议是使用临时表来存储数据。但是这个过程可能会被很多用户同时执行,table的数据会相互冲突。现在我正在查看 LOCAL Temporary tables,它应该根据会话过滤数据,但我不确定我是否想为我的程序创建虚拟 tables,因为我想避免在模式中留下垃圾(此过程适用于应用程序的自定义部分)。有这样做的标准方法吗?有什么想法吗?
样本:
DECLARE
USERID INT := 1000000;
TEXT1 VARCHAR(100);
TEXT_INDEX INT;
CURSOR NODES IS SELECT * FROM NODE_TABLE WHERE DESCRIPTION LIKE TEXT || '%';
CURSOR USERS IS SELECT * FROM USERGROUPS JOIN NODES ON NODES.ID = USERGROUPS.ID;
BEGIN
SELECT TEXT INTO TEXT1 FROM TABLE_1 WHERE ID = USERID;
TEXT_INDEX = INSTR(TEXT, '-');
TEXT = SUBSTR(TEXT, 0, TEXT_INDEX);
OPEN NODES;
OPEN USERS;
END;
注意:这不起作用。 Oracle 不支持游标之间的连接。
注 2:这可以在单个查询中完成,但为了论证(以及在我的实际用例中),我想在一个过程中分解这些步骤。示例代码描述了我要实现的目标,如果游标之间的连接有效。但他们没有,我正在寻找替代方案。
您可以通过多种方式进行此类分解,但与单个 SQL 语句相比,所有这些方式都有显着的性能损失。
可维护性的提升也值得商榷,视具体情况而定。
要查看所有可能性,请查看 documentation.
以下是一些基于简单逻辑的可能变体:
- 根据给定的Id计算Oracle用户名前缀;
- 获取名称以此前缀开头的所有用户;
- 查找步骤 2 中用户拥有的所有 table;
- 计算找到的总数 table。
1.流水线
准备函数要使用的类型:
create or replace type TUserRow as object (
username varchar2(30),
user_id number,
created date
)
/
create or replace type TTableRow as object (
owner varchar2(30),
table_name varchar2(30),
status varchar2(8),
logging varchar2(3)
-- some other useful fields here
)
/
create or replace type TUserList as table of TUserRow
/
create or replace type TTableList as table of TTableRow
/
通过用户 ID 查找前缀的简单函数:
create or replace function GetUserPrefix(piUserId in number) return varchar2
is
vUserPrefix varchar2(30);
begin
select substr(username,1,3) into vUserPrefix
from all_users
where user_id = piUserId;
return vUserPrefix;
end;
/
搜索用户功能:
create or replace function GetUsersPipe(
piNameStart in varchar2
)
return TUserList pipelined
as
vUserList TUserList;
begin
for cUsers in (
select *
from
all_users
where
username like piNameStart||'%'
)
loop
pipe row( TUserRow(cUsers.username, cUsers.user_id, cUsers.created) ) ;
end loop;
return;
end;
函数搜索 tables:
create or replace function GetUserTablesPipe(
piUserNameStart in varchar2
)
return TTableList pipelined
as
vTableList TTableList;
begin
for cTables in (
select *
from
all_tables tab_list,
table(GetUsersPipe(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
)
loop
pipe row ( TTableRow(cTables.owner, cTables.table_name, cTables.status, cTables.logging) );
end loop;
return;
end;
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTablesPipe(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
2。简单 table 函数
此解决方案使用与上述流水线函数变体相同的类型。
搜索用户功能:
create or replace function GetUsers(piNameStart in varchar2) return TUserList
as
vUserList TUserList;
begin
select TUserRow(username, user_id, created)
bulk collect into vUserList
from
all_users
where
username like piNameStart||'%'
;
return vUserList;
end;
/
函数搜索 tables:
create or replace function GetUserTables(piUserNameStart in varchar2) return TTableList
as
vTableList TTableList;
begin
select TTableRow(owner, table_name, status, logging)
bulk collect into vTableList
from
all_tables tab_list,
table(GetUsers(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
;
return vTableList;
end;
/
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTables(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
3。光标 - xml - 光标
这是一种特殊情况,可以在没有用户定义类型的情况下实现,但性能损失很大,涉及不必要的类型转换并且可维护性低。
搜索用户功能:
create or replace function GetUsersRef(
piNameStart in varchar2
)
return sys_refcursor
as
cUserList sys_refcursor;
begin
open cUserList for
select * from all_users
where username like piNameStart||'%'
;
return cUserList;
end;
函数搜索 tables:
create or replace function GetUserTablesRef(
piUserNameStart in varchar2
)
return sys_refcursor
as
cTableList sys_refcursor;
begin
open cTableList for
select
tab_list.*
from
(
XMLTable('/ROWSET/ROW'
passing xmltype(GetUsersRef(piUserNameStart))
columns
username varchar2(30) path '/ROW/USERNAME'
)
) user_list,
all_tables tab_list
where
tab_list.owner = user_list.username
;
return cTableList;
end;
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from
XMLTable('/ROWSET/ROW'
passing xmltype(GetUserTablesRef(GetUserPrefix(vUserId)))
columns
table_name varchar2(30) path '/ROW/TABLE_NAME'
)
;
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
当然,所有变体都可以混合使用,但 SQL 至少对于简单的情况看起来更好:
declare
vUserId number := 5;
vUserPrefix varchar2(100);
vTableCount number;
begin
-- Construct prefix from Id
select max(substr(user_list.username,1,3))
into vUserPrefix
from
all_users user_list
where
user_list.user_id = vUserId
;
-- Count number of tables owned by users with name started with vUserPrefix string
select
count(1) into vTableCount
from
all_users user_list,
all_tables table_list
where
user_list.username like vUserPrefix||'%'
and
table_list.owner = user_list.username
;
dbms_output.put_line('Users with name started with "'||vUserPrefix||'" owns '||vTableCount||' tables');
end;
P.S。所有代码仅用于演示目的:没有优化等。
我最终使用了一个函数(尽管也可以使用一个过程)以及 tables。学习和注意事项:
- PL/SQL 函数只能return 预先在schema 中声明的类型,并且是明确的。您不能创建 return 类似于
MY_TABLE%ROWTYPE
的函数,即使看起来类型信息可用,但它不接受 table。你必须创建一个自定义类型 MY_TABLE%ROWTYPE
是你想要 return 它。
Oracle 对待声明类型的 table 与 %ROWTYPE
的 table 不同。一开始这让我很困惑,但从我收集到的信息来看,这就是它的工作原理。
DECLARE TYPE MY_CUSTOM_TABLE IS TABLE OF MY_TABLE%ROWTYPE;
声明了 MY_TABLE 行类型的集合。为了添加到这一点,我们必须使用查询 MY_TABLE 的 SQL 语句中的 BULK COLLECT INTO
。生成的 集合 不能在 JOIN 语句中使用,不可查询并且不能被函数 return 编辑。
DECLARE
CREATE TYPE MY_CUSTOM_TYPE AS OBJECT (COL_A NUMBER, COL_B NUMBER);
CREATE TYPE MY_CUSTOM_TABLE AS TABLE OF MY_CUSTOM_TYPE;
my_custom_tab MY_CUSTOM_TABLE;
This create my_custom_tab which is a table (not a collection) 如果填充可以在使用 TABLE(my_custmo_tab)
查询FROM
语句。作为预先在模式中声明的 table,可以从函数中 returned。但是它不能使用 BULK COLLECT INTO
填充,因为它不是集合。我们必须改为使用正常的 SELECT INTO
语句。但是,如果我们想用现有的 table 中的数据填充它,它有 2 个数字列,我们不能简单地执行 SELECT * INTO my_custom_tab FROM DOUBLE_NUMBER_TABLE
,因为 my_custom_tab
尚未初始化并且不包含足够的行接收数据。如果我们不知道查询 return 有多少行,我们就无法对其进行初始化。填充 table 的技巧是使用 CAST
命令并将我们的 select 结果集转换为 MY_CUSTOM_TABLE
然后添加它。
SELECT CAST(MULTISET(SELECT COL_A, COL_B FROM DOUBLE_NUMBER_TABLE) AS MY_CUSTOM_TABLE) INTO my_custom_tab FROM DUAL
现在我们可以通过使用 TABLE()
函数轻松地在查询等中使用 my_custom_tab。
SELECT * FROM TABLE(my_custom_tab)
有效。
我想在 PL/SQL 中创建一个包含 5 个步骤的程序。第 1 步和第 2 步首先执行 return 和 ID
。在第 3 步中,我们有一个 SELECT
语句,它的条件是 returned ID
。然后我想获取该 SELECT
语句的所有结果,并在另一个 SELECT
语句的 JOIN
中使用它们,并在第三个 SELECT
语句中再次使用这些结果JOIN
。据我所知,我不能在 JOIN
语句中使用 CURSOR
。我的一些同事建议我将结果保存在 CURSOR
中,然后使用循环遍历每一行并将该数据用于下一个 SELECT
。然而,由于我要进行 2 次选择,这将创建一个巨大的内部循环分支,而这正是我要避免的。
另一个建议是使用临时表来存储数据。但是这个过程可能会被很多用户同时执行,table的数据会相互冲突。现在我正在查看 LOCAL Temporary tables,它应该根据会话过滤数据,但我不确定我是否想为我的程序创建虚拟 tables,因为我想避免在模式中留下垃圾(此过程适用于应用程序的自定义部分)。有这样做的标准方法吗?有什么想法吗?
样本:
DECLARE
USERID INT := 1000000;
TEXT1 VARCHAR(100);
TEXT_INDEX INT;
CURSOR NODES IS SELECT * FROM NODE_TABLE WHERE DESCRIPTION LIKE TEXT || '%';
CURSOR USERS IS SELECT * FROM USERGROUPS JOIN NODES ON NODES.ID = USERGROUPS.ID;
BEGIN
SELECT TEXT INTO TEXT1 FROM TABLE_1 WHERE ID = USERID;
TEXT_INDEX = INSTR(TEXT, '-');
TEXT = SUBSTR(TEXT, 0, TEXT_INDEX);
OPEN NODES;
OPEN USERS;
END;
注意:这不起作用。 Oracle 不支持游标之间的连接。 注 2:这可以在单个查询中完成,但为了论证(以及在我的实际用例中),我想在一个过程中分解这些步骤。示例代码描述了我要实现的目标,如果游标之间的连接有效。但他们没有,我正在寻找替代方案。
您可以通过多种方式进行此类分解,但与单个 SQL 语句相比,所有这些方式都有显着的性能损失。
可维护性的提升也值得商榷,视具体情况而定。
要查看所有可能性,请查看 documentation.
以下是一些基于简单逻辑的可能变体:
- 根据给定的Id计算Oracle用户名前缀;
- 获取名称以此前缀开头的所有用户;
- 查找步骤 2 中用户拥有的所有 table;
- 计算找到的总数 table。
1.流水线
准备函数要使用的类型:
create or replace type TUserRow as object (
username varchar2(30),
user_id number,
created date
)
/
create or replace type TTableRow as object (
owner varchar2(30),
table_name varchar2(30),
status varchar2(8),
logging varchar2(3)
-- some other useful fields here
)
/
create or replace type TUserList as table of TUserRow
/
create or replace type TTableList as table of TTableRow
/
通过用户 ID 查找前缀的简单函数:
create or replace function GetUserPrefix(piUserId in number) return varchar2
is
vUserPrefix varchar2(30);
begin
select substr(username,1,3) into vUserPrefix
from all_users
where user_id = piUserId;
return vUserPrefix;
end;
/
搜索用户功能:
create or replace function GetUsersPipe(
piNameStart in varchar2
)
return TUserList pipelined
as
vUserList TUserList;
begin
for cUsers in (
select *
from
all_users
where
username like piNameStart||'%'
)
loop
pipe row( TUserRow(cUsers.username, cUsers.user_id, cUsers.created) ) ;
end loop;
return;
end;
函数搜索 tables:
create or replace function GetUserTablesPipe(
piUserNameStart in varchar2
)
return TTableList pipelined
as
vTableList TTableList;
begin
for cTables in (
select *
from
all_tables tab_list,
table(GetUsersPipe(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
)
loop
pipe row ( TTableRow(cTables.owner, cTables.table_name, cTables.status, cTables.logging) );
end loop;
return;
end;
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTablesPipe(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
2。简单 table 函数
此解决方案使用与上述流水线函数变体相同的类型。
搜索用户功能:
create or replace function GetUsers(piNameStart in varchar2) return TUserList
as
vUserList TUserList;
begin
select TUserRow(username, user_id, created)
bulk collect into vUserList
from
all_users
where
username like piNameStart||'%'
;
return vUserList;
end;
/
函数搜索 tables:
create or replace function GetUserTables(piUserNameStart in varchar2) return TTableList
as
vTableList TTableList;
begin
select TTableRow(owner, table_name, status, logging)
bulk collect into vTableList
from
all_tables tab_list,
table(GetUsers(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
;
return vTableList;
end;
/
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTables(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
3。光标 - xml - 光标
这是一种特殊情况,可以在没有用户定义类型的情况下实现,但性能损失很大,涉及不必要的类型转换并且可维护性低。
搜索用户功能:
create or replace function GetUsersRef(
piNameStart in varchar2
)
return sys_refcursor
as
cUserList sys_refcursor;
begin
open cUserList for
select * from all_users
where username like piNameStart||'%'
;
return cUserList;
end;
函数搜索 tables:
create or replace function GetUserTablesRef(
piUserNameStart in varchar2
)
return sys_refcursor
as
cTableList sys_refcursor;
begin
open cTableList for
select
tab_list.*
from
(
XMLTable('/ROWSET/ROW'
passing xmltype(GetUsersRef(piUserNameStart))
columns
username varchar2(30) path '/ROW/USERNAME'
)
) user_list,
all_tables tab_list
where
tab_list.owner = user_list.username
;
return cTableList;
end;
代码中的用法:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from
XMLTable('/ROWSET/ROW'
passing xmltype(GetUserTablesRef(GetUserPrefix(vUserId)))
columns
table_name varchar2(30) path '/ROW/TABLE_NAME'
)
;
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
当然,所有变体都可以混合使用,但 SQL 至少对于简单的情况看起来更好:
declare
vUserId number := 5;
vUserPrefix varchar2(100);
vTableCount number;
begin
-- Construct prefix from Id
select max(substr(user_list.username,1,3))
into vUserPrefix
from
all_users user_list
where
user_list.user_id = vUserId
;
-- Count number of tables owned by users with name started with vUserPrefix string
select
count(1) into vTableCount
from
all_users user_list,
all_tables table_list
where
user_list.username like vUserPrefix||'%'
and
table_list.owner = user_list.username
;
dbms_output.put_line('Users with name started with "'||vUserPrefix||'" owns '||vTableCount||' tables');
end;
P.S。所有代码仅用于演示目的:没有优化等。
我最终使用了一个函数(尽管也可以使用一个过程)以及 tables。学习和注意事项:
- PL/SQL 函数只能return 预先在schema 中声明的类型,并且是明确的。您不能创建 return 类似于
MY_TABLE%ROWTYPE
的函数,即使看起来类型信息可用,但它不接受 table。你必须创建一个自定义类型MY_TABLE%ROWTYPE
是你想要 return 它。 Oracle 对待声明类型的 table 与
%ROWTYPE
的 table 不同。一开始这让我很困惑,但从我收集到的信息来看,这就是它的工作原理。DECLARE TYPE MY_CUSTOM_TABLE IS TABLE OF MY_TABLE%ROWTYPE;
声明了 MY_TABLE 行类型的集合。为了添加到这一点,我们必须使用查询 MY_TABLE 的 SQL 语句中的 BULK COLLECT INTO
。生成的 集合 不能在 JOIN 语句中使用,不可查询并且不能被函数 return 编辑。
DECLARE
CREATE TYPE MY_CUSTOM_TYPE AS OBJECT (COL_A NUMBER, COL_B NUMBER);
CREATE TYPE MY_CUSTOM_TABLE AS TABLE OF MY_CUSTOM_TYPE;
my_custom_tab MY_CUSTOM_TABLE;
This create my_custom_tab which is a table (not a collection) 如果填充可以在使用 TABLE(my_custmo_tab)
查询FROM
语句。作为预先在模式中声明的 table,可以从函数中 returned。但是它不能使用 BULK COLLECT INTO
填充,因为它不是集合。我们必须改为使用正常的 SELECT INTO
语句。但是,如果我们想用现有的 table 中的数据填充它,它有 2 个数字列,我们不能简单地执行 SELECT * INTO my_custom_tab FROM DOUBLE_NUMBER_TABLE
,因为 my_custom_tab
尚未初始化并且不包含足够的行接收数据。如果我们不知道查询 return 有多少行,我们就无法对其进行初始化。填充 table 的技巧是使用 CAST
命令并将我们的 select 结果集转换为 MY_CUSTOM_TABLE
然后添加它。
SELECT CAST(MULTISET(SELECT COL_A, COL_B FROM DOUBLE_NUMBER_TABLE) AS MY_CUSTOM_TABLE) INTO my_custom_tab FROM DUAL
现在我们可以通过使用 TABLE()
函数轻松地在查询等中使用 my_custom_tab。
SELECT * FROM TABLE(my_custom_tab)
有效。