Sum totals of timestamp - oracle

I have the following query to calculate the summation timestamp
SELECT SUM(TIME_SPENT) FROM
(
select a - b as time_spent from tbl1 where name = 'xxx'
union all
select c - d as time_spent from tbl2 where name= 'yyy'
)a;
The sub-query return result as
+00 00:01:54.252000
But the entire query return error as ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL DAY TO SECOND.
Understand it required something like this
SELECT COALESCE (
(to_timestamp('2014-09-22 16:00:00','yyyy/mm/dd HH24:MI:SS') - to_timestamp('2014-09-22 09:00:00','yyyy/mm/dd HH24:MI:SS')) -
(to_timestamp('2014-09-22 16:00:00','yyyy/mm/dd HH24:MI:SS') - to_timestamp('2014-09-22 09:00:00','yyyy/mm/dd HH24:MI:SS')), INTERVAL '0' DAY) FROM DUAL;
How can achieve along with sub-queries that retrieve data from Timestamp type columns?

You cannot sum INTERVAL DAY TO SECOND in Oracle. I think this is one of the top rated open feature request.
You can cast the TIMESTAMP into DATE values, then the result is the difference in days. Multiply by 24*60*60 is you like to get seconds instead:
SELECT SUM(TIME_SPENT) * 24*60*60 FROM FROM
(
select CAST(a AS DATE) - CAST(b AS DATE) as time_spent from tbl1 where name = 'xxx'
union all
select CAST(d AS DATE) - CAST(d AS DATE) as time_spent from tbl2 where name= 'yyy'
);
Or you can write a function which converts INTERVAL DAY TO SECOND into seconds:
CREATE OR REPLACE FUNCTION GetSeconds(ds INTERVAL DAY TO SECOND) DETERMINISTIC RETURN NUMBER AS
BEGIN
RETURN EXTRACT(DAY FROM ds)*24*60*60
+ EXTRACT(HOUR FROM ds)*60*60
+ EXTRACT(MINUTE FROM ds)*60
+ EXTRACT(SECOND FROM ds);
END;
and use it like this:
SELECT SUM(TIME_SPENT), numtodsinterval(SUM(TIME_SPENT), 'second')
(
select GetSeconds(a-b) as time_spent from tbl1 where name = 'xxx'
union all
select GetSeconds(c-d) as time_spent from tbl2 where name= 'yyy'
);

try using below query
SELECT sum(extract(second from time_spent)) FROM
(
select a - b as time_spent from test2 where name = 'xxx'
union all
select c - d as time_spent from tbl2 where name= 'yyy'
)a;
Looks like time_spent column is timestamp type in your table an d it is not able to pass the correct data type in Sum function. Use extract function to get Seconds from time_spent.

with t(a,b) as (
select timestamp'2014-09-22 16:00:00.000', timestamp'2014-09-23 16:00:00.001' from dual union all
select timestamp'2014-09-22 16:00:00.000', timestamp'2014-09-24 16:00:00.001' from dual union all
select timestamp'2014-09-22 16:00:00.000', timestamp'2014-09-25 16:00:00.001' from dual union all
select timestamp'2014-09-22 16:00:00.000', timestamp'2014-09-26 16:00:00.001' from dual union all
select timestamp'2014-09-22 16:00:00.000', timestamp'2014-09-27 16:00:00.001' from dual
)
select
sum( (date'1-1-1'+(b-a)*24*60*60 - date'1-1-1')) as ssum_seconds_1,
round(sum( (date'1-1-1'+(b-a)*24*60*60 - date'1-1-1')), 3) as ssum_seconds_rounded,
numtodsinterval( round(sum( (date'1-1-1'+(b-a)*24*60*60 - date'1-1-1')), 3), 'second') dsint
from t
/

Related

oracle how to generate a list of period from a list of date

i would like to write a query to transform a list of date
list of date
15/02/2021
12/04/2021
28/07/2021
31/08/2021
to a list of period
start
end
15/02/2021
11/04/2021
12/04/2021
27/07/2021
28/07/2021
31/08/2021
Is it possible to do it in a oracle query ? Thanks for your help
Variant 1:
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;
Full test with test data:
alter session set nls_date_format='dd/mm/yyyy';
with t(dt) as (
select to_date('15/02/2021','dd/mm/yyyy') from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;
DT_START DT_END
---------- ----------
15/02/2021 11/04/2021
12/04/2021 27/07/2021
28/07/2021 31/08/2021
Variant 2: match_recognize (Oracle 12+):
select
dt_start,dt_end
from t
match_recognize(
order by dt
MEASURES
prev(dt) as dt_start,
nvl2(next(dt),e.dt-1,e.dt) as dt_end
all rows per match
PATTERN (e+)
DEFINE
e AS dt > prev(dt)
);
Full example with test data:
with t(dt) as (
select to_date('15/02/2021','dd/mm/yyyy') from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select
dt_start,dt_end
from t
match_recognize(
order by dt
MEASURES
prev(dt) as dt_start,
nvl2(next(dt),e.dt-1,e.dt) as dt_end
all rows per match
PATTERN (e+)
DEFINE
e AS dt > prev(dt)
);
DT_START DT_END
---------- ----------
15/02/2021 11/04/2021
12/04/2021 27/07/2021
28/07/2021 31/08/2021
DBFiddle: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=a9f9e256762c87e67ce3783f51f8d7c8
That's exactly what I'm looking for !
thanks !
I've just changed few things because I'm in Oracle 10g probably
with t as (
select to_date('15/02/2021','dd/mm/yyyy') dt from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;

Adding hours variable to date variable in Oracle

OK all-This may be simple but I can't see to find an answer via Google.
So I have a date value ('01/01/2020') and in another column I have a variable of hours (let's say 5) that needs to be added. SO I would have 01-JAN-20 05:00:00 in the end.
Any suggestions helpful. Thanks-
with t1 as (select TO_DATE('01/01/2020','DD/MM/YYYY') as DT, '5' as HR FROM DUAL)
select t1.* , ???? from t1;
You may simply add the correct fraction of a day, given the hour value:
WITH t1 AS (
SELECT TO_DATE('01/01/2020', 'DD/MM/YYYY') AS DT, '5' AS HR
FROM DUAL
),
t2 AS (
SELECT DT, DT + TO_NUMBER(HR) / 24 AS NEW_DT
FROM t1
)
SELECT
TO_CHAR(DT, 'DD-MM-YYYY HH24:MI:SS') AS DT,
TO_CHAR(NEW_DT, 'DD-MM-YYYY HH24:MI:SS') AS NEW_DT
FROM t2;
Demo
You can also use interval clause as follows:
with t1 as (select TO_DATE('01/01/2020','DD/MM/YYYY') as DT, '5' as HR FROM DUAL)
select t1.* ,
t1.dt + hr * interval '1' hour as new_dt -- this is solution
from t1;

Compare date in Oracle when some value is null?

I want to get records that have an expired_date less than "31/12/2015", but some value of expired_date field is null in the database. This is my SQL:
"AND TO_DATE(EXPIRED_DATE,'DD/MM/YYYY') < TO_DATE('31/12/2015','DD/MM/YYYY')"
so I have got an error
"day of the month must between 1 and the last day of the month"
How can I get just records that have an expired_date < '31/12/2015'?
EDIT: The problem is not the null value, but my format in my DB. I store "22-APR-15" and mistake is trying to assign it to 'DD/MM/YYYY' by to_date function
If your column EXPIRED_DATE is of date type then, you don't need to convert it to date again using TO_DATE.
I think you need the following:
AND EXPIRED_DATE < TO_DATE('31/12/2015','DD/MM/YYYY')
This will return false for any EXPIRED_DATE which is null and that record will not be included in the result.
If you want Null EXPIRED_DATE to be included in the result then you can use OR as following:
AND (EXPIRED_DATE IS NULL OR EXPIRED_DATE < TO_DATE('31/12/2015','DD/MM/YYYY'))
Your code ...E(EXPIRED_DATE,'DD/MM/YYY')... has 3 YYY for date instead of 4 YYYY. Tried below and it works
with da(date_a) as (
select '03/04/2015' from dual
union all select '03/04/2015' from dual
union all select '03/04/2017' from dual
union all select '03/04/2015' from dual
union all select '03/04/2016' from dual
union all select NULL from dual
)
SELECT * FROM da WHERE TO_DATE(date_a,'DD/MM/YYYY') < TO_DATE('31/12/2015', 'DD/MM/YYYY');
Even when your date column is in date format it will still work
with da(date_a) as (
select '03/04/2015' from dual
union all select '03/04/2015' from dual
union all select '03/04/2017' from dual
union all select '03/04/2015' from dual
union all select '03/04/2016' from dual
union all select NULL from dual
)
SELECT to_date(date_a, 'DD/MM/YYYY') date_a FROM da WHERE TO_DATE(date_a,'DD/MM/YYYY') < TO_DATE('31/12/2015', 'DD/MM/YYYY');
db<>fiddle

How to convert this code from oracle to redshift?

I am trying to implement the same in redshift and i am finding it little difficult to do that. Since redshift is in top of postgresql engine, if any one can do it in postgresql it would be really helpfull. Basically the code gets the count for previous two month at column level. If there is no count for exact previous month then it gives 0.
This is my code:
with abc(dateval,cnt) as(
select 201908, 100 from dual union
select 201907, 200 from dual union
select 201906, 300 from dual union
select 201904, 600 from dual)
select dateval, cnt,
last_value(cnt) over (order by dateval
range between interval '1' month preceding
and interval '1' month preceding ) m1,
last_value(cnt) over (order by dateval
range between interval '2' month preceding
and interval '2' month preceding ) m2
from (select to_date(dateval, 'yyyymm') dateval, cnt from abc)
I get error in over by clause. I tried to give cast('1 month' as interval) but still its failing. Can someone please help me with this windows function.
expected output:
Regards
This is how I would do it. In Redshift there's no easy way to generate sequences, do I select row_number() from an arbitrary table to create a sequence:
with abc(dateval,cnt) as(
select 201908, 100 union
select 201907, 200 union
select 201906, 300 union
select 201904, 600),
cal(date) as (
select
add_months(
'20190101'::date,
row_number() over () - 1
) as date
from <an arbitrary table to generate a sequence of rows> limit 10
),
with_lag as (
select
dateval,
cnt,
lag(cnt, 1) over (order by date) as m1,
lag(cnt, 2) over (order by date) as m2
from abc right join cal on to_date(dateval, 'YYYYMM') = date
)
select * from with_lag
where dateval is not null
order by dateval

calculate the running total over the column contain date difference in HH:MI:SS format in oracle

I have to find the running total over the column interval.
SELECT
( ( EXTRACT(DAY FROM intrvl) * 24 ) + ( EXTRACT(HOUR FROM intrvl) ) ) ||':'||
EXTRACT(MINUTE FROM intrvl) ||':'||
EXTRACT(SECOND FROM intrvl) ||':'|| as interval
FROM
(
SELECT
( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) - ( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) intrvl
FROM
dual
);
currrently Interval column of table has below data:
Interval(HH:mi:ss)
0:4:23
696:1:36
696:4:51
8760:1:18
The best I can come up with is this. Note that the interval data type does not take a format model for displaying - you can't force an interval of 25 hours to be displayed as 25:00:00 (although you can use that to INPUT an interval). Instead, it will be shown as 01 01:00:00 (meaning, a day and an hour).
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
as sum_interv
from tbl;
SUM_INTERV
--------------------
+423 00:12:08.000000
In your original attempt you were trying to get a STRING output. I am not sure that's wise, but if that's what you need you can do it like so:
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
, prep (sum_interv) as (
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
from tbl
)
select to_char( extract(day from sum_interv) * 24
+ extract(hour from sum_interv), 'fm999999999' ) || ':' ||
to_char( extract(minute from sum_interv), 'fm00' ) || ':' ||
to_char( extract(second from sum_interv), 'fm00' ) as sum_interv
from prep
;
SUM_INTERV
------------------
10152:12:08

Resources