【问题标题】:Exit Loop when first record found找到第一条记录时退出循环
【发布时间】:2021-07-30 04:58:32
【问题描述】:

我有一个游标,它可能会为 id 返回 2 行。我想遍历光标并在找到每个 id 的第一行时退出。

Cursor find_c is 
Select t1.year,t1.sum(charges) charge
  from table t1
  join table t2 on t1.id=t2.id and t1.charge_id=t2.charge_id
where t1.id='3456'
group by t1.year
order by 2 desc;

数据:

Year Charge
2021  12
2020  56 

程序

Begin 
For lc in find_c loop 
   IF lc.charge > 0 then 
      lv_year = lc.year
   END IF 
END LOOP
End;

循环应该只返回第一行,即 lv_year='2021'。 在光标中添加 rownum=1 将使我获得最新的行,但我正在寻找 for 循环中的某些内容,以便在找到第一年的 id 时退出

【问题讨论】:

  • 您的问题含糊不清。您最初说“循环通过光标并在找到第一行时退出”,但后来说“在为 id 找到第一年时退出”。那些不一样。此外,您的示例数据中没有 ID。那么你想要第一行还是每个id的第一行。请通过更新问题来澄清。
  • @Belayer:编辑了问题。寻找每个 id 的第一行,而不使用 rownum 并仅在 cursor 中获取下 1 行。

标签: sql oracle for-loop plsql plsql-package


【解决方案1】:

您没有指定您使用的 Oracle 数据库版本。

如果是 11g,那么fetch 子句将不起作用(因为它还不存在):

SQL> select * from dept
  2  fetch next 1 rows only;
fetch next 1 rows only
      *
ERROR at line 2:
ORA-00933: SQL command not properly ended

另一个答案建议根本不要使用游标或循环 - 这完全有道理,但是你必须编写更多代码以避免TOO_MANY_ROWS(因为你不能将两行放入标量变量中)和,可能是NO_DATA_FOUND。有了游标,您就不必担心,因为 Oracle 会为您处理它。

因此,您可以按照您的描述进行 - 只运行一次循环迭代。但是,完全使用循环吗?只需open - fetch - close。这意味着您的代码可能看起来像这样(基于 Scott 的示例架构,因为我没有您的表)。

“模拟”您的查询会获取每个部门的工资总额。我对最高薪水感兴趣。

SQL> select d.dname, sum(e.sal) sumsal
  2  from emp e join dept d on d.deptno = e.deptno
  3  group by d.dname
  4  order by sumsal desc;

DNAME              SUMSAL
-------------- ----------
RESEARCH            10875        --> that's what I want
SALES                9400
ACCOUNTING           8750

程序:

SQL> declare
  2    cursor find_c is
  3      select d.dname, sum(e.sal) sumsal
  4      from emp e join dept d on d.deptno = e.deptno
  5      group by d.dname
  6      order by sumsal desc;
  7    cur_r find_c%rowtype;
  8    lv_dname dept.dname%type;
  9  begin
 10    open find_c;
 11    fetch find_c into cur_r;
 12
 13    if cur_r.sumsal > 0 then
 14       lv_dname := cur_r.dname;
 15    end if;
 16
 17    dbms_output.put_line('Picked DNAME = ' || lv_dname ||
 18      '. Now, do something here, execute some more code');
 19
 20    -- some code goes here
 21    dbms_output.put_line('First iteration is over; that''s the end');
 22    close find_c;
 23  end;
 24  /
Picked DNAME = RESEARCH. Now, do something here, execute some more code    --> good, RESEARCH is here
First iteration is over; that's the end

PL/SQL procedure successfully completed.

SQL>

只是为了表明没有光标你可能会遇到一些问题:

TOO_MANY_ROWS:

SQL> declare
  2    l_dname  dept.dname%type;
  3    l_sumsal number;
  4  begin
  5    select d.dname, sum(e.sal) sumsal
  6      into l_dname, l_sumsal
  7      from emp e join dept d on d.deptno = e.deptno
  8      group by d.dname
  9      order by sumsal desc;
 10    dbms_output.put_line('Query executed');
 11  end;
 12  /
declare
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 5

NO_DATA_FOUND:

SQL> declare
  2    l_dname  dept.dname%type;
  3    l_sumsal number;
  4  begin
  5    select d.dname, sum(e.sal) sumsal
  6      into l_dname, l_sumsal
  7      from emp e join dept d on d.deptno = e.deptno
  8      where 1 = 2                    --> will cause NO_DATA_FOUND
  9      group by d.dname
 10      order by sumsal desc;
 11    dbms_output.put_line('Query executed');
 12  end;
 13  /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 5


SQL>

当然,您可以解决这个问题(例如,使用异常处理程序部分),但是 - 为什么要麻烦?


[编辑,根据您的评论]

如果游标为每个 ID 返回多于一行并且您想跳过其余行,一个选择是使用 ROW_NUMBER 分析函数对行进行“排序”,然后仅获取每个 ID 的第一行.在我基于 Scott 表格的示例中,它看起来像这样:

cursor find_c is
  select dname, sumsal
  from (select d.dname, sum(e.sal) sumsal,
          row_number() over (partition by d.dname order by sum(e.sal) desc) rn
        from emp e join dept d on d.deptno = e.deptno
        group by d.dname
       )
  where rn = 1;

根据您的数据模型调整它。

【讨论】:

  • -谢谢。我的光标将为通过循环的每个 id 获取 2 行。但我希望循环获取 id 的第一行,然后移动到下一个 id。
  • 不客气。我在答案的底部添加了更多信息。请看一下。
【解决方案2】:

如果您的目标是一条记录,那么游标就是开销。我建议变量。由于另一个答案引起了对异常的关注,这也是一个有效的场景。您可以处理 no data found 异常,因为在我的查询中已经处理了多行,仅获取第一行。

Declare
Var1 <type> :=<initial value>;
Var2 <type> :=<initial value>;

Begin

Select x,y into var1,var2 from <table>
Order by x,y
Fetch first row only;

Exception when no_data_found
Var1 := <some value>;
Var2 := <some value>;
End;

【讨论】:

    【解决方案3】:

    然后对一行使用游标循环没有多大意义,但这应该可以工作

    Cursor find_c is 
    Select t1.year,t1.sum(charges) charge
      from table t1
      join table t2 on t1.id=t2.id and t1.charge_id=t2.charge_id
    where t1.id='3456'
    group by t1.year
    order by 2 desc
    FETCH NEXT 1 ROWS ONLY;
    

    【讨论】:

    • 谢谢,但我不想限制光标中的行数。
    猜你喜欢
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 1970-01-01
    • 2012-05-09
    相关资源
    最近更新 更多