如何执行SQL游标转换

时间:2019-09-23 14:50:39

标签: sql oracle stored-procedures

在我的Oracle存储过程中,有一个基于选择的查询,不是那么复杂,只有几个联接表,带有附加的普通过滤器和单个排序依据。

查询包含一个用户(按名称排序),该用户具有一些属性,每个用户可能有几行。

但是我需要添加一些计算出的额外列。

  1. 将组中第一行的“ flags”列设置为1,且名称相同(其他行均为0)。

  2. 累积列“权利”确实将同一字段“权利”的前一行值与其他列和当前行值(在本例中为“组”)结合在一起,并且也应按用户重新设置

例如:

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

我需要执行一些格式化操作,并从存储过程中返回结果。 不知道如何设置所需的值并从存储过程中返回该值。

我应该将初始查询声明为游标,循环遍历并设置(是否可能?对于非数据库字段?)以及如何从存储过程中返回此游标?

4 个答案:

答案 0 :(得分:2)

您还可以将其他一些分析函数与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

最好的问候,炖阿什顿

答案 1 :(得分: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)]);

出于安全原因,我不会从数据库中发布示例结果,但我认为它们与您的要求相符。

答案 2 :(得分:1)

不幸的是,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是您的查询现在正在执行的操作;整个过程可能只需要在末尾加上rn计算就可以放在CTE内,但是却看不到现有查询,这还不是很清楚。希望这将适合您的数据。

我的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表达式根据正确的值应用自己的内容。

答案 3 :(得分:1)

这是一种如何转换存储过程返回的游标的可能性。

您必须执行三个步骤:

1)为原始光标定义行和表TYPE

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;
/