【问题标题】:Selecting Oracle object with collection of objects without bulk collect选择带有对象集合而不批量收集的 Oracle 对象
【发布时间】:2019-08-25 17:15:37
【问题描述】:

有没有办法在 jdbc 的正常选择中选择/填充 Oracle 集合(“IS TABLE OF”),即在 中没有声明变量用于批量收集或匿名块的过程Oracle12c?

问题在于选择具有对象集合的对象,因为我找不到填充集合的方法,因此它将每个对象放在单独的集合中,而不是将它们收集在一个集合中(即“返回单行查询多于一排”)。

长篇大论: 大多数out services/apis都有动态搜索参数,然后将它们转换为SQL语句。

Java 代码确实会解析有限的“搜索/位置”,对其进行清理和参数化,然后从 oracle 中选择一个对象,因此在大多数情况下,它的执行速度比 oracle 的硬解析快得多,但是我找不到解决方案用对象列表填充对象。

当前的解决方法是调用过程并让它们在声明的对象中进行批量收集,然后返回它们, 然而,由于“搜索”子句的动态特性,它们中的大多数都执行 EXECUTE IMMEDIATE,这对数据库的 CPU 来说是相当沉重的。

其他解决方案,例如每行多个查询以获取其列表或左连接并在单个 ResultSet 中获取所有内容只会以指数方式增加时间,因为提取通常用于大量列、行和嵌套列表:

CREATE OR REPLACE TYPE SUB_TYPE_OBJ Force AS OBJECT
(
  SVAR1 VARCHAR2(100 CHAR);
  SVAR2 DATE,
  SVAR3 VARCHAR2(100 CHAR)
);

CREATE OR REPLACE TYPE SUB_TYPE_LST IS TABLE OF SUB_TYPE_OBJ;

CREATE OR REPLACE TYPE MAIN_TYPE_OBJ Force AS OBJECT
(
  MVAR1 VARCHAR2(100 CHAR),
  MVAR2 VARCHAR2(100 CHAR),
  MVAR3 SUB_TYPE_OBJ,
  MVAR4 SUB_TYPE_LST
);

SELECT MAIN_TYPE_OBJ (
    tab1.val1, 
    tab1.val2,
    (SELECT SUB_TYPE_OBJ( table2.val1, table2.val2, table2.val3) obj FROM table2 WHERE tab1.val1 = tab2.val1),
    (SUB_TYPE_LST ( (SELECT SUB_TYPE_OBJ( table3.val1, table3.val2, table3.val3) obj FROM table3 DYNAMIC_WHERE_WITH_LIMITED_FUNCTIONALITY)))
    /* if it return more than one row everything breaks :( */
), CNT
FROM (
SELECT table1.*, COUNT(*) OVER(table1.val1) AS CNT FROM table1
WHERE DYNAMIC_WHERE_WITH_LIMITED_FUNCTIONALITY
ORDER BY val1 ASC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY) tab1;

如果 table3 返回多行,我们得到“单行查询返回多行” 因为我只是在创建一堆列表而不是包含所有对象的列表,但我不知道该怎么做;

欢迎提出任何想法,但请暂时排除 DBMS_SQLNoSQL :)。

【问题讨论】:

    标签: java database oracle jdbc oracle12c


    【解决方案1】:

    这里有几个选项。您可以将查询作为显式游标打开,然后 FETCH...BULK COLLECT INTO 适当的集合;您可以使用 EXECUTE IMMEDIATE...BULK COLLECT INTO;或者,如您所说,您不想听到,您可以使用 DBMS_SQL。

    要使用 EXECUTE IMMEDIATE...BULK COLLECT 你会使用类似的东西

    CREATE TABLE DATA_TABLE(FIELD1         NUMBER,
                            FIELD2         VARCHAR2(100));
    
    INSERT INTO DATA_TABLE (FIELD1, FIELD2)
      SELECT 1, 'ONE' FROM DUAL UNION ALL
      SELECT 1, 'TWO' FROM DUAL UNION ALL
      SELECT 2, 'THREE' FROM DUAL UNION ALL
      SELECT 2, 'FOUR' FROM DUAL UNION ALL
      SELECT 3, 'LAST' FROM DUAL;
    
    DECLARE
      TYPE typeCol IS TABLE OF DATA_TABLE%ROWTYPE;
      colVals        typeCol;
      strField_name  VARCHAR2(30) := 'FIELD1';
      nField_val     NUMBER := 2;
    
      strQuery       VARCHAR2(4000);
    BEGIN
      strQuery := 'SELECT * FROM DATA_TABLE WHERE ' || strField_name || '=' || nField_val;
    
      DBMS_OUTPUT.PUT_LINE(strQuery);
    
      EXECUTE IMMEDIATE strQuery BULK COLLECT INTO colVals;
    
      FOR i IN colVals.FIRST..colVals.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(i || ': FIELD1=' || colVals(i).FIELD1 || '  FIELD2=' || colVals(i).FIELD2);
      END LOOP;
    END;
    /
    

    dbfiddle here

    docs here (from 10.1 - better write-up than later versions IMO)

    【讨论】:

    • 这是当前 proc 的工作方式(传递 clob where),问题是硬解析,因为根据我们的 DBA,每个语句都是唯一的。它击中数据库,因为每分钟有数千个查询,并且自从应用程序以来这是一项双重工作。服务器已经清理了查询(约 300 条唯一语句),只是为了将其再次计划 SQL 以立即执行。我希望将所有内容直接更改为简单的参数化 sql,因为它们优于执行即时过程,其中只有主表需要搜索参数,并且所有列表都是使用 ID 作为输入的函数获取的。
    • @Plamen 你应该尝试一下EXECUTE IMMEDIATE 语句的using_clause。请参阅下面对@BobJarvis 提案的注释。
    【解决方案2】:

    实际上EXECUTE IMMEDIATE 有一个using_clause 允许传递绑定变量

    改编@Bob简单调整的例子

    strQuery := 'SELECT * FROM DATA_TABLE WHERE ' || strField_name || '= :x' ;
    
    EXECUTE IMMEDIATE strQuery BULK COLLECT INTO colVals USING nField_val;
    

    这将生成一个带有绑定变量的 SQL 语句,该变量仅被软解析

     SELECT * FROM DATA_TABLE WHERE FIELD1= :x
    

    当然这种方法有一个限制,绑定变量的数量是静态的EXECUT EIMMEDIATE 语句中定义为一个列表,例如USING a,b,c,d,e

    我想到了两个问题第一个是可选参数,即一些查询搜索 namebirth_date 一些只搜索 name

    您可以通过ORNVL 找到很多解决方案,例如herehere,但 Tom Kyte 推广的 superior solution 使用了 1=1 OR 捷径:

    如果使用绑定变量,则在 WHEREclause 中生成它

     birth_date = :birth_date 
    

    if is not used 你摆脱它生成以下谓词

    (1=1 or :birth_date  is NULL)
    

    效果如下 1) 绑定变量的数量保持固定(即使 :birth_date 未使用)和 2) 将 1=1 评估为 true 将跳过谓词的其余部分,即绑定变量无效。

    第二个问题涉及可以拥有动态成员数量的 IN 列表;在 Oracle 上从 1 到 1000。

    这里的benchmark是Hibernate的解决方案,计算IN列表的长度并生成相应的动态SQL,例如col1 in (?,?,?,?) 用于四个成员 IN 列表。您最终会得到多达 1000 个硬解析 SQL(每个长度一个),但这无论如何是对将列表粘贴到动态 SQL 中的最坏情况的对数改进

    从上述限制中您已经知道,使用EXECUTE IMMEDIATE 是不可能的(并且您需要学习新的东西;),例如DBMS_SQL)

    您可以尝试一个技巧,将 IN 列表限制为 10 个成员,并用NULLs 填充它。

    即要传递 4 个成员,您将绑定以下变量:

     col1 IN (1,2,3,4,null,null,null,null,null,null)
    

    但请注意,我没有经验是否会对长 IN 列表造成性能影响(对于短 IN 列表,这可以正常工作)。此外,如果 IN 列表中有 NULLs,这种方法对于 col1 NOT IN 肯定会失败。

    【讨论】:

      【解决方案3】:

      很简单,使用 EXTEND 将新行添加到集合中!

      首先创建一个指向集合结构的本地变量(例如:one_row) 之后:

      SUB_TYPE_LST.extend;
                                      one_row.SVAR1 := 'VALUE1';
                                      one_row.SVAR2 := sysdate;
                                      one_row.SVAR3 := 'value2';
                                      SUB_TYPE_LST(1) := one_row; -- 1 - number of row in the table - you can put a variable which will be incremented inside a loop 
      

      【讨论】:

      • 您必须为每个 MAIN_TYPE OBJECT 和子查询声明局部变量,即您需要调用一个函数,此时 BULK COLLECT INTO 会做同样的事情,但要快得多。问题是函数中的子查询需要获取“where”子句,此时你再次进入EXECUTE IMMEDIATE路由。
      • 不,不立即执行,而是打开光标,它将所有结果作为普通表返回给您
      猜你喜欢
      • 2021-01-08
      • 1970-01-01
      • 1970-01-01
      • 2013-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-08
      • 1970-01-01
      相关资源
      最近更新 更多