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

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;

Related

Date range comparison off-by-one

There is a table in Oracle 19c with a DATE abc column. A row with value '2000-01-01' does not get picked up with query
select abc from t where abc <= DATE '2000-01-01'
The row does surface if I modify the query as
select abc from t where abc < DATE '2000-01-01' + interval '1' day
The displayed value (in DBeaver, VSCode + Oracle Dev Tools, Oracle SQL Developer) is always '2000-01-01'.
Issue
Oracle stores also time portion of inserted/copied data and while it doesn't always show, it affects queries regardless of output data.
You can notice it with query:
select to_char(abc, 'YYYY-MM-DD hh24:MI:ss') from t
where trunc(abc) <= date '2000-01-01'
Mitigation
use the comparable date (you're searching for) as variable and add + interval '1' day to it. Notice to adjust your comparison limits, use < instead of <= to not accidentally find items from next day with 00:00:00 time.
don't use trunc(abc) or to_char(abc ..), it will likely wreck the index performance`
add ALTER SESSION SET nls_date_format='YYYY-MM-DD HH24:MI:SS'; to your sql editor startup script.
In Oracle, a DATE is a binary data type that consists of 7 bytes representing: century, year-of-century, month, day, hour, minute and second. It ALWAYS has those 7 bytes and it is NEVER stored in any particular (human-readable) format.
The displayed value (in DBeaver, VSCode + Oracle Dev Tools, Oracle SQL Developer) is always '2000-01-01'.
What you are seeing is the client application receiving the binary DATE value and trying to be helpful and displaying the binary value as a string; however, the client application's default format is YYYY-MM-DD and it is not showing the time component of the date. This does not mean that the time component does not exist, it only means that it is not displayed.
What you need to do is to go into the preferences and change how your client application formats dates.
In SQL Developer, you can use:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
Which will set the format for your current session. Or you can go into Tool > Settings > Database > NLS and in set the "Date Format" field to YYYY-MM-DD HH24:MI:SS which will set the preference for the current session and any future session that you create from that client.
You can do something similar and change the settings in most (all) client applications.
select abc from t where abc <= DATE '2000-01-01'
Is the equivalent of:
select abc from t where abc <= TIMESTAMP '2000-01-01 00:00:00'
It will not match rows where abc is between 2000-01-01 00:00:01 and 2000-01-01 23:59:59 but the client application is not displaying the time component so you cannot see that the rows have a non-midnight time component and should not be matched.
If you do:
select abc from t where abc < DATE '2000-01-01' + interval '1' day
Then it will match those rows because it will match the entire 24-hour period for the day.

How to compare dates in a PL/SQL procedure?

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

Subtract one hour from datetime rather than one day

I have a datetime column in Oracle (MM/DD/YYYY HH:MM:SS AM/PM) but when I do this:
SELECT MAX(D_DTM)-1 FROM tbl1
...it goes back a day. How do I remove one hour from the column rather than one day?
I've also noticed that the datetime records for 12AM look like MM/DD/YYYY and not MM/DD/YYYY 00:00:00; I'm not sure if that matters.
Randy's answer is good, but you can also use intervals:
SELECT MAX(D_DTM)- interval '1' hour FROM tbl1
yes - dates go by integer days.
if you want hours you need to do some math - like -(1/24)
Or use the INTERVAL function. It has the same result but I think it reads more clearly - that's of course just an opinion :)
SELECT MAX(D_DTM) - INTERVAL '1' HOUR FROM tbl1
The nice thing about the INTERVAL function is that you can make the interval be years, months, days, hours, minutes or seconds when dealing with a DATE value, though the month interval can be tricky when dealing with end-of-month dates.
And yes, the quote around the 1 in the example is required.
You can also use the Oracle-specific NumToDSInterval function, which is less standard but more flexible because it accepts variables instead of constants:
SELECT MAX(D_DTM) - NUMTODSINTERVAL(1, 'HOUR') FROM tbl1
select sysdate - numtodsinterval(1,'hour') from dual
Its simple.
sysdate - 5/(24*60*60) --> Subtracts 5 seconds from systime
sysdate - 5/(24*60) --> Subtracts 5 minutes from systime
sysdate - 5/(24) --> Subtracts 5 hours from systime
Hence
select (sysdate - (1/24)) from dual
Another method of using intervals is
NUMTODSINTERVAL( number, expression )
examples
NUMTODSINTERVAL(150, 'DAY')
NUMTODSINTERVAL(1500, 'HOUR')
NUMTODSINTERVAL(15000, 'MINUTE')
NUMTODSINTERVAL(150000, 'SECOND')
I bring this up because it is useful for situations where using INTERVAL wont work.

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

complex sql query for finding the average working time of a person in office

I need to retrieve using an oracle based query the working time of an employee which is the time coming in and time going out for an employee based on his transactions in the transaction table.
The transaction table has a date-time field which can be used for this purpose.
The steps involved would be as follows:
1) find the first transaction on a date and the last transaction on the same date - that would be his time in and timeout for that date
2) calculate the overall time-in as the avg of all time-ins on each date, similarly do for time-out
Transaction table is as follows: transctn(transid, resourceid, event, currentdate)
The second requirement is to find the average transactions performed each day, which is basically find the count of transids for each day and then average of that.
The final answer should be, when a userid is provided the query the return result is:
frequent working time(based on average): 9:43 am - 6:45 pm
average transactions performed/day = 43
How do I write the above requirement in oracle SQL or more smartly using Hibernate if Transctn is my Domain class
I have something like this:
select 'frequent working time: '
||(select rtrim(to_char(min(currentdate),'hh:mi pm')) from transctn)
||' - '||(select rtrim(to_char(max(currentdate),'hh:mi pm')) from transctn)
||', average transactions performed/day = '
||(select rtrim(to_char(count(distinct transid)/
count(distinct(to_char(currentdate,'rrmmdd')))) from transctn)
from dual
Firstly, your query has lots of selects from the same table stuck together, which isn't efficient, and makes it harder to read than necessary. And the rtrim isn't doing anything as you've already dictated the format. What you have can be rewritten as:
select 'frequent working time: '|| to_char(min(currentdate),'hh:mi pm')
||' - '|| to_char(max(currentdate),'hh:mi pm'),
'average transactions performed/day = '
|| to_char(count(distinct transid)
/count(distinct to_char(currentdate,'rrmmdd')))
from transctn;
But this isn't averaging properly, and isn't for a specific user. I'm join to assume this is homework and in the usual spirit of the site, try to give you pointers rather than the complete answer...
1) find the first transaction on a date and the last transaction on
the same date - that would be his time in and timeout for that date
You're not far off here, but you're breaking down by date. To get the time-in and time-out for every user, for each day, you could use:
select resourceid, trunc(currentdate), min(currentdate), max(currentdate)
from transctn
group by resourceid, trunc(currentdate)
order by resourceid, trunc(currentdate);
2) calculate the overall time-in as the avg of all time-ins on each
date, similarly do for time-out
You can't average dates directly in Oracle; you'd get ORA-00932: inconsistent datatypes: expected NUMBER got DATE. There are various ways to achieve the effect, you just need to figure out a safe way to treat the date - or more specifically here the time part - as a number. For example, you could treat the time portion as the difference between the actual time and the start of the day, which Oracle returns as a number:
select avg(min(currentdate) - trunc(currentdate)),
avg(max(currentdate) - trunc(currentdate))
from transctn
group by trunc(currentdate);
But you than have to translate that fractional number back into something recognisable. One way of doing that is to add the number to an arbitrary fixed date, and then just extract the time part as a string as you were already doing:
select to_char(date '2000-01-01' + avg(min(currentdate) - trunc(currentdate)),
'HH:MI pm') as avg_time_in,
to_char(date '2000-01-01' + avg(max(currentdate) - trunc(currentdate)),
'HH:MI pm') as avg_time_out
from transctn
group by trunc(currentdate);
This look messy and you might find a better way to do it. If it is homework then I would assume you've been taught methods for doing this sort of thing, or something that can be adapted to be applicable.
This is still for all resources, so you'll need to add a filter to restrict to one user ID. Hopefully this gives you some ideas for tackling the second requirement as well.

Resources