Convert TIMESTAMP difference column data to string format - sql

From my view, I am getting a column with value like '0 0:0:0.343009' which shows the difference between two timestamps, to millisecond precision.
I would like to show them as a string like 343 milliseconds or other value but should be a millisecond conversion.
I had similar post in the past but that time column was a DATE datatype, and this time it is a TIMESTAMP. I am using the FLOOR function to change the output to a numeric value to show a more user friendly result.
I used sample query to find a difference of dates. Here created_time is a TIMESTAMP datatype:
select msg_guid,
(max(case when payload_type = 1 then created_time end) -
(case when max(case when payload_type = 2 then created_time end) <>
trunc(max(case when payload_type = 2 then created_time end))
then max(case when payload_type = 2 then created_time end)
when max(case when payload_type = 3 then created_time end) <>
trunc(max(case when payload_type = 3 then created_time end))
then max(case when payload_type = 3 then created_time end)
end)
) as diff
from table t
group by msg_guid;

When you add or subtract timestamps, the result is an interval, not another timestamp. You can use the extract function to pull the components out of that. If you value is always going to be sub-second you can just extract the seconds, and multiply by a thousand to get the milliseconds:
with t as (
select 1 as msg_guid,
interval '0 0:0:0.343009' day to second as diff
from dual
)
select trunc(extract (second from diff) * 1000)
from t;
TRUNC(EXTRACT(SECONDFROMDIFF)*1000)
-----------------------------------
343
Here your real query would take the place of the dummy CTE I used with an interval literal.
If the interval might be more than a second then you would presumably want to get the entire value in milliseconds, so you'd need to extract all the elements and add them together, multiplying each based on what they represent - so a full day would be 86400000 milliseconds etc.; the plain elements would come out like:
column diff format a25
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
extract (day from diff) as dd,
extract (hour from diff) as hh,
extract (minute from diff) as mi,
extract (second from diff) as ss
from t;
DIFF DD HH MI SS
---------------------- ---------- ---------- ---------- ----------
0 9:13:26.150627 0 9 13 26.150627
And you'd combine them like:
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
trunc(1000 * (
extract (day from diff) * (60*60*24)
+ extract (hour from diff) * (60*60)
+ extract (minute from diff) * 60
+ extract (second from diff)
)) as milliseconds
from t;
DIFF MILLISECONDS
---------------------- ------------
0 9:13:27.650365 33207650
But based on your previous question, maybe you want it as a string, as the separate components:
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
extract (day from diff) || ' DAYS '
|| extract (hour from diff) || ' HOURS '
|| extract (minute from diff) || ' MINUTES '
|| trunc(extract (second from diff)) || ' SECONDS '
|| (trunc(extract (second from diff) * 1000)
- (trunc(extract (second from diff)) * 1000)) || ' MILLISECONDS'
as text
from t;
DIFF TEXT
---------------------- -------------------------------------------------------
0 9:43:38.896007 0 DAYS 9 HOURS 43 MINUTES 38 SECONDS 896 MILLISECONDS
SQL Fiddle based on your sample data, sort of, and with the time calculation reversed so the value is positive.

Related

Oracle SQL round up time interval to next day

How to round up time interval to next day in Oracle SQL?
select apppackage
, numtodsinterval(
sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
), 'SECOND') as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
The output of this is
'com.Freesoul.Rotter' '+2969 04:32:47.000000' '3'
and desired output is
'com.Freesoul.Rotter' '2970' '3'
but if the output of query is
'com.Freesoul.Rotter' '+2969 00:00:00.000000' '3'
then desired output is
'com.Freesoul.Rotter' '2969' '3'
column period is of INTERVAL DAY(9) TO SECOND(6) type
and i won't mind if the retention_period is changed to number datatype.
I'll be grateful if anyone can suggest change in my query to attain the desired output.
The result of your sum is in seconds, so you don't really need to convert it to an interval at all. Just divide by 60*60*24 to get the answer in days, and round it up with ceil():
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
Demo with artificial data in a CTE just to mimic your expected results, for both scenarios:
-- CTE for sample data
with retentions (apppackage, periods) as (
select 'com.Freesoul.Rotter', interval '+2967 04:32:47.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '+2967 00:00:00.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
)
-- actual query
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
-- extra clause for dummy data
or apppackage = 'com.Freesoul.XYZ'
group by apppackage;
APPPACKAGE RETENTION_PERIOD USERS
------------------- ---------------- ----------
com.Freesoul.XYZ 2969 3
com.Freesoul.Rotter 2970 3
Your expected output shows a plain number. If you actually want it as an interval, but as the whole number of days, just pass ceil'd number into numtodsinterval or more simply (and usually faster for some reason) multiply by interval '1' day.
With the same dummy data:
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) * interval '1' day as retention_period
, count(apppackage) as users
...
APPPACKAGE RETENTION_PERIOD USERS
------------------- --------------------- ----------
com.Freesoul.XYZ +2969 00:00:00.000000 3
com.Freesoul.Rotter +2970 00:00:00.000000 3
As #mathguy pointed out, you probably don't need or want the trunc() call in there; that is removing the fractional seconds from each period before they are summed, which sounds insignificant but could easily affect the result you get.
How about this?
CASE WHEN numtodsinterval(extract (day from periods), 'DAY') = periods THEN
extract (day from periods)
ELSE
extract (day from periods) + 1
END

Convert time in the form mm:ss into seconds in Postgres

Using Postgres 9.3.5
My table has duration data in the as a text field stored as minutes:seconds,
i.e 4:52
I want the value as total seconds (i.e 4 x 60) + 52 = 292
But table also contains values such as
3:34:21 (3 hours, 34 minutes and 21 seconds)
21 (21 seconds)
How can I write SQL to correct calculate duration in seconds for all these cases.
Update
select (case when duration like '%:%:%'
then extract(epoch from duration::time)
else extract(epoch from ('00:' || duration) ::time)
end
)as seconds
from discogs.track t1
;
So I have this but unfortunately some values are not quite valid causing it to fail
00:70:01
How do I either ignore such values or convert them (i.e 70 x 60 + 1)
http://sqlfiddle.com/#!17/9eecb/19379
I expand %:% text to %:%:% texts and then simply split on delimiter ';'. So I am getting integer values which can be used in calculations.
SELECT
orig_duration,
hours_in_seconds + minutes_in_seconds + seconds as seconds
FROM (
SELECT
orig_duration,
(split_part(duration, ':', 1))::int * 60 * 60 as hours_in_seconds,
(split_part(duration, ':', 2))::int * 60 as minutes_in_seconds,
(split_part(duration, ':', 3))::int as seconds
FROM (
SELECT
duration as orig_duration,
case when duration like '%:%:%' then duration else '00:' || duration end as duration
FROM (
SELECT unnest(ARRAY['70:01','3:34:21','4:52']::text[]) as duration
)s
)s
)s
Result:
orig_duration seconds
------------- -------
70:01 4201
3:34:21 12861
4:52 292
You can convert the value to time and extract the time component. You do need to take the variable format into account:
select (case when t like '%:%:%'
then extract(epoch from t::time)
else extract(epoch from ('00:' || t) ::time)
end) as seconds
You added a new format. You can do:
select (case when t like '%:%:%'
then extract(epoch from t::time)
when t like '%:%'
then extract(epoch from ('00:' || t) ::time)
else t::int
end) as seconds

How to convert milliseconds to Time(hh:mm:ss) in Oracle

I would like to convert in PL/SQL miliseconds to Time(hh:mm:ss)
21649000 to 06:00:49
or
83293000 to 23:08:13
Use NUMTODSINTERVAL with SECOND option :
select NUMTODSINTERVAL( 83293000 / 1000, 'SECOND' ) "Time" from dual t;
Time
-------------------
+000000000 23:08:13
This is a quite self-explanatory way:
select val,
floor( val / 1000 / 60 / 60 ) as hours,
floor( mod(val / 1000 / 60 , 60) ) as minutes,
floor( mod(val / 1000 , 60) ) as seconds
from (
select 21649000 val from dual union
select 83293000 val from dual
)
VAL HOURS MINUTES SECONDS
---------- ---------- ---------- ----------
21649000 6 0 49
83293000 23 8 13
This does not handle days, so the number of milliseconds must be less than 24 hours.
This gives numbers, you can edit it the way you need to get your desired output format/type.
If you only want second precision you could divide the number by 1000 to get seconds, and by 86400 to get a fraction of a day, then add that to midnight on any nominal date - and convert the result to a string:
select to_char(date '1970-01-01' + (21649000/86400000), 'HH24:MI:SS') as time
from dual;
TIME
--------
06:00:49
select to_char(date '1970-01-01' + (83293000/86400000), 'HH24:MI:SS') as time
from dual;
TIME
--------
23:08:13
This only works properly for values less than a day, i.e. where your original number is less than 86400000; higher than that and you only see the leftover in the second day.
The below query worked for me to convert to the Milliseconds to the HH:MM:SS
SELECT
TO_CHAR(TRUNC([ColumnName]/3600000),'FM9900') || ':' ||
TO_CHAR(TRUNC(MOD([ColumnName],3600000)/60000),'FM00') || ':' ||
TO_CHAR( trunc(MOD([ColumnName],60000)/1000),'FM00') FROM [TableName]`

Display values for specific time-frame

when I run below select:
select name, time, value from table1 where name like '%Z' or '%V'
I got result:
I need to do two things:
1) query will be run every hour, so if we have 12.00.00PM range should be between (11 and 12> PM, if it's 02.00.00AM range will be (01 and 02> AM
2) second thing is to display avg(value) for name ending by '%Z' or '%V' into one row, but into two columns
Below is example of desirable result, when query was run at 12.00.00 PM:
You are asking for three things, which can be thought of as three steps. Getting the time window is fairly straightforward, and only slightly complicated by your column being a timestamp rather than a date. You implied this will run on the hour, but it's possible it will be slightly afterwards - maybe a second or two? - so it's probably safer to take that into account. You can use the trunc() function to modify a date value to the required precision, so to only look at the current hour you would truncate to HH[24]. You can then cast that back to a timestamp. And you can use interval arithmetic to find the hour before that:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF3';
alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS.FF3 TZR';
select systimestamp,
trunc(systimestamp, 'HH24') as a,
cast(trunc(systimestamp, 'HH24') as timestamp) as b,
cast(trunc(systimestamp, 'HH24') as timestamp) - interval '1' hour as c
from dual;
SYSTIMESTAMP A B C
------------------------------ ------------------- ----------------------- -----------------------
2017-03-01 09:25:39.342 +00:00 2017-03-01 09:00:00 2017-03-01 09:00:00.000 2017-03-01 08:00:00.000
The alter session commands are just to control how the different data types are displayed, for comparison. (Don't rely on NLS settings in your real code; use to_char() for final formatting of datetime values as strings).
Notice that the result of the truncation is now a date (A in that output), so I've cast it back to a timestamp (B). The range you want is essentially time >= A and time < B. And you could use sysdate instead of systimestamp as the input to trunc().
For your sample data using systimestamp or sysdate isn't going to find anything, so I'll use a fake fixed time for the rest, generated in a CTE for separation. Where I've used now from the CTE, you would use systimestamp or sysdate.
The second part is to get the average for each name within that time period. That's simple aggregation:
with fake_time(now) as (
select timestamp '2017-02-10 13:01:07' from dual
)
select name,
avg(value) as avg_value,
cast(trunc(now, 'HH24') as timestamp) as time
from fake_time
join table1 on time >= cast(trunc(now, 'HH24') as timestamp) - interval '1' hour
and time < cast(trunc(now, 'HH24') as timestamp)
group by name, now;
NAME AVG_VALUE TIME
------- ---------- -----------------------
QWER1_Z 20 2017-02-10 13:00:00.000
QWER1_V 35 2017-02-10 13:00:00.000
TEST1_Z 15 2017-02-10 13:00:00.000
TEST1_V 10 2017-02-10 13:00:00.000
To pick up the rows you want I've made the fake time 13:00 instead of 12:00. The average you showed for TEST1_V was also wrong.
The next stage it pivoting those into the format you wanted, as a single row. For that you can add the root (i.e. TEST1 or QWER1) and the letter (Z or V) as extra columns in the result set, and then use that as the subquery for the pivot operation - this requires 11g or higher:
with fake_time(now) as (
select timestamp '2017-02-10 13:01:07' from dual
)
select z_name, z_value, v_name, v_value, time
from (
select substr(name, 1, length(name) - 2) as root,
substr(name, -1) as zv,
name,
avg(value) as avg_value,
cast(trunc(now, 'HH24') as timestamp) as time
from fake_time
join table1 on time >= cast(trunc(now, 'HH24') as timestamp) - interval '1' hour
and time < cast(trunc(now, 'HH24') as timestamp)
group by substr(name, 1, length(name) - 2), name, now
)
pivot (max(name) as name, max(avg_value) as value for (zv) in ('Z' as z, 'V' as v));
Z_NAME Z_VALUE V_NAME V_VALUE TIME
------- ---------- ------- ---------- -----------------------
TEST1_Z 15 TEST1_V 10 2017-02-10 13:00:00.000
QWER1_Z 20 QWER1_V 35 2017-02-10 13:00:00.000
There may be another step required; in your sample output you included a list of the original values that were averaged, but didn't confirm if you actually wanted those or if they were just to show how the average was caculated to help us understand what you needed to do. If you really do want to include that you can use listagg() and concatenation to build up the 'average' string before pivoting:
'avg(' || listagg(value, ',') within group (order by value) || ') = ' || avg(value)
as avg_value,
to get
Z_NAME Z_VALUE V_NAME V_VALUE TIME
------- -------------------- ------- -------------------- -----------------------
TEST1_Z avg(10,20) = 15 TEST1_V avg(10) = 10 2017-02-10 13:00:00.000
QWER1_Z avg(20) = 20 QWER1_V avg(30,40) = 35 2017-02-10 13:00:00.000
As I said earlier, I've only used the fake_date CTE to get a date that matches your sample data. Your real query will be more like:
select z_name, z_value, v_name, v_value, time
from (
select substr(name, 1, length(name) - 2) as root,
substr(name, -1) as zv,
name,
avg(value) as avg_value,
cast(trunc(sysdate, 'HH24') as timestamp) as time
from table1
where time >= cast(trunc(sysdate, 'HH24') as timestamp) - interval '1' hour
and time < cast(trunc(sysdate, 'HH24') as timestamp)
group by substr(name, 1, length(name) - 2), name
)
pivot (max(name) as name, max(avg_value) as value for (zv) in ('Z' as z, 'V' as v));
select T1.Z, T1.V from (
(select avg(values) from table1 where name like '%Z' and time between sysdate and sysdate - interval '1' group by INSTR(name,'Z')) Z,
(select avg(values) from table1 where name like '%V' and time between sysdate and sysdate - interval '1' group by INSTR(name,'V')) V ) T1

How to filter query with EXTRACT statement oracle

I have this query where I get the difference between the SYSDATE and some date column. I need to add another filter to this query to filter the records where DAY = 0. is it possible?
SELECT REQUEST_ID,MIG_STATUS,
EXTRACT(Day FROM( sysdate - START_DATE ) DAY TO SECOND) as Day,
EXTRACT(HOUR FROM( sysdate - START_DATE) DAY TO SECOND) as Hour,
EXTRACT(Minute FROM(sysdate - START_DATE) DAY TO SECOND) as Minute,
EXTRACT(SECOND FROM(sysdate - START_DATE) DAY TO SECOND) as Second
FROM NET_MIG
results:
T1_ID DAY HOUR MINUTE SECOND
1 2,817 12 12 8
2 2,817 8 26 32
3 0 1 0 0
3 1 8 26 32
3 0 13 0 0
3 0 0 59 0
3 0 0 59 0
need to add filter
where Day = 0
is this the correct approach?
just to be more clear, as a result I need to get the records where the
difference between the dates is less than 1 day.
You can use:
SELECT REQUEST_ID,MIG_STATUS
-- rest of columns
FROM NET_MIG
WHERE START_DATE >= (SYSDATE - 1);
If you want records from last 5 minutes just use:
SELECT REQUEST_ID,MIG_STATUS
-- rest of columns
FROM NET_MIG
WHERE START_DATE >= (SYSDATE - 5 * 1/(24 * 60));
The same for 1 hour:
WHERE START_DATE >= (SYSDATE - 1/24);
EDIT:
As #a-horse-with-no-name in comment you can use INTERVAL:
WHERE START_DATE >= (SYSDATE - INTERVAL '5' MINUTE)
for better readability.
And #kordirko comment:
This solution is SARG-able. It means it will use index on START_DATE if exists any, where EXTRACT(Day FROM( sysdate - START_DATE ) DAY TO SECOND) = 0 will skip index on that column and enforce full table scan.
SELECT * FROM
(SELECT REQUEST_ID,MIG_STATUS,
EXTRACT(Day FROM( sysdate - START_DATE ) DAY TO SECOND) as Day,
EXTRACT(HOUR FROM( sysdate - START_DATE) DAY TO SECOND) as Hour,
EXTRACT(Minute FROM(sysdate - START_DATE) DAY TO SECOND) as Minute,
EXTRACT(SECOND FROM(sysdate - START_DATE) DAY TO SECOND) as Second
FROM NET_MIG)
WHERE Day=0
I'm not sure this is the best approach, since you should be using direct date comparisons as much as possible, but this is one way you can re-use your custom fields in a where clause.