在游标中使用函数(通过使用变量)

时间:2015-03-27 10:31:30

标签: sql oracle stored-procedures plsql cursor

我想确认正确使用以下内容:

1)使用全局变量只从函数中获取一次返回值  (因为我的函数将返回一些序列值)

2)多次在光标内使用该变量

3)所有这些都将在程序中

下面显示了一个示例。

CREATE OR REPLACE Procedure insert_myTable is
  --declare variables for insert
  v_firstNO VARCHAR2(10);
  v_secondNO VARCHAR2(6);

  --declare variable to store the sequence number
  var_ASeqno varchar2(6);

  -- Validation
  v_check VARCHAR2 (10 Byte);
  v_table_name varchar2(50):='myTable';

  cursor c1 is
    select distinct firstNO,
                    secondNO
      from (SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno secondNO
              FROM someOtherTable hdr
              WHERE -- some condition
            union
            SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno  secondNO
              FROM someOtherTable hdr
              WHERE -- some other conditions
            union
            SELECT hdr.someNum firstNO,
                   -- using variable to assign the sequence no
                   var_ASeqno secondNO
              FROM someOtherTable hdr
              WHERE -- some other other conditions


begin
  if c1%isopen then
    close c1;
  end if;

  v_check:=null;

  FOR i IN c1 LOOP
    --assign variables for insert
    v_firstNO := i.firstNO ;
    v_secondNO  := i.secondNO ;

    begin
      -- calling the Function aSeqNoFunc and assign the
      --Sequence Number into the variable var_ASeqno
      var_ASeqno := aSeqNoFunc();

      select firstNO
        into v_check
        from myTable a
        where firstNO = i.firstNO
              and secondNO =i.secondNO;
    exception
      when no_data_found then
        --insert into target table
        INSERT INTO myTable (firstNO, secondNO)  
          values (v_firstNO, v_secondNO);
    end ;
  end loop;
end;

可以看出,函数' aSeqNoFunc'在插入接近结束之前调用。这些值被分配给变量< var_ApmSeqno'而在光标内部又使用了三次。

谢谢。

2 个答案:

答案 0 :(得分:3)

一些建议:

  1. END;声明之后,您有一个cursor c1声明与任何内容都不匹配。您应该从代码中删除它。

  2. 进入程序时,无需检查光标是否打开。它不会成功。更好的是,不要使用显式游标声明 - 使用游标FOR-loop。

  3. 除非您知道两者之间的区别,否则请使用UNION ALL而不是UNION。 (And go read up on that。99.9%的时间你想要UNION ALL ......)。

  4. 但是,由于看起来所有行都是从同一个表中选择的,因此您可以完全取消UNION,如下所示。

  5. 在函数开头为变量赋值NULL没有任何好处。如果没有给出其他显式初始化值,变量将初始化为NULL。

  6. IMO对于从序列中返回下一个值的函数没有任何好处。它只是让理解代码更加困难。摆脱FUNCTION aSeqNoFunc并在适当的时候调用SOME_SEQUENCE.NEXTVAL - 所以在上面我建议您使用var_ASeqno := SOME_SEQUENCE.NEXTVAL

  7. 在光标var_ASeqno打开之前,您需要将值指定给c1。如上所述,var_ASeqno在光标打开时将为null,因此光标可能不会返回您期望的内容。但更重要的是,我没有看到任何理由让光标返回var_ASeqno的值。只需在INSERT语句中使用var_ASeqno的值或其他任何需要的值。

  8. 如果数据尚未存在,请使用MERGE语句插入数据。这避免了尴尬的" SELECT ...捕获NO_DATA_FOUND异常... INSERT在异常处理程序"逻辑。

  9. 正如@boneist在她的评论中指出的那样,当我们走到这一步时,光标真的没有意义。您也可以使用MERGE语句在不使用游标的情况下执行INSERT。

  10. 所以我尝试将此程序重写为:

    CREATE OR REPLACE Procedure insert_myTable is
    begin
      MERGE INTO MYTABLE m
        USING (SELECT FIRSTNO,
                      SOME_SEQUENCE.NEXTVAL AS SECONDNO
                 FROM (SELECT DISTINCT hdr.someNum AS FIRSTNO
                         FROM someOtherTable hdr
                         WHERE (/* some condition */)
                            OR (/* some other conditions */)
                            OR (/* some other other conditions */))) d
          ON d.FIRSTNO = m.FIRSTNO AND
             d.SECONDNO = m.SECONDNO
        WHEN NOT MATCHED THEN INSERT (FIRSTNO, SECONDNO)
          VALUES (d.FIRSTNO, d.SECONDNO);
    end INSERT_MYTABLE;
    

    分享并享受。

答案 1 :(得分:0)

考虑到您希望插入的所有行都分配了相同的序列号,我想您可能会将您的过程重写为:

create or replace procedure insert_mytable
is
  v_seq_no number;
begin
  v_seq_no := somesequence.nextval;

  merge into mytable tgt
  using (select firstno,
                v_seq_no secondno
         from   (select hdr.somenum firstno
                 from   someothertable1 hdr
                 where  -- some condition
                 union
                 select hdr.somenum firstno
                 from   someothertable2 hdr
                 where  -- some other conditions
                 union
                 select hdr.somenum firstno
                 from   someothertable3 hdr
                 where  -- some other other conditions
                 )
         ) src
    on (tgt.firstno = src.firstno and tgt.secondno = src.secondno)
  when not matched then
    insert (tgt.firstno, tgt.secondno)
    values (src.firstno, src.secondno);
end insert_mytable;
/

如果这与您尝试做的事情不匹配,请编辑您的问题,以提供有关该程序目标的更多信息。我们将欣赏示例输入和输出数据,以便我们更好地了解您的需求(因为我们无法查看您的表结构,数据等)。


ETA:有关基于集合和逐行方法之间性能考虑的信息。

这是一个简单的脚本,可以插入一百万行,包括逐行和单个插入语句:

create table test (col1 number,
                   col2 number);

set timing on;

-- row-by-row (aka slow-by-slow) approach
begin
  for rec in (select level col1, level * 10 col2
              from   dual
              connect by level <= 1000000)
  loop
    insert into test (col1, col2)
    values (rec.col1, rec.col2);
  end loop;
end;
/

commit;
truncate table test;

-- set based approach (keeping in an anonymous block for comparison purposes)
begin  
  insert into test (col1, col2)
  select level, level*10
  from   dual
  connect by level <= 1000000;
end;
/

commit;

drop table test;

这是我得到的输出,当我在Toad中运行上述内容时:

Table created.
 PL/SQL procedure successfully completed.
Elapsed: 00:00:21.87
Commit complete.
Elapsed: 00:00:01.03
Table truncated.
Elapsed: 00:00:00.22
 PL/SQL procedure successfully completed.
Elapsed: 00:00:01.96
Commit complete.
Elapsed: 00:00:00.03
Table dropped.
Elapsed: 00:00:00.18

您是否看到逐行方法的经过时间为21秒,基于集合的方法为2秒?性能差异很大,你不同意吗?如果没有理由考虑将您的代码编写为基于第一个实例的设置,那么我不知道还有什么能说服您/您的老板!