【问题标题】:Compare Set result from SYS_REFCURSOR比较来自 SYS_REFCURSOR 的设置结果
【发布时间】:2019-01-08 16:10:57
【问题描述】:

我尝试与来自 SYS_REFCURSOR 的集合结果进行比较:

declare
 v_RC sys_refcursor;
 v_RC_union sys_refcursor;
 v_REC userA.table1%rowtype;
 v_REC_union userB.table2%rowtype;
 i number := 0;
 j number := 0;
 z number := 0;
begin         
  open v_RC for select * from userA.table1;
   open v_RC_union for select * from userB.table2;
    loop fetch v_RC into v_REC;
     exit when v_RC%notfound;
     i := i+1;

      loop fetch v_RC_union into v_REC_union;--
       exit when v_RC_union%notfound;
       j := j+1;

            If v_REC_union.id= v_REC.id then                
             z :=z+1;                
            End if;

       end loop;

    end loop;
   close v_RC;
  close v_RC_union;

dbms_output.put_line(z);--too small
dbms_output.put_line('v_RC: '||i||', v_REC_union: '||j);
end;

我知道两个游标都获得了正确的行数(i & j 都可以),但相等行的结果 (z) 是错误的(太小了)。

【问题讨论】:

  • 你确定两个游标中的数据有相同的顺序,尝试按id排序数据?
  • 是的,当我运行 select * from userA.table1 union select * from userB.table2 时,它正在工作
  • z 不是总是会被报告为 1,而不仅仅是“太小”吗?

标签: sql oracle plsql cursor sys-refcursor


【解决方案1】:

我改变了顺序,现在可以正常工作了

open v_RC for select * from select * from userA.table1;  
 loop fetch v_RC into v_REC;    
  exit when v_RC%notfound;

       open v_RC_union for select * from select * from userB.table2 where id = v_REC.id;     
        loop fetch v_RC_union into v_REC_union;
         exit when v_RC_union%notfound;
          if  v_REC.id = v_REC_union.id then
           z :=z+1;
           dbms_output.put_line(v_REC.id ||'='|| v_REC_union.id);
          end if;
        end loop; 
       close v_RC_union;   

 end loop;
close v_RC;

但我仍然不知道出了什么问题?

【讨论】:

  • if v_REC.id = v_REC_union.id then 测试是多余的 - 您知道它们匹配是因为这是 v_RC_union 查询中的条件吗?
【解决方案2】:

在外循环的第一次迭代期间,您正在使用来自 v_RC_union 的所有行。您似乎期望每次循环时都会看到所有这些行,但第二个 ref 游标不会重置为开头(也不能)。

如果您将调试添加到代码中,您会看到这种情况发生;我创建了两个小的虚拟表,每个表都有三个匹配的 ID 和一个不匹配的 ID:

create table table1 (id, dummy) as select level + 1, 'x' from dual connect by level <= 4;
create table table2 (id, dummy) as select level, 'x' from dual connect by level <= 4;

declare
...
begin
  open v_RC for select * from table1;
  open v_RC_union for select * from table2;
  loop
    fetch v_RC into v_REC;
    exit when v_RC%notfound;
    i := i+1;
    dbms_output.put_line('i: ' || i || ' table1 id ' || v_REC.id);
    loop
      fetch v_RC_union into v_REC_union;--
      exit when v_RC_union%notfound;
      j := j+1;
      dbms_output.put_line('i: ' || i || ' j: ' || j || ' table1 id ' || v_REC_union.id);
      If v_REC_union.id= v_REC.id then
        z :=z+1;
      end if;
    end loop;
  end loop;
  close v_RC;
  close v_RC_union;
  dbms_output.put_line('z: ' || z);--too small
  dbms_output.put_line('v_RC: '||i||', v_REC_union: '||j);
end;
/

输出是:

i: 1 table1 id 2
i: 1 j: 1 table1 id 1
i: 1 j: 2 table1 id 2
i: 1 j: 3 table1 id 3
i: 1 j: 4 table1 id 4
i: 2 table1 id 3
i: 3 table1 id 4
i: 4 table1 id 5
z: 1
v_RC: 4, v_REC_union: 4

在第一次迭代中,当 i 为 1 时,您执行内部循环并从 v_RC_union 获取所有行,仅在 notfound 时停止。假设其中一个确实与第一个 v_RC 行的 ID 匹配,那么 z 会递增,因此在第一个外部循环之后它是 1 或 0。

在第二次迭代中,当 i 为 2 时,内部循环在第一次 fetch 后立即退出,因为您已经用尽了 v_RC_union 结果集。您在第一次迭代中获取了它的所有行,所以没有任何东西可以获取。所以,没有第二个 v_RC 行的 ID 永远不会与任何东西进行比较,z 也不会被触及,因为它没有达到那么远。

对于外循环的所有其他迭代,依此类推。内部循环总是立即退出,并且没有做任何有用的事情。

您可以在每次回答时重新查询 table2 以获取特定 ID,但如果您只计算它们而不使用任何列值,则您实际上不需要打开/循环/获取并且可以将该查询更改为标量变量的计数。不过,即使对于逐行处理,这似乎也不是很有效。

如果您想坚持使用全表查询,您可以使用集合:

declare
  type t_table1 is table of table1%rowtype;
  type t_table2 is table of table2%rowtype;
  v_table1 t_table1;
  v_table2 t_table2;
  i number := 0;
  j number := 0;
  z number := 0;
begin
  select * bulk collect into v_table1 from table1;
  select * bulk collect into v_table2 from table2;
  for r1 in v_table1.first..v_table1.last loop
    i := i+1;
    dbms_output.put_line('i: ' || i || ' table1 id ' || v_table1(r1).id);
    j := 0;
    for r2 in v_table2.first..v_table2.last loop
      j := j+1;
      dbms_output.put_line('i: ' || i || ' j: ' || j || ' table2 id ' || v_table2(r2).id);
      if v_table2(r2).id = v_table1(r1).id then
        z := z+1;
      end if;
    end loop;
  end loop;

  dbms_output.put_line('z: ' || z);
  dbms_output.put_line('v_RC: '||i||', v_REC_union: '||j);
end;
/

通过循环遍历两个集合,每个 i 迭代会看到每个 j 行并可以比较 ID。

i: 1 table1 id 2
i: 1 j: 1 table2 id 1
i: 1 j: 2 table2 id 2
i: 1 j: 3 table2 id 3
i: 1 j: 4 table2 id 4
i: 2 table1 id 3
i: 2 j: 1 table2 id 1
i: 2 j: 2 table2 id 2
i: 2 j: 3 table2 id 3
i: 2 j: 4 table2 id 4
i: 3 table1 id 4
i: 3 j: 1 table2 id 1
i: 3 j: 2 table2 id 2
i: 3 j: 3 table2 id 3
i: 3 j: 4 table2 id 4
i: 4 table1 id 5
i: 4 j: 1 table2 id 1
i: 4 j: 2 table2 id 2
i: 4 j: 3 table2 id 3
i: 4 j: 4 table2 id 4
z: 3
v_RC: 4, v_REC_union: 4

当您可以使用集合运算符直接在普通 SQL 中进行计数和比较时,这似乎仍然需要做很多工作,例如:

select 
  (select count(*) from table1) as i,
  (select count(*) from table2) as j,
  (select count(*) from (select id from table1 intersect select id from table2)) as z
from dual;

         I          J          Z
---------- ---------- ----------
         4          4          3

在这里使用 PL/SQL 似乎并没有太多好处; SQL 版本可能会做更多的工作,因为它会两次查询两个表(尽管可能会命中缓存),但无论如何您都可以使用 CTE 避免这种情况,而且总体上它可能比使用 PL/SQL 更快。

【讨论】:

  • 感谢@Alex 的精彩解释和示例,感谢!
猜你喜欢
  • 2019-07-05
  • 1970-01-01
  • 2012-06-28
  • 2019-07-29
  • 1970-01-01
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多