Oracle Date Comparison Issue Totally confused - what am I doing wrong? - oracle

I am newbie & learning Oracle. I want to compare dates in function, while implementing the function I got an issue that output is wrong and not getting a valid answer I search a lot for a solution but in vain....
Solution so far I found was:
Do not compare Datetime to date.
If using DateTime, first TRUNC is needed to remove time factor.
While comparing date first convert dates to To_char(anyDate,'dd-mon-yyyy');
if we are comparing date like 1-dec-13 to 27-dec-13, make sure that both of the year are same. like both should be of 2013.
I have passed through all the constraints so far i found but fail to find the solution. Can any body help me. what blunder am i doing? Thanks in advance for anticipation.
SET serveroutput on;
DECLARE
startDate DATE := to_date('1'|| '/' ||to_char(sysdate, 'MM') || '/2013','DD/MM/YYYY');
secDate DATE :=to_date(SYSDATE, 'DD/MM/YYYY ');
BEGIN
DBMS_OUTPUT.PUT_LINE( 'if secDate '|| secDate ||' is Greater Then');
DBMS_OUTPUT.PUT_LINE( 'StartDate '|| startDate || ' Output = ' );
if(secDate>startDate)
then
DBMS_OUTPUT.PUT_LINE('True' );
else
DBMS_OUTPUT.PUT_LINE('false');
end if;
end;

Your points in order:
i An Oracle DATE holds date and time, accurate to the second. You can compare them, for example 12/27/2013 00:00:00 is less than 12/27/2013 09:00:00.
ii If you want to see if two DATE values with a time component (for example 12/27/2013 14:00:00 and 12/27/2013 12:34:56) are on the same day, then yes, you want TRUNC: IF TRUNC(firstDate) = TRUNC(secondDate).
iii There's nothing to be gained by using TO_CHAR for comparing DATE values. Use TRUNC if you don't want to include the time in the comparison; leave it out if you want the time.
iv The years don't have to be the same. A date like 12/27/2013 is greater than 12/27/2012 and less than 12/27/2014. If you leave it as a DATE type Oracle will do the right thing.
As for what's going wrong with your code, take a look at your startDate initialization:
'1'|| '-' ||to_char(sysdate, 'MM') || '2013'
For today, that will give you 1-122013, which doesn't match the format string DD/MM/YYYY in the TO_DATE call. That's why you're not getting a decent date.
If you want to get the first day of the current month, Oracle provides a convenient shortcut:
startDate := TRUNC(SYSDATE, 'MONTH');
So I'd forget about all the formatting and go with something like this; note how it's a lot simpler:
SET serveroutput on;
DECLARE
startDate DATE := TRUNC(SYSDATE, 'MONTH');
secDate DATE := SYSDATE;
BEGIN
DBMS_OUTPUT.PUT_LINE( 'if secDate '|| secDate ||' is Greater Then');
DBMS_OUTPUT.PUT_LINE( 'StartDate '|| startDate || ' Output = ' );
if(secDate>startDate) then
DBMS_OUTPUT.PUT_LINE('True' );
else
DBMS_OUTPUT.PUT_LINE('false');
end if;
END;
Note that secDate will be equal to startDate only at midnight on the first day of the month, when both values (for December 2013) will be 12/27/2013 00:00:00. At all other times of the month, secDate will be greater.
And note that I used 12/27/2013 00:00:00 to explain December 27, 2013 at midnight. That's just so I could explain the date in English. It's not how Oracle stores dates. How does Oracle store dates? It doesn't really matter - the important thing is that Oracle handles the date just fine if you leave it in DATE format and use TRUNC when you care only about the date and not about the time.

to_date(SYSDATE, 'DD/MM/YYYY ') that is your problem. It gives 27.12.0013 as a result. That is why you get false printed. Actually you don't need to convert sysdate to date as it is already of type date. Use this instead:
secDate DATE := SYSDATE;

See this line to_date('1'|| '-' ||to_char(sysdate, 'MM') || '2013','DD/MM/YYYY');
'1'|| '-' ||to_char(sysdate, 'MM') || '2013' gives you '1-122013', so you make
to_date('1-122013','DD/MM/YYYY');
The format don't match, you have to change one of them, either
to_date('01/'|| to_char(sysdate, 'MM')|| '/2013','DD/MM/YYYY');
or
to_date('01'|| '-' ||to_char(sysdate, 'MM')|| '2013','DD-MMYYYY')
Instead of secDate DATE :=to_date(SYSDATE, 'DD/MM/YYYY '); simply use secDate DATE :=TRUNC(SYSDATE);, there is no need for a conversion.

Related

Using a date variable in a query to fetch records based on a given date in oracle

I need to write a function in oracle plsql that with take a date as an input and return records from a table for that particular day. If no date is given then fetch the records for current day.
Note that the column (purchase_date) is a timestamp(6) type not null column and has an index on it so I would not like to use trunc() function on the column.
Example value present in purchase_date column is --> 01-DEC-21 06.14.06.388855001 AM
create or replace FUNCTION getRecordsForDate(
input_date DATE DEFAULT SYSDATE
) RETURN sys_refcursor IS
data_out SYS_REFCURSOR;
BEGIN
OPEN data_out FOR
SELECT
p.product_name,
p.product_type,
p.purchased_by
FROM
product_details p
WHERE
AND p.purchase_date BETWEEN TO_DATE(input_date, 'DD-MON-YY')
-- AND TO_DATE('03-MAR-22 23:59:59', 'DD-MON-YY HH24:MI:SS'); --harcoded value works but I need to use input_date
AND 'TO_DATE' ||'(''' || input_date || ' 23:59:59''' ||',' || '''YYYY-MM-DD HH24:MI:SS''' ||')';
return data_out;
END getRecordsForDate;
My concatenation is not working in the last line. It gives me ORA-01858: a non-numeric character was found where a numeric was expected. Not sure what's wrong here. Would someone be able to help.
Do not use TO_DATE on a DATE.
The last line of the cursor will not work as it is a (concatenated) string literal that cannot be converted to a TIMESTAMP or a DATE.
Even if it did work (which it will not), your purchase_date is a TIMESTAMP(6) data type so you are going to exclude all value from the time 23:59:59.0000001 until 23:59:59.999999.
You want to use:
create or replace FUNCTION getRecordsForDate(
input_date DATE DEFAULT SYSDATE
) RETURN sys_refcursor
IS
data_out SYS_REFCURSOR;
BEGIN
OPEN data_out FOR
SELECT product_name,
product_type,
purchased_by
FROM product_details
WHERE purchase_date >= TRUNC(input_date)
AND purchase_date < TRUNC(input_date) + INTERVAL '1' DAY;
return data_out;
END getRecordsForDate;
/

How to understand given sysdate is Date or Timestamp

I have a procedure.It takes date parameter with type Date.What i want to know is the other users send sysdate to my procedure.Which format they send sysdate to my procedure?
For example:
01/02/2021 or 01/02/2020 00:00:00(timestamp)
Does my procedure accepts all sending formats?Maybe Date type converts sending formats this style 01/02/2020.I am not sure.
I mean that does my Date parameter accepts all date formats because I want to use date in my procedure without seconds or minutes.
My procedure is
DECLARE
var_P_DATE DATE; (for example : 01/01/2021)
BEGIN
SELECT last_day(var_P_DATE) INTO v_last_day FROM DUAL; (31/01/2021)
if v_last_day = var_P_DATE (it returns false because no second or minutes)
.....
END;
I used DBMS.OUTPUT. I think Date type converts just like this 01/02/2021 and i do not get any error but i am not sure.
Your procedure will only ever receive a date, because that is the data type of the formal parameter. When the procedure is called the caller can supply a date, or something that can be implicitly converted to a date (though they shouldn't; implicit conversions are generally a bad thing, particularly from strings).
The date data type includes time components. If you are being passed a date with a non-midnight time that you want to ignore, such as sysdate, you can use the trunc() function, with it's default 'DD' format; and you don't need to select from dual:
v_last_day := last_day(trunc(var_P_DATE));
If the caller passes in systimestamp then that will still be implcitly converted to a date by the time you see it - which means it loses any fractional seconds and time zone information, but retains hours, minutes and seconds.
Dates and timestamps do not have have any inherent human-readable format. A date can be displayed using various formats - see the documentation - either explicitly with to_char() and a format model, or implicitly using your session settings.
When you do
dbms_output.put_line(var_P_DATE);
you are doing an implicit conversion of the date value to a string, using the session's NLS_DATE_FORMAT setting. So, different users might see that in different formats. You have no control over that. If you want to see a specific format then specify that, e.g.:
dbms_output.put_line(to_char(var_P_DATE, 'YYYY-MM-DD'));
You also have no control over whether the caller sees that output - it's down to the application/client and its settings. It looks like you are probably only using it for debugging the comparison issue though, so that probably doesn't matter here.
So as a demonstration:
declare
var_P_DATE date := sysdate;
v_last_day date;
begin
v_last_day := last_day(var_P_DATE);
dbms_output.put_line(to_char(v_last_day, 'YYYY-MM-DD HH24:MI:SS'));
v_last_day := last_day(trunc(var_P_DATE));
dbms_output.put_line(to_char(v_last_day, 'YYYY-MM-DD HH24:MI:SS'));
end;
/
2021-02-28 09:59:02
2021-02-28 00:00:00
db<>fiddle demo
Date type in Oracle has hours, minutes and seconds, timestamp has fractions:
SQL> declare
2 vDate date := sysdate;
3 vTimeStamp timestamp := systimestamp;
4 begin
5 dbms_output.put_line('Date: ' || vDate);
6 dbms_output.put_line('Timestamp: ' || vTimestamp);
7 end;
8 /
Date: 2021-02-18 09:49:32
Timestamp: 18-FEB-21 09.49.32.015953 AM
If you want to use just the date part, with no time, of a date variable, use something like trunc(vDate):
SQL> declare
2 vDate date := sysdate;
3 vTimeStamp timestamp := systimestamp;
4 begin
5 dbms_output.put_line('Date: ' || vDate);
6 dbms_output.put_line('Date truncated: ' || trunc(vDate));
7 dbms_output.put_line('Timestamp: ' || vTimestamp);
8 end;
9 /
Date: 2021-02-18 09:51:51
Date truncated: 2021-02-18 00:00:00
Timestamp: 18-FEB-21 09.51.51.024384 AM
An example of how comparison works on date variables:
SQL> declare
2 vDate1 date;
3 vDate2 date;
4 begin
5 vDate1 := sysdate;
6 dbms_lock.sleep(5); /* wait 5 seconds */
7 vDate2 := sysdate;
8 --
9 if vDate1 = vDate2 then
10 dbms_output.put_line('Equal');
11 else
12 dbms_output.put_line('NOT equal');
13 end if;
14 --
15 if trunc(vDate1) = trunc(vDate2) then
16 dbms_output.put_line('Equal, truncated');
17 else
18 dbms_output.put_line('NOT equal, truncated');
19 end if;
20 end;
21 /
NOT equal
Equal, truncated
Just apply TRUNC() function and use that variable of type DATE within the procedure after defining the data type of var_P_DATE as TIMESTAMP
CREATE OR REPLACE PROCEDURE myproc( var_P_DATE TIMESTAMP) AS
dt DATE := TRUNC(var_P_DATE) ;
BEGIN
...
...
END;
/
If you mean DBMS_OUTPUT.PUT_LINE by DBMS.OUTPUT, then that's completely irrelevant with your current conversion, that's just used to display result to the console as a string.

date + 7 working days

I need to write a function that will give me a new due date for an invoice. This needs to be 12 working days after the current due date
Say the current due date is 01.Oct.2014. If I look at my calendar manually, I can see that the new date would be 17.Oct.2014 (need to exclude weekends).
However, I also have a table with Bank Holidays. This would have to be taken into consideration. So if I would have a Bank Holiday on 04.Oct.2014, the new due date should be 18.Oct.2014.
EDIT: My table with Bank Holidays would look something like this:
Year: Date: Description
2014 04.Oct.2014 Bank Holiday 1
Any help with this would be deeply appreciated, I'm stuck at this for almost a day now.
Thanks a lot in advance.
Kind regards
Gerben
Something like this should work:
DECLARE
l_date DATE := SYSDATE;
FUNCTION IS_WEEKEND(P_DATE IN DATE)
RETURN BOOLEAN
IS
l_daynum VARCHAR2(1) := to_char (P_DATE, 'D');
BEGIN
RETURN l_daynum = '6' OR l_daynum = '7';
END;
FUNCTION IS_HOLIDAY(P_DATE IN DATE)
RETURN BOOLEAN
IS
CURSOR c_exists IS
SELECT 1 FROM bank_holidays WHERE date = TRUNC(P_DATE)
;
l_count NUMBER;
BEGIN
OPEN c_exists;
l_count := c_exists%ROWCOUNT;
CLOSE c_exists;
RETURN l_count > 0;
END;
PROCEDURE ADD_WORKING_DAYS(P_DATE IN OUT DATE, P_DAYS IN NUMBER)
IS
l_workdays_added NUMBER := 0;
BEGIN
WHILE TRUE
LOOP
P_DATE := P_DATE + 1;
IF NOT IS_WEEKEND(P_DATE) AND NOT IS_HOLIDAY(P_DATE) THEN
l_workdays_added := l_workdays_added + 1;
END IF;
IF l_workdays_added = P_DAYS THEN
RETURN;
END IF;
END LOOP;
END;
BEGIN
ADD_WORKING_DAYS(l_date, 12);
END;
I ended up doing things slightly different. I have a table with all my bank holiday. I created a second table as a kind of calendar. In here, I loaded all dates in a year. I then flag it as weekend or bank holiday (2 separate columns).
I take my original due date, and add the 12 days. I then have a start and end date (v_due_date_old and v_due_date_new)
After that, I count how many days there are in my 'calendar' table, where either my flag for weekend or bank holiday is set to Yes. If v_due_date_new is on a Saturday, I add another day to my count.
I then add the new count to v_due_date_new.
As a last step, I check what day v_due_date_new is. If it is Saturday or Sunday, I add another 2 days

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