SQL query to sum timestamp diff with null handling - sql

I have a (Oracle)DB table with 2 columns t1 and t2 both with datatype timestamp. column t2 is nullable. I need a SQL query to give me something like the below pseudocode.
sum ((t2 if t2 !=null else sys.currentTimestamp) - t1)

There are two issues here. Firstly, how to substitute a default value for a null. That's easy, we have nvl and coalesce. For example:
with demo (t1, t2) as
( select timestamp '2020-01-01 00:00:00'
, timestamp '2020-01-01 01:02:03'
from dual
union all
select timestamp '2020-01-01 00:00:00', null from dual )
select t1
, t2
, nvl(t2, current_timestamp)
from demo;
T1 T2 NVL(T2,CURRENT_TIMESTAMP)
---------------------- ---------------------- -----------------------------
2020-01-01 00:00:00.00 2020-01-01 01:02:03.00 2020-01-01 01:02:03.000000000
2020-01-01 00:00:00.00 2020-08-28 11:25:22.989000000
The harder part is how to sum t2 - nvl(t2,current_timestamp). The difference between two timestamps is an interval day to second, and although you can do arithmetic with intervals (add, subtract, multiply etc), you can't currently sum them. (You can add your vote to this suggestion on the Oracle Database Ideas forum to get the functionality added.)
In the meantime, you can either write your own using the Oracle Data Cartridge Interface, or use a workaround such as this one from Stew Stryker:
with demo (t1, t2) as
( select timestamp '2020-01-01 00:00:00'
, timestamp '2020-01-01 01:02:03'
from dual
union all
select timestamp '2020-01-01 00:00:00', null from dual )
select numtodsinterval(
sum(
((sysdate + (nvl(t2,systimestamp) -t1)) - sysdate) * 86400
+ extract(second from (nvl(t2,systimestamp) -t1))
- trunc(extract(second from (nvl(t2,systimestamp) -t1)))
)
, 'second'
) as duration
from demo;
DURATION
--------------------------------------------------------------------------------
+000000240 12:37:23.646000000

Related

Problem with date matching between a variable and a date column Oracle SQL

The problem is that this query is working fine:
CREATE TABLE PROCESGEN_TEST
(PROCESENDTIME TIMESTAMP);
INSERT INTO PROCESGEN_TEST
(SELECT DISTINCT PROCESENDTIME FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME IS NOT NULL
AND PROCESENDTIME > '10-09-2020 01:00:00');
Def TIME2 = (SELECT MAX_EXEC_TIME FROM EXEC_TIME);
SELECT PROCESENDTIME
FROM PROCESGEN_TEST
WHERE PROCESENDTIME < &TIME2
AND PROCESEINDTIJD IS NOT NULL
In the above situation we first put the data into a table created in de database management system we use (named xor01 and not the xob10). In the query beneath we extract the data directly from xob10. This isn’t working when we want to select a date (greater or lower then..) and we don’t know why.
CREATE TABLE EXEC_TIME
(MAX_EXEC_TIME DATE);
INSERT INTO EXEC_TIME
(
SELECT TO_DATE(
TO_CHAR(
MAX(EXEC_DATE),
'DD-MM-YYYY HH24:MI:SS'
),
'DD-MM-YYYY HH24:MI:SS'
) - 1.1666
from L3DD_MIN_ACTIVITIES_BRD_BAK_Will
);
Def TIME3 = (SELECT MAX_EXEC_TIME FROM EXEC_TIME);
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME IS NOT NULL
AND TO_DATE(PROCESENDTIME,'DD-MM-YY HH24:MI:SS')
> TO_DATE(&TIME3, 'DD-MM-YY HH24:MI:SS');
The problem is that the query is not finding a single date in the last query and keeps on executing. If we replace TO_DATE(&TIME3, 'DD-MM-YY HH24:MI:SS') with a certain date like '10-08-2020 20:00:00' the query will find the right dates. We have tried all kinds of things, like working with TIMESTAMP format and TO_TIMESTAMP. Nothing works. It looks like a rather simple problem.
Does anyone know what’s causing the problem the query can’t find any dates in the second query?
You don't need:
the EXEC_TIME table;
to convert a timestamp to a string and then back to a date;
to use a variable; or
to filter on PROCESENDTIME IS NOT NULL (since the > filter only works on non-NULL values).
Then you can use:
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME
> (
SELECT MAX(EXEC_DATE) - INTERVAL '1 4' DAY TO HOUR
FROM L3DD_MIN_ACTIVITIES_BRD_BAK_Will
);
If you do want the EXEC_TIME table then:
CREATE TABLE EXEC_TIME( MAX_EXEC_TIME DATE );
INSERT INTO EXEC_TIME
SELECT MAX(EXEC_DATE) - INTERVAL '1 4' DAY TO HOUR
FROM L3DD_MIN_ACTIVITIES_BRD_BAK_Will;
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME > ( SELECT MAX_EXEC_TIME FROM EXEC_TIME );

Duplicate row based on date difference

help needed. I have a set of data from oracle import to tableau for calculation. But in order to do that, i need to duplicate charts as shown in table below. For example, if there is date diff between start and end, then i need to duplicate it and assign with code 0,1 depend on how many date differences. The purpose is i need to use this function in Tableau for time interval calculation. Thanks
Pregenerate codes up to max possible value and join original table to code series so that number of row duplications is determined by difference between dates on particular row:
with t (s,e) as (
select timestamp '2020-08-16 18:30:00', timestamp '2020-08-16 20:00:00' from dual union all
select timestamp '2020-08-17 08:00:00', timestamp '2020-08-18 08:00:00' from dual union all
select timestamp '2020-08-19 08:00:00', timestamp '2020-08-19 00:00:00' from dual union all
select timestamp '2020-08-20 10:00:00', timestamp '2020-08-22 03:00:00' from dual
), series (code) as (
select level - 1 from dual connect by level <= (select count(*) from t)
)
select t.*, series.code
from t
join series on trunc(e) - trunc(s) >= series.code
order by s,code;

Need to split datetime ranges by each day

I have a table that needs to be split on the basis of datetime
Input Table
ID| Start | End
--------------------------------------------
A | 2019-03-04 23:18:04| 2019-03-04 23:21:25
--------------------------------------------
A | 2019-03-04 23:45:05| 2019-03-05 00:15:14
--------------------------------------------
Required Output
ID| Start | End
--------------------------------------------
A | 2019-03-04 23:18:04| 2019-03-04 23:21:25
--------------------------------------------
A | 2019-03-04 23:45:05| 2019-03-04 23:59:59
--------------------------------------------
A | 2019-03-05 00:00:00| 2019-03-05 00:15:14
--------------------------------------------
Thanks!!
Try this below code. This will only work if the start and end date fall in two consecutive day. Not if the start and end date difference is more than 1 day.
MSSQL:
SELECT ID,[Start],[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) = 0
UNION ALL
SELECT ID,[Start], CAST(CAST(CAST([Start] AS DATE) AS VARCHAR(MAX)) +' 23:59:59' AS DATETIME)
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0
UNION ALL
SELECT ID,CAST(CAST([End] AS DATE) AS DATETIME),[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0
ORDER BY 1,2,3
PostgreSQL:
SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(endDate, 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) = 0
UNION ALL
SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(CONCAT(CAST(CAST (startDate AS DATE) AS VARCHAR) ,
' 23:59:59') , 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) > 0
UNION ALL
SELECT ID,
TO_TIMESTAMP(CAST(CAST (endDate AS DATE) AS VARCHAR) ,
'YYYY-MM-DD HH24:MI:SS') ,
TO_TIMESTAMP(endDate,'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) > 0;
PostgreSQL Demo Here
demo:db<>fiddle
This works even when range crosses more than one day
WITH cte AS (
SELECT
id,
start_time,
end_time,
gs,
lag(gs) over (PARTITION BY id ORDER BY gs) -- 2
FROM
a
LEFT JOIN LATERAL
generate_series(start_time::date + 1, end_time::date, interval '1 day') gs --1
ON TRUE
)
SELECT -- 3
id,
COALESCE(lag, start_time) AS start_time,
gs - interval '1 second'
FROM
cte
WHERE gs IS NOT NULL
UNION
SELECT DISTINCT ON (id) -- 4
id,
CASE WHEN start_time::date = end_time::date THEN start_time ELSE end_time::date END, -- 5
end_time
FROM
cte
CTE: the generate_series function generates one row per day new day. So, there is no value if there is no date change
CTE: the lag() window function allows to move the current date value into the next row (the current end is the next start)
With this data set you can calculate the new start and end values. If there is no gs value: There is no date change. This ignored at this point. For all cases with date changes: If there is no lag value, it is the beginning (so it cannot got a previous value). In this case, the normal start_time is taken, otherwise it is a new day which takes the date break time. The end_time is taken with the last second of the day (interval - '1 second')
The second part: Because of the date breaks there is always one additional record which need to be unioned. The last record is from the beginning of the end_time (so cast to date). The CASE clause combines this step with the case of no date change which has been ignored so far. So if start_time and end_time are at the same date, here the original start_time is taken.
Unfortunately, Redshift doesn't have a convenient way to generate a series of numbers. If you table is big enough, you can use it to generate numbers. "Big enough" means that the number of rows is greater than the longest span. Perhaps another table would work, if not this one.
Once you have that, you can use this logic:
with n as (
select row_number() over () - 1 as n
from t
)
select t.id,
greatest(t.s, date_trunc('day', t.s) + n.n * interval '1 day') as s,
least(t.e, date_trunc('day', t.s) + (n.n + 1) * interval '1 day' - interval '1 second') as e
from t join
n
on t.e >= date_trunc('day', t.s) + n.n * interval '1 day';
Here is a db<>fiddle. It uses an old version of Postgres, but not quite old enough for Redshift.
Simulate loop for interval generation using recursive CTE, i.e. take range from start to midnight in seed row, take another day in subsequent rows etc.
with recursive input as (
select 'A' as id, timestamp '2019-03-04 23:18:04' as s, timestamp '2019-03-04 23:21:25' as e union
select 'A' as id, timestamp '2019-03-04 23:45:05' as s, timestamp '2019-03-05 00:15:14' as e union
select 'B' as id, timestamp '2019-03-06 23:45:05' as s, timestamp '2019-03-08 00:15:14' as e union
select 'C' as id, timestamp '2019-03-10 23:45:05' as s, timestamp '2019-03-15 00:15:14' as e
), generate_id as (
select row_number() over () as unique_id, * from input
), rec (unique_id, id, s, e) as (
select unique_id, id, s, least(e, s::date::timestamp + interval '1 day')
from generate_id seed
union
select remaining.unique_id, remaining.id, previous.e, least(remaining.e, previous.e::date::timestamp + interval '1 day')
from rec as previous
join generate_id remaining on previous.unique_id = remaining.unique_id and previous.e < remaining.e
)
select id, s, e from rec
order by id,s,e
Note:
your id column appears not to be unique, so I added custom unique_id column. If id was unique, CTE generate_id was unnecessary. Uniqueness is unavoidable for recursive query to work.
close-open range is better for representation of such data, rather than close-close range. So end time in my query returns 00:00:00, not 23:59:59. If it's not suitable for you, modify query as an exercise.
UPDATE: query works on Postgres. OP originally tagged question postgres, then changed tag to redshift.

Trying to resolve a SQL Query issue

I’ve this 2 below SQL Queries which only differs in date-time criteria, one will return data and other will not… Can someone please help…?
Query which returns data –
SELECT *
FROM CA_CLN_CAPM_TRANSPORT,
CA_ENCOUNTER_REF TRANSPORT_PATIENT_ENCOUNTER,
(SELECT DISTINCT TRANSPORT_PATIENT_ATTRIBUTES.ENCOUNTER_ID,
TRANSPORT_PATIENT_ATTRIBUTES.ATTRIBUTE_NAME
FROM CA_CAPM_ENCOUNTER_ATTR TRANSPORT_PATIENT_ATTRIBUTES,
CA_CLN_CAPM_TRANSPORT
WHERE (CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM BETWEEN TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM AND NVL(TRANSPORT_PATIENT_ATTRIBUTES.EFF_END_DTTM,SYSDATE)
OR NVL(CA_CLN_CAPM_TRANSPORT.COMPLETE_DTTM,SYSDATE) BETWEEN TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM AND NVL(TRANSPORT_PATIENT_ATTRIBUTES.EFF_END_DTTM,SYSDATE)
OR TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM BETWEEN CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AND NVL(CA_CLN_CAPM_TRANSPORT.COMPLETE_DTTM,SYSDATE))
AND CAST(CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AS DATE) BETWEEN '30-06-2016 12:00:00' AND **'30-06-2016 23:59:00'**
) PATIENT_ATTRIBUTES
WHERE CA_CLN_CAPM_TRANSPORT.ENCOUNTER_ID = TRANSPORT_PATIENT_ENCOUNTER.ENCOUNTER_ID
AND TRANSPORT_PATIENT_ENCOUNTER.ENCOUNTER_ID = PATIENT_ATTRIBUTES.ENCOUNTER_ID(+)
AND TRANSPORT_PATIENT_ENCOUNTER.PATIENT_ID <> '0'
AND CAST(CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AS DATE) BETWEEN '30-06-2016 12:00:00' AND **'30-06-2016 23:59:00'**;
Query which doesn’t return data -
SELECT *
FROM CA_CLN_CAPM_TRANSPORT,
CA_ENCOUNTER_REF TRANSPORT_PATIENT_ENCOUNTER,
(SELECT DISTINCT TRANSPORT_PATIENT_ATTRIBUTES.ENCOUNTER_ID,
TRANSPORT_PATIENT_ATTRIBUTES.ATTRIBUTE_NAME
FROM CA_CAPM_ENCOUNTER_ATTR TRANSPORT_PATIENT_ATTRIBUTES,
CA_CLN_CAPM_TRANSPORT
WHERE (CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM BETWEEN TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM AND NVL(TRANSPORT_PATIENT_ATTRIBUTES.EFF_END_DTTM,SYSDATE)
OR NVL(CA_CLN_CAPM_TRANSPORT.COMPLETE_DTTM,SYSDATE) BETWEEN TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM AND NVL(TRANSPORT_PATIENT_ATTRIBUTES.EFF_END_DTTM,SYSDATE)
OR TRANSPORT_PATIENT_ATTRIBUTES.EFF_BEGIN_DTTM BETWEEN CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AND NVL(CA_CLN_CAPM_TRANSPORT.COMPLETE_DTTM,SYSDATE))
AND CAST(CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AS DATE) BETWEEN '30-06-2016 12:00:00' AND **'01-07-2016 23:59:00'** –Running for longer time frame
) PATIENT_ATTRIBUTES
WHERE CA_CLN_CAPM_TRANSPORT.ENCOUNTER_ID = TRANSPORT_PATIENT_ENCOUNTER.ENCOUNTER_ID
AND TRANSPORT_PATIENT_ENCOUNTER.ENCOUNTER_ID = PATIENT_ATTRIBUTES.ENCOUNTER_ID(+)
AND TRANSPORT_PATIENT_ENCOUNTER.PATIENT_ID <> '0'
AND CAST(CA_CLN_CAPM_TRANSPORT.SCHEDULED_DTTM AS DATE) BETWEEN '30-06-2016 12:00:00' AND **'01-07-2016 23:59:00'**;
The problem is your use of strings to represent the date-time values. If we run DUMP() on your strings we get this:
SQL> select dump('30-06-2016 12:00:00') as dt
2 from dual
3 /
DT
-----------------------------------------------------------------------
Typ=96 Len=19: 51,48,45,48,54,45,50,48,49,54,32,49,50,58,48,48,58,48,48
SQL>
Typ=96 means it's a CHAR value (a date would be Typ=12). So Oracle will apply character semantics to the operation. In effect this ...
x BETWEEN '30-06-2016 12:00:00' AND '01-07-2016 23:59:00'
... evaluates to ...
'30' >= '30' AND '30' <= '01'
... which is clearly nonsense.
What you need to do is use explicit casts with a format mask:
BETWEEN to_date('30-06-2016 12:00:00', 'DD-MM-YYYY HH24:MI:SS')
AND to_date('01-07-2016 23:59:00', 'DD-MM-YYYY HH24:MI:SS')

How to subtract Sys with Timestamp from field and get hours and minutes

This is my current query
SELECT
TABLE1.OUT_NO,
To_char(sysdate, 'MM-DD-YYYY HH24:MI:SS') as "Current_Time",
To_char(TABLE2.time_stamp, 'MM-DD-YYYY HH24:MI:SS') as "Recorded_Time"
FROM TABLE1 TABLE1 LEFT OUTER JOIN TABLE2 TABLE2 ON TABLE1.OUT_NO = TABLE2.OUT_NO
WHERE TABLE1.OUT_NO > '12345'
I need to subtract Current_time - Recorded_Time and get result in Hours and Minutes.
how can I achieve this?
Simply subtract one from the other
select sysdate - systimestamp from dual;
Ensure that they're still a DATE and a TIMESTAMP, do not convert them to characters first. This returns an INTERVAL datatype.
Apparently this is giving you an integer, which means your timestamp is being implicitly converted to a date (or is in fact already a date and not a timestamp).
If this is the case you have the number of days between the two dates; multiply by the number of minutes in a day in order to get this in minutes:
select ( sysdate - systimestamp ) * 60 * 24 from dual;
use datediff() built-in function
SELECT
TABLE1.OUT_NO,
To_char(sysdate, 'MM-DD-YYYY HH24:MI:SS') as "Current_Time",
To_char(TABLE2.time_stamp, 'MM-DD-YYYY HH24:MI:SS') as "Recorded_Time",
datediff(mi,time_stamp,getdate()) as DiffInTime
FROM TABLE1 TABLE1 LEFT OUTER JOIN TABLE2 TABLE2 ON TABLE1.OUT_NO = TABLE2.OUT_NO
WHERE TABLE1.OUT_NO > '12345'
will show results in minutes. Dance from there.