Oracle PL/SQL speed of NVL/LENGTH/TRIM calls versus IS NOT NULL AND != ' ' - oracle

I try to find the best way to check if a CHAR/VARCHAR2 variable contains characters (NULL or spaces should be considered the same, as "no-value"):
I know there are several solutions, but it appears that (NVL(LENGTH(TRIM(v)),0) > 0) is faster than (v IS NOT NULL AND v != ' ')
Any idea why? Or did I do something wrong in my test code?
Tested with Oracle 18c on Linux, UTF-8 db charset ...
I get the following results:
time:+000000000 00:00:03.582731000
time:+000000000 00:00:02.494980000
set serveroutput on;
create or replace procedure test1
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if v is not null and v != ' ' then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
create or replace procedure test2
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if nvl(length(trim(v)),0) > 0 then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
begin
test1();
test2();
end;
/
drop procedure test1;
drop procedure test2;
quit;

The best practice is to ignore the speed difference between small functions and use whatever is easiest.
In realistic database programming, the time to run functions like NVL or IS NOT NULL is completely irrelevant compared to the time needed to read data from disk or the time needed to join data. If one function saves 1 seconds per 50 million rows, nobody will notice. Whereas if a SQL statement reads 50 million rows with a full table scan instead of using an index, or vice-versa, that could completely break an application.
It's unusual to care about these kinds of problems in a database. (But not impossible - if you have a specific use case, then please add it to the question.) If you really need optimal procedural code you may want to look into writing an external procedure in Java or C.

Related

ORA-01555: snapshot too old error when deleting with transactions

I hope someone can explain me why I'm seeing ORA-01555. I have a function and a procedure to perform a cleanup in a huge table:
-- Small cleanup in separate transaction.
FUNCTION clean_single(ts_until IN TIMESTAMP, datapoint_id IN NUMBER) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
DELETE FROM VALUES WHERE DATAPOINT_ID = datapoint_id AND TS < ts_until;
COMMIT;
RETURN sql%rowcount;
END clean_single;
PROCEDURE prc_clean IS
count_all_deleted_vals NUMBER(10) := 0;
BEGIN
BEGIN
FOR dps IN (
SELECT x AS dpid, y as tsUntil FROM Z where some conditions
LOOP
count_all_deleted_vals := count_all_deleted_vals + clean_single(dps.tsUntil, dps.dpid);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Removed ' || count_all_deleted_vals || ' values');
END;
END prc_clean;
The idea is to run prc_clean() from a job and after the relevant datapoint ids are selected, the deletion per datapoint-id is done in a single transaction to avoid having one huge transaction.
But when I run this it runs for a while and then fails with ORA-01555.
In detail i do not understand why this is happening. Why does the PRAGMA AUTONOMOUS_TRANSACTION; in the function not prevent this?
What can I do to prevent it?
As far as I can tell, cause of ORA-01555 is often committing within a loop - and that's exactly what you're doing.
Skip the function altogether (doing DML in it is usually wrong anyway) and use only procedure, e.g.
PROCEDURE prc_clean
IS
count_all_deleted_vals NUMBER (10) := 0;
BEGIN
FOR dps IN (SELECT x AS dpid, y AS tsUntil
FROM Z
WHERE some conditions)
LOOP
DELETE FROM VALUES
WHERE DATAPOINT_ID = dps.dpid
AND TS < dps.tsuntil;
count_all_deleted_vals := count_all_deleted_vals + SQL%ROWCOUNT;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE ('Removed ' || count_all_deleted_vals || ' values');
END;

Can PL/SQL procedures call themselves recursively?

Using PL/SQL, it is possible to call a stored function from within that same function. This can be demonstrated with the following example:
CREATE OR REPLACE FUNCTION factorial(x in number)
RETURN number
IS
f number;
BEGIN
IF x = 0 THEN
f := 1;
ELSE
f := x * factorial(x-1);
END IF;
RETURN f;
END;
/
DECLARE
num number;
factorial number;
BEGIN
num := &num;
factorial := factorial(num);
dbms_output.put_line(' The factorial of '|| num || ' is ' || factorial);
END;
/
Can this be done using PL/SQL stored procedures as well?
Yes, you can write a procedure that calls itself recursively in PL/SQL. Here is an example - implementing the factorial.
With that said, don't ever write a procedure (or a function like yours) without error handling. If you don't understand why, change 5 to 5.3 in the anonymous block below, and you'll see why.
CODE window:
create or replace procedure fact ( x in number, x_fact out number )
as
begin
if x = 0 then x_fact := 1;
else fact(x-1, x_fact);
x_fact := x * x_fact;
end if;
end;
/
set serveroutput on
declare
z number;
begin
fact(5, z);
dbms_output.put_line(z);
end;
/
SCRIPT OUTPUT window (matching each "result" to the corresponding part of the code left as an exercise):
Procedure FACT compiled
PL/SQL procedure successfully completed.
120
You can certainly call PL/SQL functions recursively (with all the usual warnings about the dangers of doing so in any language!).
You are, however, going to run into trouble if you name a local variable the same as your function. You will, for example, get this error when you try to execute the block:
PLS-00222: no function with name 'FACTORIAL' exists in this scope

Finding the exponent (without using POW or built in functions) of a number using a procedure and/or Function

I am trying to create a way to find the exponent of a number (in this case the base is 4 and the exponent 2 so the answer should be 16) using a procedure without using the POW Function or any built in functions to find the exponent. Eventually I would like to take input numbers from the user.
set serveroutput on;
CREATE OR REPLACE PROCEDURE Exponent(base number, exponent number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
/
Error(7,25): PLS-00103: "expression 'BASE' cannot be used as an assignment target" and "expression 'EXPONENT' cannot be used as an assignment target"
Any ideas on how to solve the error and/or better ways of getting the exponent without using built-in functions like POW?
In your procedure base and exponent are input parameters and can't be changed. You've got a couple of options:
1) copy the parameters to variables internal to the procedure and manipulate those internal values, or
2) change the parameters to be input/output parameters so you can change them.
Examples:
1)
CREATE OR REPLACE PROCEDURE Exponent(pin_base number, pin_exponent number) as
base number := pin_base;
exponent number := pin_exponent;
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
2)
CREATE OR REPLACE PROCEDURE Exponent(base IN OUT number,
exponent IN OUT number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
The best thing is that whatever Oracle provides as inbuilt functionality that serves the purpose in a best possible. (Almost all the times better then customized codes) Try to use EXP function. I have tried to make customized code per my understanding. Hope this helps.
CREATE OR REPLACE
FUNCTION EXP_DUMMY(
BASE_IN IN NUMBER,
EXPO_IN IN NUMBER)
RETURN PLS_INTEGER
AS
lv PLS_INTEGER:=1;
BEGIN
FOR I IN
(SELECT base_in COL1 FROM DUAL CONNECT BY level < expo_in+1
)
LOOP
lv:=lv*i.col1;
END LOOP;
RETURN
CASE
WHEN EXPO_IN = 0 THEN
1
ELSE
lv
END;
END;
SELECT EXP_DUMMY(2,4) FROM DUAL;

Call package into a procedure, Oracle PL/SQL

I'm trying to figure out this homework assignment we're supposed to solve in Oracle's 11g PL/SQL, but neither me nor my four friends have found a solution to this problem yet, and we're hoping someone more talented than us would give us a few pointers and/or comments about it.
Let's begin with the assignment in question (it's divided into two parts, and it's the last part we can't figure out):
First, create a procedure which fetches the current time with DBMS_UTILITY.GET_TIME, then performs a complex calculation. In the end, present how long it took (in seconds) to get the result/reach the answer.
And we've solved that one very well by creating a for-loop that calculates prime numbers (took roughly 5 seconds to calculate each prime number from 1 to 5000), however, the last part says this:
Now, create a package that will contain this complex calculation, and then re-write your procedure so that it calls in the "package method" of that calculation. Your procedure will now just measure the starting-time, call in the package with the calculation, and present how long it took (in seconds) to get the result/reach the answer.
So, to recap: the procedure will measure the time it takes for the package to make the calculation. And this is where we're stuck. There's not much information on the net about this, but my code looks like this:
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE time_measure_procedure
AS
time_start NUMBER;
time_end NUMBER;
time_diff NUMBER;
BEGIN
time_start := dbms_utility.get_time;
--This is where I'd presumably call in the
--package into the procedure, whereas the time
--will be measured until the calculation is done.
heavy_calculation_package;
time_end := dbms_utility.get_time;
time_diff := ( time_end - time_start ) / 100;
dbms_output.Put_line('Time it took: ' || time_diff || ' seconds.');
END time_measure_procedure;
/
CREATE OR REPLACE PACKAGE BODY heavy_calculation_package
AS
PROCEDURE time_measure_procedure
IS
x NUMBER;
counter NUMBER;
BEGIN
--This is the prime number calculation, which
--we're supposed to call into the procedure.
FOR n IN 1 .. 5000 LOOP
counter := 0;
x := Floor(n / 2);
FOR i IN 2 .. x LOOP
IF ( MOD(n, i) = 0 ) THEN
counter := 1;
END IF;
END LOOP;
IF ( counter = 0 ) THEN
dbms_output.Put_line(n ||' is a prime number.');
END IF;
END LOOP;
END time_measure_procedure;
END heavy_calculation_package;
/
The errors I receive are typically:
object package_heavy_calc is invalid
cannot compile body of package_heavy_calc without its specification
Does anyone have any clue to why this isn't working? Any hints or pointers at all would be lovely!
You need to have a package in a valid state before you can call it. In this case you need to declare the package first like this:
CREATE OR REPLACE PACKAGE package_heavy_calc
AS
PROCEDURE heavy_calc;
END PACKAGE_HEAVY_CALC;
You can have a package without a body but not a body without a package. You could do this by putting the code in the package head. For your purposes declare the package and the package body, much easier.
As an aside naming is critical for good readable code. I would have named your package PKG_MATH and the procedure P_CALC_PRIME_NUMBERS so when you call it you have an idea of what it does. Naming however is like sport's teams, everyone has a favorite one and the style can be quite different.
Please add the package specs and then try. Below is a snippet which
will help you.
CREATE OR REPLACE PACKAGE package_heavy_calculation
AS
PROCEDURE heavy_calc;
END package_heavy_calculation;
CREATE OR REPLACE PACKAGE BODY package_heavy_calculation
AS
PROCEDURE heavy_calc
IS
x NUMBER;
counter NUMBER;
BEGIN
--This is the prime number calculation, which
--we're supposed to call into the procedure.
FOR n IN 1 .. 5000
LOOP
counter := 0;
x := Floor(n / 2);
FOR i IN 2 .. x
LOOP
IF ( MOD(n, i) = 0 ) THEN
counter := 1;
END IF;
END LOOP;
IF ( counter = 0 ) THEN
dbms_output.Put_line(n ||' is a prime number.');
END IF;
END LOOP;
END heavy_calc;
END package_heavy_calculation;

Is the use of SELECT COUNT(*) before SELECT INTO slower than using Exceptions?

My last question got me thinking.
1)
SELECT COUNT(*) INTO count FROM foo WHERE bar = 123;
IF count > 0 THEN
SELECT a INTO var FROM foo WHERE bar = 123;
-- do stuff
ELSE
-- do other stuff
END IF;
2)
BEGIN
SELECT a INTO var FROM foo where bar = 123;
-- do stuff
EXCEPTION
WHEN no_data_found THEN
--do other stuff
END ;
I assume number 2 is faster because it requires one less trip to the database.
Is there any situation where 1 would be superior, that I am not considering?
EDIT: I'm going to let this question hang for a few more days, to gather some more votes on the answers, before answering it.
If you use exact queries from the question then 1st variant of course slower because it must count all records in table which satisfies criteria.
It must be writed as
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
or
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
because checking for record existence is enough for your purpose.
Of course, both variants don't guarantee that someone else don't change something in foo between two statements, but it's not an issue if this check is a part of more complex scenario. Just think about situation when someone changed value of foo.a after selecting it's value into var while performing some actions which refers selected var value. So in complex scenarios better to handle such concurrency issues on application logic level.
To perform atomic operations is better to use single SQL statement.
Any of variants above requires 2 context switches between SQL and PL/SQL and 2 queries so performs slower then any variant described below in cases when row found in a table.
There are another variants to check existence of row without exception:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
If row_count = 1 then only one row satisfies criteria.
Sometime it's enough to check only for existence because of unique constraint on the foo which guarantees that there are no duplicated bar values in foo. E.g. bar is primary key.
In such cases it's possible to simplify query:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
or use cursor for processing values:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Next variant is from link, provided by #user272735 in his answer:
select
(select a from foo where bar = 123)
into var
from dual;
From my experience any variant without exception blocks in most cases faster then a variant with exceptions, but if number of executions of such block is low then better to use exception block with handling of no_data_found and too_many_rows exceptions to improve code readability.
Right point to choose to use exception or don't use it, is to ask a question "Is this situation are normal for application?". If row not found and it's a expected situation which can be handled (e.g. add new row or take data from another place and so on) is better to avoid exception. If it's unexpected and there are no way to fix a situation, then catch exception to customize error message, write it to event log and re-throw, or just don't catch it at all.
To compare performance just make a simple test case on you system whith both variants called many times and compare.
Say more, in 90 percent of applications this question is more theoretical than practical because there are a lot of another sources of performance issues which must be taken into account first.
Update
I reproduced example from this page at SQLFiddle site with a little corrections (link).
Results prove that variant with selecting from dual performs best: a little overhead when most of queries succeed and lowest performance degradation when number of missing rows raises.
Surprisingly variant with count() and two queries showed best result in case if all queries failed.
| FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
----------------------------------------------------------------
| f1 | 2000 | 2.09 | 0.28 | exception |
| f2 | 2000 | 0.31 | 0.38 | cursor |
| f3 | 2000 | 0.26 | 0.27 | max() |
| f4 | 2000 | 0.23 | 0.28 | dual |
| f5 | 2000 | 0.22 | 0.58 | count() |
-- FNAME - tested function name
-- LOOP_COUNT - number of loops in one test run
-- ALL_FAILED - time in seconds if all tested rows missed from table
-- ALL_SUCCEED - time in seconds if all tested rows found in table
-- variant name - short name of tested variant
Below is a setup code for test environment and test script.
create table t_test(a, b)
as
select level,level from dual connect by level<=1e5
/
insert into t_test(a, b) select null, level from dual connect by level < 100
/
create unique index x_text on t_test(a)
/
create table timings(
fname varchar2(10),
loop_count number,
exec_time number
)
/
create table params(pstart number, pend number)
/
-- loop bounds
insert into params(pstart, pend) values(1, 2000)
/
-- f1 - exception handling
create or replace function f1(p in number) return number
as
res number;
begin
select b into res
from t_test t
where t.a=p and rownum = 1;
return res;
exception when no_data_found then
return null;
end;
/
-- f2 - cursor loop
create or replace function f2(p in number) return number
as
res number;
begin
for rec in (select b from t_test t where t.a=p and rownum = 1) loop
res:=rec.b;
end loop;
return res;
end;
/
-- f3 - max()
create or replace function f3(p in number) return number
as
res number;
begin
select max(b) into res
from t_test t
where t.a=p and rownum = 1;
return res;
end;
/
-- f4 - select as field in select from dual
create or replace function f4(p in number) return number
as
res number;
begin
select
(select b from t_test t where t.a=p and rownum = 1)
into res
from dual;
return res;
end;
/
-- f5 - check count() then get value
create or replace function f5(p in number) return number
as
res number;
cnt number;
begin
select count(*) into cnt
from t_test t where t.a=p and rownum = 1;
if(cnt = 1) then
select b into res from t_test t where t.a=p;
end if;
return res;
end;
/
Test script:
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f1(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f2(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f3(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f4(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
--v_end := v_start + trunc((v_end-v_start)*2/3);
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f5(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
select * from timings order by fname
/
First see: Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance? that is essentially the same question than yours. And after that see About the performance of exception handling.
In both scenarios you should be prepared also to handle too_many_rows exception unless your database schema enforces uniqueness of bar.
This is PL/SQL so you're on a constant database trip - instead you should be afraid/aware of PL/SQL - SQL context switches. See also what Tom says:
But don't be afraid at all to invoke SQL from PLSQL - that is what PLSQL does best.
In first place you shouldn't be worried about the performance but the correctness of a program. In this regard my vote goes for scenario #2.
I'm not sure about faster, but I'd say (2) is clearly superior because you're not considering the case where someone issues DELETE FROM foo where bar='123' in between your statements in (1).
Such a situation I usually do like this:
DECALRE
CURSOR cur IS
SELECT a FROM foo where bar = 123;
BEGIN
OPEN cur;
FETCH cur INTO var;
IF cur%FOUND THEN
-- do stuff, maybe a LOOP if required
ELSE
--do other stuff
END;
END;
This has some benfits:
You read only one record from database, the rest you skip. Should be the fastest way of doing it in case you just have to know if number of rows > 1.
You don't handle a "normal" situation with an "exception" handler, some people consider this as "more beautiful" coding.

Resources