How to compare dates in a PL/SQL procedure? - oracle

I'm a newbie in SQL programming.
I need to make a procedure that will compare dates. The only argument in the procedure is a date typed in by the user in a Java program. I need to check if that argument (date) is before a year ago. In other words, I need to compare it with SYSDATE minus a year. If that is the case, I have to "purge" all of the tables related to "schedules" (there are 2 of them).
So for example, say the user types 2013-04-13, my procedure has to compare it with SYSDATE - 1 year (in that case, it would be 2014-12-03). Since the value is less than today minus a year, the tables "MovieSchedule" and "ChannelSchedule" have to be purged. If the entered date was 2014-12-16, since it's now more than SYSDATE minus a year, the procedure has to send back an explicit error that I will be able to use in the Java program.
Now, as I said I'm completely new to procedure programming in PL/SQL, so here is what I could come up with by looking up tutorials on the internet:
CREATE OR REPLACE
PROCEDURE purge_schedule(purgeDate date) AS
DECLARE
currentDate := to_date(SYSDATE, 'YYYMMDD');
BEGIN
-- IF purgeDate < (currentDate - 1year)
-- delete content in "MovieSchedule" and "ChannelSchedule"
-- ELSE
-- return explicit error
END purge_schedule;
I don't even know if any of this is the right way to write a procedure like I want. And as you can see, my problem is how to implement my condition in the procedure, not the logic behind it. I blame my lack of practice with the language.
Please tell me if I have to be more specific of if you need more information in order to help me. Thank you for your help and have a nice day :)

Step 1, define year. Should be obvious, but is not. I have found month and year definitions vary in the wild. I've seen year defined as 52 weeks, which is never an actual year, and 365 days, which matches one year a little less than 3 of every 4 years, and occasionally 360 days! (30 days / month * 12 months)
sysdate - 365 gives a date 365 days ago.
ADD_MONTHS(sysdate, -12) will give the date 12 months ago. In the case that sysdate is February 29, the result will be February 28 of the prior year.
sysdate - interval '1' year is tempting, but interval year to month arithmetic throws errors when the "result" is a day that is not there.
select date '2012-02-28' - interval '1' year from dual;
02/28/2011
select date '2012-02-29' - interval '1' year from dual;
ORA-01839: date not valid for month specified

It should fill your needs:
IF purgeDate < SYSDATE -365 THEN
...
END IF;

I have illustrated a below working code which help you. Let me know if this helps.
--Compiling the stored proc
CREATE OR REPLACE PROCEDURE purge_schedule(
purgeDate IN DATE )
AS
currentDate DATE:=SYSDATE;
BEGIN
IF purgeDate < ADD_MONTHS(SYSDATE,-12) THEN
DELETE FROM EMP;
DELETE FROM EMP_V1;
dbms_output.put_line('Records purged successfully');
ELSE
RAISE_APPLICATION_ERROR(-20001,'Date provided is not in range deletion not invoked',TRUE);
END IF;
END purge_schedule;
-- Exceuting the proc
set serveroutput on;
exec purge_schedule(to_date('12/12/2013','MM/DD/YYYY'));
-- Output
Records purged successfully
--Executing for negative scenario
set serveroutput on;
exec purge_schedule(TO_DATE('12/12/2017','MM/DD/YYYY'));
--Explicit exception raise
exec purge_schedule(TO_DATE('12/12/2017','MM/DD/YYYY'));
Error report -
ORA-20001: Date provided is not in range deletion not invoked
ORA-06512: at "AVROY.PURGE_SCHEDULE", line 11
ORA-06512: at line 1

Related

Create TimeStamp with fixed Time Part

What's the best way to get a timestamp that consists of the actual date but a fixed time part in oracle.
e.g.Today and always 09:00:00
2020-10-20 09:00:00
in MSSQL I would use FORMAT(GETDATE(),'yyyy-MM-dd 09:00:00')
Assuming you want a date rather than a varchar2, I'd use
trunc(sysdate) + interval '9' hour
trunc(sysdate) returns today at midnight and then interval '9' hour adds 9 hours to give you 9am. You can also add fractions of a day to a date so you could say
trunc(sysdate) + 9/24
I tend to find the interval notation more self-explanatory particularly if you're coming from a non-Oracle background.
You can use something like this:
SQL> alter session set NLS_DATE_FORMAT='YYYY-MM-DD';
Session altered.
SQL> set head off
SQL> select sysdate||' 09:00:00' from dual;
2020-10-19 09:00:00
Hope this is what you were looking for :)

SQL Query with date that does not exist

I was recently working on some SQL queries where I had to separate the values into various months (e.g. December, January, Feb...) and make some comparisons. However I was at a loss for wondering about what to use for the ending day of each month. So I was wondering what happens when you define a date that does not technically exist. For example.
WHERE myDate BETWEEN '2016-01-01' AND '2016-02-31' //note Feb 31 does not exist.
My assumption (based on my current query seeming to return the proper results) is that it simply ignores the extra dates that do not exist (e.g. when counting the dates, it simply has no dates for the range outside of the regular dates).
Is this undefined behavior that I may run into trouble with in the future? Or is there a better way to do this to cover all basis?
Why don't you want to use LAST_DAY() function:
SELECT SYSDATE, trunc(LAST_DAY(SYSDATE)) last,
LAST_DAY(SYSDATE) - SYSDATE days_left FROM DUAL;
Output:
SYSDATE LAST DAYS_LEFT
----------------- ----------------- ----------
03.02.16 18:38:26 29.02.16 00:00:00 26
1 row selected.

Oracle 9.2 Procedure to compare date and time separately [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
I have the below logic to compare the date and time separately.
If current_date = DOG_Date and current time is less than 11.59pm
Set ARD date to current date + 1
Set ARD time to 08:00am
End if;
If current date greater than DOG Date and current time is less than 07.00am
Set ARD date to next Day (Can be same day)
Set ARD time to 08:00am
End if;
The ARD format should be dd.mm.yyyy
The ARD time should be 24hh.mi
Can you please help me with the PLSQL implementation of this? I tried and able to validate date part successfully, however finding problem with time part.
Thanks in advance,
Regards
There doesn't seem to be any benefit to splitting out the time and the date here. It seems to have obliged you to store both in character columns, which is a classic mistake - normally ending in disaster. Oracle's "date" data type is a datetime; it's worth using it.
Assuming all of your dates and times are actual dates you can simplify your logic considerably. Firstly "ARD Time" is 8am if any of your conditions are true. I suspect that your condition on 11.59pm is a mistake as well. If you mean less than midnight then this condition is always true and can be dispensed with. This in turn makes your logic for your first condition.
If the current date is equal to the DOG date then
ARD date is 8am tomorrow
This can be represented in PL/SQL as:
if trunc(sysdate) = trunc(dog_date) then
ard_date := trunc(sysdate) + interval '32' hour;
end if;
TRUNC(date) removes the time portion of a date by default. I know you're going to say that you want the date and the time separately, you can convert this date back to characters using TO_CHAR() in whatever manner you wish. Oracle includes datetime format models for time:
ard_date_char := to_char(ard_date, 'dd.mm.yyyy');
ard_time_char := to_char(ard_date, 'hh24:mi');
The mention of the "same" day in your second statement isn't as intuitive to strangers as you might expect. I assume it means here that your "day" starts at 7am.
To take your logic here bit-by-bit:
If current date greater than DOG Date
Can be represented as:
if trunc(sysdate) > trunc(dog_date)
and your second condition:
and current time is less than 07.00am
Is a comparison of the time on the current date against an arbitrary time. In order to do this comparison you can remove the time portion from the current date by using TRUNC() and then add 7 hours. By comparing this to the current system date you get your answer.
and sysdate < trunc(sysdate) + interval '7' hour
Now the difficult bit
Set ARD date to next Day (Can be same day)
Assuming (once again) that your "day" starts at 7am the increment required here could be anything from 1 minute to 24 hours. However, you already know that your current time is before 7am, which means that you are never in your "next" day. You can then always assume that it's the current day. This makes the complete logic:
if trunc(sysdate) > trunc(dog_date)
and sysdate < trunc(sysdate) + interval '7' hour
ard_date := trunc(sysdate) + interval '8' hour;
end if;
If your first IF statement really required 11.59pm you can use the same logic as in the second statement to do the comparison, i.e. trunc(sysdate) + 1 - interval '1' minute.
Although you haven't provided any code I suspect that the manner in which you're obtaining these dates would not preclude the placement in almost all of this logic in the creation of the date variable itself. Consider whether you actually need to do any of this and whether, for instance, a CASE statement when selecting the dates (if that is indeed what you're doing) would suffice.
Now, this all assumes you've actually used a date in order to store your dates. It doesn't sound like you have so you might have to convert everything to a date first and then back from a date afterwards. You do this using TO_DATE() and TO_CHAR().
I would highly recommend, though, that you convert everything to a date.
DECLARE
dog_date DATE := to_date( '20/11/2013','dd/mm/yyyy');
ard_date DATE;
BEGIN
IF TRUNC(CURRENT_DATE) = TRUNC(dog_date) AND CURRENT_DATE < TRUNC(CURRENT_DATE) + 1 THEN
ard_date := TRUNC(CURRENT_DATE) + 8/24 +1;
END IF;
IF TRUNC(CURRENT_DATE) > TRUNC(dog_date) AND CURRENT_DATE < TRUNC(CURRENT_DATE) + 7/24 THEN
ard_date := TRUNC(CURRENT_DATE) + 8/24 +1; --as your need
END IF;
END;

months_between for a leap year

When I find months between 28-FEB-11 and 29-FEB-12, months_between function in oracle returns 12. Actually it should be 12.096. This function is not calculating for the leap year proper.
For between 28-FEB-11 and 29-FEB-12, it is 1 year(12 months) and 1 day.
select months_between('28-FEB-12', '28-FEB-11') from dual; -- 12
**select months_between('29-FEB-12', '28-FEB-11') from dual; -- 12**
select months_between('28-FEB-12', '27-FEB-11') from dual; -- 12.0322
select months_between('27-FEB-12', '28-FEB-11') from dual; -- 11.9677
is this an Oracle bug??..
-Vishwa
From the Oracle documentation:
MONTHS_BETWEEN returns number of months between dates date1 and date2. If date1 is later than date2, then the result is positive. If date1 is earlier than date2, then the result is negative. If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.
So it's following the documented behavior. It's just not what you expected.
It's not a bug because ORACLE says so, it's a logical error driven by a human (and documenting as something rigth) which is worse.
How come if difference between the last days of January and February months is exactly 1 month (29 exact days). Please see below:
MONTHS_BETWEEN('29-FEB-12','31-JAN-12')
1
With an extra day (30 days) the difference in months is less than 1. Please see below:
MONTHS_BETWEEN('29-FEB-12','30-JAN-12')
.967741935
WRONG. That's not rigth at all!
SQL Server in another hand handle this correctly:
select DATEDIFF(MM,'29-FEB-12','30-JAN-12')
select DATEDIFF(MM,'29-FEB-12','31-JAN-12')
both are 1

Oracle date corruption during update

I'm migrating some data from one oracle schema/table to a new schema/table on the same database.
The migration script does the following:
create table newtable as select
...
cast(ACTIVITYDATE as date) as ACTIVITY_DATE,
...
FROM oldtable where ACTIVITYDATE > sysdate - 1000;
If I look at the original data, it looks fine - here's one record:
select
activitydate,
to_char(activitydate, 'MON DD,YYYY'),
to_char(activitydate, 'DD-MON-YYYY HH24:MI:SS'),
dump(activitydate),
length(activitydate)
from orginaltable where oldpk = 1067514
Result:
18-NOV-10 NOV 18,2010 18-NOV-2010 12:59:15 Typ=12 Len=7: 120,110,11,18,13,60,16
The migrated data, showing that the data is corrupt:
select
activity_date,
to_char(activity_date, 'MON DD,YYYY'),
to_char(activity_date, 'DD-MON-YYYY HH24:MI:SS'),
dump(activity_date),
length(activity_date)
from newtable
where id = 1067514
Result:
18-NOV-10 000 00,0000 00-000-0000 00:00:00 Typ=12 Len=7: 120,110,11,18,13,0,16
Around 5000 out of 350k records show this problem.
Can anyone explain how this happened?
UPDATE:
I don't find any published reference to this specific type of DATE corruption on the Oracle support site. (It may be there, my quick searches just didn't turn it up.)
Baddate Script To Check Database For Corrupt dates [ID 95402.1]
Bug 2790435 - Serial INSERT with parallel SELECT and type conversion can insert corrupt data [ID 2790435.8]
The output from the DUMP() function is showing the date value is indeed invalid:
Typ=12 Len=7: 120,110,11,18,13,0,16
We expect that the minutes byte should be a value between one and sixty, not zero.
The 7 bytes of a DATE value represent, in order, century(+100), year(+100), month, day, hour(+1), minutes(+1), seconds(+1).
The only time I have seen invalid DATE values like this when a DATE value was being supplied as a bind variable, from a Pro*C program (where the bind value is supplied in the internal 7 byte representation, entirely bypassing the normal validation routines that catch invalid dates e.g. Feb 30)
There is no reason to expect the behavior you're seeing, given the Oracle syntax you posted.
This is either a spurious anomaly (memory corruption?) or if this is repeatable, then it's a flaw (bug) in the Oracle code. If it's a flaw in the Oracle code, the most likely suspects would be "newish" features in an un-patched release.
(I know CAST is a standard SQL function that's been around for ages in other databases. I guess I'm old school, and have never introduced it into my Oracle-syntax repertoire. I don't know what version of Oracle it was that introduced the CAST, but I would have stayed away from it in the first release it appeared in.)
The big 'red flag' (that another commenter noted) is that CAST( datecol AS DATE).
You would expect the optimizer to treat that as equivalent to date_col ... but past experience shows us that TO_NUMBER( number_col ) is actually interpreted by the optimizer as TO_NUMBER( TO_CHAR ( number_col ) ).
I suspect something similar might be going on with that unneeded CAST.
Based on that one record you showed, I suspect the issue is with values with a "59" value for minutes or seconds, and possibly a "23" value for hours, would be the ones that show the error.
I would try checking for places where the minutes, hour or seconds are stored as 0:
SELECT id, DUMP(activitydate)
FROM newtable
WHERE DUMP(activitydate) LIKE '%,0,%'
OR DUMP(activitydate) LIKE '%,0'
I've seen similar things to spence7593, again with Pro*C.
It is possible to create invalid dates programmatically using a DBMS_STATS package.
Not sure if there is a similar mechanism to reverse that.
create or replace function stats_raw_to_date (p_in raw) return date is
v_date date;
v_char varchar2(25);
begin
dbms_stats.CONVERT_RAW_VALUE(p_in, v_date);
return v_date;
exception
when others then return null;
end;
/
select stats_raw_to_date(utl_raw.cast_to_raw(
chr(120)||chr(110)||chr(11)||chr(18)||chr(13)||chr(0)||chr(16)))
from dual;

Resources