Date difference get different results from function than from select statement - sql

I need to get the difference of 2 date fields, if the greater date is null then I'll use SYSDATE instead. Having this requirement, I created a function to solve this issues (note: this code follows the standard of the organization, not my personal taste)
CREATE FUNCTION F_GET_DIFFERENCE (P_WORKFLOWID NUMBER)
RETURN NUMBER --result in minutes
IS
TIME NUMBER;
BEGIN
TIME := 0
SELECT
F_WORKTIME_DIFF(NVL(X.ENDDATE, SYSDATE), X.STARTDATE)
INTO
TIME
FROM
TABLEX X
WHERE
X.WORKFLOWID = P_WORKFLOWID;
RETURN TIME;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
The F_WORKTIME_DIFF function already exists and calculates the worktime of the day (assumming nobody works at 12 a.m. and things like that). The problem is when calling this function, the result contains an additional amount of time. That's very strange, because when executing the query in the function, it returns the expected output.
Example (important: date format in Peru is DD/MM/YYYY HH24:MI:SS)
TABLEX
WORKFLOWID STARTDATE ENDDATE
1 '01/12/2012 10:00:00' null
Assumming that the server day is the same day (01/12/2012) but greater time (10:01:00), we execute the function:
SELECT F_GET_DIFFERENCE(1)
FROM DUAL;
The result is: 14.
Now, executing the query in the function and having the server time at 10:02:00, the result is 2 (exact output).
I even tried executing this
SELECT
F_WORKTIME_DIFF(NVL(X.ENDDATE, SYSDATE), X.STARTDATE) SELECT_WAY,
F_GET_DIFFERENCE(1) FUNCTION_WAY
FROM
TABLEX X
WHERE
X.WORKFLOWID = 1
And the result is (having the server time at 10:10:00)
SELECT_WAY FUNCTION_WAY
10 24
Is maybe any consideration that I must take into account when working with Oracle dates in inner functions or anything that could explain this odd behavior?

It is difficult to tell anything without seeing the function F_WORKTIME_DIFF.
Whatever is the datatype returned from F_WORKTIME_DIFF, it is casted to number when assigned to the variable time. This may be a clue.

This may not be exactly what are you looking for but the first example gives you hours diff between two dates:
Select EXTRACT(HOUR FROM (SYSDATE - trunc(SYSDATE )) DAY TO SECOND ) From dual
/
Select
EXTRACT(hour From Cast(SYSDATE as timestamp)) hh,
EXTRACT(minute From Cast(SYSDATE as timestamp)) mi,
EXTRACT(second From Cast(SYSDATE as timestamp)) ss
From dual
/

Related

Query with CASE WHEN / LAST_DAY is not giving any output

Running below query and not getting the output. Can someone please tell whats wrong in it?
Select distinct (table.datex)
from table
where table.datex =
(
CASE when extract( day from sysdate) >=19
then last_day(add_months(sysdate, -1))
else last_day(add_months(sysdate, -2))
END
)
Sample data
Datex
ID
30-JUN-21
A
31-MAY-21
B
29-JUN-21
C
Expected result
Datex
30-JUN-21
When I am passing the value hard-coded(calculated by the case) to where clause it's working fine, but when I apply the case it's not working. No error. No output is coming.
Date or datetime?
Oracle's LAST_DAY doesn't do what the name suggests, and the docs (https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LAST_DAY.html#GUID-296C7C02-7FB9-4AAC-8927-6A79320CE0C6) fail to explain that, too.
Unlike several other DBMS Oracle doesn't have a date type. It only has a datetime type and they even call that inappropriately DATE. This means that a "date" in Oracle always has a time part. A date with its time part set to 00:00:00 can be considered a day's midnight (i.e. the very beginning of the day) or the whole day.
The function SYSDATE gives us a date in the sense of the DATE datatype, not in the sense of a real day, i.e. it gives us the datetime of "now", e.g. 2021-07-20 14:38:00. ADD_MONTHS changes the month in that datetime (and sometimes the year and sometimes even the day), i.e. leaves the time part untouched. LAST_DAY, too, changes the date part to get to the last day of the month, but leaves the time part untouched.
Your CASE expression hence results in something like TIMESTAMP '2021-07-20 14:38:00' and not in DATE '2021-07-20' as one might expect.
You say that you tried your query with the date you computed wth your case expression, and it worked. Did you compute the resulting day in your head or with a query? If the latter: The tool you are using may be set to only display a datetime's date part and omit the time part. This would explain why you only saw 30-JUN-21 when checking the CASE expression.
Solution
Truncate the datetime down to a whole day
Select distinct datex
from mytable
where (extract(day from sysdate) >=19 and datex = trunc(last_day(add_months(sysdate, -1))))
or (extract(day from sysdate) < 19 and datex = trunc(last_day(add_months(sysdate, -2))))
It doesn't matter whether you apply TRUNC late as in my example or right away on SYSDATE (with TRUNC(SYSDATE)) by the way. The only aim is to get rid of the time part at some point in the expression.
Don't use case in where clauses. Boolean logic can handle that.
And take a look if it is really the condition you want
Select distinct datex
from your_table
where
(
extract(day from sysdate) >=19
and datex = last_day(add_months(sysdate,-1))
)
or
(
extract(day from sysdate) < 19
and datex = last_day(add_months(sysdate,-2))
)

Difference between 2 timestamps in Oracle SQL

Please help in finding the difference between 2 timestamps in SQL
I tried using
Select (cast(sysdate as timestamp) - cast(sysdate-2 as timestamp)) diff from dual;
I got 'ORA-00911:invalid character' error
If you want something that looks a bit simpler, try this for finding events in a table which occurred in the past 1 minute:
With this entry you can fiddle with the decimal values till you get the minute value that you want. The value .0007 happens to be 1 minute as far as the sysdate significant digits are concerned. You can use multiples of that to get any other value that you want:
select (sysdate - (sysdate - .0007)) * 1440 from dual;
Result is 1 (minute)
Then it is a simple matter to check for
select * from my_table where (sysdate - transdate) < .00071;
You are not finding the difference between two timestamps; you are finding the difference between two dates and casting them to timestamps in the process. This is unnecessary and you can just leave them as date values and explicitly cast the difference to an INTERVAL DAY TO SECOND data type (which would be the output if you used timestamps):
SELECT (SYSDATE - (SYSDATE - 2)) DAY TO SECOND AS diff
FROM DUAL;
Which outputs the interval:
DIFF
+02 00:00:00.000000
However, your code works db<>fiddle.
You will probably find that the error:
ORA-00911:invalid character
Is nothing to do with your query and is related to the ; statement terminator at the end. If you are running your query through a 3rd-party application (i.e. C#, Java, Python, etc.) then they will pass single statements to the database to execute and, in this case, having the statement terminator is invalid syntax (as there will only be one SQL statement) and you just need to remove it.

Why am I getting different results when comparing dates two ways?

This query:
select count(*), trim(data_date)
from man
where data_status = 'received' and data_date > sysdate-7
group by trim(data_date);
gives result like:
199 05-APR-16
But this query:
select count(*), trim(data_date)
from man
where data_status = 'received' and trunc(data_date) = date '2016-04-05'
group by trim(data_date);
gives results like:
347 05-APR-16
Why are the queries giving different results for the same day?
Because your man_date_sub values are not all at midnight. If you keep running the first query the number of records returned will (probably) gradually reduce. That is only happening to the count for the 5th as that is a week ago. Your sysdate - 7 is a moving target, not just as you move from day to day, but as time passes during the day.
You can check the times with:
select to_char(man_date_sub, 'YYYY-MM-DD HH24:MI:SS'),
to_char(sysdate - 7, 'YYYY-MM-DD HH24:MI:SS'),
man_date_sub - (sysdate - 7)
from man
where trunc(man_date_sub) = date '2016-04-05';
You'll see that some have times before the current sysdate time, while others have times after it. The third, generated, column will show some positive and some negative values.
In your second query you're comparing trunc(man_date_sub), which sets the time part to midnight, with date '2016-04-05', which is also at midnight; so all the records at any time on that day now match.
You can go back to midnight on your 7-day range, and get an equivalent result, by truncating sysdate:
select count(*), trim(man_date_sub)
from man
where man_status = 'SUBMITTED' and man_date_sub > trunc(sysdate)-7
group by trim(man_date_sub);
Your use of the trim() function is a bit odd; all you're doing is removing leading and trailing whitespace from the string '05-APR-16', which isn't actually doing anything. You're also relying on implcit conversion of the date to a string using your session NLS_DATE_FORMAT. It would be better to specify the format:
select count(*), to_char(man_date_sub, 'YYYY-MM-DD')
from man
where man_status = 'SUBMITTED' and man_date_sub > trunc(sysdate)-7
group by to_char(man_date_sub, 'YYYY-MM-DD');
If you ran your original query in a session that had an NLS_DATE_FORMAT that included time elements then you wouldn't get the result you expect.
I'm not sure if you're confusing it with trunc(), though clearly you're using that elsewhere. Truncating a date sets the time portion to midnight (by default; it can do other things), but leaves it as a date, which would be suitable for grouping but should still be formatted explicitly for display.

Oracle SQL - Determine if a timestamp falls within a range, excluding date

I'm looking for a way to determine if a timestamp falls between two times, regardless of the date in that timestamp. So for example, if the time in the timestamp falls between '00:00:00.000' (midnight) and '01:00:00.000' (1 A.M.), I'd want to select that row regardless of the particular date.
I've tried lots of different variations on the to_char and to_date functions, but I keep getting errors. Coming from Informix, Oracle seems much more complicated.
The thing closest to "correct" (I think) that I've tried is:
SELECT *
FROM my_table
WHERE SUBSTR(TO_CHAR(my_timestamp), 10) > '00:00:00.000'
AND SUBSTR(TO_CHAR(my_timestamp), 10) < '01:00:00.000'
... But nothing works. Any tips or tricks?
I found a way to do it, but I'd still prefer something a little less hacky, if it exists.
SUBSTR(SUBSTR(TO_CHAR(my_timestamp), 11), 0, 12) > '01.00.00.000'
Your solution looks correct to me except I haven't tried substr function. This is what I used in one of my previous project:
select * from orders
where to_char(my_timestamp,'hh24:mi:ss.FF3')
between '00:00:00.000' and '01:00:00.123';
Use TRUNC(my_timestamp, 'J') to remove the hours and get only the '2013-08-15 00:00:00.00'.
So:
WHERE my_timestamp - TRUNC(my_timestamp, 'J') > 0
AND my_timestamp - TRUNC(my_timestamp, 'J') < 1/24 ;
As a variation on #kubanczyk's answer, since these are timestamps you get an interval when you subtract a value from its truncated form:
select systimestamp - trunc(systimestamp) from dual;
SYSTIMESTAMP-TRUNC(SYSTIMESTAMP)
---------------------------------------------------------------------------
+000000000 09:46:46.589795
Which isn't very helpful. But if you're always looking for exact hours, as in your example, you can extract the hour number from that:
select extract (hour from systimestamp - trunc(systimestamp)) from dual;
EXTRACT(HOURFROMSYSTIMESTAMP-TRUNC(SYSTIMESTAMP))
-------------------------------------------------
9
So in your example you could use:
SELECT *
FROM my_table
WHERE EXTRACT(HOUR FROM my_timestamp - TRUNC(my_timestamp)) = 0
SQL Fiddle demo.
But, this will only be straightforward if the timeslots are exactly aligned with hours; otherwise you'd need to extract other elements too and the logic could get confusing, and #Ankit's approach will be simpler overall.

Optimizing SQL Query and Dynamically using current date

I am trying to optimize a simple SQL query and was wondering if anyone has any suggestions. I am developing using Oracle SQL Developer (which I don't like) on an Oracle 11g database. The query I am using is:
SELECT count(*)
FROM my_table
WHERE my_date
BETWEEN TO_DATE('2012-5-09T05.00.00','YYYY-MM-DD"T"HH24:MI:SS')
AND TO_DATE('2012-5-10T04.59.59','YYYY-MM-DD"T"HH24:MI:SS')
AND my_code='33'
GROUP BY my_code;
Also, I want to be able to use this query dynamically by changing the part of the date to be whatever the current date is, but I want to be able to specify the hour. So I want to be comparing something like:
getdate() + 'T05.00.00'
I have no idea how to do this and the getdate() function doesn't seem to work in SQL Developer/I don't know how to use it correctly.
So what I'm looking for is optimization suggestions and pointers on how to just dynamically change the day-month-year part of the date I want to constrain my results to. Thanks!
To get current date, you can use SYSDATE. To add x number of hours to it, you can add x/24. So something like this:
Example: Get current date + 5 hours
SELECT SYSDATE + 5/24 FROM dual
So in your example:
SELECT count(*)
FROM my_table
WHERE my_date
BETWEEN sysdate
AND sysdate + 5/24 -- if you want 5 hours ahead, for example
AND my_code='33'
GROUP BY my_code;
If you want to be able to change the number of hours, you could make this code into a function, and pass in the hours and code as variables.
Something like this:
CREATE FUNCTION myfunc
(
p_num_hours INT
, p_my_code VARCHAR
) RETURN INT
AS
l_ret INT;
BEGIN
SELECT count(*)
INTO l_ret
FROM my_table
WHERE my_date
BETWEEN sysdate
AND sysdate + p_num_hours/24
AND my_code=p_my_code
RETURN l_ret;
END;
As an alternative to adding fractional days via expressions such as "5 / 24" you might want to use an INTERVAL constant. For example:
SELECT count(*)
FROM my_table
WHERE my_date BETWEEN (TRUNC(SYSDATE) + INTERVAL '5' HOUR)
AND (TRUNC(SYSDATE) + INTERVAL '1' DAY +
INTERVAL '5' HOUR - INTERVAL '1' SECOND) AND
my_code='33'
GROUP BY my_code
I like to use INTERVAL constants because it's quite clear what these constants represent. With the fractional-day constants I sometimes get confused ('course, I sometimes get confused, regardless... :-)
Share and enjoy.
If I understand correctly, something like
select count(*)
from my_table
where trunc(my_date) = trunc(sysdate)
and my_code = '33'
group by my_code;
or
select count(*)
from my_table
where my_date
between sysdate and sysdate + 5/24
and my_code = '33'
group by my_code;
HTH.
Alessandro