暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle 在pl/sql中使用关联数组时的副作用

askTom 2018-06-12
311

问题描述

我注意到在pl/sql中使用关联数组时会产生奇怪的副作用。基本上,它看起来,当数组的元素作为 “in out nocopy” 传递给过程时,然后在过程完成后,Oracle可能会将更新的元素复制回关联数组。问题是,Oracle在调用过程之前使用包含键的变量。一切都很好,直到程序也改变了那个键。

我已经提供了代码作为LiveSQL链接,但以防万一也包括在这里:
--%<---%<---%<---
declare
    type tr_zs_info is record(
        von varchar2(10),
        bis varchar2(10)
    );
    type th_zs_set is table of tr_zs_info index by varchar2(10);

    l_key varchar2(10);
    l_other varchar2(10);
    l_hash th_zs_set;

    procedure split_period(
        pl_el in out nocopy tr_zs_info,
        p_at varchar2,
        pl_key in out nocopy varchar2,
        pl_dst in out nocopy th_zs_set
    ) is
        l_obj tr_zs_info;
    begin
        dbms_output.put_line('start of split von ' || pl_el.von || ' bis ' || pl_el.bis);
        l_obj.von := p_at;
        l_obj.bis := pl_el.bis;
        pl_el.bis := p_at;
        pl_dst(p_at) := l_obj;
        pl_key := p_at;
        dbms_output.put_line('end of split '
            || p_at || ' von ' || pl_dst(p_at).von || ' bis ' || pl_dst(p_at).bis);
    end split_period; -- }}}
begin
    l_key := '2017-01-01';
    l_hash(l_key).von := l_key;
    l_hash(l_key).bis := '2017-02-01';
    l_other := l_key;
    split_period(l_hash(l_key), '2017-01-15', l_key, l_hash);
    dbms_output.put_line('after split '
        || l_key || ' von ' || l_hash(l_key).von || ' bis ' || l_hash(l_key).bis);
    dbms_output.put_line('original '
        || l_other || ' von ' || l_hash(l_other).von || ' bis ' || l_hash(l_other).bis);
end;
--%<---%<---%<---


上述代码产生

--%<---%<---%<---
start of split von 2017-01-01 bis 2017-02-01
end of split 2017-01-15 von 2017-01-15 bis 2017-02-01
after split 2017-01-15 von 2017-01-01 bis 2017-01-15
original 2017-01-01 von 2017-01-01 bis 2017-02-01
--%<---%<---%<---


因此,在程序完成后,预计键为 “2017-01” 的记录的 “bis” 等于2017-01-15,而键为 “2017-01-15” 的新记录的 “von” 等于2017-01-15。在程序结束之前就是这样。程序结束后,将交换记录。

我的问题是,这是一个错误,还是一个功能?换句话说,这应该得到修复还是记录在案?

专家解答

这个例子没有提到,但是文档确实进入了一个 * 很多 * 的细节 (和警告),关于通过引用和值传递参数,以及nocopy和参数别名的含义。

https://docs.oracle.com/en/database/oracle/oracle-database/12.2/lnpls/plsql-subprograms.html

我对您的程序进行了一些更改,以使读者更容易理解以证明差异 (作为示例和警告)

SQL> declare
  2      type my_rec is record(
  3          dateidx varchar2(20),
  4          data    varchar2(20)
  5      );
  6      type my_array is table of my_rec index by varchar2(20);
  7
  8      l_key   varchar2(10);
  9      l_array  my_array;
 10
 11      procedure print is
 12        x varchar2(20);
 13      begin
 14        dbms_output.put_line('-------------------------');
 15        dbms_output.put_line('l_key='||l_key);
 16        x :=   l_array.first;
 17        while x is not null
 18        loop
 19          dbms_output.put_line('IDX:'||x||' DATEIDX:'||l_array(x).dateidx||', DATA:'||l_array(x).data);
 20          x := l_array.next(x);
 21        end loop;
 22      end;
 23
 24      procedure split_period(
 25          pl_el  in out  nocopy  my_rec,
 26          p_at_date              varchar2,
 27          pl_key in out  nocopy  varchar2,
 28          pl_dst in out  nocopy  my_array
 29      ) is
 30          l_obj my_rec;
 31      begin
 32          print;
 33          --
 34          -- create new entry with the data from existing key
 35          --
 36          l_obj.dateidx       := p_at_date;
 37          l_obj.data          := pl_el.data;
 38          --
 39          -- update existing key with new data
 40          --
 41          pl_el.data          := 'SOME_MORE_DATA';
 42
 43          --
 44          -- insert new entry into the array
 45          --
 46          pl_dst(p_at_date)   := l_obj;
 47          --
 48          -- last used key becomes this entry
 49          --
 50          pl_key              := p_at_date;
 51          print;
 52      end split_period;
 53  begin
 54      l_key := '2017-01-01';
 55      l_array(l_key).dateidx := l_key;
 56      l_array(l_key).data := 'SOME_DATA';
 57      split_period(l_array(l_key), '2017-01-15', l_key, l_array);
 58      print;
 59  end;
 60  /
-------------------------
l_key=2017-01-01
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
-------------------------
l_key=2017-01-15
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
IDX:2017-01-15 DATEIDX:2017-01-15, DATA:SOME_DATA
-------------------------
l_key=2017-01-15
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
IDX:2017-01-15 DATEIDX:2017-01-01, DATA:SOME_MORE_DATA

PL/SQL procedure successfully completed.

SQL>
SQL> declare
  2      type my_rec is record(
  3          dateidx varchar2(20),
  4          data    varchar2(20)
  5      );
  6      type my_array is table of my_rec index by varchar2(20);
  7
  8      l_key   varchar2(10);
  9      l_array  my_array;
 10
 11      procedure print is
 12        x varchar2(20);
 13      begin
 14        dbms_output.put_line('-------------------------');
 15        dbms_output.put_line('l_key='||l_key);
 16        x :=   l_array.first;
 17        while x is not null
 18        loop
 19          dbms_output.put_line('IDX:'||x||' DATEIDX:'||l_array(x).dateidx||', DATA:'||l_array(x).data);
 20          x := l_array.next(x);
 21        end loop;
 22      end;
 23
 24      procedure split_period(
 25          pl_el  in out   my_rec,
 26          p_at_date       varchar2,
 27          pl_key in out   varchar2,
 28          pl_dst in out   my_array
 29      ) is
 30          l_obj my_rec;
 31      begin
 32          print;
 33          --
 34          -- create new entry with the data from existing key
 35          --
 36          l_obj.dateidx       := p_at_date;
 37          l_obj.data          := pl_el.data;
 38          --
 39          -- update existing key with new data
 40          --
 41          pl_el.data          := 'SOME_MORE_DATA';
 42
 43          --
 44          -- insert new entry into the array
 45          --
 46          pl_dst(p_at_date)   := l_obj;
 47          --
 48          -- last used key becomes this entry
 49          --
 50          pl_key              := p_at_date;
 51          print;
 52      end split_period;
 53  begin
 54      l_key := '2017-01-01';
 55      l_array(l_key).dateidx := l_key;
 56      l_array(l_key).data := 'SOME_DATA';
 57      split_period(l_array(l_key), '2017-01-15', l_key, l_array);
 58      print;
 59  end;
 60  /
-------------------------
l_key=2017-01-01
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
-------------------------
l_key=2017-01-01
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
-------------------------
l_key=2017-01-15
IDX:2017-01-01 DATEIDX:2017-01-01, DATA:SOME_DATA
IDX:2017-01-15 DATEIDX:2017-01-15, DATA:SOME_DATA

PL/SQL procedure successfully completed.



「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论