如何执行 SQL 游标转换
Posted
技术标签:
【中文标题】如何执行 SQL 游标转换【英文标题】:How to perform SQL cursor transformation 【发布时间】:2019-09-23 14:50:39 【问题描述】:在我的 Oracle 存储过程中,有一个基于选择的查询,不是那么复杂,只是几个连接表,带有额外的普通过滤器和单一排序依据。
查询包含一个用户(按名称排序),每个用户有一些属性和可能的几行。
但是我需要添加一些计算出来的额外列。
将 1 设置为组中第一行中具有相同“名称”的“标志”列(其他行为 0)。
累积列“权利”,它确实加入了相同的字段“权利”前行值以及其他列和当前行值(在我的情况下为“组”),也应该为每个用户重新设置
例如:
john; admin; 1; admin
john; poweruser; 0; admin | poweruser
ken; guest; 1; guest
ted; developer; 1; developer
ted; user; 0; developer | user
ted; techwriter; 0; developer | user | techwriter
我需要执行一些格式化并作为存储过程的结果返回。 不知道如何设置所需的值并从存储过程中返回。
我应该将初始查询声明为游标,循环遍历并设置(可能吗?对于非数据库字段?)以及如何从存储过程中返回此游标?
【问题讨论】:
以及如何在组内进行排序?为什么 admin 是第一条记录,而 poweruser 是 john 的第二条记录?有排序标准吗? 你确定你真的想要每个用户多行吗?似乎您可能真的想listagg()
所有“权利”,为每个“名称”提供一行(并使标志变得不必要)?
除了“名称”之外没有排序。不幸的是,我无法删除标志或/和聚合一组行,输出格式由消费者系统预定义,我们无权访问
简单问题:为什么 admin 的标志是 1 而 poweruser 的标志是 0?
另见this问题
【参考方案1】:
您还可以将其他一些分析函数与 LISTAGG() 一起使用来解决问题。由于您不提供测试数据,因此我将使用 EMP 表进行演示:
select deptno, ename,
case rn when 1 then 1 else 0 end flags,
case cnt when rn
then enames
else substr(enames, 1, instr(enames, ' | ', 1, rn) - 1)
end enames
from (
select deptno, ename,
row_number() over(partition by deptno order by ename) rn,
count(*) over(partition by deptno) cnt,
listagg(ename, ' | ') within group(order by ename) over(partition by deptno) enames
from emp
) a;
DEPTNO ENAME FLAGS ENAMES
10 CLARK 1 CLARK
10 KING 0 CLARK | KING
10 MILLER 0 CLARK | KING | MILLER
20 ADAMS 1 ADAMS
20 FORD 0 ADAMS | FORD
20 JONES 0 ADAMS | FORD | JONES
20 SCOTT 0 ADAMS | FORD | JONES | SCOTT
20 SMITH 0 ADAMS | FORD | JONES | SCOTT | SMITH
30 ALLEN 1 ALLEN
30 BLAKE 0 ALLEN | BLAKE
30 JAMES 0 ALLEN | BLAKE | JAMES
30 MARTIN 0 ALLEN | BLAKE | JAMES | MARTIN
30 TURNER 0 ALLEN | BLAKE | JAMES | MARTIN | TURNER
30 WARD 0 ALLEN | BLAKE | JAMES | MARTIN | TURNER | WARD
最好的问候,斯图阿什顿
【讨论】:
【参考方案2】:我知道您已经找到了适合您的答案。我会为后代添加这个。
SQL MODEL
子句可以很好地解决这个问题。这是一个使用DBA_ROLE_PRIVS
表的示例,该表存在于每个Oracle 数据库中,并且与您帖子中的数据具有相似的结构和概念。显然,您应该将 DBA_ROLE_PRIVS
替换为您的表名。
select grantee,
granted_role,
DECODE(rn,1,1,0) flag,
role_list
FROM dba_role_privs
MODEL
PARTITION BY (grantee)
DIMENSION BY (ROW_NUMBER() OVER ( PARTITION BY grantee ORDER BY granted_role) AS rn)
MEASURES (CAST(NULL AS VARCHAR2(4000)) as role_list, granted_role)
RULES UPSERT
( role_list[1] = granted_role[1],
role_list[rn>1] = role_list[cv(rn)-1] || ',' || granted_role[cv(rn)]);
我不会发布我的数据库中的示例结果(出于安全原因),但我认为它们符合您的要求。
【讨论】:
不错。 (我真的需要掌握示范条款... *8-)。 db<>fiddle demo 与我的答案相同的假数据,只是为了表明它符合预期。【参考方案3】:不幸的是,listagg()
的解析版本不允许使用 window 子句,这会使这变得相当简单。
您可以通过递归子查询分解来模拟这一点,但是由于您没有真正的排序标准(除了名称之外),您需要添加一些东西来代替它,例如行号或排名函数;也可以在 CTE 中:
with cte (name, right, rn) as (
select name,
right,
row_number() over (partition by name order by null)
from your_data
),
rcte (name, right, rn, flag, rights) as (
select name, right, rn, 1, right
from cte
where rn = 1
union all
select c.name, c.right, c.rn, 0, r.rights || ' | ' || c.right
from rcte r
join cte c on c.name = r.name and c.rn = r.rn + 1
)
select name, right, flag, rights
from rcte
order by name, rn;
NAME RIGHT FLAG RIGHTS
---- ---------- ---------- ------------------------------
john admin 1 admin
john poweruser 0 admin | poweruser
ken guest 1 guest
ted developer 1 developer
ted user 0 developer | user
ted techwriter 0 developer | user | techwriter
这里your_data
是您的查询现在正在执行的操作;整个事情可能会进入 CTE 内部,并在最后加上 rn
计算,但如果没有看到您现有的查询,它并不完全清楚。希望这能适应您的数据。
我的rn
计算是按null
排序的,这不是确定性的 - 它在一次查询执行中为您提供一个固定值,但如果再次运行,您可能会得到一个不同的值。由于权利的顺序似乎并不重要,因此您也可以按照这些顺序进行排序,以给出确定性的结果;这可能会也可能不会改变输出(因为上面是不确定的,它可能会碰巧匹配这个;有时......):
cte (name, right, rn) as (
select name,
right,
row_number() over (partition by name order by right)
from your_data
),
rcte (name, right, rn, rights) as (
select name, right, rn, right
from cte
where rn = 1
union all
select c.name, c.right, c.rn, r.rights || ' | ' || c.right
from rcte r
join cte c on c.name = r.name and c.rn = r.rn + 1
)
select name, right, case when rn = 1 then 1 else 0 end as flag, rights
from rcte
order by name, rn;
NAME RIGHT FLAG RIGHTS
---- ---------- ---------- ------------------------------
john admin 1 admin
john poweruser 0 admin | poweruser
ken guest 1 guest
ted developer 1 developer
ted techwriter 0 developer | techwriter
ted user 0 developer | techwriter | user
实际上,您可能还有其他一些可以使用的标准,例如您要加入的其中一个表中的标志或序列;或者您可以通过 case 表达式根据正确的值应用自己的值。
【讨论】:
【参考方案4】:以下是一种可能如何转换由存储过程返回的游标。
你必须做三个步骤:
1) 为原始游标定义一个行和表TYPE
s
2) 定义一个表函数返回原始光标
3) 定义在查询表函数(第 2 点)并将其与其他源连接时打开新游标的新过程
示例
原程序
create procedure P1(cur OUT SYS_REFCURSOR) IS
begin
open cur for
select id, col from V1;
end;
/
1) 定义类型
create or replace type t_row is object
(id int,
col VARCHAR2(5));
/
create or replace type t_table is table of t_row;
/
2) 定义返回原始光标的表函数
create or replace function F1 return t_table PIPELINED as
cv sys_refcursor;
v_row v1%rowtype;
begin
P1(cv);
loop
FETCH cv INTO v_row;
EXIT WHEN cv%NOTFOUND;
PIPE ROW(t_row(v_row.id, v_row.col));
end loop;
close cv;
return;
end;
/
请注意,现在您可以使用 SQL 访问原始 游标:
select * from table(F1);
ID COL
---------- -----
1 A
2 B
3) 定义执行转换的新过程
请注意,我忽略了您的详细信息,只是通过添加新的虚拟列来模拟转换
create procedure P2(cur OUT SYS_REFCURSOR) IS
begin
open cur for
select id, col, 'new' col2
from table(F1);
end;
/
【讨论】:
以上是关于如何执行 SQL 游标转换的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Oracle PL/SQL 过程的开始部分之后声明游标