PLSQL - Measure the execution duration of a procedure - plsql

I have a procedure that runs every one hour populating a table. The records handled from the procedure are many so it takes approximately 12~17 mins each time it is executed.
Do you now if there is a way (i.e. trigger) to record the duration of each execution (i.e. into a table)?

I don't know of a trigger that would allow this to be done automatically. One way to do this would be something like
PROCEDURE MY_PROC IS
tsStart TIMESTAMP;
tsEnd TIMESTAMP;
BEGIN
tsStart := SYSTIMESTAMP;
-- 'real' code here
tsEnd := SYSTIMESTAMP;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('MY_PROC', tsStart, tsEnd);
END MY_PROC;
If you only need this for a few procedures this might be sufficient.
Share and enjoy.

I typically use a log table with a date or timestamp column that uses a default value of sysdate/systimestamp. Then I call an autonomous procedure that does the log inserts at certain places I care about (starting/ending a procedure call, after a commit, etc):
See here (look for my answer).
If you are inserting millions of rows, you can control when (how often) you insert to the log table. Again, see my example.

To add to the first answer, once you have start and end timestamps, you can use this function to turn them into a number of milliseconds. That helps with readability if nothing else.
function timestamp_diff(
start_time_in timestamp,
end_time_in timestamp) return number
as
l_days number;
l_hours number;
l_minutes number;
l_seconds number;
l_milliseconds number;
begin
select extract(day from end_time_in-start_time_in)
, extract(hour from end_time_in-start_time_in)
, extract(minute from end_time_in-start_time_in)
, extract(second from end_time_in-start_time_in)
into l_days, l_hours, l_minutes, l_seconds
from dual;
l_milliseconds := l_seconds*1000 + l_minutes*60*1000
+ l_hours*60*60*1000 + l_days*24*60*60*1000;
return l_milliseconds;
end;

Related

PL/SQL if then else statements not running

I have written following code in oracle pl/sql
create or replace procedure sorting_criteria(criteria in varchar)
as
begin
if(criteria='lowest price')
then
declare
p_name product.p_name%type;
cursor ptr is select p_name from product order by amount ASC;
begin
open ptr;
loop
fetch ptr into p_name;
exit when ptr%notfound;
dbms_output.put_line(p_name);
end loop;
close ptr;
end;
else if(criteria='highest price')
then
declare
p_name product.p_name%type;
cursor ptr is select p_name from product order by amount DESC;
begin
open ptr;
loop
fetch ptr into p_name;
exit when ptr%notfound;
dbms_output.put_line(p_name);
end loop;
close ptr;
end;
else
dbms_output.put_line('Enter valid criteria!');
end if;
end;
/
But it is giving following error: Error at line 35: PLS-00103: Encountered the symbol ";" when expecting one of the following: Please help
The ELSE-IF statement in PL/SQL has to be written as ELSIF. Otherwise, you should close the second IF with an other END IF; statement.
You can solve the issue by changing the ELSE IF at line 17 to an ELSIF
The answer by #GregorioPalamà correctly addresses your issues. But you can drastically reduce the workload by changing your thinking away from "If...then...else" to the "set of" and letting SQL do the work. In this case the only difference is sorting either ascending or descending on amount. The same effect can be achieved by sorting ascending on amount or minus amount; and SQL can make that decision. So you can reduce the procedure to validating the parameter and a single cursor for loop:
create or replace procedure sorting_criteria(criteria in varchar2)
as
cursor ptr(c_sort_criteria varchar2) is
select p_name
from product
order by case when c_sort_criteria = 'lowest price'
then amount
else -amount
end ;
begin
if criteria in ('lowest price', 'highest price')
then
for rec in ptr(criteria)
loop
dbms_output.put_line('Product: ' || rec.p_name );
end loop;
else
dbms_output.put_line('Enter valid criteria!');
end if;
end sorting_criteria;
/
See demo here. For demonstration purposed I added the amount to the dbms_output.
A couple notes:
While it is not incorrect using p_... as a column name, it is also
not a good idea. A very common convention (perhaps almost a
standard) to use p_... to indicate parameters. This easily leads to
confusion; confusion amongst developers is a bad thing.
IMHO it is a bug to name a local variable the same as a table
column name. While the compiler has scoping rules which one to use
it again leads to confusion. The statement "where table.name = name"
is always true, except when at least one of them is null, which possible could lead to updating/deleting every row in your table. In this
case p_name is both a column and a local variable.

Creating a Database Trigger that checks if more than one record was added on a date?

CREATE OR REPLACE TRIGGER POSITION_NUMBER
BEFORE UPDATE OR INSERT OR DELETE ON APPLIES
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
NUMBER_OF_POSITIONS NUMBER;
BEGIN
SELECT count(pnumber) INTO NUMBER_OF_POSITIONS
FROM APPLIES WHERE anumber = :NEW.anumber;
IF( NUMBER_OF_POSITIONS > 2 AND count(APPDATE) > 2 )
THEN
RAISE_APPLICATION_ERROR(-20000,'an Employee cannot apply for
more than two positions');
END IF;
END;
/
Im attemtping to create a trigger that goes off if an Applicant applys for more than two Positions on the Same Day, but im not sure how i would implement the Date side of it. Below is the set of relational Schemeas
You can use the TRUNC function to remove the time portion and then see if the application date matches today's date, regardless of time.
Also, there is no need for the autonomous transaction pragma. You are not executing any DML.
CREATE OR REPLACE TRIGGER position_number
BEFORE UPDATE OR INSERT OR DELETE
ON applies
DECLARE
number_of_positions NUMBER;
BEGIN
SELECT COUNT (pnumber)
INTO number_of_positions
FROM applies
WHERE anumber = :new.anumber AND TRUNC (appdate) = TRUNC (SYSDATE);
IF number_of_positions > 2
THEN
raise_application_error (
-20000,
'An Employee cannot apply for more than two positions on the same day');
END IF;
END;
/

Deal with column list of multiple queries executed at runtime.

I have a list of queries stored in an Oracle DB table. My requirement is to fetch each of those queries one by one and fire them in a procedure and log their start end and elapsed times in another table
My problem is how should I handle the column list as that's going to be different for each of those queries and the number of columns and their datatypes cannot be anticipated at runtime.
Please suggest a way out.
For now, I have written down the code below. Here I have enclosed each query fetched with a count() to avoid the problem. However, the actual time taken for the count() query will be different from the time taken for the original query to execute.
Thanks a lot!
DECLARE
before_time TIMESTAMP;
after_time TIMESTAMP;
elapsed_time_in_ms NUMBER;
stmnt CLOB; --varchar(32000);
counts NUMBER;
sql_no NUMBER;
err_mess VARCHAR2(100);
CURSOR get_queries
IS
SELECT * FROM SLOW_RUNNING_SQL WHERE curr_ind = 1;
FUNCTION get_elapsed_time(
start_time_in TIMESTAMP ,
end_time_in TIMESTAMP )
RETURN NUMBER
AS
l_days NUMBER;
hours NUMBER;
minutes NUMBER;
seconds NUMBER;
milliseconds NUMBER;
BEGIN
<calculates elapsed time in milliseconds and returns that>
RETURN milliseconds ;
END;
BEGIN
dbms_output.put_line(CURRENT_TIMESTAMP);
before_time := SYSTIMESTAMP;
FOR i IN get_queries
LOOP
stmnt := i.SQL_DESC;
sql_no := i.sql_no;
stmnt := 'SELECT count(*) FROM ('||stmnt||') a';
dbms_output.put_line(stmnt);
EXECUTE IMMEDIATE stmnt INTO counts;
after_time := SYSTIMESTAMP;
elapsed_time_in_ms:= get_elapsed_time(before_time,after_time);
dbms_output.put_line(elapsed_time_in_ms);
INSERT
INTO query_performance_log VALUES
(
i.sql_no,
stmnt,
counts,
before_time,
after_time,
elapsed_time_in_ms/1000,
'No exception',
elapsed_time_in_ms );
dbms_output.put_line(stmnt);
dbms_output.put_line(counts);
dbms_output.put_line(after_time);
dbms_output.put_line(TO_CHAR(after_time - before_time));
COMMIT;
END LOOP;
ROLLBACK;
EXCEPTION
WHEN OTHERS THEN
err_mess:= SQLERRM;
INSERT
INTO query_performance_log VALUES
(
sql_no,
stmnt,
0,
NULL,
NULL,
0,
err_mess,
0
);
dbms_output.put_line(SQLERRM);
ROLLBACK;
END;
A solution that might suit you is to select a constant for every line returned by your query and make a bulk collect INTO a collection of varchar2 variables.
Here is what you're looking for:
-- declare a list of varchar2:
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
-- then use this type in your proc.:
[..]
declare
v_res t_my_list;
[..]
-- then run the query
execute immediate 'SELECT ''x'' FROM ('||stmnt||') '
bulk collect into v_res;
If the columns selected by your queries are "simple", the above should work fair enough to evaluate performances. But if you start calling other functions and procedures for the data you retrieve in the select, then its more complicate.
In this other case, then you should try to work something out to build a concatenation of the columns returned (and enlarge the VARCHAR2(100) in the declaration of t_my_list). This implies you start work on stmnt and extract the columns, a part being the replacement of , by ''||'' or so.

I want to create a unique time stamp in oracle, it should check previous time stamp and overwrite if it is previously used?

I want to create a unique time stamp in Oracle, it should check previous time stamp and overwrite if it is previously used. I tried creating one in SQL Server but it doesn't work. Please help
I cannot imagine why you would want to do such a thing. Your request appears to translate to this
create or replace function this_is_silly
return timestamp
is
t1 timestamp := systimestamp;
t2 timestamp := systimestamp;
begin
if( t1 = t2 )
then
dbms_lock.sleep(0.01);
t2 := systimestamp;
end if;
return t2;
end;
Maybe instead you are looking for something like
create table all_timestamps_returned (
ts timestamp primary key
);
create or replace procedure get_a_new_timestamp( p_new_ts out timestamp )
as
l_ts timestamp := systimestamp;
begin
insert into all_timestamps_returned( ts )
values( l_ts );
p_new_ts := l_ts;
exception
when dup_val_on_index
then
dbms_lock.sleep( 0.01 );
get_a_new_timestamp( p_new_ts );
end;
This will iterate (potentially for a number of times in a reasonably busy system) until it happens to get a unique timestamp or you exhaust your max recursion limit. You could implement this iteratively using a loop as well but then you'd probably want to have some sort of failsafe if you've retried 50 times without getting a unique timestamp. Again, I'm not sure why you'd want to do this rather than recognizing that multiple events can happen at the same time and using a sequence to generate the unique key. But you could.

How to use a WHILE loop in PL/SQL with decrementing

I've been trying to do a lot of research on this problem I need to do for a class, but I'm not having much luck. IE, I can't find a good example. I'm an example person.
Create a PL/SQL block that uses a WHILE loop structure to generate a payment schedule for a donor's pledge, which is to be paid monthly in equal increments. Values available for the block are starting payment due date, monthly payment amount, and number of total monthly payments for the pledge. The list that is generated should display a line for each monthly payment showing payment number, date due, payment amount and donation balance (remaining amount of pledge owed).
Instead of displaying the donation balance on each line of output, display the total paid to date.
After some consideration, it's obvious that the payments have to be decremented on a monthly basis until the balance is zero at which the loop will exit. But I am not sure how to do it properly. I've looked all over the Internet and there's nothing that seems to help me finish the pl/sql block, example wise.
This is what I have so far.
declare
lv_paymentnumber_num number(3,0);
lv_paymentamount_num number(4,2);
lv_datepaymentpaid_date date;
lv_amountpaidtodate_num number(4,2);
lv_balanceremaining_num number(4,2);
lv_nextduedate_date date;
begin
lv_balanceremaining_num :-lv_paymentamount_num - lv_amountpaidtodate_num;
dbms_output.put_line(lv_balanceremaining_num);
loop
after that, that's where I get lost.
To terminate a loop use the EXIT statement as demonstrated below:
declare
lv_paymentnumber_num number(3,0) := 0;
lv_paymentamount_num number(4,2);
lv_datepaymentpaid_date date;
lv_amountpaidtodate_num number(4,2);
lv_balanceremaining_num number(4,2);
lv_nextduedate_date date;
nPayment NUMBER;
begin
loop
nPayment := LEAST(lv_paymentamount_num, lv_balanceremaining_num);
lv_balanceremaining_num := lv_balanceremaining_num - nPayment;
lv_amountpaidtodate_num := lv_amountpaidtodate_num + nPayment;
lv_paymentnumber_num := lv_paymentnumber_num + 1;
dbms_output.put_line('Payment = ' || nPayment ||
' Balance remaining = ' ||
lv_balanceremaining_num);
IF lv_balanceremaining_num <= 0 THEN
EXIT;
END IF;
END LOOP;
END;
Amend as needed.
Share and enjoy.

Resources