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

Using function inside a cursor (by using a variable)

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

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

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'在Insert near the end之前被调用。这些值被分配给变量 'var_ApmSeqno' ,该变量又在游标内使用了三次。

谢谢。

几点建议:

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

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

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

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

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

  6. 在我看来,使用 return 序列中的下一个值的函数没有任何好处。这只会让理解代码变得更加困难。摆脱 FUNCTION aSeqNoFunc 并在适当的地方调用 SOME_SEQUENCE.NEXTVAL - 所以在上面我建议你使用 var_ASeqno := SOME_SEQUENCE.NEXTVAL.

  7. 您需要在游标c1打开之前将值赋给var_ASeqno。如上所述,var_ASeqno 在游标打开时将为空,因此游标可能不会 return 您所期望的。但更重要的是,我看不出有任何理由让光标 return 成为 var_ASeqno 的值。只需在您的 INSERT 语句或其他任何需要它们的地方使用 var_ASeqno 的值。

  8. 如果数据尚不存在,请使用 MERGE 语句插入数据。这避免了笨拙的 "SELECT...catch the NO_DATA_FOUND exception...INSERT in the exception handler" 逻辑。

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

所以我会尝试将此过程重写为:

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;

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

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

如果这与您尝试执行的操作不符,请编辑您的问题以提供有关程序目的的更多信息。示例输入和输出数据将不胜感激,以便我们更好地了解您的需求(因为我们看不到您的 table 结构、数据等)。


预计到达时间:有关基于集合的方法和逐行方法之间的性能注意事项的信息。

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

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 秒?性能上的巨大差异,你不同意吗?如果这不是考虑在第一个实例中编写基于集合的代码的理由,那么我不知道还有什么能说服 you/your 老板!