Number of rows affected by an UPDATE in PL/SQL - oracle

I have a PL/SQL function (running on Oracle 10g) in which I update some rows. Is there a way to find out how many rows were affected by the UPDATE? When executing the query manually it tells me how many rows were affected, I want to get that number in PL/SQL.

You use the sql%rowcount variable.
You need to call it straight after the statement which you need to find the affected row count for.
For example:
set serveroutput ON;
DECLARE
i NUMBER;
BEGIN
UPDATE employees
SET status = 'fired'
WHERE name LIKE '%Bloggs';
i := SQL%rowcount;
--note that assignment has to precede COMMIT
COMMIT;
dbms_output.Put_line(i);
END;

For those who want the results from a plain command, the solution could be:
begin
DBMS_OUTPUT.PUT_LINE(TO_Char(SQL%ROWCOUNT)||' rows affected.');
end;
The basic problem is that SQL%ROWCOUNT is a PL/SQL variable (or function), and cannot be directly accessed from an SQL command. By using a noname PL/SQL block, this can be achieved.
... If anyone has a solution to use it in a SELECT Command, I would be interested.

alternatively, SQL%ROWCOUNT
you can use this within the procedure without any need to declare a variable

SQL%ROWCOUNT can also be used without being assigned (at least from Oracle 11g).
As long as no operation (updates, deletes or inserts) has been performed within the current block, SQL%ROWCOUNT is set to null. Then it stays with the number of line affected by the last DML operation:
say we have table CLIENT
create table client (
val_cli integer
,status varchar2(10)
)
/
We would test it this way:
begin
dbms_output.put_line('Value when entering the block:'||sql%rowcount);
insert into client
select 1, 'void' from dual
union all select 4, 'void' from dual
union all select 1, 'void' from dual
union all select 6, 'void' from dual
union all select 10, 'void' from dual;
dbms_output.put_line('Number of lines affected by previous DML operation:'||sql%rowcount);
for val in 1..10
loop
update client set status = 'updated' where val_cli = val;
if sql%rowcount = 0 then
dbms_output.put_line('no client with '||val||' val_cli.');
elsif sql%rowcount = 1 then
dbms_output.put_line(sql%rowcount||' client updated for '||val);
else -- >1
dbms_output.put_line(sql%rowcount||' clients updated for '||val);
end if;
end loop;
end;
Resulting in:
Value when entering the block:
Number of lines affected by previous DML operation:5
2 clients updated for 1
no client with 2 val_cli.
no client with 3 val_cli.
1 client updated for 4
no client with 5 val_cli.
1 client updated for 6
no client with 7 val_cli.
no client with 8 val_cli.
no client with 9 val_cli.
1 client updated for 10

Please try this one..
create table client (
val_cli integer
,status varchar2(10)
);
---------------------
begin
insert into client
select 1, 'void' from dual
union all
select 4, 'void' from dual
union all
select 1, 'void' from dual
union all
select 6, 'void' from dual
union all
select 10, 'void' from dual;
end;
---------------------
select * from client;
---------------------
declare
counter integer := 0;
begin
for val in 1..10
loop
update client set status = 'updated' where val_cli = val;
if sql%rowcount = 0 then
dbms_output.put_line('no client with '||val||' val_cli.');
else
dbms_output.put_line(sql%rowcount||' client updated for '||val);
counter := counter + sql%rowcount;
end if;
end loop;
dbms_output.put_line('Number of total lines affected update operation: '||counter);
end;
---------------------
select * from client;
--------------------------------------------------------
Result will be like below:
2 client updated for 1
no client with 2 val_cli.
no client with 3 val_cli.
1 client updated for 4
no client with 5 val_cli.
1 client updated for 6
no client with 7 val_cli.
no client with 8 val_cli.
no client with 9 val_cli.
1 client updated for 10
Number of total lines affected update operation: 5

Use the Count(*) analytic function OVER PARTITION BY NULL
This will count the total # of rows

Related

PL/SQL IF statement and variable declaration

I am trying to make the code posted below work, but I am receiving an error about the IF statement:
DECLARE
a number;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
EXCEPTION
WHEN no_data_found THEN a := 0;
END;
IF (a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
In the first block I am declaring an 'A' variable and setting it to 0 (by the way, the API call result will be 0 or 1, nothing else). The first block works fine or - at least - I do think so. But the IF block is not working and having this error:
PLS-00103:Encountered the symbol "IF"
Any help is appreciated.
Update: I have tried it this way:
DECLARE
a number:=0;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
IF (:a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
END;
I received the exception below:
ORA-01008: not all variables bound
Update
In other words, what I am trying to do is:
if Pers_API.Is_Manager_(10018) returns true (as 1), I have to do another select statement
if it's false (as 0), I have to return null.
Any other ideas are appreciated.
A few objections, if I may.
there's no need to select from dual in order to assign a value to a variable, when that value is returned by a function - simply assign it
exception handler seems to be superfluous. You said that the is_manager_ function returns 0 or 1 and nothing else. Therefore, it'll always return something, so - why do you expect that SELECT to return NO_DATA_FOUND? Besides, such a case should be handled by the function itself (i.e. you have to make sure that it returns 0 or 1 and nothing else)
the last paragraph you wrote is somewhat strange. You said that - if the function returns 1, you have to run another SELECT statement. Otherwise, you have to return null (while code you wrote, select 0 ... suggests a zero (0), not null); so, which one is true?
also, saying that you have to return something (null, 0, whatever) suggests that piece of code you wrote should be a function that returns a value. Is that so?
Code that actually compiles might look like in this example.
First, a function (based on Scott's schema) that checks whether an employee is a manager:
SQL> create or replace function is_manager_ (par_empno in number)
2 return number
3 is
4 /* function checks whether PAR_EMPNO belongs to a manager and returns:
5 - 1 - employee is a manager
6 - 0 - employee is NOT a manager
7 */
8 retval number := 0;
9 begin
10 select max(1)
11 into retval
12 from emp e
13 where mgr = par_empno;
14
15 return retval;
16 exception
17 when no_data_found then
18 return retval;
19 end;
20 /
Function created.
SQL>
Code you posted, rewritten so that it does something:
if an employee is a manager, returns his/her salary
otherwise, it does not
SQL> declare
2 a number := is_manager_(&&l_empno);
3 l_sal emp.sal%type;
4 begin
5 if a > 0 then
6 select sal
7 into l_sal
8 from emp
9 where empno = &&l_empno;
10 dbms_output.put_line('Employee is a manager and his/her salary is ' || l_sal);
11 else
12 null;
13 dbms_output.put_line('Employee is not a manager');
14 end if;
15 end;
16 /
Enter value for l_empno: 7698
Employee is a manager and his/her salary is 2850
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: 7369
Employee is not a manager
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: -1
Employee is not a manager
PL/SQL procedure successfully completed.
SQL>
See if you can adjust it so that it fits your needs. If not, do answer to my objections posted at the beginning of this message, and we'll see what to do next.
It appears to me that what you are really trying to achieve here is to run a query which must use the result obtained from the package function as a condition. If so, the PL/SQL block wouldn't be needed and can be written as a basic select operation.
What is still not clear from your explanation is whether your select query
SELECT 1 from dual is the actual query you are about to use in your code.Generally, in the select statement, we use CASE expression to evaluate the conditional logic, which essentially does what IF ELSE condition was supposed to do to return the desired result.The query would have a structure of this form,
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
END
as
FROM DUAL;
More specifically, if you want to have the resultant expression come from another database table, the queries can take the form
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
as
FROM table_to_run_query
WHERE where_clauses = some_values;
If you say that your resultant expression from the table involves many other constructs, the challenge would be to compose it in a single query cleverly to avoid any PL/SQL at all. That would be achievable, but it will require that you explain us with some sample data and expected result, as to what exactly you want,preferably in a new question.
It would not be complete if I don't mention the REFCURSOR technique to obtain results from a select query in PL/SQL. It is either through DBMS_SQL.RETURN_RESULT(Oracle 12c and above) or using a REFCURSOR bind variable running it in SQL* Plus or as Script in other tools (F5).
VARIABLE x refcursor;
DECLARE BEGIN
IF
pers_api.is_manager_(10018) = 1
THEN
OPEN :x FOR SELECT some_columns FROM some_tables;
ELSE
OPEN :x FOR SELECT some_columns FROM other_tables;
END IF;
--DBMS_SQL.RETURN_RESULT(:x); --12c and above
END;
/
PRINT x -- 11g

Plsql oracle apex

i am using this loop in my procedure and taking the email ids in i but when i am trying to run i am getting no data found error, so i want to check the values storing in i
code used:
for i in ( select EMAIL
into l_user_mail
from employee A CONNECT BY PRIOR lower(EMAIL) = lower(MANAGER_EMAIL)
START WITH lower(GUID) in (select replace(lower(group_name),'_org_slack')
from dynamic_group where id = '81')
) loop
l_user_mail:=l_user_mail || i.EMAIL;
end loop;
how to check the return value of i in sql command prompt.
i want to see the values getting in i
i want to see the values getting in i
Your FOR LOOP syntax is not correct. INTO clause should not be used in For Loop. See below how you can do that.
FOR I IN
(SELECT EMAIL
FROM EMPLOYEE A
CONNECT BY PRIOR LOWER (EMAIL) = LOWER (MANAGER_EMAIL)
START WITH LOWER (GUID) IN (
SELECT REPLACE (LOWER (GROUP_NAME), '_org_slack')
FROM DYNAMIC_GROUP
WHERE ID = '81') )
LOOP
-- l_user_mail:=:P60_IDP_GROUPS;
L_USER_MAIL := L_USER_MAIL || I.EMAIL;
-- To display value of I
DBMS_OUTPUT.PUT_LINE (I.EMAIL);
END LOOP;
Demo:
SQL> DECLARE
2 L_USER_MAIL VARCHAR2 (10);
3 BEGIN
4 FOR I IN (SELECT LEVEL
5 FROM DUAL
6 CONNECT BY LEVEL < 10)
7 LOOP
8 -- l_user_mail:=:P60_IDP_GROUPS;
9 L_USER_MAIL := L_USER_MAIL || I.level;
10 DBMS_OUTPUT.PUT_LINE (I.level);
11 END LOOP;
12 END;
13 /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.

Oracle - How to execute a query with parameters?

I am new to Oracle and i am trying to execute a simple select with some parameters but i cant get it to work.
For
SELECT idl.column_value clientguid
FROM TableName idl
LEFT JOIN :ParamName_Type olt ON olt.clientguid = idl.column_value
WHERE (olt.flag = 0)
But declare does not work. I could not find any help on internet.
Thanks.
Oracle SQL Developer should handle variables the same way SQLPlus does, that is with the &.
For example ( in SQLPlus for simplicity):
SQL> select 1 from &tableName;
Enter value for tablename: dual
old 1: select 1 from &tableName
new 1: select 1 from dual
1
----------
1
What you can not do is use the parameter as a part of a table name, assuming that Developer "knows" which part is the parameter name and which one is the fixed part.
For example:
SQL> select * from &ParamName_Type;
Enter value for paramname_type:
that is, all the string ParamName_Type wil be interpreted as a variable name and substituited with the value you enter.
Also, consider that this is a client-specific behaviour, not an Oracle DB one; so, the same thing will not work in a different client (Toad for Oracle for example).
Consider that you are trying to use a "parameter" that represents a table name, and you only can do this by the means of some client, because plain SQL does not allow it.
If you need to do such a thing in some piece of code that has to work no matter the client, you need dynamic SQL
If you need something more complex, you may need some dynamic SQL; for example:
SQL> declare
2 vTableName varchar2(30) := '&table_name';
3 vSQL varchar2(100):= 'select 1 from ' || vTableName ||
' union all select 2 from ' || vTableName;
4 type tResult is table of number;
5 vResult tResult;
6 begin
7 execute immediate vSQL bulk collect into vResult;
8 --
9 -- do what you need with the result
10 --
11 for i in vResult.first .. vResult.last loop
12 dbms_output.put_line(vResult(i));
13 end loop;
14 end;
15 /
Enter value for table_name: dual
old 2: vTableName varchar2(30) := '&table_name';
new 2: vTableName varchar2(30) := 'dual';
1
2
PL/SQL procedure successfully completed.
SQL>

How to use parameters in a 'where value in...' clause?

This works when I have only one state code as a parameter.
How can I get code to work when I have more than one state_code in parm_list?
Requirements:
(1)I don't want to hard code the state codes in my cursor definition
(2) I do want to allow for more than one state code in my where clause
For example: I want to run this code for parm_list = ('NY','NJ','NC').
I'm encountering difficulties in reconciling single quotes in parm_list with the single quotes in the 'where state_code in ' query.
set serveroutput on;
DECLARE
parm_list varchar2(40);
cursor get_state_codes(in_state_codes varchar2)
is
select state_name, state_code from states
where state_code in (in_state_codes);
BEGIN
parm_list := 'NY';
for get_record in get_state_codes(parm_list) loop
dbms_output.put_line(get_record.state_name || get_record.state_code);
end loop;
END;
Using dynamic SQL is the simplest approach from a coding standpoint. The problem with dynamic SQL, though, is that you have to hard parse every distinct version of the query which not only has the potential of taxing your CPU but has the potential to flood your shared pool with lots of non-sharable SQL statements, pushing out statements you'd like to cache, causing more hard parses and shared pool fragmentation errors. If you're running this once a day, that's probably not a major concern. If hundreds of people are executing it thousands of times a day, that is likely a major concern.
An example of the dynamic SQL approach
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos varchar2(100) := '10,20';
3 l_rc sys_refcursor;
4 l_dept_rec dept%rowtype;
5 begin
6 open l_rc for 'select * from dept where deptno in (' || l_deptnos || ')';
7 loop
8 fetch l_rc into l_dept_rec;
9 exit when l_rc%notfound;
10 dbms_output.put_line( l_dept_rec.dname );
11 end loop;
12 close l_rc;
13* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
Alternately, you can use a collection. This has the advantage of generating a single, sharable cursor so you don't have to worry about hard parsing or flooding the shared pool. But it probably requires a bit more code. The simplest way to deal with collections
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos := tbl_deptnos(10,20);
3 begin
4 for i in (select *
5 from dept
6 where deptno in (select column_value
7 from table(l_deptnos)))
8 loop
9 dbms_output.put_line( i.dname );
10 end loop;
11* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
If, on the other hand, you really have to start with a comma-separated list of values, then you will have to parse that string into a collection before you can use it. There are various ways to parse a delimited string-- my personal favorite is to use regular expressions in a hierarchical query but you could certainly write a procedural approach as well
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos;
3 l_deptno_str varchar2(100) := '10,20';
4 begin
5 select regexp_substr(l_deptno_str, '[^,]+', 1, LEVEL)
6 bulk collect into l_deptnos
7 from dual
8 connect by level <= length(replace (l_deptno_str, ',', NULL));
9 for i in (select *
10 from dept
11 where deptno in (select column_value
12 from table(l_deptnos)))
13 loop
14 dbms_output.put_line( i.dname );
15 end loop;
16* end;
17 /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
One option is to use INSTR instead of IN:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE instr(',TABLE,VIEW,', ',' || uo.object_type || ',') > 0;
Although this looks ugly, it works well and as long as no index on the column being tested was going to be used (because this prevents the use of any index) the performance won't suffer much. If the column being tested is a primary key for instance, definitely this should not be used.
Another option is:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE uo.object_type IN
(SELECT regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL) IS NOT NULL);
In this case, the list of values should be concatenated into a single varchar variable, delimited by commas (or anything you like.)

How to determine row/value throwing error in PL/SQL statement?

(Oracle PL/SQL)
If I have a simple SQL statement that is throwing an error, ie:
DECLARE
v_sql_errm varchar2(2048);
BEGIN
UPDATE my_table SET my_column = do_something(my_column)
WHERE my_column IS NOT NULL;
EXCEPTION
when others then
-- How can I obtain the row/value causing the error (unknown)?
v_sql_errm := SQLERRM;
insert into log_error (msg) values ('Error updating value (unknown): '||
v_sql_errm);
END;
Is there any way within the exception block to determine the row/value on which the query is encountering an error? I would like to be able to log it so that I can then go in and modify/correct the specific data value causing the error.
This can be done using DML error logging, if you are on 10gR2 or later.
An example:
SQL> create table my_table (my_column)
2 as
3 select level from dual connect by level <= 9
4 /
Tabel is aangemaakt.
SQL> create function do_something
2 ( p_my_column in my_table.my_column%type
3 ) return my_table.my_column%type
4 is
5 begin
6 return 10 + p_my_column;
7 end;
8 /
Functie is aangemaakt.
SQL> alter table my_table add check (my_column not in (12,14))
2 /
Tabel is gewijzigd.
SQL> exec dbms_errlog.create_error_log('my_table')
PL/SQL-procedure is geslaagd.
This creates an error logging table called err$_my_table. This table is filled by adding a log errors clause to your update statement:
SQL> begin
2 update my_table
3 set my_column = do_something(my_column)
4 where my_column is not null
5 log errors reject limit unlimited
6 ;
7 end;
8 /
PL/SQL-procedure is geslaagd.
SQL> select * from err$_my_table
2 /
ORA_ERR_NUMBER$
--------------------------------------
ORA_ERR_MESG$
--------------------------------------------------------------------
ORA_ERR_ROWID$
--------------------------------------------------------------------
OR
--
ORA_ERR_TAG$
--------------------------------------------------------------------
MY_COLUMN
--------------------------------------------------------------------
2290
ORA-02290: check constraint (RWK.SYS_C00110133) violated
AAGY/aAAQAABevcAAB
U
12
2290
ORA-02290: check constraint (RWK.SYS_C00110133) violated
AAGY/aAAQAABevcAAD
U
14
2 rijen zijn geselecteerd.
Prior to 10gR2, you can use the SAVE EXCEPTIONS clause: http://rwijk.blogspot.com/2007/11/save-exceptions.html
A solution using the SAVE EXCEPTIONS clause:
SQL> create table my_table (my_column)
2 as
3 select level from dual connect by level <= 9
4 /
Table created.
SQL> create function do_something
2 ( p_my_column in my_table.my_column%type
3 ) return my_table.my_column%type
4 is
5 begin
6 return 10 + p_my_column;
7 end;
8 /
Function created.
SQL> alter table my_table add check (my_column not in (12,14))
2 /
Table altered.
SQL> declare
2 e_forall_error exception;
3 pragma exception_init(e_forall_error,-24381)
4 ;
5 type t_my_columns is table of my_table.my_column%type;
6 a_my_columns t_my_columns := t_my_columns()
7 ;
8 begin
9 select my_column
10 bulk collect into a_my_columns
11 from my_table
12 ;
13 forall i in 1..a_my_columns.count save exceptions
14 update my_table
15 set my_column = do_something(a_my_columns(i))
16 where my_column = a_my_columns(i)
17 ;
18 exception
19 when e_forall_error then
20 for i in 1..sql%bulk_exceptions.count
21 loop
22 dbms_output.put_line(a_my_columns(sql%bulk_exceptions(i).error_index));
23 end loop;
24 end;
25 /
2
4
PL/SQL procedure successfully completed.
For very large data sets, you probably don't want to blow up your PGA memory, so be sure to use the LIMIT clause in that case.
try outputting your error and see if it gives you the information you are looking for. For example:
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
For more detailing information regarding how execution arrived at the line in question, you could try displaying the output returned by these functions:
DBMS_UTILITY.format_error_stack:
Format the current error stack. This can be used in exception handlers to look at the full error stack.
DBMS_UTILITY.format_error_backtrace:
Format the backtrace from the point of the current error to the exception handler where the error has been caught. NULL string is returned if no error is currently being handled.
Try this (not tested):
DECLARE
cursor c1 is
select key_column, my_column
from my_table
WHERE my_column IS NOT NULL
ORDER BY key_column;
my_table_rec my_table%ROWTYPE;
BEGIN
FOR my_table_rec in c1
LOOP
UPDATE my_table SET my_column = do_something(my_column)
WHERE key_column = my_table_rec.key_column;
END LOOP;
EXCEPTION
when others then
insert into log_error (msg) values ('Error updating key_column: ' || my_table_rec.key_column || ', my_column: ' || my_table_rec.my_column);
END;
PL/SQL defines 2 global variables to refer to errors:
SQLERRM : SQL error Message
SQLERRNO: SQL error Number
This is readable in the EXCEPTION block in your PL/SQL.
DECLARE
x number;
BEGIN
SELECT 5/0 INTO x FROM DUAL;
EXCEPTION
WHEN OTHERS THEN:
dbms_output.put_line('Error Message: '||SQLERRM);
dbms_output.put_line('Error Number: '||SQLERRNO);
END;

Resources