plsql sleep without using dbms_lock.sleep/DBMS_SESSION.sleep - oracle

Has anyone have any oracle sql that will let the program wait for 10 seconds without using dbms_lock.sleep/DBMS_SESSION.sleep functions.
In UAT instance, i want run the update statement every 10s and my current db role does not have privilages to use dbms_lock.sleep/DBMS_SESSION.sleep

One of the alternative i could think of is use of the method sleep from the Java class Thread, which you can easily use through providing a simple PL/SQL wrapper procedure as shown below:
Procedure:
CREATE OR REPLACE PROCEDURE sleep (
p_milli_seconds IN NUMBER
) AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
Execution
BEGIN
DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
SLEEP(5 * 1000); -- Resting for 5 sec
DBMS_OUTPUT.PUT_LINE('End ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
Output:
Start 2020-03-25 12:57:24
End 2020-03-25 12:57:36

I had the same problem and wrote this code to approximate a wait function using only PL/SQL:
DECLARE
v_minimum_seconds_to_wait NUMBER := 10; /* this is the only value you need to edit */
v_time_to_output DATE;
PROCEDURE wait_at_least(
p_minimum_seconds_to_wait IN NUMBER
)
IS
v_result VARCHAR2(5) := 'TRUE';
v_target_time DATE := SYSDATE + (p_minimum_seconds_to_wait / 86400); /* convert incoming number to seconds */
FUNCTION is_it_after(
p_target_time IN DATE
)
RETURN VARCHAR2
IS
v_result_after VARCHAR2(5) := 'TRUE';
BEGIN
IF SYSDATE < p_target_time THEN
v_result_after := 'FALSE';
END IF;
RETURN v_result_after;
END is_it_after;
BEGIN
v_result := is_it_after(v_target_time);
WHILE v_result != 'TRUE' LOOP
v_result := is_it_after(v_target_time);
END LOOP;
END wait_at_least;
BEGIN
v_time_to_output := SYSDATE;
DBMS_Output.put_line('Starting time: '|| TO_CHAR(v_time_to_output, 'DD-MON-YYYY HH24:MI:SS'));
wait_at_least(
p_minimum_seconds_to_wait => v_minimum_seconds_to_wait
);
v_time_to_output := SYSDATE;
DBMS_Output.put_line('Ending time: '|| TO_CHAR(v_time_to_output, 'DD-MON-YYYY HH24:MI:SS'));
END;
This is tested against Oracle 11g:
Starting time: 25-AUG-2022 17:09:01
Ending time: 25-AUG-2022 17:09:11

Related

Oracle error: ORA-01847: day of month must be between 1 and last day of month

I tried the following anonymous block and get ORA-01847 error at line 14.
Is there anything wrong?
What is the definition of line 14th 'SSSS.FF' into TO_CHAR FUNCTION?
Thanks in advance.
DECLARE
PROCEDURE_NAME VARCHAR2(100) := 'CONV_CIB';
TIME_START TIMESTAMP;
TIME_END TIMESTAMP;
EXECUTION_TIME TIMESTAMP;
BEGIN
dbms_output.enable;
TIME_START := SYSTIMESTAMP;
select sysdate into procedure_name from dual;
TIME_END := SYSTIMESTAMP;
EXECUTION_TIME := TO_CHAR (TIME_END - TIME_START, 'SSSS.FF');
--dbms_output.put_line ('Start: ' || TIME_START);
--dbms_output.put_line (' End: ' || TIME_END);
dbms_output.put_line (PROCEDURE_NAME ||' PROCEDURE EXECUTION TIME: ' || TO_CHAR (TIME_END - TIME_START, 'SSSS.FF'));
DBMS_OUTPUT.PUT_LINE (EXECUTION_TIME);
EXECUTION_TIME := (EXECUTION_TIME || ' HRS');
DBMS_OUTPUT.PUT_LINE (EXECUTION_TIME);
END;
Yes. There is an error. You have declared variable EXECUTION_TIME as TIMESTAMP but you are assigning character value in it. As you changed the datatype of this variable to VARCHAR2, This will work like charm -
DECLARE
PROCEDURE_NAME VARCHAR2(100) := 'CONV_CIB';
TIME_START TIMESTAMP;
TIME_END TIMESTAMP;
EXECUTION_TIME VARCHAR2(50);
BEGIN
dbms_output.enable;
TIME_START := SYSTIMESTAMP;
select sysdate into procedure_name from dual;
TIME_END := SYSTIMESTAMP;
EXECUTION_TIME := TO_CHAR (TIME_END - TIME_START, 'SSSS.FF');
--dbms_output.put_line ('Start: ' || TIME_START);
--dbms_output.put_line (' End: ' || TIME_END);
dbms_output.put_line (PROCEDURE_NAME ||' PROCEDURE EXECUTION TIME: ' || TO_CHAR (TIME_END - TIME_START, 'SSSS.FF'));
DBMS_OUTPUT.PUT_LINE (EXECUTION_TIME);
EXECUTION_TIME := (EXECUTION_TIME || ' HRS');
DBMS_OUTPUT.PUT_LINE (EXECUTION_TIME);
END;
Demo.
Is there anything wrong?
Yes, as you state in the question, you get an ORA-01847 error at line 14.
Apart from that:
You overwrite the procedure name with the current time.
If you subtract two TIMESTAMPs you get an INTERVAL DAY TO SECOND data type as the result and not another TIMESTAMP.
You cannot use EXECUTION_TIME := (EXECUTION_TIME || ' HRS'); as the right-hand side results in a string and not a TIMESTAMP.
This will run without syntax errors:
DECLARE
PROCEDURE_NAME VARCHAR2(100) := 'CONV_CIB';
value DATE;
TIME_START TIMESTAMP;
TIME_END TIMESTAMP;
EXECUTION_TIME INTERVAL DAY TO SECOND(9);
BEGIN
dbms_output.enable;
TIME_START := SYSTIMESTAMP;
SELECT sysdate INTO value FROM DUAL;
TIME_END := SYSTIMESTAMP;
EXECUTION_TIME := TIME_END - TIME_START;
dbms_output.put_line ('Start: ' || TIME_START);
dbms_output.put_line (' End: ' || TIME_END);
dbms_output.put_line (
PROCEDURE_NAME
||' PROCEDURE EXECUTION TIME: ' || execution_time
);
END;
/
and outputs:
Start: 06-SEP-22 12.14.01.551337
End: 06-SEP-22 12.14.01.553333
CONV_CIB PROCEDURE EXECUTION TIME: +00 00:00:00.001996000
DB<>Fiddle here

Pass a table name and time stamp variable to a PL/SQL

I am writing a below PL/SQL code in SQL developer to delete data from a table with a timestamp column in the where condition. How can I modify this code to pass the table name and the timestamp value to values that I want based on what table and time records I want to delete the data from and create a stored procedure that can be reused.
DBMS_OUTPUT.ENABLE;
DECLARE
counter INTEGER := 0;
stop INTEGER;
BEGIN
dbms_output.put_line('START');
LOOP
counter := counter + 1;
DELETE my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM'
AND ROWNUM <= 100000;
SELECT COUNT(*)
INTO stop
FROM my_schema.test
WHERE t = '10-JUN-20 04.33.46.000000000 AM';
EXIT WHEN stop <= 0;
COMMIT;
END LOOP;
dbms_output.put_line('Counter: ' || counter);
dbms_output.put_line('Left: ' || stop);
COMMIT;
END;
Adapting your anonymous to a stored procedure will, as indicated, require converting it to dynamic SQL. Always more difficult. And subject to SQL injection. For this you should validate string replacement parameters. I have a couple other changes:
Pass the desired as a timestamp, not a string, this allows/forces the
calling routine to determine the format and necessary conversion, if
any.
Added a parameter for column name as well. This frees naming columns
from the requirement of the procedure.
There is no need to count remaining items. Your loop processes until
that value reaches 0, but this can be determined by the number of
rows deleted on the last pass. Delete sets sql%rowcount to the number
of rows deleted. When the pass deletes 0 rows the process is
complete.
Removed the results display and the commit from the procedure, again
offloading this to the caller.
create or replace
procedure delete_values_by_timestamp
( p_table_name in varchar2
, p_column_name in varchar2
, p_timestamp in timestamp
, p_result_msg out varchar2
)
IS
table_name_parameter_invalid exception;
pragma exception_init(table_name_parameter_invalid, -44002);
column_name_parameter_invalid exception;
pragma exception_init(column_name_parameter_invalid, -44003);
k_nl constant varchar2(1) := chr(10);
k_max_delete_per_interation constant integer := 100000;
k_base_delete varchar2(256) :=
'delete from <table_name>' ||
' where <column_name> <= :1' ||
' and rownum <= :2';
v_delete_sql varchar2 (256) ;
v_rows_deleted integer := 0;
begin
v_delete_sql := replace(replace(k_base_delete,'<table_name>', dbms_assert.sql_object_name(p_table_name))
,'<column_name>',dbms_assert.simple_sql_name(p_column_name));
dbms_output.put_line('Running SQL:' || k_nl || v_delete_sql);
loop
execute immediate v_delete_sql using p_timestamp, k_max_delete_per_interation;
exit when sql%rowcount = 0;
v_rows_deleted :=v_rows_deleted + sql%rowcount;
end loop;
if v_rows_deleted = 0
then
p_result_msg := 'No Data Found';
else
p_result_msg := 'Number of Rows Deleted ' || to_char(v_rows_deleted);
end if;
exception
when table_name_parameter_invalid then
raise_application_error(-20199,'Invalid Table Name (' || p_table_name || ') specified.');
when column_name_parameter_invalid then
raise_application_error(-20198,'Invalid Column Name (' || p_column_name || ') specified.');
end delete_values_by_timestamp;
See example: In the example I reduce the number of rows deleted on each iteration from 100000 to 20. An additionally enhancement would be to pass the number of rows for each iteration as a parameter.
I couldn't test it but you could create a function whcih takes the table name and the timestamp as parameter.
As long you want to delete every record with the given timestamp you don't need to loop for each record.
This function should be just an example.
FUNCTION delete_values_by_timestamp (p_table_name IN VARCHAR2 DEFAULT NULL,
p_timestamp IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
IS
v_count NUMBER := 0;
v_query VARCHAR2 (500) := '';
BEGIN
IF p_table_name IS NOT NULL
THEN
IF p_timestamp IS NOT NULL
THEN
v_query := 'SELECT COUNT(*)
FROM my_schema.' || p_table_name | '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query INTO v_count;
IF v_count > 0
THEN
v_query := 'DELETE FROM my_schema.' || p_table_name || '
WHERE t = TO_DATE(''' || p_timestamp ||''', ''DD.MM.YYYY HH24:MI:SS.SSSS'')';
EXECUTE IMMEDIATE v_query;
ELSE
RETURN 'NO RECORDS FOUND!';
END IF;
ELSE
RETURN 'TIMESTAMP EMPTY!';
END IF;
ELSE
RETURN 'TABLE NAME EMPTY!';
END IF;
END;

data loss with parallel enabled pipelined function

I have a pipelined function that loads data into file.
The following is the code of function.
CREATE OR REPLACE FUNCTION DATA_UNLOAD
( p_source IN SYS_REFCURSOR,
p_filename IN VARCHAR2,
p_directory IN VARCHAR2
) RETURN dump_ntt PIPELINED PARALLEL_ENABLE (PARTITION p_source BY ANY)
AS
TYPE row_ntt IS TABLE OF VARCHAR2(32767);
v_rows row_ntt;
v_file UTL_FILE.FILE_TYPE;
v_buffer VARCHAR2(32767);
v_sid VARCHAR(255);
v_name VARCHAR2(255);
v_lines PLS_INTEGER := 0;
v_start_dttm TIMESTAMP WITH TIME ZONE:= SYSTIMESTAMP;
v_end_dttm TIMESTAMP WITH TIME ZONE;
c_eol CONSTANT VARCHAR2(1) := CHR(10);
c_eollen CONSTANT PLS_INTEGER := LENGTH(c_eol);
c_maxline CONSTANT PLS_INTEGER := 32767;
BEGIN
--v_sid := lpad(sys_context('USERENV', 'sid'), 10, '0');
v_name:=p_filename;
LOOP
if utl_file.is_open(v_file)
then
utl_file.fclose(v_file);
end if;
v_file := UTL_FILE.FOPEN(p_directory, v_name, 'A', c_maxline);
FETCH p_source BULK COLLECT INTO v_rows LIMIT 100;
FOR i IN 1 .. v_rows.COUNT LOOP
IF LENGTH(v_buffer) + c_eollen + LENGTH(v_rows(i)) <= c_maxline THEN
v_buffer := v_buffer || c_eol || v_rows(i);
ELSE
IF v_buffer IS NOT NULL THEN
UTL_FILE.PUT_LINE(v_file, v_buffer);
END IF;
v_buffer := v_rows(i);
END IF;
END LOOP;
v_lines := v_lines + v_rows.COUNT;
EXIT WHEN p_source%NOTFOUND;
END LOOP;
CLOSE p_source;
UTL_FILE.PUT_LINE(v_file, v_buffer);
UTL_FILE.FCLOSE(v_file);
v_end_dttm := SYSTIMESTAMP;
--PIPE ROW (dump_ot(v_name, p_directory, v_lines, v_sid, v_start_dttm, v_end_dttm));
--RETURN ;
END;
i call the function this way.
SELECT * from table(DATA_UNLOAD(
CURSOR(select /*+ PARALLEL */ a || b || c from sample_table),
'sample.txt',
'99_DIR'));
a real life select that i pass as a parameter to function returns 30000 rows, but when i use the function to load the result into a file some rows are lost. During the execution with PARALLEL hint there are 24 parallel sessions, and i dont want to make it less. My guess is that the problem is in parallel execution, because when i dont use PARALLEL hint no data is lost. Can anyone suggest something to get rid of that problem without removing the hint?
Even though you are creating sample.txt with Append mode - you have 24 parallel sessions each writing to it. I always use unique filenames by appending the SID to your variable:
SELECT sid INTO v_sid FROM v$mystat WHERE ROWNUM = 1;
v_name := p_filename || '_' || v_sid || '.dat';
Depending on the # of parallel sessions you should 1 to many files with the format sample_nnnn.txt where nnnn is the SID number.

measure time of an sql statement in a procedure in plsql

I must write a procedure which save the execute time of any sql-statement in a table.
The procedure is calling by exec measuresqltime('sql statement as string');
My idea is like this:
--declarations
timestart NUMBER;
BEGIN
dbms_output.enable;
timestart:=dbms_utility.get_time();
EXECUTE IMMEDIATE sql
COMMIT;
dbms_output.put_line(dbms_utility.get_time()-timestart);
-- save time
But it didn't work for me for a SELECT *... clause. (I think sql need a INTO-order)
Is there a way to execute any sql-atatements in a procedure?
If your SQL statement is a SELECT, you need to fetch from the cursor to have a meaningful measure of its execution time.
If you don't fetch from the cursor, you only measure the time spent in "parse" and "execution" phases, whereas much of the work is usually done in the "fetch" phase for SELECT statements.
You won't be able to fetch with EXECUTE IMMEDIATE or OPEN cursor FOR 'string' if you don't know the number of columns the actual statement will have. You will have to use the dynamic SQL package DBMS_SQL if the number/type of columns of the SELECT is unknown.
Here's an example:
SQL> CREATE OR REPLACE PROCEDURE demo(p_sql IN VARCHAR2) AS
2 l_cursor INTEGER;
3 l_dummy NUMBER;
4 timestart NUMBER;
5 BEGIN
6 dbms_output.enable;
7 timestart := dbms_utility.get_time();
8 l_cursor := dbms_sql.open_cursor;
9 dbms_sql.parse(l_cursor, p_sql, dbms_sql.native);
10 l_dummy := dbms_sql.execute(l_cursor);
11 LOOP
12 EXIT WHEN dbms_sql.fetch_rows(l_cursor) <= 0;
13 END LOOP;
14 dbms_sql.close_cursor(l_cursor);
15 dbms_output.put_line(dbms_utility.get_time() - timestart);
16 END;
17 /
Procedure created.
SQL> exec demo('SELECT * FROM dual CONNECT BY LEVEL <= 1e6');
744
PL/SQL procedure successfully completed.
Note that this will measure the time needed to fetch to the last row of the SELECT.
completing devosJava answered... avoid using it at dawn ;P
PROCEDURE MY_PROCEDURE IS
timeStart TIMESTAMP;
timeEnd TIMESTAMP;
timeSecond NUMBER
BEGIN
timeStart := SYSTIMESTAMP;
-- YOUR CODE HERE
timeEnd := SYSTIMESTAMP;
timeSecond :=((extract(hour from timeEnd)*3600)+(extract(minute from timeEnd)*60)+extract(second from timeEnd))-((extract(hour from timeStart)*3600)+(extract(minute from timeStart)*60)+extract(second from timeStart));
dbms_output.put_line('finished: '||timeSecond||' seconds');
END MY_PROC;
To calculate the duration for an execution time
PROCEDURE MY_PROCEDURE IS
timeStart TIMESTAMP;
timeEnd TIMESTAMP;
BEGIN
timeStart := SYSTIMESTAMP;
-- YOUR CODE HERE
timeEnd := SYSTIMESTAMP;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('MY_PROCEDURE ', timeStart , timeEnd );
END MY_PROC;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('PROC_NAME', TO_CHAR
(SYSDATE, 'DD/MM/YYYY HH24:MI:SS'),NULL );
Your query here;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('PROC_NAME',NULL, TO_CHAR
(SYSDATE, 'DD/MM/YYYY HH24:MI:SS'));

How to dynamically add interval to timestamp?

I need at some point to increment dynamically a timestamp plsql variable.
So, instead of doing this:
timestamp_ := timestamp_ + INTERVAL '1' DAY;
I would like to do thomething like this:
timestamp_ := timestamp_ + INTERVAL days_ DAY;
It doesn't really work.
My final goal is to dynamically create some scheduler jobs for some entities that have an variable expiration date, to avoid creating a single one which would be often executed.
It sounds like you want
timestamp_ := timestamp + numtodsinterval( days_, 'day' );
I would be somewhat cautious, however, about an architecture that involves creating thousands of scheduler jobs rather than one job that runs periodically to clear out expired rows. A single job is a heck of a lot easier to manage and oversee.
Special note:
1. INTERVAL YEAR TO MONTH and
2. INTERVAL DAY TO SECOND
are the only two valid interval datatypes;
Sample Example:
=============================
DECLARE
l_time INTERVAL YEAR TO MONTH;
l_newtime TIMESTAMP;
l_year PLS_INTEGER := 5;
l_month PLS_INTEGER := 11;
BEGIN
-- Notes :
-- 1. format is using "-" to connect year and month
-- 2. No need to mention any other keyword ; Implicit conversion takes place to set interval
l_time := l_year || '-' || l_month;
DBMS_OUTPUT.put_line ( l_time );
SELECT SYSTIMESTAMP + l_time INTO l_newtime FROM DUAL;
DBMS_OUTPUT.put_line ( 'System Timestamp :' || SYSTIMESTAMP );
DBMS_OUTPUT.put_line ( 'New Timestamp After Addition :' || l_newtime );
END;
=============================
Try this:
DECLARE
l_val NUMBER;
l_result VARCHAR2( 20 );
BEGIN
l_val := 1;
SELECT SYSDATE - INTERVAL '1' DAY * l_val INTO l_result FROM DUAL;
DBMS_OUTPUT.put_line( 'Current Date is ' || SYSDATE || ' minus ' || l_val || ' day(s) is ' || l_result );
END;
Output will be:
Current Date is 25-FEB-16 minus 1 day(s) is 24-FEB-16

Resources