Oracle GROUP BY and date precision - sql

I've got a table with two date fields : BEGIN_DATE and END_DATE
When I subtract these two fields, I get a number in days.
I want this number in seconds because the difference between these two fields is very tiny (~ 1s). So I proceed by doing :
SELECT ROUND(AVG((END_DATE-BEGIN_DATE)*3600*24),2) AS DELTA,
TO_CHAR(BEGIN_DATE, 'yyyy-mm-dd hh24:mi:ss') AS DEB,
TO_CHAR(END_DATE, 'yyyy-mm-dd hh24:mi:ss') AS FIN
FROM MYTABLE
GROUP BY TO_CHAR(BEGIN_DATE, 'yyyy-mm-dd hh24:mi:ss'),
TO_CHAR(END_DATE, 'yyyy-mm-dd hh24:mi:ss');
Here is the result (same precision with group by minutes) :
Well.
Then if I group the results by hour or by day :
SELECT ROUND(AVG((END_DATE-BEGIN_DATE)*3600*24),2) AS DELTA,
TO_CHAR(BEGIN_DATE, 'yyyy-mm-dd hh24') AS DEB,
TO_CHAR(END_DATE, 'yyyy-mm-dd hh24') AS FIN
FROM MYTABLE
GROUP BY TO_CHAR(BEGIN_DATE, 'yyyy-mm-dd hh24'),
TO_CHAR(END_DATE, 'yyyy-mm-dd hh24');
I've got this result :
The DELTA precision is better and I can't understand why !
Could someone explain me ?

My bad, don't go further, the problem is relatively simple.
Floating results are given by : SUM(DELTA) / COUNT(Grouped rows).
So if I've got 20 values at 2015-11-02 19.
9 of these values are equal to 1, remainder equals to 0.
We've got 9/20 = 0.45 which is absolutely logic.
I just verified it.
Thank you anyway for your time.

Related

Oracle SQL time difference in HH:MM:SS

I am trying to get time difference in Oracle database. The time difference for all the rows is okay except for one row. Here is the query
SELECT MAX(REGEXP_SUBSTR (CAST(TO_DATE(call_end, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP) - CAST(TO_DATE(call_start, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP), '\d{2}:\d{2}:\d{2}')) AS call_time
FROM calls
The time difference occurs with the following row:
call_end: '2020-02-20 13:00:20'
call_start: '2020-02-20 12:56:03'
The returned result is '11:55:43' which is wrong. As the correct answer should be '00:04:17'
It looks like your table already stores the call start/end times as dates, and you're doing an implicit conversion from date to string, then converting back to a date. You can see that result if you have HH instead of HH24 in your NLS_DATE_FORMAT setting:
alter session set nls_date_format = 'YYYY-MM-DD HH:MI:SS';
with calls (call_end, call_start) as (
select cast(timestamp '2020-02-20 13:00:20' as date), cast(timestamp '2020-02-20 12:56:03' as date) from dual
)
SELECT MAX(REGEXP_SUBSTR (CAST(TO_DATE(call_end, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP) - CAST(TO_DATE(call_start, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP), '\d{2}:\d{2}:\d{2}')) AS call_time
FROM calls;
CALL_TIME
---------------------------
11:55:43
When you do
TO_DATE(call_end, 'YYYY/MM/DD HH24:MI:SS')
because it's already a date you're really doing:
TO_DATE(TO_CHAR(call_end, <NLS_DATE_FORMAT>), 'YYYY/MM/DD HH24:MI:SS')
so with my setting (and yours must be similar, maybe with slashes instead of dashes) that is:
TO_DATE(TO_CHAR(call_end, 'YYYY-MM-DD HH:MI:SS'), 'YYYY/MM/DD HH24:MI:SS')
and the mismatch between HH and HH24 becomes more obvious. So, you're actually converting the strings 2020-02-20 01:00:20 and 2020-02-20 12:56:03 back to dates, and the time difference between 01:00:20 and 12:56:03 is 11:55:43. Well - actually, it is minus 11 hours:
SELECT CAST(TO_DATE(call_end, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP) - CAST(TO_DATE(call_start, 'YYYY/MM/DD HH24:MI:SS') AS TIMESTAMP)
FROM calls;
CAST(TO_DATE(CALL_E
-------------------
-00 11:55:43.000000
but your regex isn't picking that up.
As they are dates skip that part of the conversion completely, and just cast directly to timestamps if you want intervals to work from:
SELECT MAX(REGEXP_SUBSTR (CAST(call_end AS TIMESTAMP) - CAST(call_start AS TIMESTAMP), '\d{2}:\d{2}:\d{2}')) AS call_time
FROM calls;
CALL_TIME
---------------------------
00:04:17
or use a substring instead of a regex, as #MTO showed.
You could also leave them as dates, get the difference as a fraction of a day, add that back to any nominal date at midnight, and then convert the resulting date to a string:
SELECT TO_CHAR(date '2000-01-01' + MAX(call_end - call_start), 'HH24:MI:SS') AS call_time
FROM calls;
CALL_TIM
--------
00:04:17
This won't work properly for a call that lasts more than 24 hours (used to see that a lot with modem calls, but can still happen); but neither will your interval approach. Both ignore any full days and just show the remainder. There are ways to handle that of course, but you'd need to decide how you want to display it - with separate day count (like interval does anyway), or with the 'hours' number allowed to go above 24... but then you may go above 99 hours...
Your question shows the call end time as 2020-02-20 13:00:20, which suggests that's how your client is displaying it when you query the table. Some clients (I think PL/SQL Developer, but not sure, it's been a while) use their own preferences/settings instead of honouring the session's NLS settings. But that has no effect on how Oracle behaves internally when it has to do implicit conversions.
I'm not sure if you want the result as an interval or timestamp, but this should do what you want:
select t.*, call_end - call_start,
substr(to_char(call_end - call_start, 'HH24:MI:SS'), 12, 8) as str from (select timestamp '2020-02-20 13:00:20.000' as call_end,
timestamp '2020-02-20 12:56:03.000' as call_start
from dual) t
Here is a db<>fiddle.
Assuming your data types are strings (you really should store them as DATE data types), you can use:
SELECT SUBSTR(
MAX(
( TO_DATE( call_end, 'YYYY-MM-DD HH24:MI:SS' )
- TO_DATE( call_start, 'YYYY-MM-DD HH24:MI:SS' )
) DAY(1) TO SECOND
),
4,
8
) AS call_time
FROM calls
If they are already DATE data types then, in the MAX aggregation, just use:
( call_end - call_start ) DAY(1) TO SECOND
So for your data:
CREATE TABLE calls ( call_end, call_start ) AS
SELECT '2020-02-20 13:00:20', '2020-02-20 12:56:03' FROM DUAL
This outputs:
| CALL_TIME |
| :-------- |
| 00:04:17 |
db<>fiddle here
Mr Gyl, You can as well do: db <> fiddle :This solution will also consider when the call has lapsed over days by adding those hours together:
WITH da AS (
SELECT
NUMTODSINTERVAL(TO_DATE('2020-02-20 13:00:20', 'yyyy-mm-dd hh24:mi:ss') - TO_DATE('2020-02-20 12:56:03', 'yyyy-mm-dd hh24:mi:ss'), 'DAY') AS call_diff
FROM
dual ) SELECT
EXTRACT( DAY FROM call_diff )*24 + EXTRACT( HOUR FROM call_diff )|| ':' || EXTRACT( MINUTE FROM call_diff ) || ':' || EXTRACT( SECOND FROM call_diff ) DIFFERENCE
FROM
da
DIFFERENCE|
----------|
0:4:17 |
So you can implement as
WITH da AS (
SELECT
NUMTODSINTERVAL(TO_DATE(call_end, 'yyyy-mm-dd hh24:mi:ss') - TO_DATE(call_start, 'yyyy-mm-dd hh24:mi:ss'), 'DAY') AS call_diff
FROM
calls
) SELECT
EXTRACT( DAY FROM call_diff )*24 + EXTRACT( HOUR FROM call_diff )|| ':' || EXTRACT( MINUTE FROM call_diff ) || ':' || EXTRACT( SECOND FROM call_diff ) TIMESTAMP
FROM
da
Please see below response but this will only work within 24hrs and returns both '00'
WITH sample_lt AS(
SELECT '2020-02-20 12:56:03' START_TIME, '2020-02-20 13:00:20' END_TIME FROM dual
)
SELECT start_time,
end_time,
TO_CHAR (TRUNC (SYSDATE) + (to_date(end_time, 'yyyy-mm-dd HH24:MI:SS') -
to_date(start_time, 'yyyy-mm-dd HH24:MI:SS')
) , 'hh24:mi:ss' ) duration
FROM sample_lt ;

Subtract Dates in Oracle

I run this query in oracle:
select TO_CHAR(parsed_on, 'DD-MON-YYYY HH24:MI:SS'), TO_CHAR(creation_date, 'DD-MON-YYYY HH24:MI:SS'),
(parsed_on - creation_date) * 1000
from hotels
where TO_CHAR(CREATION_DATE, 'dd/mm/yyyy') = TO_CHAR(SYSDATE, 'dd/mm/yyyy') and incoming_psd_id = 608423671;
and this is the result, where I was excepting 12 seconds instead of 8.4
10-NOV-2017 07:49:54 10-NOV-2017 07:37:46 8,42592592592
The correct expression for seconds in Oracle would be:
(parsed_on - creation_date) * 24*60*60
The difference is decimal days.
If you want minutes, then use:
(parsed_on - creation_date) * 24*60
select ( to_date('10-NOV-2017 07:49:54', 'DD-MON-YYYY HH:MI:SS') - to_date('10-NOV-2017 07:37:46', 'DD-MON-YYYY HH:MI:SS') )* 86400
from dual
Correctly gives 728 seconds.
And if you want to compare dates, you have this option:
where trunc(CREATION_DATE) = trunc(SYSDATE)
TRUNC cuts out the time portion of dates by default, whereas trunc(SYSDATE, YYYY) would cut it down to years only.

TO_CHAR only considering dates while comparing two time stamps?

In the below query condition is failing but logically it should pass. Is something wrong with to_char??
SELECT data
FROM table1
WHERE
TO_CHAR(TO_DATE(value1, 'DD/MM/RRRR HH24:MI:SS'), 'DD/MM/RRRR HH24:MI:SS') <=
TO_CHAR( SYSDATE, 'DD/MM/RRRR HH24:MI:SS')
AND TO_CHAR(TO_DATE(value2, 'DD/MM/RRRR HH24:MI:SS'), 'DD/MM/RRRR HH24:MI:SS') >=
TO_CHAR( SYSDATE, 'DD/MM/RRRR HH24:MI:SS');
value1='02/07/2014 12:30:10'
value2='06/08/2015 09:57:33'
in both the conditions it is only checking the dates i.e.,02<=07 (7th is todays date).First condition is getting satisfied regardless of month and year.if i change value1 to '15/08/2014 12:30:10' it is failing. Same with second condition.
Why are you comparing dates as strings? This also begs the question of why you would store dates as strings in the first place. You should store date/times using the built-in types.
Try this instead:
SELECT data
FROM table1
WHERE TO_DATE(value1, 'DD/MM/RRRR HH24:MI:SS') <= sysdate AND
TO_DATE(value2, 'DD/MM/RRRR HH24:MI:SS') >= sysdate;
Your problem is presumably that you are comparing strings, rather than dates. And the format you are using DD/MM/YYYY doesn't do comparisons the same way. This is, in fact, why you should just use the ISO format of YYYY-MM-DD whenever you are storing date/time values in strings (which I don't recommend in most cases anyway).
If your values are already stored in proper types, then you can just do:
SELECT data
FROM table1
WHERE value1 <= sysdate AND
value2 >= sysdate;
If these are timestamps with time zone, then you can use SYSTIMESTAMP instead of SYSDATE.
Finally after lot of googling i got the solution. Below query is working for me :) :)
SELECT MESSAGE
FROM TABLE1
WHERE TO_TIMESTAMP(value1, 'DD/MM/RRRR HH24:MI:SS') <= CAST(SYSDATE AS TIMESTAMP)
AND TO_TIMESTAMP(value2, 'DD/MM/RRRR HH24:MI:SS') >= CAST(SYSDATE AS TIMESTAMP);
value1='02/07/2014 12:30:10'
value2='06/08/2015 09:57:33'
You have to use TO_TIMESTAMP instead of TO_DATE:
SELECT data
FROM table1
WHERE
TO_CHAR(TO_TIMESTAMP(value1, 'DD/MM/RRRR HH24:MI:SS'), 'DD/MM/RRRR HH24:MI:SS') <=
TO_CHAR( SYSDATE, 'DD/MM/RRRR HH24:MI:SS')
AND TO_CHAR(TO_TIMESTAMP(value2, 'DD/MM/RRRR HH24:MI:SS'), 'DD/MM/RRRR HH24:MI:SS') >=
TO_CHAR( SYSDATE, 'DD/MM/RRRR HH24:MI:SS');

Oracle Timestamp Conversion with Dates

Assuming this has a simple solution, but I can't find it.
I'm trying to do some logic on a DATE field in Oracle. My desire is to take a DATE field and subtract X hours from it.
For instance: SELECT A.MyDATE - 100 Hours from dual;
however, I need a result in a timestamp format 'YYYY-MM-DD hh:mm'.
I've tried CAST(A.MyDATE as TIMESTAMP) - NUMTODSINTERVAL(100/24,'day') however it didn't work.
I found out that the issue is that the MyDATE field when cast to a timestamp still contained some residual time elements. How can I reset these??
Thanks!
You can just do this with subtraction:
select a.MyDate - 100.0/24
To convert to varchar:
select to_char(a.MyDate - 100.0/24, 'YYYY-MM-DD')
And, if you want to get rid of that pesky time on the date:
select trunc(a.MyDate - 100.0/24) as JustTheDate
The formats and dates in my example can be changed to any other formats and dates:
SELECT To_Timestamp(To_Char(Sysdate - INTERVAL '100' HOUR, 'MM/DD/YYYY HH24:MI'), 'MM/DD/YYYY HH24:MI')
FROM dual
/
Output:
2/4/2013 10:18:00.000000000 AM
To remove time element add Trunc() to any of your dates...:
SELECT Trunc(To_Timestamp(To_Char(Sysdate - INTERVAL '100' HOUR, 'MM/DD/YYYY HH24:MI'), 'MM/DD/YYYY HH24:MI'))
FROM dual
/
Output: 2/4/2013
Conversion/Casting - when using other dates in place of sysdate then add formats as in my other examples:
SELECT CAST(SYSDATE AS TIMESTAMP) - INTERVAL '100' HOUR FROM dual
/
Output: 2/4/2013 10:26:35.000000000 AM
SELECT start_date tstamp_to_date, CAST(start_date AS timestamp) date_to_tstamp FROM
(
SELECT to_date(to_char(to_timestamp ('2013-02-07 10:07:47.000' , 'YYYY-MM-DD HH24:MI:SS.FF'),'DD-MON-YYYY HH24:MI:SS'), 'DD-MON-YYYY HH24:MI:SS') start_date
FROM dual
)
/
Output:
tstamp_to_date date_to_tstamp
-------------------------------------------------------
2/7/2013 10:07:47 AM 2/7/2013 10:07:47.000000 AM
In Oracle, a DATE always has a day and a time component. Depending on the tool you are using and your session's NLS_DATE_FORMAT, it is entirely possible that the tool may not display the time component when you look at the data. But that is simply a display question, it has no impact on the actual data.
If you want to subtract 100 hours from midnight on the day that MyDate represents
SELECT TRUNC(MyDate) - interval '100' hour
FROM dual
This will return a DATE. If you want to return a string in a particular format
SELECT TO_CHAR( TRUNC(MyDate) - interval '100' hour, 'YYYY-MM-DD hh:mi am' )
FROM dual
Note that I'm assuming that there was a typo in your question. I assume that you want to display the minutes after the hour (mi) rather than the month (mm).
I am trying to fetch the records which is older than 30 days (from Mod_date) and I am using the below query and it is returning all the data and I want only 30 days old data.
Sample :- Mod_date 03-NOV-12 12.00.00.000000000 AM
Query :-
select Mod_date from fil_cnfact where Mod_date <= sysdate -30 order by Mod_date asc ;

Oracle Set operator query

Have a SQL query on Oracle 11g which returns the count of whether a record having certain ID and status exists within +/- 15 minutes range in a table.
Now I wish to ignore the current date by adding a condition like AND TIMESTAMP < trunc(sysdate).
However, for cases where the record exists in todays date I wish to ignore the date comparison check in the query '2010-07-20 19:15:11' >= TO_CHAR(TIMESTAMP - (1/1440*15), 'YYYY-MM-DD HH24:MI:SS')
AND '2010-07-20 19:15:11' <= (TO_CHAR(TIMESTAMP + (1/1440*15), 'YYYY-MM-DD HH24:MI:SS'))
SELECT count(1) AS COUNT
FROM MASTER_ONE
WHERE ID='123' AND STATUS= 'ACTIVE'
AND '2010-07-20 19:15:11' >= TO_CHAR(TIMESTAMP - (1/1440*15), 'YYYY-MM-DD HH24:MI:SS')
AND '2010-07-20 19:15:11' <= (TO_CHAR(TIMESTAMP + (1/1440*15), 'YYYY-MM-DD HH24:MI:SS'))
UNION ALL
SELECT count(1) AS COUNT
FROM MASTER_TWO
WHERE ID='321' AND STATUS= 'ACTIVE'
AND '2010-07-20 19:15:11' >= TO_CHAR(TIMESTAMP - (1/1440*15), 'YYYY-MM-DD HH24:MI:SS')
AND '2010-07-20 19:15:11' <= (TO_CHAR(TIMESTAMP + (1/1440*15), 'YYYY-MM-DD HH24:MI:SS'))
How do I do this?
The first problem with your query is that you're doing a string comparison on the date. Use to_date instead of to_char and let Oracle help you out.
SELECT
to_date('2010-07-20 19:15:11', 'YYYY-MM-DD HH24:MI:SS') AS orig_date
, to_date('2010-07-20 19:15:11', 'YYYY-MM-DD HH24:MI:SS') - 1 / 24 / 4 AS fifteen_min_prior
, to_date('2010-07-20 19:15:11', 'YYYY-MM-DD HH24:MI:SS') + 1 / 24 / 4 AS fifteen_min_after
FROM dual;
Output:
ORIG_DATE FIFTEEN_MIN_PRIOR FIFTEEN_MIN_AFTER
------------------------- ------------------------- -------------------------
20-JUL-10 07:15:11 PM 20-JUL-10 07:00:11 PM 20-JUL-10 07:30:11 PM
Then use can use those dates in a BETWEEN condition in the predicate. See Oracle date "Between" Query.
I'm not quite clear what you mean by "However, for cases where the record exists in todays date I wish to ignore the date comparison check in the query." You'd just written that you want to exclude values from the current day. Either you're excluding today's records or you're not.
Ok, you can try something like this, if I understood you correctly:
SELECT count(1) AS COUNT
FROM MASTER_ONE
WHERE ID='123' AND STATUS= 'ACTIVE'
AND (timestamp > trunc(sysdate)
OR (timestamp < trunc(sysdate)
AND timestamp BETWEEN to_date(:yourInputDate,'DD/MM/YYYY HH24:MI:SS') - (1/1440*15)
AND to_date(:yourInputDate,'DD/MM/YYYY HH24:MI:SS') + (1/1440*15)))
UNION ALL
SELECT count(1) AS COUNT
FROM MASTER_TWO
WHERE ID='321' AND STATUS= 'ACTIVE'
AND (timestamp > trunc(sysdate)
OR (timestamp < trunc(sysdate)
AND timestamp BETWEEN to_date(:yourInputDate,'DD/MM/YYYY HH24:MI:SS') - (1/1440*15)
AND to_date(:yourInputDate,'DD/MM/YYYY HH24:MI:SS') + (1/1440*15)))
In this Select, you only apply the 15 minutes condition if your timestamp column has a date prior to sysdate.