如何从选择查询向 PL/SQL 循环提供值

Posted

技术标签:

【中文标题】如何从选择查询向 PL/SQL 循环提供值【英文标题】:How to supply values to a PL/SQL loop from a select query 【发布时间】:2015-04-22 10:33:54 【问题描述】:

各位朋友,我有一个 perl 脚本,它通过 sqlplus 与 oracle Db 交互。在这里,我正在运行两个查询。第一个查询从视图中获取特定日期的序列号并写入文件。

SELECT DISTINCT serialnumber FROM VOUCHERHISTORYCLIENTV WHERE at LIKE '&1';    (where &1 = $date)

然后 perl 循环读取此文件,逐行获取序列号并运行以下查询,将数据写入另一个文件,然后由以下 perl 代码处理。现在这些序列号太大了,所以循环的每次迭代都连接到 sqlplus 运行查询并生成输出然后断开连接。这就是为什么它花费了太多时间。

SELECT * FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV WHERE serialnumber = '&1';    (where &1 = $serialnumber)

有什么方法不需要我一次又一次地连接和断开 sqlplus 会话?我最终想要的是附加在 unix 文件上的每个迭代的第二个查询的结果..以便我的 perl 脚本可以格式化它们。我猜 pl/sql 循环可以做到这一点……但我从来没有在 pl/sql 上工作过……你能帮帮我吗?

PS:我不能在这里使用 DBD::oracle,因为我在 solaris 机器上安装这个模块时遇到了太多问题,因为这个服务器是第 3 方服务器,所以我无法对此进行任何更改

更新:1

我尝试了以下两个程序并针对表有 600 万条记录的特定日期运行。但是查询一直在运行并且即使在 2 天后也没有产生任何输出......

程序#1:

  DECLARE

    CURSOR CUR1 IS
    SELECT DISTINCT serialnumber FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV WHERE at LIKE '&1';

    CURSOR CUR2(p_ser_num NUMBER) IS
    SELECT serialnumber, state, at as time1, operatorid  FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV WHERE serialnumber = p_ser_num;

    BEGIN

    FOR l_cur1 IN CUR1
    LOOP
      NULL;
      FOR l_cur2 IN CUR2(l_cur1.serialnumber)
      LOOP

       DBMS_OUTPUT.PUT_LINE(l_cur2.serialnumber||' '||l_cur2.state ||' '||l_cur2.time1 ||' '||l_cur2.operatorid );

      END LOOP;
    END LOOP;
    END;

    /

    quit;

程序#2:

DECLARE

CURSOR CUR1 IS
SELECT DISTINCT serialnumber FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV WHERE at LIKE '&1';

TYPE t_tab1 IS TABLE OF CUR1%ROWTYPE;

l_tab1 t_tab1;

CURSOR CUR2(p_ser_num NUMBER) IS
SELECT serialnumber, state, at as time1, operatorid  FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV WHERE serialnumber = p_ser_num;

TYPE t_tab2 IS TABLE OF CUR2%ROWTYPE;

l_tab2 t_tab2;


BEGIN

  OPEN CUR1;
  LOOP
   FETCH CUR1 BULK COLLECT INTO l_tab1;
   EXIT WHEN CUR1%NOTFOUND;
  END LOOP;
  CLOSE CUR1;
 FOR i in 1..l_tab1.COUNT
   LOOP
    OPEN CUR2(l_tab1(i).serialnumber);
       LOOP
          FETCH CUR2 BULK COLLECT INTO l_tab2;
          EXIT WHEN CUR2%NOTFOUND;
       END LOOP;
    CLOSE CUR2;
       for j in 1..l_tab2.COUNT
            LOOP
              DBMS_OUTPUT.PUT_LINE(l_tab2(j).serialnumber||' '||l_tab2(j).state ||' '||l_tab2(j).time1 ||' '||l_tab2(j).operatorid );
            END LOOP;
   END LOOP;

END;

/

quit;

我可以改进上述程序还是有其他方法可以为我们完成这项工作?请帮忙。

【问题讨论】:

您能否从代码中删除 DBMS_OUTPUT(整个循环)并运行并检查时间? 另外,为了安全,请在 FETCH 中使用 LIMIT 子句 - “FETCH CUR2 BULK COLLECT INTO l_tab2 限制 10000" 您尝试过使用索引吗?当然,以上所有内容都假设 VOUCHERHISTORYCLIENTV 是您提到的表而不是视图 对不起,我犯了一个错误。我忘了提到 VOUCHERHISTORYCLIENTV 是一个视图而不是表...它从另外 2 个视图和其他 6 到 7 个表中获取数据...同时我将根据您的建议工作并让您知道结果... Man.. 这意味着您需要优化底层视图查询 - 递归地 - 直到您到达所有视图 - 因为这是实际触发的内容。(SELECT * FROM MY_VIEW == THE QUERY OF风景)。你有很多分析要做:) 【参考方案1】:

一次,您可以用这个单一的查询替换 2 个查询:

SELECT * FROM VSOWNERUPE07.VOUCHERHISTORYCLIENTV 
   WHERE serialnumber IN (SELECT DISTINCT serialnumber FROM  
                           VOUCHERHISTORYCLIENTV WHERE at LIKE '&1');

并写入输出到您的最终文件。

您发现这有什么问题吗?

更新1 首先,将它分成 2 个不同的查询几乎从来都不是更好的解决方案。SQL 上下文切换会杀死你。 分别在两个表的serialnumberat 列上建立索引并尝试使用单个查询。 如果即使速度很慢,您也可以尝试 UPDATE2。

更新2

如果有太多记录是问题所在,则使用批量处理。 这个过程有点乏味,但旨在处理这类情况。

如何进行批量处理的一些示例是here、here 和here。

为此,您可能必须编写一个 PLSQL 过程。 在该过程中,从您的第一个查询中将所有数据批量收集到一个可变数组中。 现在使用此变量运行第二个选择查询并收集您的数据。

下一步 您可以将这些序列号放入,比如说另一个 v 数组并将其写入文件 或 创建一个新的单列表并将其写入此新表。 然后将其写入文件/分别从 perl 查询这个新表。 注1: 如果您使用单独的表,请在每次运行后截断它;并在其上建立索引 注意 2:如果您不知道我在说什么,请先查看这些链接。

【讨论】:

谢谢.. 是的,我尝试了这个查询,但是由于 Db 视图太大并且包含数百万条记录.. 查询被挂起并且无法产生输出。这就是为什么我把它分成两部分...... 感谢您的帮助.. 我浏览了链接并试图找出解决方案,但又卡住了.. 我已经在原始问题中解释了我的问题。你能帮忙吗..

以上是关于如何从选择查询向 PL/SQL 循环提供值的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PL/SQL 中使用循环多次运行相同的查询?

如何在 Dbms_output.put_line(''); 中使用 <br> 标签在 PL/SQL 甲骨文中?

如何在 Oracle PL/SQL 过程的开始部分之后声明游标

带有参数的 PL/SQL 过程/函数从选择查询返回表

从 PL/SQL 中的过程返回值数组

从循环/选择语句中插入表值作为变量