【问题标题】:How to declare a cursor after BEGIN?如何在 BEGIN 之后声明游标?
【发布时间】:2016-01-27 23:43:40
【问题描述】:

我想知道在BEGIN之后是否可以声明游标。

以及如何将 plsql 的结果导出到 Excel 工作表,因为我必须将此过程作为作业运行。

CREATE OR REPLACE PROCEDURE masc(v_amsprogramid VARCHAR2) AS

  v_mid VARCHAR2(50);
  v_sid VARCHAR2(50);

  CURSOR c1 IS
    SELECT DISTINCT mid
    FROM table_a WHERE aid = v_aid
    ORDER BY mid;

  BEGIN

    OPEN c1;

    LOOP
      FETCH c1 INTO v_mid;
      EXIT WHEN c1%NOTFOUND;

      DECLARE
        CURSOR c2 IS
          SELECT DISTINCT sid INTO v_sid
          FROM table_b WHERE mid = v_mid;

      BEGIN
        OPEN c2;
        LOOP

          FETCH c1 INTO v_mid;
          EXIT WHEN c1%NOTFOUND;

          dbms_output.PUT_LINE('MID : ' || v_mid);
          dbms_output.PUT_LINE('Sid : ' || v_sid);

        END LOOP;

        CLOSE c2;
      END LOOP;

      CLOSE c1;
  END masc;

【问题讨论】:

  • 可以,但只能在另一个 declare-begin 块内
  • 你为什么要在 PL/SQL 中这样做,或者使用游标?看起来您只需要一个简单的选择来连接两个表吗?显示您需要的输出并解释它将如何进入 Excel 可能会有所帮助 - 您是在生成 CSV 文件,还是从 Excel 中查询?
  • 嗨,Alex,我有一个包含 MID 的表和另一个包含 SID 的表。对于相应的 MID,SID 的数量可以是一个或多个。我想显示 MID 以及与这些 MID 匹配的 SID。
  • @olivia,此要求与您的实际问题“我可以在开始后声明游标吗?”无关。

标签: oracle syntax plsql cursor


【解决方案1】:

我想知道是否可以在开始后声明一个游标

不完全是。但是你可以使用 cursor for loop 而不是声明一个explicit cursor

例如,

FOR i IN (SELECT distinct MID from table_a WHERE AID = V_AID ORDER BY MID)
LOOP
   <do something>
END LOOP;

但无论如何,这会更慢,因为逐行缓慢。我认为根本不需要程序。如果您确实需要在 PL/SQL 中执行此操作,请考虑 BULK COLLECT

以及如何将 plsql 的结果导出到 Excel 工作表,因为我将这个过程作为作业运行。

在这种情况下,我认为不需要 PL/SQL。您可以简单地在 SQL*Plus 中使用 SPOOL

例如,

sqlplus user/pass@service_name
<required formatting options>

SPOOL /location/myfile.csv
SELECT distinct MID from table_a WHERE AID = V_AID ORDER BY MID;
SPOOL OFF

【讨论】:

    【解决方案2】:

    也许你正在寻找这个:

    create or replace PROCEDURE MASC (V_AMSPROGRAMID VARCHAR2) AS
    
    V_MID VARCHAR2(50);
    V_SID VARCHAR2(50);
    
    CURSOR C1 IS
    SELECT distinct MID from table_a WHERE AID = V_AID
    ORDER BY MID;
    
    CURSOR C2 IS
    SELECT DISTINCT SID INTO V_SID FROM table_b WHERE MID = V_MID
    ORDER BY MID;
    
    BEGIN    
    ...
    

    create or replace PROCEDURE MASC (V_AMSPROGRAMID VARCHAR2) AS
    
    V_MID VARCHAR2(50);
    V_SID VARCHAR2(50);
    
    CURSOR C1 IS
    SELECT distinct MID from table_a WHERE AID = V_AID
    ORDER BY MID;
    
    CURSOR C2(v in NUMBER) IS
    SELECT DISTINCT SID INTO V_SID FROM table_b WHERE MID = v
    ORDER BY MID;
    
    BEGIN
    
    OPEN C1;
    ...
    OPEN C2(V_MID);
    ...
    

    【讨论】:

      【解决方案3】:

      你可以为此目的使用参考光标

                  create or replace PROCEDURE MASC (V_AMSPROGRAMID VARCHAR2) AS
      
                  V_MID VARCHAR2(50);
                  V_SID VARCHAR2(50);
      
                  C1 sys_refcursor ;
                  c2 sys_refcursor ;
      
                  BEGIN
      
                  OPEN C1 for SELECT distinct MID from table_a WHERE AID = V_AID
                  ORDER BY MID;
      
                  LOOP
      
                  FETCH C1 INTO V_MID;
                  EXIT WHEN C1%NOTFOUND;
      
      
                  open C2 for SELECT DISTINCT SID INTO V_SID FROM table_b WHERE MID = V_MID;
      
                  LOOP
      
                  FETCH C1 INTO V_MID;
                  EXIT WHEN C1%NOTFOUND;
      
                  DBMS_OUTPUT.PUT_LINE('MID : ' || V_MID);
                  DBMS_OUTPUT.PUT_LINE('Sid : ' || V_SID);
      
      
      
                  END LOOP;
      
                  CLOSE C2;
                  CLOSE C1;
                  END LOOP;
      

      【讨论】:

      • 这不显示任何值。我想要 MID 以及每个 MID 的相应 SID。
      【解决方案4】:

      您可以在同一个 pl/sql 块中声明多个游标。打开第一个游标后,无需再声明第二个游标!

      你会这样写:

      create or replace procedure masc (p_amsprogramid varchar2)
      as
        v_mid varchar2(50);
        v_sid varchar2(50);
      
        cursor c1 
        is
          select   distinct mid
          from     table_a
          where    aid = p_amsprogramid
          order by mid;
      
        cursor c2
        is
          select distinct sid
          from   table_b
          where  mid = v_mid;
      
      begin
        open c1;
        loop
          fetch c1 into v_mid;
          exit when c1%notfound;
      
          open c2;
          loop
            fetch c1 into v_mid;
            exit when c1%notfound;
      
            dbms_output.put_line('mid : ' || v_mid);
            dbms_output.put_line('sid : ' || v_sid);
          end loop;
      
          close c2;
        end loop;
      
        close c1;
      end masc;
      /
      

      但是,如果您要将 open-cursor-loop-fetches 替换为 cursor-for-loop,则可以稍微简化一下:

      create or replace procedure masc (p_amsprogramid varchar2)
      as
        cursor c1 
        is
          select   distinct mid
          from     table_a
          where    aid = p_amsprogramid
          order by mid;
      
        cursor c2
        is
          select distinct sid
          from   table_b
          where  mid = v_mid;
      
      begin
        for rec1 in c1
        loop
          for rec2 in c2
          loop
            dbms_output.put_line('mid : ' || rec1.mid);
            dbms_output.put_line('sid : ' || rec2.sid);
          end loop;
        end loop;
      end masc;
      /
      

      看看,你有一个嵌套的游标循环。这尖叫着程序性思维,而不是基于集合的思维,当您使用数据库中的数据集时,这几乎是一个很大的禁忌(即它很慢。您必须不断在 SQL 和 PL/ 之间切换SQL 引擎,而不是简单地要求 SQL 引擎在将所有内容交付给 PL/SQL 引擎之前对其进行计算)。

      通过执行嵌套游标循环,您基本上是在重新发明 NESTED LOOP 连接 - SQL 引擎可以做得比您做得更好(更不用说它可能不是最有效的连接,SQL 引擎可以选择一个更好的加入方式!)。任何时候看到嵌套的游标循环时,都应该立即停下来看看是否可以将查询组合成一个 select 语句。 (实际上,任何时候你看到一个循环你都应该停下来考虑一下你是否真的需要它;有时这是必要的,但如果你正在做一些事情,比如选择一组结果,然后遍历每一行,然后进行更新,考虑将 select 合并到更新中,这样你就有一个语句可以一次更新所有行。它会快得多!)

      例如,您的原始程序可以重写为:

      create or replace procedure masc (p_amsprogramid varchar2)
      as
        cursor c1 
        is
          select   distinct a.mid,
                            b.sid
          from     table_a a
                   inner join table_b b on (a.mid = b.mid)
          where    a.aid = p_amsprogramid
          order by mid;
      
      begin
        for rec1 in c1
        loop
          dbms_output.put_line('mid : ' || rec1.mid);
          dbms_output.put_line('sid : ' || rec1.sid);
        end loop;
      end masc;
      /
      

      阅读、理解和维护要简单得多,我想你会同意的!

      如果您想将 sql 查询的结果写成一个文件,您需要使用UTL_FILE,而不是DBMS_OUTPUT。请记住,写入文件的目录必须是安装/映射到数据库所在服务器的目录。如果将结果以字符分隔的形式写入,则可以轻松将该文件导入 Excel。

      你可能会找到this to be of use

      【讨论】:

      • 嗨 Boneist,实际上每个 MID 都有不止一个 sid。所以我想显示 MID 以及所有 SID。这会这样做吗?很抱歉无法明确地解释我想要什么。
      • 你试过了吗?您甚至可以尝试运行 SQL 语句,看看它是否返回您期望的结果。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-04
      相关资源
      最近更新 更多