I have a HIVE table of orders from different locations. This table contains a string column containing the date and time of each order. There is also another column with the corresponding timezone.For some reason, however, the value in the timezone column is not the local one. Then for each date, I have the corresponding timezone and a target timezone to convert. Like this:
with orders as (
select 1 as order_id, '2022-07-20 15:04:12' as create_time, 'UTC' as create_tz, 'BRT' as target_tz
union all
select 2 as order_id, '2022-07-20 17:34:14' as create_time, 'CET' as create_tz, 'PST' as target_tz
) select * from orders;
order_id
create_time
create_tz
target_tz
1
2022-07-20 15:04:12
UTC
BRT
2
2022-07-20 17:34:14
CET
PST
I need to print a string column with the local date and time (and also make the timezone explicit). Like this:
order_id
local_time
1
2022-07-20 12:04:12 BRT
2
2022-07-20 08:34:14 PST
My approach was to convert everything to UTC and then print it using the function from_unixtime, however it seems that this function always print the result in the client's time (in my case CST). I didn't find a way to set a target timezone for each record.
with orders as (
select 1 as order_id, '2022-07-20 15:04:12' as create_time, 'UTC' as create_tz, 'BRT' as target_tz
union all
select 2 as order_id, '2022-07-20 17:34:14' as create_time, 'CET' as create_tz, 'PST' as target_tz
) select
order_id,
from_unixtime(unix_timestamp(concat(create_time, ' ', create_tz), 'yyyy-MM-dd HH:mm:ss z'), 'yyyy-MM-dd HH:mm:ss z')
from orders;
It is not an elegant workaround, but it seems to work:
with orders as (
select 1 as order_id, '2022-07-20 15:04:12' as create_time, 'UTC' as create_tz, 'BRT' as target_tz
union all
select 2 as order_id, '2022-07-20 17:34:14' as create_time, 'CET' as create_tz, 'PST' as target_tz
) select
order_id,
concat(from_unixtime(
unix_timestamp(create_time)
+ unix_timestamp(concat('1970-01-01 00:00:00', ' ', create_tz), 'yyyy-MM-dd HH:mm:ss z')
- unix_timestamp(concat('1970-01-01 00:00:00', ' ', target_tz), 'yyyy-MM-dd HH:mm:ss z')
), ' ', target_tz)
from orders;
Related
If I run following query I will have duplicates for 2021-03-28 04:00:00 . How can I avoid these duplicates?
SELECT distinct(ts) as date FROM ( --(ts, '%Y-%m-%d %H:00:00') TO_CHAR(ts,'YYYY-MM-DD HH24:00:00')
SELECT '2020-10-21'::TIMESTAMP AT TIME ZONE 'UTC' AS tm
UNION
SELECT '2021-03-29'::TIMESTAMP AT TIME ZONE 'UTC' AS tm
) AS t TIMESERIES ts AS '1 Hour' OVER (ORDER BY tm)
I don't get any duplicates, otherwise this query would return some rows:
SELECT ts,count(*) as occ FROM (
SELECT ts FROM (
SELECT '2020-10-21'::TIMESTAMP AT TIME ZONE 'UTC' AS tm
UNION
SELECT '2021-03-29'::TIMESTAMP AT TIME ZONE 'UTC' AS tm
) AS t
TIMESERIES ts AS '1 Hour' OVER (ORDER BY tm)
) b
GROUP BY 1 HAVING COUNT(*) > 1;
-- out ts | occ
-- out ----+-----
-- out (0 rows)
What is different in your query?
Given:
INSERT INTO EP_ACCESS (PROFILE_ID, EPISODE_ID, START_TIMESTAMP, DISCONNECT_TIMESTAMP)
VALUES ('1', '1', TO_DATE('2020-01-01 00:00:01','yyyy/mm/dd hh24:mi:ss'), TO_DATE('2020-01-01 00:00:02','yyyy/mm/dd hh24:mi:ss'));
How can I select those who start_timestamp is in 2020?
You would use:
where start_timestamp >= date '2020-01-01' and
start_timestamp < date '2021-01-01'
Of course, you can use a timestamp literal if you prefer typing longer strings.
There are several options.
1 - Use BETWEEN
SELECT *
FROM EP_ACCESS
WHERE START_TIMESTAMP BETWEEN TO_DATE('2020-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND TO_DATE('2020-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
or
SELECT *
FROM EP_ACCESS
WHERE START_TIMESTAMP BETWEEN DATE '2020-01-01'
AND DATE '2021-01-01' - INTERVAL '1' SECOND
2 - Use EXTRACT
SELECT *
FROM EP_ACCESS
WHERE EXTRACT(YEAR FROM START_TIMESTAMP) = 2020
3 - Use TRUNC
SELECT *
FROM EP_ACCESS
WHERE TRUNC(START_TIMESTAMP, 'YYYY') = DATE '2020-01-01'
Of these options, BETWEEN will probably provide the best performance as the other two require executing a function against the START_TIMESTAMP field in every row in the table.
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
I'm trying to work out the difference in minutes between 2 timestamps but im getting error stating 'invalid number' any ideas why this might be?
The code im trying to run is as follows:
TO_CHAR(FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London') AT TIME ZONE 'America/New_York', 'DD-MON-YYYY HH24:MI')
- TO_CHAR(min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AT TIME ZONE 'America/New_York', 'DD-MON-YYYY HH24:MI') * 24 * 60) ||'-mins' as difference
When I split the code and run these independently the output is as follows:
TO_CHAR(FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London') AT TIME ZONE 'America/New_York', 'DD-MON-YYYY HH24:MI') as created_time
30-OCT-2016 21:08:34
TO_CHAR(min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AT TIME ZONE 'America/New_York', 'DD-MON-YYYY HH24:MI') as Flight_arrival_Time
30/10/2016 21:06:34
What I'm hoping to do it subtract the difference in minutes between the 'created time' column and the Flight_arrival_time column
Any assistance will be greatly appreciated.
As already noted you cannot substract a string from a string. Try
FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London') AT TIME ZONE 'America/New_York'
- min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AT TIME ZONE 'America/New_York'
This returns an INTERVAL DAY TO SECOND Data Type
Demo with sample data in CTEs, cross-joined for brevity:
alter session set time_zone = 'America/New_York';
with k (car_id) as (select 1 from dual),
m (fll_id, act_onblk_datt_bu) as (select 1, timestamp '2016-10-30 21:06:34' from dual),
q (created_date) as (select timestamp '2016-10-31 01:08:34' from dual)
select FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London') AT TIME ZONE 'America/New_York'
- min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AT TIME ZONE 'America/New_York'
from m, k, q;
FROM_TZ(MIN(Q.CREAT
-------------------
+00 00:02:00.000000
In order to get the Minutes use EXTRACT(datetime)
Another note, in order to calculate timestamp difference you don't have to convert them into a common time zone. Oracle does it properly across different time zones.
Some update
You use EXTRACT like this:
WITH t AS (
SELECT
FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London') AT TIME ZONE 'America/New_York'
- min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AT TIME ZONE 'America/New_York' AS difference
FROM q, k, m)
SELECT 60*EXTRACT(HOUR FROM difference) + EXTRACT(MINUTE FROM difference)
FROM t;
The CTE i.e. the WITH t AS clause is just for better visibility. EXTRACT(MINUTE FROM ...) extracts only the minute part of the interval, not the total minutes over all. You mention column "Flight_arrival_time", so I assume you have to consider also hours for difference. Perhaps you have to add even 24*60*EXTRACT(DAY FROM difference).
As I mention you don't have to convert time zone for timestamp intervals. Provided data type of column act_onblk_datt_bu is TIMESTAMP WITH TIME ZONE or TIMESTAMP WITH LOCAL TIME ZONE you can even simplify it to
WITH t AS (
SELECT
FROM_TZ(min(q.created_date) over (partition by k.car_id,m.fll_id), 'Europe/London')
- min(m.act_onblk_datt_bu) over (partition by k.car_id, m.fll_id) AS difference
FROM q, k, m)
SELECT 60*EXTRACT(HOUR FROM difference) + EXTRACT(MINUTE FROM difference)
FROM t;
Well Alex Poole already provide a method to put the date on the same format. But I was trying to understand what was happenning and got this sample for you.
SQL DEMO
Here source is the result of your query, using TO_DATE convert both string to date so are in same format, and then calculate the difference.
ALTER SESSION SET NLS_LANGUAGE="American"
\\
WITH source as (
SELECT '30-OCT-2016 21:08:34' as A, '30/10/2016 21:06:34' as B
FROM Dual
)
SELECT TO_DATE(A, 'DD-MON-YYYY HH24:MI:SS') as new_a,
TO_DATE(B, 'DD/MM/YYYY HH24:MI:SS') as new_b,
( TO_DATE(A, 'DD-MON-YYYY HH24:MI:SS')
- TO_DATE(B, 'DD/MM/YYYY HH24:MI:SS')
) * 24 * 60 as result
FROM source
OUTPUT
I have DB with timezone +04:00 (Europe/Moscow) and need to convert a string in format YYYY-MM-DD"T"HH24:MI:SSTZH:TZM to DATE data type in Oracle 11g.
In other words, I have a string 2013-11-08T10:11:31+02:00 and I want to convert it to DATE data type (in local DB timezone +04:00 (Europe/Moscow)).
For string 2013-11-08T10:11:31+02:00 my desired transformation should return DATE data type with date 2013-11-08 12:11:31 (i.e. with local timezone transformation of time to +04:00 (Europe/Moscow)). Timezone of string may be different and +02:00 in string above is just example.
I tried to do this with TIMESTAMP data type, but no success with time zone transformation.
to_timestamp_tz() function with at time zone clause can be used to convert your string literal to a value of timestamp with time zone data type:
SQL> with t1(tm) as(
2 select '2013-11-08T10:11:31+02:00' from dual
3 )
4 select to_timestamp_tz(tm, 'yyyy-mm-dd"T"hh24:mi:ss TZH:TZM')
5 at time zone '+4:00' as this_way
6 , to_timestamp_tz(tm, 'yyyy-mm-dd"T"hh24:mi:ss TZH:TZM')
7 at time zone 'Europe/Moscow' as or_this_way
8 from t1
9 /
Result:
THIS_WAY OR_THIS_WAY
----------------------------------------------------------------------------
2013-11-08 12.11.31 PM +04:00 2013-11-08 12.11.31 PM EUROPE/MOSCOW
And then, we use cast() function to produce a value of date data type:
with t1(tm) as(
select '2013-11-08T10:11:31+02:00' from dual
)
select cast(to_timestamp_tz(tm, 'yyyy-mm-dd"T"hh24:mi:ss TZH:TZM')
at time zone '+4:00' as date) as this_way
, cast(to_timestamp_tz(tm, 'yyyy-mm-dd"T"hh24:mi:ss TZH:TZM')
at time zone 'Europe/Moscow' as date) as or_this_way
from t1
This_Way Or_This_Way
------------------------------------------
2013-11-08 12:11:31 2013-11-08 12:11:31
Find out more about at time zone clause and to_timestamp_tz() function.
SELECT
CAST((FROM_TZ(CAST(timezonefield AS TIMESTAMP),'GMT') AT TIME ZONE 'CET') AS DATE)
FROM table;
Converts a timestamp in GMT to date in Central European time
if you want your timestamp with timezone to convert to a date in sync with "sysdate" then use the following:
select cast(to_timestamp_tz('2013-11-08T10:11:31-02:00'
,'yyyy-mm-dd"T"hh24:mi:sstzh:tzm'
)
at time zone to_char(systimestamp
,'tzh:tzm'
)
as date
)
from dual
to cast time stamp to date :
cast(registrationmaster.Stamp5DateTime as date) >= '05-05-2018' AND
cast(registrationmaster.Stamp5DateTime as date) <= '05-05-2018'
you can manage timezones with CAST(x AT TIME ZONE 'YYYY' AS DATE), this helps me:
WITH t1 (tm) AS (SELECT TIMESTAMP '2021-12-14 15:33:00 EET' FROM DUAL)
SELECT 'EET' tz, CAST (tm AT TIME ZONE 'Europe/Kaliningrad' AS DATE) AS datetime FROM t1
union SELECT 'MSK' tz, CAST (tm AT TIME ZONE 'Europe/Moscow' AS DATE) AS datetime FROM t1
union SELECT 'CET' tz, CAST (tm AT TIME ZONE 'Europe/Prague' AS DATE) AS datetime FROM t1
union SELECT 'UTC' tz, CAST (tm AT TIME ZONE 'UTC' AS DATE) AS datetime FROM t1