【问题标题】:Oracle Updating value in one collection using another collectionOracle 使用另一个集合更新一个集合中的值
【发布时间】:2019-03-07 17:18:44
【问题描述】:

我有一个使用 for 循环的解决方案,但这种方法效率低下且性能不佳。我是使用集合的新手,还不能在互联网上找到我的答案。谁能指出我正确的方向? 这是我想要实现的简化版本。

DECLARE
TYPE rec_numbers IS RECORD
(
  DIGIT NUMBER,
  ENGLISH VARCHAR2 (10),
  FRENCH VARCHAR2(10),
  ITALIAN VARCHAR2(10)
);
TYPE tab_numbers IS TABLE OF rec_numbers;
blk_num tab_numbers;
blk_num2 tab_numbers;

BEGIN

SELECT 1 DIGIT, 'ONE' ENGLISH, NULL FRENCH, NULL ITALIAN 
BULK COLLECT INTO blk_num
FROM DUAL;

SELECT 1 DIGIT, NULL ENGLISH, 'UN' FRENCH, 'UNO' ITALIAN 
BULK COLLECT INTO blk_num2
FROM DUAL;    

FOR i IN blk_num.FIRST .. blk_num.LAST LOOP
    FOR j IN blk_num2.FIRST .. blk_num2.LAST LOOP
        IF blk_num(i).digit = blk_num2(j).digit THEN
            blk_num(i).french := blk_num2(j).french;
            blk_num(i).italian := blk_num2(j).italian;
        END IF;
    END LOOP;      
END LOOP;

END;    

【问题讨论】:

  • 为什么要在这里使用集合?您的版本似乎表明您首先将表中的数据加载到集合中并进行一些比较。为什么不做所有这些都是纯 SQL?

标签: sql oracle join plsql collections


【解决方案1】:

你没有提到为什么你需要一个收藏品。如果您将 TYPE 和集合定义为模式对象,则可以使用 TABLE 函数执行简单的连接查询,以完全执行您的 for 循环所做的事情。

CREATE OR REPLACE
         TYPE rec_numbers AS OBJECT 
         ( digit      NUMBER,
         english    VARCHAR2(10),
         french     VARCHAR2(10),
         italian    VARCHAR2(10) );
         /

CREATE OR REPLACE    TYPE tab_numbers AS
              TABLE OF rec_numbers;
              /

代码

SET SERVEROUTPUT ON
DECLARE
     blk_num    tab_numbers;
     blk_num2   tab_numbers;
     blk_num3   tab_numbers;
BEGIN
     SELECT rec_numbers(1,'ONE',NULL,NULL) BULK COLLECT
       INTO blk_num
      FROM dual;

     SELECT rec_numbers(1,NULL,'UN','UNO') BULK COLLECT
       INTO blk_num2
     FROM dual;

SELECT rec_numbers
 ( a.digit,
   a.english, 
   COALESCE(b.french,a.french ),
   COALESCE(b.italian,a.italian)  
  ) BULK COLLECT
  INTO blk_num3
     FROM TABLE   ( blk_num  ) a
  LEFT JOIN TABLE ( blk_num2 ) b 
  ON a.digit = b.digit;
  blk_num := blk_num3;

  for i in 1..blk_num.count
  loop
  dbms_output.put_line(blk_num(i).digit ||','||blk_num(i).english
                ||','||blk_num(i).french||','||blk_num(i).italian
                );
  END LOOP;
END;
/

输出

1,ONE,UN,UNO

PL/SQL procedure successfully completed.

Demo

【讨论】:

  • 向任何集合添加第二个值,您会发现它不起作用。工作的一个演示dbfiddle.uk/…
  • @RavshanAbdulaev:“它不起作用”是什么意思?使用第二个值检查 this example 。此外,您的解决方案在 Oracle 11g 或 12c 中不起作用。此外,您使用过时的(+) 语法进行outer 连接,而不是ANSI 标准显式连接。见this
  • 我的错,正如你所说。我无法重现以某种方式获得意外结果的示例。无法理解我在 Oracle 11g 或 12c 中的解决方案的错误,您能解释一下吗?
【解决方案2】:

嵌套循环可能很慢。如果 blk_numblk_num2 各有 1000 条记录,则您正在执行 1000x1000 = 100 万次迭代。这是 O(n^2) 的性能。

您可以在加载后花时间将blk_num2 转换为关联数组,从而将其降低到 O(n)。然后,您无需循环遍历 blk_num2 中的所有条目,只需通过索引值查询即可找到您要查找的条目。

这是我的意思的一个例子:

DECLARE
TYPE rec_numbers IS RECORD
(
  DIGIT NUMBER,
  ENGLISH VARCHAR2 (10),
  FRENCH VARCHAR2(10),
  ITALIAN VARCHAR2(10)
);
TYPE tab_numbers IS TABLE OF rec_numbers;
blk_num tab_numbers;
blk_num2 tab_numbers;
-- Define an associative array to copy blk_num2 into.
-- Note: I did not INDEX BY PLS_INTEGER because I do not know how big DIGIT can be.
-- if DIGIT will always fit into a PLS_INTEGER, you can use that instead.
TYPE assoc_numbers IS TABLE OF rec_numbers INDEX BY VARCHAR2(30);
blk_num2_aa assoc_numbers;


BEGIN

SELECT 1 DIGIT, 'ONE' ENGLISH, NULL FRENCH, NULL ITALIAN 
BULK COLLECT INTO blk_num
FROM DUAL;

SELECT 1 DIGIT, NULL ENGLISH, 'UN' FRENCH, 'UNO' ITALIAN 
BULK COLLECT INTO blk_num2
FROM DUAL;    

-- Copy blk_num2 into blk_num2_aa
FOR i in blk_num2.FIRST..blk_num2.LAST LOOP
  blk_num2_aa(to_char(blk_num2(i).digit)) := blk_num2(i);
END LOOP;

FOR i IN blk_num.FIRST .. blk_num.LAST LOOP
  -- Find the match based on the associative array's index value
  IF blk_num2_aa.exists(to_char(blk_num(i).digit)) THEN
    blk_num(i).french := blk_num2_aa(to_char(blk_num(i).digit)).french;
    blk_num(i).italian := blk_num2_aa(to_char(blk_num(i).digit)).italian;
  END IF;
END LOOP;

END;

【讨论】:

    【解决方案3】:

    这个怎么样?

    update (select * from table(blk_num )) a
    set (FRENCH, ITALIAN) = 
        (select FRENCH, ITALIAN 
         from table(blk_num2) b 
         where a.DIGIT = b.DIGIT);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-18
      • 2011-08-10
      相关资源
      最近更新 更多