【问题标题】:Using function inside a cursor (by using a variable)在游标内使用函数(通过使用变量)
【发布时间】:2015-05-31 15:43:28
【问题描述】:

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

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”,该变量又在游标内使用了 3 次。

谢谢。

【问题讨论】:

  • 这似乎是一种非常低效的决定是否插入表格的方法。此外,您在打开游标后为 var_ASeqno 分配了一个值,因此游标中的值将为空。然后,一旦你分配了它,你就不会对它做任何事情。您要解决的实际问题是什么?我的直觉是,这可以在单个 SQL 语句中完成(可能是 MERGE)。还有,aSeqNoFunc() 是做什么的?
  • 是的,其实这就是问题所在。一旦我将值分配给变量,我应该在光标内使用它。函数 aSeqNoFunc 只返回序列的下一个值
  • 那么所有被插入的行都应该有相同的序列号?
  • 另外,光标中的表(“someOtherTable”)都是同一个表吗?如果是这样,为什么不将 where 子句组合成一个单独的选择,而不是将它们联合在一起。此外,如果您要使用联合,则不需要在外部查询中使用 distinct,因为联合会自动删除重复项。
  • 是的,行应该有相同的序列号。不,表格不同。我同意,不同的并不重要,可以删除。

标签: sql oracle stored-procedures plsql cursor


【解决方案1】:

一些建议:

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

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

  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. 您需要在打开光标c1 之前将值分配给var_ASeqno。如上所述var_ASeqno 在打开游标时将为空,因此游标可能不会返回您期望的内容。但更重要的是,我认为没有任何理由让光标返回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;

【讨论】:

  • 我什至不认为MERGE 是必需的,假设为secondno 列生成的序列号尚未在表中(这通常是您使用的顺序!)。此外,如果您打算在MERGE 或INSERT 中执行此操作,则不需要循环光标;既然可以一举搞定,为什么还要逐行呢?
  • 事实上,我想捕捉异常并转储到error_log表中。我省略了这部分以使代码更简单。
  • 听起来像 MERGE 是要走的路,考虑到 OP 在他们的问题上所说的内容。
  • 我想进一步提一下,在开始时,我曾尝试直接在光标中使用“nextval”,但这不起作用,我收到错误“ORA-02287 sequence number not allowed”这里'。我想到的想法是使用变量。
  • 您使用的是什么版本的 Oracle?
【解决方案2】:

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

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

【讨论】:

  • 感谢您的帮助,我很感激 :) 实际上不准确是我的错。以下是要点: 1) 必须使用光标。将来它可能会导致一些问题 2)可能会发生一些异常,我需要记录它。
  • 我同意你的观点之一 - 在光标内使用变量之前将 nextval 分配给它。
  • 1) “必须使用光标”真的吗?为什么?所以你可以减慢你的应用程序?您预计未来会出现什么样的问题?基于集合的方法通常是最快、最容易维护和最容易调试的做事方式! 2)什么样的例外? dml error logging 可能会为您解决这个问题。
  • 原因很简单,但很难理解。你不能和老板争论:v
  • 你可以和老板争论,但如果他们决定要实现一些低劣且性能或扩展性不佳的东西,那么我想你无能为力。我希望您至少指出,如果不采用基于集合的方法,您实际上是在阻碍代码!
猜你喜欢
  • 2022-01-04
  • 1970-01-01
  • 2020-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多