oracle 尝试使用PL/SQL管道函数进行更新和选择

mccptt67  于 2023-06-22  发布在  Oracle
关注(0)|答案(1)|浏览(101)

我有一个简单的表格:

CREATE TABLE "MY_TABLE" (
  id varchar2(255) NOT NULL PRIMARY KEY,
  name varchar2(255),
  website varchar2(500)
);

website的格式可以是https://www.google.com,如果是,那么我需要更新该行并将其设置为google.com
然后,我需要计算有多少个name具有相同的更新域。我需要尽可能高效地完成这项工作,因此我正在尝试使用游标和函数。如果我有:

insert into MY_TABLE2(id, name, website) values ('1', 'a', 'dom1');
insert into MY_TABLE2(id, name, website) values ('2', 'b', 'dom2');
insert into MY_TABLE2(id, name, website) values ('3', 'c', 'dom1');
insert into MY_TABLE2(id, name, website) values ('4', 'd', null);

好的代码应该这样做:

select mt1.name AS "SPOT NAME",  (select count(mt2.id) 
                                   from my_table2 mt2 
                                   where  mt1.website = mt2.website  )
 from my_table2 mt1;

我有以下代码:
这将检查传递的website是否满足域的条件(假设regex是ok的):

CREATE OR REPLACE FUNCTION is_domain_format(website my_table.website%type) RETURN varchar2 AS
v_is_in_domain_format boolean;
v_bool_as_varchar varchar2(5);
begin
    v_is_in_domain_format := regexp_like(website, '^[a-z0-9][-a-z.0-9]*[a-z0-9]$') and not regexp_like(website, '\.\.');
    IF v_is_in_domain_format THEN
        v_bool_as_varchar := 'true';
    ELSE
         v_bool_as_varchar := 'false';
    END IF;

    RETURN v_bool_as_varchar;
end is_domain_format;

然后我们将website实际格式化为域:

CREATE OR REPLACE FUNCTION formate_url (website_ my_table.website%type) RETURN varchar2 AS 
v_domain varchar2(255) default null;
BEGIN
  v_domain := REGEXP_REPLACE(website_, '(http[s]?://)?(www\.)?(.*?)((/|:)(.)*|$)', '\3');
  RETURN v_domain;
END formate_url;

然后我有一个嵌套表:

CREATE TYPE t_spot AS OBJECT (
  v_id varchar2(255),
  v_name varchar2(255),
  v_domain varchar2(255)
);

最后是一个流水线函数:

CREATE TYPE t_spot_tab IS TABLE OF t_spot;

create or replace function f_get_spots_piped
              return t_spot_tab PIPELINED as

    type r_spot is record ( v_id my_table.id%type,
                            v_website my_table.website%type);
 
   cursor c_spots_unformated is select id,website 
                                      from my_table 
                                      where website is not null and is_domain_format(website) = 'false';        

begin
  for spot_unformated_domain in c_spots_unformated loop
    update my_table set website = formate_url(spot_unformated_domain.website) where spot_unformated_domain.id = id;
  end loop;  

 for i in 1 .. 5 loop
  --this is only for a test...
  PIPE ROW (t_spot(1, 'a','b'));
 end loop;
 RETURN;
end;

我不明白为什么会出现以下错误:

ORA-14551: cannot perform a DML operation inside a query 
ORA-06512: at "SYS.F_GET_SPOTS_PIPED", line 13
ORA-06512: at "SYS.F_GET_SPOTS_PIPED", line 13
14551. 00000 -  "cannot perform a DML operation inside a query "
*Cause:    DML operation like insert, update, delete or select-for-update
           cannot be performed inside a query or under a PDML slave.
*Action:   Ensure that the offending DML operation is not performed or
           use an autonomous transaction to perform the DML operation within
           the query or PDML slave.

这是故意的还是我在某个地方犯了错?
我的意思是,如果这个异常是预期的,我怎么能写PL/SQL函数,将有效地更新和选择?
最后,我需要以下内容:

Name| Domain | Number_of_occurances_of_that_domain

因此,具有管道函数的查询应该看起来像这样:

select myTable.v_name as "Name", myTable.v_domain as "Domain", (select count(myTable1.v_id) 
                                                                    from f_get_spots_piped()   myTable1 where myTable.v_id=myTable1.v_id) as "Number_of_occurances_of_that_domain "

编辑1
谢谢@Litlefoot给我很好的解释!
然而,我已经尝试了这两种选择,他们都失败了,与以前类似的信息。
尝试1。
在管道函数中添加了pragma_transaction,如下所示:

create or replace function f_get_spots_piped
                  return t_spot_tab PIPELINED as
    
     pragma autonomous_transaction;

尝试2.
创建一个将执行更新的过程,然后从管道函数调用它。

create or replace procedure update_spots as 
    
    pragma autonomous_transaction; 
    type r_spot is record ( v_id my_table.id%type,
                            v_website my_table.website%type);
 
   cursor c_spots_unformated is select id,website 
                                      from my_table 
                                      where website is not null and is_domain_format(website) = 'false';        

begin
    
  for spot_unformated_domain in c_spots_unformated loop
    update my_table set website = formate_url(spot_unformated_domain.website) where spot_unformated_domain.id = id;
  end loop;  

end;

和原始功能:

CREATE TYPE t_spot_tab IS TABLE OF t_spot;

create or replace function f_get_spots_piped
              return t_spot_tab PIPELINED as
    
                                       

begin

  update_spots;

 for i in 1 .. 5 loop
  PIPE ROW (t_spot('a','b'));
 end loop;
 RETURN;
end;

当我这样做时,它失败了:select * from f_get_spots_piped() ;
再次感谢你的回答,希望你能从这里给我指出一个好的方向,因为我浪费了很多时间来设置这个查询:(

mrphzbgm

mrphzbgm1#

错误描述对此进行了解释;虽然,你不明白它说的是什么,这里有一个简单的例子:
这是一个示例表:

SQL> select * From test;

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      CLERK
      7566 JONES      MANAGER
      7788 SCOTT      ANALYST
      7876 ADAMS      CLERK
      7902 FORD       ANALYST

函数更新该表并将作业设置为小写,返回更新的行数。这基本上类似于你的函数:

SQL> create or replace function f_test return number
  2  is
  3    l_cnt number;
  4  begin
  5    update test set job = lower(job);
  6    l_cnt := sql%rowcount;
  7    return l_cnt;
  8  end;
  9  /

Function created.

如果在查询中调用函数(即在select语句中),您将得到错误:

SQL> select f_test from dual;
select f_test from dual
       *
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "SCOTT.F_TEST", line 5

在这个函数中执行的DML显然是update。错误提示你不能在查询中做**,查询是select f_test from dual
但是,如果您在PL/SQL中调用该函数,则可以工作:

SQL> declare
  2    result number;
  3  begin
  4    result := f_test;
  5    dbms_output.put_Line('result = ' || result);
  6  end;
  7  /
result = 5               --> this is number of updated rows

PL/SQL procedure successfully completed.

SQL> select * from test;

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      clerk            --> jobs are now lowercase
      7566 JONES      manager
      7788 SCOTT      analyst
      7876 ADAMS      clerk
      7902 FORD       analyst

让我们恢复更改(即作业再次大写):

SQL> rollback;

Rollback complete.

错误描述还说,如果函数被声明为自治事务,你可以克服这个问题。那是什么?
自治事务允许您离开调用事务的上下文,执行独立事务,并返回到调用事务而不影响其状态。自治事务没有到调用事务的链接,因此只有提交的数据可以由两个事务共享。
Tim Hall比我描述得更好)。
好吧,让我们来做:

SQL> create or replace function f_test return number
  2  is
  3    pragma autonomous_transaction;          --> here
  4    l_cnt number;
  5  begin
  6    update test set job = lower(job);
  7    l_cnt := sql%rowcount;
  8    commit;
  9    return l_cnt;
 10  end;
 11  /

Function created.

现在,您可以从查询中调用该函数(请记住,它之前引发了一个错误):

SQL> select f_test from dual;

    F_TEST
----------
         5          --> 5 rows updated

表格内容:

SQL> select * From test;

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      clerk
      7566 JONES      manager
      7788 SCOTT      analyst
      7876 ADAMS      clerk
      7902 FORD       analyst

SQL>

就是这样现在,这取决于你是否可以将函数设置为自治事务(通常,我们在用于日志记录目的的过程或应该返回顺序和非无间隙数据的函数中使用它)。最好让函数 * 保持原样 * 并在PL/SQL中使用它;或者,切换到一个过程并在其中执行DML。

相关问题