什么导致在使用Oracle PL/SQL表类型的MERGE语句时出现“无效数据类型”错误?

huangapple go评论65阅读模式
英文:

What is causing the 'invalid datatype' error when using MERGE statement with Oracle PL/SQL table types?

问题

以下是翻译好的内容:

MERGE使用Oracle PL/SQL表类型

我想使用MERGE语句创建/更新记录。但是出现错误"无效的数据类型"。

表设置

CREATE TABLE ps_test01 (id Number primary key,
                        name varchar2(30),
                        active_flag VARCHAR2(1));
                        
                        
CREATE TABLE ps_test02 (id Number primary key,
                        name varchar2(30));
                        
                        
insert into ps_test01 (id, name, active_flag) values (1, 'test01', 'Y');
insert into ps_test01 (id, name, active_flag) values (2, 'test02', 'Y');
insert into ps_test01 (id, name, active_flag) values (3, 'test03', 'Y');

insert into ps_test02 (id, name) values (1, 'test001');

记录类型

create or replace package test_pkg as

TYPE test_rec IS RECORD
   (
      id    number,
      name  varchar2(30),
      active_flag   varchar2(1)
   );
   
   TYPE test_tab_type IS TABLE OF test_rec;

end test_pkg;
/

引发错误的代码

set SERVEROUTPUT on;
declare
    l_test varchar(10);
    
    cursor cur_ps_test01 IS
        select id, name ,active_flag from ps_test01 where active_flag='Y';
        
    --TYPE test_tab_type is TABLE OF cur_ps_test01%ROWTYPE index by pls_integer;
    test_tab test_pkg.test_tab_type;
    test_tab2 test_pkg.test_tab_type;
        
begin
    
    open cur_ps_test01;
    fetch cur_ps_test01 bulk collect into test_tab;
    close cur_ps_test01;
    
    dbms_output.put_line('number of rows fetched : ' || test_tab.count); 
    
    select * bulk collect into test_tab2 from table(test_tab);
    dbms_output.put_line('number of rows fetched : ' || test_tab2.count); 
    
    merge into ps_test02 tab2 -- error was reported on this line
        using ( select id,name,active_flag from table(test_tab) ) tab1
        on (tab1.id = tab2.id)
        when matched then
            update set name = tab1.name
        when not matched then
            insert (id, name) values (tab1.id, tab1.name);
    
end;
/

错误信息

00902. 00000 -  "无效的数据类型"
*原因:
*操作:

请帮助我解决这个问题。如果有的话,欢迎提出更好的解决方法。

英文:

MERGE using Oracle PL/SQL table type

I want to create/update records using MERGE statement. But getting an error "Invalid datatype".

Table setup

CREATE TABLE ps_test01 (id Number primary key,
                        name varchar2(30),
                        active_flag VARCHAR2(1));
                        
                        
CREATE TABLE ps_test02 (id Number primary key,
                        name varchar2(30));
                        
                        
insert into ps_test01 (id, name, active_flag) values (1, 'test01', 'Y');
insert into ps_test01 (id, name, active_flag) values (2, 'test02', 'Y');
insert into ps_test01 (id, name, active_flag) values (3, 'test03', 'Y');

insert into ps_test02 (id, name) values (1, 'test001');

Record type

create or replace package test_pkg as

TYPE test_rec IS RECORD
   (
      id    number,
      name  varchar2(30),
      active_flag   varchar2(1)
   );
   
   TYPE test_tab_type IS TABLE OF test_rec;

end test_pkg;
/

Code that is giving error

set SERVEROUTPUT on;
declare
    l_test varchar(10);
    
    cursor cur_ps_test01 IS
        select id, name ,active_flag from ps_test01 where active_flag='Y';
        
    --TYPE test_tab_type is TABLE OF cur_ps_test01%ROWTYPE index by pls_integer;
    test_tab test_pkg.test_tab_type;
    test_tab2 test_pkg.test_tab_type;
        
begin
    
    open cur_ps_test01;
    fetch cur_ps_test01 bulk collect into test_tab;
    close cur_ps_test01;
    
    dbms_output.put_line('number of rows fetched : ' || test_tab.count); 
    
    select * bulk collect into test_tab2 from table(test_tab);
    dbms_output.put_line('number of rows fetched : ' || test_tab2.count); 
    
    
    merge into ps_test02 tab2 -- error was reported on this line
        using ( select id,name,active_flag from table(test_tab) ) tab1
        on (tab1.id = tab2.id)
        when matched then
            update set name = tab1.name
        when not matched then
            insert (id, name) values (tab1.id, tab1.name);
    
end;
/

Error

00902. 00000 -  "invalid datatype"
*Cause:    
*Action:

Please help me resolve this issue.
Appreciate if you could suggest a better way of doing it if any.

答案1

得分: 1

以下是您要翻译的内容:

Oracle Database 12c Release 1 (12.1)中的更改中列出了对PL/SQL类型使用的限制。

更多的PL/SQL专用数据类型可以跨越PL/SQL到SQL接口

自Oracle Database 12c起,可以将PL/SQL专用数据类型的值绑定到匿名块(这些块是SQL语句)、SQL查询中的PL/SQL函数调用和CALL语句以及SQL查询中的TABLE运算符。但是:

  • ...
  • 如果PL/SQL专用数据类型是关联数组,则不能在非查询DML语句(INSERT、UPDATE、DELETE、MERGE)中使用它,也不能在子查询中使用它。

您可以使用FORALL语句从集合执行upsert:

declare
    l_test varchar(10);

    cursor cur_ps_test01 IS
        select id, name ,active_flag from ps_test01 where active_flag='Y';

    --TYPE test_tab_type is TABLE OF cur_ps_test01%ROWTYPE index by pls_integer;
    test_tab test_pkg.test_tab_type;
    test_tab2 test_pkg.test_tab_type;
    var number;
begin

    open cur_ps_test01;
    fetch cur_ps_test01 bulk collect into test_tab;
    close cur_ps_test01;

    dbms_output.put_line('number of rows fetched : ' || test_tab.count);

    select * bulk collect into test_tab2 from table(test_tab);
    dbms_output.put_line('number of rows fetched : ' || test_tab2.count);

    forall i in test_tab.first..test_tab.last
      merge into ps_test02 tab2 -- error was reported on this line
      using (
        select
          test_tab(i).id as id,
          test_tab(i).name as name,
          test_tab(i).active_flag as active_flag
        from dual
      ) tab1
        on (tab1.id = tab2.id)
      when matched then
        update set name = tab1.name
      when not matched then
        insert (id, name) values (tab1.id, tab1.name);

end;
/
1 行受影响

dbms_output:
抓取的行数:3
抓取的行数:3
select *
from ps_test02
ID NAME
1 test01
2 test02
3 test03

fiddle

然而,如果不对获取的数据执行额外的PL/SQL处理,最好将所有内容保持在SQL级别,不要使用上下文切换来移动数据。

英文:

Restriction on usage of PL/SQL types is listed in Changes in Oracle Database 12c Release 1 (12.1).

> More PL/SQL-Only Data Types Can Cross PL/SQL-to-SQL Interface
> ---
>
> As of Oracle Database 12c, it is possible to bind values with PL/SQL-only data types to anonymous blocks (which are SQL statements), PL/SQL function calls in SQL queries and CALL statements, and the TABLE operator in SQL queries. However:
> - ...
> - If the PL/SQL-only data type is an associative array, it cannot be used within a non-query DML statement (INSERT, UPDATE, DELETE, MERGE) nor in a subquery

You may use FORALL statement to perform upsert from a collection:

declare
    l_test varchar(10);
    
    cursor cur_ps_test01 IS
        select id, name ,active_flag from ps_test01 where active_flag='Y';
        
    --TYPE test_tab_type is TABLE OF cur_ps_test01%ROWTYPE index by pls_integer;
    test_tab test_pkg.test_tab_type;
    test_tab2 test_pkg.test_tab_type;
    var number;
begin
    
    open cur_ps_test01;
    fetch cur_ps_test01 bulk collect into test_tab;
    close cur_ps_test01;
    
    dbms_output.put_line('number of rows fetched : ' || test_tab.count); 
    
    select * bulk collect into test_tab2 from table(test_tab);
    dbms_output.put_line('number of rows fetched : ' || test_tab2.count); 

    forall i in test_tab.first..test_tab.last
      merge into ps_test02 tab2 -- error was reported on this line
      using (
        select
          test_tab(i).id as id,
          test_tab(i).name as name,
          test_tab(i).active_flag as active_flag
        from dual
      ) tab1
        on (tab1.id = tab2.id)
      when matched then
        update set name = tab1.name
      when not matched then
        insert (id, name) values (tab1.id, tab1.name);
    
end;
/
1 rows affected

dbms_output:
number of rows fetched : 3
number of rows fetched : 3
select *
from ps_test02
ID NAME
1 test01
2 test02
3 test03

fiddle

However, it would be better to keep everything at SQL level and do not move the data around with context swithing if you do not perform any extra PL/SQL processing of the fetched data.

答案2

得分: 1

作为一个框架挑战,PL/SQL和自定义类型都是不必要的,您可以在SQL中完成所有操作:

merge into ps_test02 tab2
using (
  select id, name, active_flag from ps_test01 where active_flag='Y'
) tab1
on (tab1.id = tab2.id)
when matched then
  update set name = tab1.name
when not matched then
  insert (id, name) values (tab1.id, tab1.name);

fiddle

如果您想使用多个表,您可以在USING子句中使用UNION ALL从多个表中选择。

英文:

As a frame challenge, PL/SQL and custom types are unnecessary and you can do it all in SQL:

merge into ps_test02 tab2
using (
  select id, name ,active_flag from ps_test01 where active_flag='Y'
) tab1
on (tab1.id = tab2.id)
when matched then
  update set name = tab1.name
when not matched then
  insert (id, name) values (tab1.id, tab1.name);

fiddle

> I should be able to have multiple queries that can populate test_tab, that would help me to create/update records in ps_test02 table.

If you want to use multiple tables then you can use UNION ALL to select from multiple tables in the USING clause.

答案3

得分: 1

创建一个SQL数据类型,而不是一个PL/SQL数据类型:

CREATE OR REPLACE TYPE test_rec AS OBJECT
   (
      id    number,
      name  varchar2(30),
      active_flag   varchar2(1)
   );

CREATE OR REPLACE TYPE test_tab_type AS TABLE OF test_rec;

然后在你的DECLARE部分中移除对包的引用:

test_tab test_tab_type;

现在你可以在SQL语句中使用 test_tab

英文:

Create a SQL datatype rather than a PL/SQL one:

CREATE OR REPLACE TYPE test_rec AS OBJECT
   (
      id    number,
      name  varchar2(30),
      active_flag   varchar2(1)
   );

CREATE OR REPLACE TYPE test_tab_type AS TABLE OF test_rec;

Then in your DECLARE section remove the reference to the package:

test_tab test_tab_type;

Now you can use test_tab in SQL statements.

huangapple
  • 本文由 发表于 2023年5月25日 18:34:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76331333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定