DBMS_JOB.SUBMIT interval interpretation - oracle

I want to understand a dbms_job.submit statement
dbms_job.submit(jobno, 'xxxsome_pl_sql_statement',next_date,interval);
next_date evaluates to Last_Day(Sysdate) ----30-apr-22
interval evaluates to Last_Day(Add_Months(Sysdate,1)) ------31-may-22
sysdate for today is 13-apr-22
1.How to interpret 31-may-22 as the interval? Should I interpret the interval as the time between Last_Day(Sysdate) and Last_Day(Add_Months(Sysdate,1)),which is approximately one month?
2.The next date to run the job has already been set, why do we need to set the interval again?

NEXT_DATE is the next time the job should execute. INTERVAL is a SQL formula in varchar2 format to calculate subsequent executions, not a date itself, and should be enclosed in single quotes like the PL/SQL statement.
dbms_job.submit(jobno, 'some_pl_sql_statement',Last_Day(Sysdate),'Last_Day(Add_Months(Sysdate,1))');
LAST_DAY and ADD_MONTHS are SQL functions.
DBMS_JOB.SUBMIT(
job OUT BINARY_INTEGER,
what IN VARCHAR2,
next_date IN DATE DEFAULT SYSDATE,
interval IN VARCHAR2 DEFAULT 'NULL',
no_parse IN BOOLEAN DEFAULT FALSE,
instance IN BINARY_INTEGER DEFAULT ANY_INSTANCE,
force IN BOOLEAN DEFAULT FALSE);
dbms_job.submit(
what=>'some_plsql_statement;',
next_date=>Last_Day(Sysdate), -- last day of this month
interval=>'Last_Day(Add_Months(Sysdate,1))'); -- Last day of the next month after each execution

Related

how to create DBMS_JOB execute every monday at 04:00

I am trying to create job which executes every monday on 04:00 with the script
declare
job number;
begin
sys.dbms_job.submit( job,
'CALC_LAST_WEEK_STAT();',
to_date('03-07-2017 04:00:00', 'dd-mm-yyyy hh24:mi:ss'),
trunc('NEXT_DAY(TRUNC(SYSDATE), "MONDAY") + 4/24'),'dd-mm-yyyy hh24:mi:ss');
commit;
end;
/
but i have error PLS-00306: wrong number or types of arguments in call to 'SUBMIT'
If you have a look at the documentation for DBMS_JOB.SUBMIT() you will see that the datatype for interval is VARCHAR2. You are passing a date whereas SUBMIT() expects the formula for working out the time of the next run.
So that would be
q'[(NEXT_DAY(TRUNC(SYSDATE), 'MONDAY') + 4/24)]'

Oracle Datetime difference issue with AM/ PM

I have the following stored procedure which calculates the time taken for a merge statement
create or replace procedure ModAuditData(
O_UpdatedCount out int
,O_EndTime out timestamp
,O_Duration out int)
as
P_StartTime timestamp(3) WITH LOCAL TIME ZONE;
-- EndTime timestamp;
begin
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
begin
select extract( second from (O_EndTime-P_StartTime) )
into O_Duration
from dual;
Exception When others then
O_Duration:=0;
end;
end ModAuditData;
The Issue is
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS')
gives exact opposite of
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
in terms of AM/PM
What is the correct way to calculate the start and end time
CURRENT_TIMESTAMP is already a TIMESTAMP WITH TIME ZONE, there is no reason to convert it first to VARCHAR2 and then back again into a TIMESTAMP.
Also timestamp(3) (which provides precision up to millisecond) does not make much sense when you return duration as INTEGER, i.e. full seconds.
Try it like this:
P_StartTime timestamp(3) WITH TIME ZONE;
begin
P_StartTime := current_timestamp;
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_Duration := EXTRACT(SECOND FROM (current_timestamp - P_StartTime));
end;
In case of SQL*Plus consider to use TIMING command.

Oracle Jobs & finding timestamp difference

I've faced today a weird problem: I have a function which is called by job. I want to find the difference from start of the function till the end to log it then to some table.
So, let's say I have function
procedure p is
starttime timestamp := systimestamp;
procedure writeTime
is
diff interval day to second := systimestamp - starttime;
begin
-- here insert diff to some table
end;
begin
-- doing some long stuff
writeTime();
exception
when others then
writeTime();
end;
The problem in the function is:
When I run this manually, it works well, difference is clear. E.g. I use extract to parse the interval: extract(hour from diff)*60*60 + extract(minute from diff)*60 + extract(second from diff)
When I set up the job and the job runs this function I have a big problem: it returns negative result, which as I, after some tests, understand is generated here systimestamp - starttime. Seems like systimestamp in this calculation is taken from Greenwich timezone, and mine is one hour bigger, so this calculation diff interval day to second := systimestamp - starttime; is returning the value like (-1 hour + difference).
By stupid brute-forcing I've found a solution:
procedure p is
starttime timestamp := systimestamp;
procedure writeTime
is
diff interval day to second;
endtime timestamp := systimestamp;
begin
diff := endtime - starttime;
-- here insert diff to some table
end;
begin
-- doing some long stuff
writeTime();
exception
when others then
writeTime();
end;
which simply writes systimestamp in the variable first, and only then calculates the difference.
My database parameters:
Oracle 11.2.0.2.0
Timezone +1 Berlin
So now the question: I really want to know is it a bug of my RDBMS or perhaps I do not see some obvious explanation why it is like that? The concrete question is: why during this operation
starttime timestamp := systimestamp;
it takes one timezone and during this
diff interval day to second := systimestamp - starttime;
it takes another one in the same procedure of the same session with the same settings?
Is the database timezone DBTIMEZONE the same like your session timezone SESSIONTIMEZONE?
Function SYSTIMESTAMP returns datatye TIMESTAMP WITH TIME ZONE, so you do an implicit convertion into TIMESTAMP datatype.
Datatype of LOCALTIMESTAMP is TIMESTAMP.
Try
starttime timestamp WITH TIME ZONE := systimestamp;
or
starttime timestamp := LOCALTIMESTAMP;
You can check with this query in which timezone the Schedule Jobs are running:
SELECT * FROM ALL_SCHEDULER_GLOBAL_ATTRIBUTE where attribute_name = 'DEFAULT_TIMEZONE'
I'm using this approach
declare
time_start number;
begin
time_start := dbms_utility.get_time();
-- some heavy lifting
dbms_output.put_line(dbms_utility.get_time() - time_start);
end;
/

How to format a Date variable in PLSQL

I am new to PL/SQL and have this question.
I created a procedure with the following specification:
PROCEDURE runschedule (i_RunDate IN DATE)
this i_RunDate comes in a specific format, and I need to change it to this format:
'MM/dd/yyyy hh:mi:ss PM'
I couldn't find how to re-format a Date variable.
You need to use the TO_CHAR function. Here's an example:
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'MM/DD/YYYY HH12:MI:SS AM') FROM dual;
The DATE has not especific format, a DATE is a DATE. When stored in the database column, date values include the time of day in seconds since midnight, and a DATE variable has the same, only that when in some point you show the variable is formatted. PL/SQL can convert the datatype of a value implicitly, for example, can convert the CHAR value '02-JUN-92' to a DATE value.. but I don't recommend you rely on this implicit conversiosn. I always use the explicit conversion.
For more on this topic, you could read this:
http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/datatypes.htm#i9118
In your case, if you want to format your DATE to log something, or to show in your UI or wathever in a different format, you need to assig it to a varchar variable as Justin said, something like this:
....
v_generated_run_date DATE;
v_var_date VARCHAR2(30);
BEGIN -- generate sysdate if required
IF (i_RunDate is null)
THEN
v_var_date:=TO_CHAR(sysdate, 'MM/DD/YYYY HH12:MI:SS AM');
ELSE
v_var_date:=TO_CHAR(i_RunDate,'MM/DD/YYYY HH12:MI:SS AM');
END IF;
pkgschedule.createschedule (v_var_date);
commit;
END runschedule;
END
Your createschedule procedure, in this case, will have a varchar2 parameter...

In Oracle, how can I detect the date on which daylight savings time begins / ends?

Is there a way in Oracle to select the date on which daylight savings will switch over for my locale?
Something vaguely equivalent to this would be nice:
SELECT CHANGEOVER_DATE
FROM SOME_SYSTEM_TABLE
WHERE DATE_TYPE = 'DAYLIGHT_SAVINGS_CHANGEOVER'
AND TO_CHAR(CHANGEOVER_DATE,'YYYY') = TO_CHAR(SYSDATE,'YYYY'); -- in the current year
Edit: I was hoping for a solution that would not require changes when Congress adjusts DST laws, as they did in 2007. The posted solutions will work, though.
To improve on Leigh Riffel's answer, this is much simpler with the same logic:
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN');
End;
We use the following two functions to calculate the start and end dates for any given year (post 2007, US).
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set the date to the 8th day of March which will effectively skip the first Sunday.
v_Date := to_date('03/08/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the second Sunday.
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set Date to the first of November this year
v_Date := to_date('11/01/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the first Sunday
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
There is probably a simpler way to do it, but these have worked for us. Of course this query doesn't know whether daylight saving time is observed for where you are. For that you will need location data.
Instead of looping to get the next sunday you can also use the next_day(date, 'SUN') function of oracle.
In the United States, Daylight Savings Time is defined as beginning on the second Sunday in March, and ending on the first Sunday in November, for the areas that observe DST, for years after 2007.
I don't think there's an easy way to get this information from Oracle, but based on the standard definition, you should be able to write a stored procedure that calculates the beginning and ending date using the Doomsday Algorithm.
Here is a way to use Oracles internal knowledge of whether a timezone observes daylight saving time or not to determine the start and end of it. Aside from the complexity and general strangeness of it, it requires two timezones to be know have identical times when daylight saving time is not in effect and different times when it is. As such it is resilient to congressional changes in when daylight saving time occurs (assuming your database is up to date with the patches), but is not resilient to regional changes effecting the timezones keyed off of. With those warnings, here is what I have.
ALTER SESSION SET time_zone='America/Phoenix';
DROP TABLE TimeDifferences;
CREATE TABLE TimeDifferences(LocalTimeZone TIMESTAMP(0) WITH LOCAL TIME ZONE);
INSERT INTO TimeDifferences
(
SELECT to_date('01/01/' || to_char(sysdate-365,'YYYY') || '12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1
FROM dual CONNECT BY rownum<=365
);
COMMIT;
ALTER SESSION SET time_zone='America/Edmonton';
SELECT LocalTimeZone-1 DaylightSavingTimeStartAndEnd
FROM
(
SELECT LocalTimeZone,
to_char(LocalTimeZone,'HH24') Hour1,
LEAD(to_char(LocalTimeZone,'HH24')) OVER (ORDER BY LocalTimeZone) Hour2
FROM TimeDifferences
)
WHERE Hour1 <> Hour2;
I told you it was strange. The code only figures out the day of the change, but could be enhanced to show the hour. Currently it returns 09-MAR-08 and 02-NOV-08. It is also sensitive to the time of year it is run, which is why I had to do the -365...+365. All in all I don't recommend this solution, but it was fun to investigate. Maybe someone else has something better.
Here's my version of the above. It's advantage is that it does not need a second 'alter session set time zone', and can be used more easily from an application.
You create the stored function, and then you simply use:
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,GetDSTDates(2012,2) DSTEnd,SessionTimeZone TZ from dual;
which will return the dst start date,dst end date, timezone for the specified year.
create or replace function GetDSTDates
(
year integer,
GetFrom integer
)
return Date
as
cursor c is
select 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24')) offset,
min(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) fromdate,
max(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) todate
from (
SELECT cast((to_date('01/01/'||to_char(year)||'12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1) as timestamp with local time zone) LocalTimeZone
FROM dual CONNECT BY rownum<=365
)
group by 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24'));
dstoffset integer;
offset integer;
dstfrom date;
dstto date;
begin
offset := 999;
dstoffset := -999;
for rec in c
loop
if rec.offset<offset
then
offset := rec.offset;
end if;
if rec.offset>dstoffset
then
dstoffset := rec.offset;
dstfrom := to_date(rec.fromdate,'DD/MM/YYYY');
dstto :=to_date(rec.todate,'DD/MM/YYYY');
end if;
end loop;
if (offset<999 and dstoffset>-999 and offset<>dstoffset)
then
if GetFrom=1
then
return dstfrom;
else
return dstto;
end if;
else
return null;
end if;
end;
/
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,
GetDSTDates(2012,2) DSTEnd,
SessionTimeZone TZ from dual;
Old question but here's a new answer. Use 08-MAR for the first date since that skips the first week
--Start of DST
select next_day(to_date('08-MAR-' || to_char(sysdate, 'YYYY')), 'SUN') from dual
--End of DST
select next_day(to_date('01-NOV-' || to_char(sysdate, 'YYYY')), 'SUN') from dual

Resources