PLSQL : ORA-14551: cannot perform a DML operation inside a query - oracle

I went through some of the already answered questions, mentioned below, but could not find a solution that worked for me.
ORA-14551: cannot perform a DML operation inside a query
ORA-14551: cannot perform a DML operation inside a query error while using Execute immediate
I have written this within a function, that I am calling from a procedure.
OPEN c_rules_details;
LOOP
FETCH c_rules_details INTO rule_conditions_record;
EXIT WHEN c_rules_details %notfound;
dbms_output.put_line('Range and rule is '|| rule_conditions_record.rdr_tmplt_id || rule_conditions_record.rdr_rng_from ||rule_conditions_record.rdr_rng_to);
SELECT COUNT(*) INTO v_balance_records FROM t_scb_temp_objects WHERE RULE_CONDITION=rule_conditions_record.rdr_tmplt_id AND CHARGE is NULL;
dbms_output.put_line('Number of records in rule are '|| v_balance_records);
IF v_balance_records>rule_conditions_record.rdr_rng_from AND v_balance_records<rule_conditions_record.rdr_rng_to THEN
v_ssql_stmnt:='UPDATE t_scb_temp_objects
SET CHARGE='||rule_conditions_record.rfr_chrg_amt||' WHERE RULE_CONDITION='||rule_conditions_record.rdr_tmplt_id;
EXECUTE IMMEDIATE v_ssql_stmnt;
END IF;
END LOOP;
CLOSE c_rules_details;

That's what you've been told - you can't perform DML (in your case, UPDATE t_scb_temp_objects ...) within a function you're (somehow - you didn't show how) calling from a procedure. That might be - for example:
create or replace function f_test (par_deptno in number)
return number
is
...
begin
open c_rules_details;
loop
-- your current code goes here
execute immediate v_sql_stmnt;
end loop;
return 1;
end;
create or replace procedure p_test is
l_ename emp.ename%type;
begin
select e.ename
into l_ename
from emp e
where e.deptno = f_test(e.deptno); --> this will cause the error
end;
So, what to do? Perform UPDATE from within a procedure, not a function.
If it must be a function, make it an autonomous transaction (using PRAGMA), but that's something I wouldn't recommend. You'd rather figure something different out. Here's how, though:
SQL> create table test (col number);
Table created.
SQL> insert into test (col) values (100);
1 row created.
SQL> commit;
Commit complete.
First, a function that'll fail (just like yours):
SQL> create or replace function f_test return number is
2 begin
3 update test set col = 10;
4 commit;
5 return 1;
6 end;
7 /
Function created.
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 3
Now, the one which will succeed:
SQL> create or replace function f_test return number is
2 pragma autonomous_transaction; --> this
3 begin
4 update test set col = 10;
5 commit;
6 return 1;
7 end;
8 /
Function created.
SQL> select f_test from dual;
F_TEST
----------
1
SQL> select * from test;
COL
----------
10
SQL>
Once again: you probably don't want to do that.

Related

How to execute Dynamic sql with insert statement in Oracle

Below sql command is not working in procedure
PROCEDURE P_EMPDETAIL
AS
V_WHERE := 'E.EMP_ID = 123'B
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E ' || V_WHERE || ;
END;
It seems to me there are various issues with your syntax and approach (you shouldn't be using dynamic SQL this way), perhaps you should learn PL/SQL and reference the manuals. The insert statement is also wrong. Below is the correct syntax.
CREATE OR REPLACE PROCEDURE P_EMPDETAIL as
V_WHERE varchar2(100);
BEGIN
V_WHERE := 'E.EMP_ID = 123';
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E (colname) values (1) ' || V_WHERE;
END;
Well, not exactly like that (obviously; otherwise, you wouldn't be asking for help).
It is unclear what you want to do because syntax is really strange. If you wanted to insert a row into the table, then:
SQL> CREATE TABLE employees
2 (
3 emp_id NUMBER
4 );
Table created.
SQL> CREATE OR REPLACE PROCEDURE p_empdetail (par_emp_id IN NUMBER)
2 AS
3 l_str VARCHAR2 (200);
4 BEGIN
5 l_str := 'insert into employees (emp_id) values (:1)';
6
7 EXECUTE IMMEDIATE l_str
8 USING par_emp_id;
9 END;
10 /
Procedure created.
Testing:
SQL> EXEC p_empdetail(123);
PL/SQL procedure successfully completed.
SQL> SELECT * FROM employees;
EMP_ID
----------
123
SQL>

How to run select statement after stored procedure run ORACLE

I am new to Oracle, trying to run select statement after my stored procedure and failing. Here I am trying to return rows or any dummy data after my store procedure is executed, because my end process needs a return data for sure to make it consider as successful run. Is there any way I can trick it here is what I tried and failed.
EX:
BEGIN
note.name;
END;
/
SELECT * from hello.table where rownum=1;
Error:
ERROR [HY000] [Microsoft][ODBC Oracle Wire Protocol driver][Oracle]ORA-06550: line 4, column 1:
PLS-00103: Encountered the symbol "/"
Activity ID: xxxxxx-xxx-xxx-xxx-xxxxxx
Show return a single row after completion of stored procedure
Code you posted looks OK. Demo:
SQL> create or replace procedure p_test as
2 begin
3 null;
4 end;
5 /
Procedure created.
SQL> begin
2 p_test;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> select * from dept where rownum = 1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
SQL>
Therefore, what exactly is your problem? What result do you expect?
If "at once" (as you commented) means that everything should be part of a PL/SQL block, then you'd declare a variable which is capable of holding the whole row, and then just select into it. rownum = 1 condition prevents too_many_rows error.
SQL> set serveroutput on
SQL>
SQL> declare
2 l_dept dept%rowtype;
3 begin
4 p_test;
5 select * into l_dept from dept where rownum = 1;
6 dbms_output.put_line(l_dept.deptno ||', '|| l_dept.dname ||', '|| l_dept.loc);
7 end;
8 /
10, ACCOUNTING, NEW YORK
PL/SQL procedure successfully completed.
SQL>

Oracle: create stored procedure to insert results from another stored procedure into a table

I have an Oracle stored procedure spFinTest that I use in an SSRS report.
I wish to create another stored procedure spFinTestInsert that takes the output of spFinTest and inserts it into a table sbceybudget_financial_year.
I can create an all in one stored procedure that inserts the data into the destination table, but what I am looking to achieve is to just have the one stored procedure that can have code updates for data extracts and not to have to update a second separate insert stored procedure that has the same code.
So, update in one place only and reuse the same stored procedure.
The following is a simplified version of the main stored procedure:
create or replace procedure spFINTEST
(s1 OUT SYS_REFCURSOR)
AS
BEGIN
OPEN s1 FOR
SELECT
FIN_YR + 1 AS FIN_YR,
SCHOOL_YEAR_WEEKS
FROM
sbceybudget_financial_year
WHERE
fin_yr = 2021
;
END spFINTEST;
This stored procedure only has an "out" variable.
The final intention is once I can do this, then I will call spFinTestInsert from an "Execute SQL Task" in an SSIS package.
I'm a bit stumped as to how I create this second stored procedure that calls the first and inserts the results into a named table, so if anyone can help I would be most grateful.
I would do this in a package: it allows you to declare cursor type easily to make it more clear.
Test table:
create table sbceybudget_financial_year(
fin_yr int,
SCHOOL_YEAR_WEEKS int
)
/
Package specification:
create or replace package pkg_spFIN as
type spFIN_RowType is record(
FIN_YR sbceybudget_financial_year.FIN_YR%type,
SCHOOL_YEAR_WEEKS sbceybudget_financial_year.SCHOOL_YEAR_WEEKS%type
);
type spFIN_CurType IS REF CURSOR RETURN spFIN_RowType;
type spFIN_tab is table of spFIN_RowType;
procedure spFINTEST (s1 OUT SYS_REFCURSOR);
procedure spFinTestInsert;
end pkg_spFIN;
/
Package body:
create or replace package body pkg_spFIN as
function get_cursor(n int) return spFIN_CurType is
c spFIN_CurType;
begin
open c for
SELECT
t.FIN_YR + 1 AS FIN_YR,
t.SCHOOL_YEAR_WEEKS
FROM
sbceybudget_financial_year t
WHERE
t.fin_yr = n;
return c;
end;
procedure spFINTEST (s1 OUT SYS_REFCURSOR)
is
begin
s1:=get_cursor(2021);
end spFINTEST;
procedure spFinTestInsert
is
cur spFIN_CurType;
tab spFIN_tab;
begin
pkg_spFIN.spFINTEST(cur);
loop
fetch cur bulk collect into tab limit 100;
exit when tab.count()=0;
for i in 1..tab.count loop
dbms_output.put_line(tab(i).FIN_YR);
dbms_output.put_line(tab(i).SCHOOL_YEAR_WEEKS);
-- or insert:
-- insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS)
-- values(tab(i).FIN_YR, SCHOOL_YEAR_WEEKS)
-- you can change it to FORALL insert
end loop;
end loop;
end spFinTestInsert;
end pkg_spFIN;
/
Test data:
begin
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2019,19);
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2020,20);
insert into sbceybudget_financial_year(fin_yr, SCHOOL_YEAR_WEEKS) values(2021,21);
commit;
end;
/
And finally test call:
call pkg_spFIN.spFinTestInsert();
Full example on DBFiddle: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c31a5714e5db74eaa3a83fae03964349
I don't have your tables so I'll use Scott's DEPT for illustration.
This is the "target" table; values fetched by ref cursor will be inserted into it:
SQL> create table test_dept as select deptno, dname from dept where 1 = 2;
Table created.
This is data I expect:
SQL> select deptno, dname from dept where deptno <= 20;
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
This is your current procedure:
SQL> create or replace procedure spfintest (s1 out sys_refcursor)
2 as
3 begin
4 open s1 for select deptno, dname from dept where deptno <= 20;
5 end spfintest;
6 /
Procedure created.
This is a procedure which calls spfintest and inserts values into test_dept:
SQL> create or replace procedure spfitestinsert as
2 rc sys_refcursor;
3 --
4 l_deptno dept.deptno%type;
5 l_dname dept.dname%type;
6 begin
7 spfintest(rc);
8 loop
9 fetch rc into l_deptno, l_dname;
10 exit when rc%notfound;
11
12 insert into test_dept (deptno, dname)
13 values (l_deptno, l_dname);
14 end loop;
15 end;
16 /
Procedure created.
Testing:
SQL> exec spfitestinsert;
PL/SQL procedure successfully completed.
SQL> select * from test_dept;
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
SQL>
Everything is here, so I guess it works.

Oracle, limit the row number per user

I need to limit the number of rows that a query can return for a specific user. I know that I can limit in the SQL query, but I need to avoid that a specific user can build a query that return a huge amount of rows. for this reason I need to limit in the configuration.
Does anybody know if it is possible?
If you have Enterprise Edition you can implement VPD rule using DBMS_RLS package:
SQL> create or replace package pac1
2 is
3 function limit_rows(owner varchar2, tab varchar2) return varchar2;
4 end;
5 /
Package created.
SQL> create or replace package body pac1
2 is
3 function limit_rows(owner varchar2, tab varchar2) return varchar2
4 is
5 begin
6 return ' rownum <= 3';
7 end;
8 end pac1;
9 /
Package body created.
SQL> begin
2 dbms_rls.add_policy('HR','EMPLOYEES','RULE1','HR','PAC1.LIMIT_ROWS','SELECT');
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> select first_name, last_name from hr.employees;
FIRST_NAME LAST_NAME
-------------------- -------------------------
Ellen Abel
Sundar Ande
Mozhe Atkinson
SQL>
You can achieve this with cursors. See example:
SQL> create or replace function exec_query(sql_text varchar2) return sys_refcursor
2 is
3 num_rows number := 3;
4 c1 sys_refcursor;
5 begin
6 open c1 for 'with test as (' || sql_text || ') select * from test where rownum <=' || num_rows ;
7 return c1;
8 end;
9 /
Function created.
SQL> variable c1 refcursor;
SQL> exec :c1 := exec_query('select last_name, salary from hr.employees');
PL/SQL procedure successfully completed.
SQL> print :c1;
LAST_NAME SALARY
------------------------- ----------
King 24000
Kochhar 17000
De Haan 17000
Amount of data is not necessarily equal to amount of load. A single row query can kill a database if it's complex enough.
Answer to this is really complex.
You can take the SQL and create a SQL PLAN from it, and from that data limit from estimated cost and/or estimated rows.
How To Create and Evolve a SQL Plan Baseline
How do I display and read the execution plans for a SQL statement
Example:
I'll create a temp table:
create table tmp_table2 as
select * from user_objects;
then I use Oracle's plan estimation with out actually running the query
declare
l_sql varchar2 (32767);
begin
delete from plan_table;
l_sql := 'select * from tmp_table2';
execute immediate 'explain plan for ' || l_sql;
for i in (select cardinality,
cost,
bytes,
cpu_cost
from PLAN_TABLE
where operation = 'SELECT STATEMENT') loop
if i.cardinality /* rows */
> 500 then
dbms_output.put_line ('Too many rows');
elsif i.cpu_cost > 500000 then
dbms_output.put_line ('Too much CPU');
else
dbms_output.put_line ('About right');
end if;
end loop;
end;
Result;
==>
PL/SQL block executed
Too many rows
Or you can use the Resource manager to limit per session:
Using the Database Resource Manager

Unable to find procedure in DBA_PROCEDURES view

I have created a procedure :
create or replace procedure gg as
begin
insert into book values ('prashant','prashant','prashant');
commit;
end;
/
Procedure has been created successfully.Now i want to check the package name for the corresponding procedure but i am not able to do so.
I am using the below query :
> SELECT *
FROM SYS.DBA_PROCEDURES
WHERE procedure_name ='gg';
Its giving 0 rows selected.Please help.
NOTE Please look at the UPDATE section for correct answer.
The procedure name cannot be in lower case in the DBA_PROCEDURES view. Use upper case, or apply UPPER function.
SELECT *
FROM SYS.DBA_PROCEDURES
WHERE procedure_name ='GG';
UPDATE
The only case when you could have the name in lower case is if you enclose it within double-quotation marks while compiling.
For example,
SQL> CREATE OR REPLACE
2 PROCEDURE "p"
3 AS
4 BEGIN
5 NULL;
6 END;
7 /
Procedure created.
SQL> SELECT object_name, procedure_name, object_type FROM user_procedures where procedure_name='p';
no rows selected
SQL>
But still the above view will not return any result for PROCEDURE_NAME.
Reason
PROCEDURE_NAME column will only have the procedure name for the procedures which are part of a PACKAGE. For STAND ALONE PROCEDURES you need to use OBJECT_NAME.
SQL> -- stand alone procedure in lower case
SQL> CREATE OR REPLACE
2 PROCEDURE "p"
3 AS
4 BEGIN
5 NULL;
6 END;
7 /
Procedure created.
SQL>
SQL> -- package
SQL> CREATE OR REPLACE
2 PACKAGE test_p
3 IS
4 PROCEDURE p;
5 END test_p;
6 /
Package created.
SQL>
SQL> -- package body with a procedure
SQL> CREATE OR REPLACE
2 PACKAGE BODY test_p
3 IS
4 PROCEDURE p
5 IS
6 BEGIN
7 NULL;
8 END;
9 END test_p;
10 /
Package body created.
SQL>
SQL> SELECT object_name, procedure_name, object_type FROM user_procedures;
OBJECT_NAME PROCEDURE_NAME OBJECT_TYPE
--------------- --------------- ---------------
TEST_P P PACKAGE
p PROCEDURE
TEST_P PACKAGE
SQL>
So, as you can see, the procedure_name is only having the package's procedure, however the stand-alone procedure is only listed under object_name.
The query I use is:
SELECT * FROM User_Procedures WHERE NVL(Procedure_Name,Object_Name) = 'PROCNAME';

Resources