通过部分填充从 Oracle 过程返回对象类型?

Posted

技术标签:

【中文标题】通过部分填充从 Oracle 过程返回对象类型?【英文标题】:Returning Object Type from Oracle Procedure by partially populating it? 【发布时间】:2017-07-12 10:28:44 【问题描述】:

我在 Oracle 中创建了一个对象类型,如下所示:

CREATE OR REPLACE TYPE Generic_MDMU_Scrub_Cols_OBJ as Object 
( 
  i_strTypeOfEntry varchar2(50),
  ID_SCRUB number,
  NM_DATA_COL1 varchar2(50),
  NM_DATA_COL2 varchar2(50),
  NM_DATA_COL3 varchar2(50),
  NM_DATA_COL4 varchar2(50),
  NM_DATA_COL5 varchar2(50),
  NM_DATA_COL6 varchar2(50),
  NM_DATA_COL7 varchar2(50),
  NM_DATA_COL8 varchar2(50),
  NM_DATA_COL9 varchar2(50),
  NM_DATA_COL10 varchar2(50),
  NM_DATA_COL11 varchar2(50),
  NM_DATA_COL12 varchar2(50),
  NM_DATA_COL13 varchar2(50),
  NM_DATA_COL14 varchar2(50),
  NM_DATA_COL15 varchar2(50),
  NM_DATA_COL16 varchar2(50),
  NM_DATA_COL17 varchar2(50),
  NM_DATA_COL18 varchar2(50),
  NM_DATA_COL19 varchar2(50),
  NM_DATA_COL20 varchar2(50),
  DATA_VAL1 varchar2(50),
  DATA_VAL2 varchar2(50),
  DATA_VAL3 varchar2(50),
  DATA_VAL4 varchar2(50),
  DATA_VAL5 varchar2(50),
  DATA_VAL6 varchar2(50),
  DATA_VAL7 varchar2(50),
  DATA_VAL8 varchar2(50),
  DATA_VAL9 varchar2(50),
  DATA_VAL10 varchar2(50),
  DATA_VAL11 varchar2(50),
  DATA_VAL12 varchar2(50),
  DATA_VAL13 varchar2(50),
  DATA_VAL14 varchar2(50),
  DATA_VAL15 varchar2(50),
  DATA_VAL16 varchar2(50),
  DATA_VAL17 varchar2(50),
  DATA_VAL18 varchar2(50),
  DATA_VAL19 varchar2(50),
  DATA_VAL20 varchar2(50),
  OLD_DATA_VAL1 varchar2(50),
  OLD_DATA_VAL2 varchar2(50),
  OLD_DATA_VAL3 varchar2(50),
  OLD_DATA_VAL4 varchar2(50),
  OLD_DATA_VAL5 varchar2(50),
  OLD_DATA_VAL6 varchar2(50),
  OLD_DATA_VAL7 varchar2(50),
  OLD_DATA_VAL8 varchar2(50),
  OLD_DATA_VAL9 varchar2(50),
  OLD_DATA_VAL10 varchar2(50),
  OLD_DATA_VAL11 varchar2(50),
  OLD_DATA_VAL12 varchar2(50),
  OLD_DATA_VAL13 varchar2(50),
  OLD_DATA_VAL14 varchar2(50),
  OLD_DATA_VAL15 varchar2(50),
  OLD_DATA_VAL16 varchar2(50),
  OLD_DATA_VAL17 varchar2(50),
  OLD_DATA_VAL18 varchar2(50),
  OLD_DATA_VAL19 varchar2(50),
  OLD_DATA_VAL20 varchar2(50)
);
/  

现在我想编写一个过程,它将这个对象类型作为输出参数返回给调用 Java 代码。基本上有几个表存储像OLD_DATA_VAL1...OLD_DATA_VAL20DATA_VAL1...DATA_VAL120 这样的列,类似地另一个表具有NM_DATA_COL1...NM_DATA_COL20 列,并且根据不同的用例,只有3 或5 个或任意数量的列将具有值,而其他列将为空。例如-OLD_DATA_VAL1,OLD_DATA_VAL2,OLD_DATA_VAL3,DATA_VAL1,DATA_VAL2,DATA_VAL3,NM_DATA_COL1,NM_DATA_COL2,NM_DATA_COL3 有值,其他列为空。我创建了一个元表,它会给我逗号分隔的列名。

现在,当我尝试使用类型或游标时,问题是由于未填充所有值,因此会引发错误。 我的程序是这样的:

create or replace PROCEDURE METADATA_AUTOMATION_MDPR (
    in_id_scrub IN NUMBER
)
AS
  V_COL_NAMES_CURRENT_SCRUB_DATA VARCHAR2(500);
  V_COL_NAMES_OLD_SCRUB_DATA VARCHAR2(500);
  V_COL_HEADER_MAP_WORKFLOW VARCHAR2(500);
  sqlstmt  VARCHAR2(2000);
  V_Generic_MDMU_Scrub_Cols Generic_MDMU_Scrub_Cols_OBJ;

SELECT COL_NAMES_CURRENT_SCRUB_DATA, COL_NAMES_OLD_SCRUB_DATA, COL_HEADER_MAP_WORKFLOW INTO V_COL_NAMES_CURRENT_SCRUB_DATA, V_COL_NAMES_OLD_SCRUB_DATA, V_COL_HEADER_MAP_WORKFLOW FROM MDDBO.MDMU_METADATA_AUTOMATION_MDTB WHERE ID_WORKFLOW_MAINTENANCE_MSTR = in_id_wf;
 DBMS_OUTPUT.PUT_LINE(V_COL_NAMES_CURRENT_SCRUB_DATA || ' --- ' || V_COL_NAMES_OLD_SCRUB_DATA || ' --- ' || V_COL_HEADER_MAP_WORKFLOW);

 sqlstmt:= 'select ' || V_COL_NAMES_CURRENT_SCRUB_DATA || ',' || V_COL_NAMES_OLD_SCRUB_DATA || ',' || V_COL_HEADER_MAP_WORKFLOW || ',AUTO.TYPE_OF_ENTRY i_strTypeOfEntry FROM MDDBO.MDMU_SCRUB_DATA_MDTB DATA, MDDBO.MDMU_SCRUB_LOG_MDTB MSTR, MDDBO.MDMU_MAP_WORK_FLOW_MDTB WKF,
 MDDBO.MDMU_METADATA_AUTOMATION_MDTB AUTO WHERE
 MSTR.ID_TBL = WKF.ID_WF and
 MSTR.id_scrub = :in_id_scrub and MSTR.id_scrub = DATA.id_scrub and
 AUTO.ID_WORKFLOW_MAINTENANCE_MSTR = WKF.ID_WF';

 DBMS_OUTPUT.PUT_LINE(sqlstmt);

 EXECUTE IMMEDIATE sqlstmt
  INTO V_Generic_MDMU_Scrub_Cols
  USING in_id_scrub;


 EXCEPTION
  WHEN OTHERS THEN
  DBMS_OUTPUT.PUT_LINE('error '||sqlerrm);

END METADATA_AUTOMATION_MDPR;

但如果是类型,它会抛出如下所示的 Oracle 错误-

error ORA-00932: inconsistent datatypes: expected - got -

一些 Oracle 专家建议使用全局临时表 (GTT),但我相信它们在所有会话中都很常见,在这里不可行。所以,如果我想部分填充,但我不确定这是否是最好的情况,或者可以用对象完成一些事情,比如将未使用的值部分初始化为 null。

更新

从 2 个语句中打印 dbms_ouput 以使事情更清晰

输出1:

DATA_VAL1,DATA_VAL2,DATA_VAL3,DATA_VAL4,DATA_VAL5 --- OLD_DATA_VAL1,OLD_DATA_VAL2,OLD_DATA_VAL3,OLD_DATA_VAL4,OLD_DATA_VAL5 --- NM_DATA_COL1,NM_DATA_COL2,NM_DATA_COL3,NM_DATA_COL4,NM_DATA_COL5

输出2:

select DATA_VAL1,DATA_VAL2,DATA_VAL3,DATA_VAL4,DATA_VAL5,OLD_DATA_VAL1,OLD_DATA_VAL2,OLD_DATA_VAL3,OLD_DATA_VAL4,OLD_DATA_VAL5,NM_DATA_COL1,NM_DATA_COL2,NM_DATA_COL3,NM_DATA_COL4,NM_DATA_COL5,AUTO.TYPE_OF_ENTRY i_strTypeOfEntry FROM MDDBO.MDMU_SCRUB_DATA_MDTB DATA, MDDBO.MDMU_SCRUB_LOG_MDTB MSTR, MDDBO.MDMU_MAP_WORK_FLOW_MDTB WKF,
 MDDBO.MDMU_METADATA_AUTOMATION_MDTB AUTO WHERE
 MSTR.ID_TBL = WKF.ID_WF and
 MSTR.id_scrub = :in_id_scrub and MSTR.id_scrub = DATA.id_scrub and
 AUTO.ID_WORKFLOW_MAINTENANCE_MSTR = WKF.ID_WF

主要问题 - 与对象类型中的列数相比,动态选择的列数更少。 (见输出 1,每个也可以有 3 个)

更新 2

现在我主要关心的是如何从由可变数量的选定列形成的动态 sql 中获取值。

使用下面的查询,我可以获取列标题,但不知何故无法获取 col 值。我的查询将只返回一行-

OPEN rc_ FOR sqlstmt using in_id_scrub;
   c_ := DBMS_SQL.to_cursor_number(rc_);
   DBMS_SQL.DESCRIBE_COLUMNS(c_, col_count_, desc_tab_);
   FOR i_ IN 1..col_count_ LOOP
      DBMS_OUTPUT.PUT_LINE(desc_tab_(i_).col_name);     
      DBMS_SQL.DEFINE_COLUMN(c_, i_, desc_tab_(i_).COL_NAME, 2000);
   END LOOP;
   res := DBMS_SQL.EXECUTE_AND_FETCH(c_, TRUE);
   --DBMS_OUTPUT.PUT_LINE(res);
   FOR i IN 1..col_count_ LOOP
        tab1.EXTEND;
        DBMS_SQL.COLUMN_VALUE(c_, i, tab1(tab1.LAST));
        --DBMS_SQL.COLUMN_VALUE(c_, i, tab1(i));
        --DBMS_SQL.COLUMN_VALUE(c_, i, arr1);
        --DBMS_OUTPUT.PUT_LINE(tab1(1));
   END LOOP;
   DBMS_SQL.CLOSE_CURSOR(c_);

  FOR l_row IN 1 .. tab1.COUNT
      LOOP
         DBMS_OUTPUT.put_line (tab1 (l_row));
      END LOOP;

【问题讨论】:

不,GTT 不是对于所有会话都是通用的,它们的数据对于插入它的会话是私有的。 那么是这种情况下的最佳解决方案还是我们仍然可以使用对象类型? 我不知道。通过 PL/SQL 过程在 GTT 中创建的数据对通过不同会话连接的 Java 程序不可用。我对使用 Java 的了解还不够,无法说出是否会这样。 托尼谢谢,请不要考虑 Java。认为我必须在 SqlDeveloper 中本地运行它。我们可以使用 Object 来做到这一点吗?如果可能的话,您可以使用 Object 或使用 GTT 的任何伪代码发布解决方案吗? 重新更新 2:DBMS_SQL.EXECUTE_AND_FETCH 将只返回 1 行;您需要在循环中使用 DBMS_SQL.EXECUTE,然后使用 DBMS_SQL.FETCH_ROWS,直到没有更多行。 【参考方案1】:

您的具体问题似乎是这一行:

sqlstmt:= 'select ' || V_COL_NAMES_CURRENT_SCRUB_DATA || ',' || V_COL_NAMES_OLD_SCRUB_DATA || ',' || V_COL_HEADER_MAP_WORKFLOW || ',AUTO.TYPE_OF_ENTRY i_strTypeOfEntry FROM MDDBO.MDMU_SCRUB_DATA_MDTB DATA, MDDBO.MDMU_SCRUB_LOG_MDTB MSTR, MDDBO.MDMU_MAP_WORK_FLOW_MDTB WKF,
 MDDBO.MDMU_METADATA_AUTOMATION_MDTB AUTO WHERE
 MSTR.ID_TBL = WKF.ID_WF and
 MSTR.id_scrub = :in_id_scrub and MSTR.id_scrub = DATA.id_scrub and
 AUTO.ID_WORKFLOW_MAINTENANCE_MSTR = WKF.ID_WF';

...据我所知,因为例如V_COL_NAMES_CURRENT_SCRUB_DATA 可能为空?

在这种情况下,一个简单的 NVL 就足够了:

sqlstmt:= 'select ' || NVL(V_COL_NAMES_CURRENT_SCRUB_DATA, 'null as col1') || ...

所以现在代替:

select ,,SOMECOL,...

你会得到:

select null as col1, null as col2, SOMECOL, ...

【讨论】:

没有任何 V_COL_XXX 不会为空。问题是与 Oracle 对象类型相比,它的列数更少。我将使用 DBMS 输出和元表条目更新我的问题,以使其更清晰。 您不能根据您的类型需要动态连接尽可能多的, null as colNN 列吗? 为此,我需要知道列名,我知道通过另一个表具有值的列名,但不存储没有值的列名。能够做到这一点的最佳做法是什么。我希望我们可以用 null 实例化一个对象的所有元素,并且当我们执行 into 子句时,只有我们选择的那些列会得到更新! 对不起,我并没有真正关注你。您正在动态创建一个 SELECT 语句,并且您需要它返回 N 列,其中 N 是您的类型中的属性数。你知道你添加了多少列(比如 M),所以你需要通过添加 ', null as colNN` (N-M) 次来将它“填充”到 N 列?例如如果您的类型有 100 个属性,但您的动态选择只返回 42,那么您知道您需要添加 null as col43, null as col44, ..., null as col100。所以你可以写一个循环来做到这一点。我错过了什么? 那么问题是默认构造函数不是你需要的吗?如果是这样,那就自己写吧。

以上是关于通过部分填充从 Oracle 过程返回对象类型?的主要内容,如果未能解决你的问题,请参考以下文章

从 0jdbc6 JDBCthin 驱动程序调用具有自定义对象返回类型的 Oracle PL/SQL 过程

Oracle DBMS 能否从 Java 存储过程调用返回 Java 对象?

当嵌套表属于记录类型时,如何将数据填充到 Oracle 中的嵌套表中

如何从 Oracle 存储过程中获取两个返回值

oracle对象之存储函数

oracle 如何返回多条记录