I have a table which contains timestamps (called triggers) per business day (CTRL_DT). I need to process data for a given business date by selecting rows between previous days trigger timestamp and current days trigger. By using LEAD(), I was able to get below output. This only works if there are triggers every day.
Let's say, one trigger is missed. How do I rewrite the same query without much complexity but get the expected output.
Input Data: (if you observe, ctrl_dt 2023-02-16 is missed).
CAPTURE_DT
CTRL_DT
INST
2023-02-17 19:21:30.612814
2023-02-18
AAA
2023-02-16 19:18:16.045126
2023-02-17
AAA
2023-02-14 18:58:40.927273
2023-02-15
AAA
2023-02-13 21:43:38.832417
2023-02-14
AAA
2023-02-12 18:30:40.595363
2023-02-13
AAA
Expected Output:
STARTTIME
ENDTIME
BS_DATE
2023-02-16 19:18:16.045126
2023-02-17 19:21:30.612814
2023-02-17
2023-02-16 19:18:16.045126
2023-02-16
2023-02-14 18:58:40.927273
2023-02-15
2023-02-13 21:43:38.832417
2023-02-14 18:58:40.927273
2023-02-14
2023-02-12 18:30:40.595363
2023-02-13 21:43:38.832417
2023-02-13
2023-02-12 18:30:40.595363
2023-02-12
However, I'm getting
STARTTIME
ENDTIME
BS_DATE
2023-02-16 19:18:16.045126
2023-02-17 19:21:30.612814
2023-02-17
2023-02-14 18:58:40.927273
2023-02-16 19:18:16.045126
2023-02-16
2023-02-13 21:43:38.832417
2023-02-14 18:58:40.927273
2023-02-14
2023-02-12 18:30:40.595363
2023-02-13 21:43:38.832417
2023-02-13
2023-02-12 18:30:40.595363
2023-02-12
Query Used:
WITH
EVENT_TRIGGER
AS
(SELECT '2023-02-17 19:21:30.612814' CAPTURE_DT, '2023-02-18' CTRL_DT, 'AAA' INST FROM DUAL
UNION ALL
SELECT '2023-02-16 19:18:16.045126' CAPTURE_DT, '2023-02-17' CTRL_DT, 'AAA' INST FROM DUAL
UNION ALL
SELECT '2023-02-14 18:58:40.927273' CAPTURE_DT, '2023-02-15' CTRL_DT, 'AAA' INST FROM DUAL
UNION ALL
SELECT '2023-02-13 21:43:38.832417' CAPTURE_DT, '2023-02-14' CTRL_DT, 'AAA' INST FROM DUAL
UNION ALL
SELECT '2023-02-12 18:30:40.595363' CAPTURE_DT, '2023-02-13' CTRL_DT, 'AAA' INST FROM DUAL)
SELECT LEAD (CAPTURE_DT) OVER (PARTITION BY INST ORDER BY CTRL_DT DESC) AS STARTTIME,
CAPTURE_DT AS ENDTIME,
TO_DATE (CTRL_DT, 'YYYY-MM-DD') - 1 AS BS_DATE
FROM EVENT_TRIGGER
WHERE INST = 'AAA';
You can generate a calendar and then use a PARTITIONed OUTER JOIN:
WITH EVENT_TRIGGER (CAPTURE_DT, CTRL_DT, INST ) AS (
SELECT TIMESTAMP '2023-02-17 19:21:30.612814', DATE '2023-02-18', 'AAA' FROM DUAL UNION ALL
SELECT TIMESTAMP '2023-02-16 19:18:16.045126', DATE '2023-02-17', 'AAA' FROM DUAL UNION ALL
SELECT TIMESTAMP '2023-02-14 18:58:40.927273', DATE '2023-02-15', 'AAA' FROM DUAL UNION ALL
SELECT TIMESTAMP '2023-02-13 21:43:38.832417', DATE '2023-02-14', 'AAA' FROM DUAL UNION ALL
SELECT TIMESTAMP '2023-02-12 18:30:40.595363', DATE '2023-02-13', 'AAA' FROM DUAL
),
calendar (day) AS (
SELECT min_dt + LEVEL - 1 AS day
FROM (
SELECT MIN(TRUNC(capture_dt)) AS min_dt,
MAX(TRUNC(capture_dt)) AS max_dt
FROM event_trigger
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
)
SELECT LAG(e.capture_dt) OVER (PARTITION BY e.inst ORDER BY c.day)
AS STARTTIME,
e.CAPTURE_DT AS ENDTIME,
c.day AS BS_DATE
FROM calendar c
LEFT OUTER JOIN EVENT_TRIGGER e
PARTITION BY (e.inst)
ON (
c.day <= e.capture_dt
AND e.capture_dt < c.day + 1
)
WHERE e.inst = 'AAA'
ORDER BY c.day DESC;
Which, outputs:
STARTTIME
ENDTIME
BS_DATE
2023-02-16 19:18:16.045126000
2023-02-17 19:21:30.612814000
2023-02-17 00:00:00
null
2023-02-16 19:18:16.045126000
2023-02-16 00:00:00
2023-02-14 18:58:40.927273000
null
2023-02-15 00:00:00
2023-02-13 21:43:38.832417000
2023-02-14 18:58:40.927273000
2023-02-14 00:00:00
2023-02-12 18:30:40.595363000
2023-02-13 21:43:38.832417000
2023-02-13 00:00:00
null
2023-02-12 18:30:40.595363000
2023-02-12 00:00:00
fiddle
I have a table that has a bunch of records that are updated frequently, and I am trying to write a code to pull only the latest record. In an ideal situation, I would use the updated_date column to pull the latest record, however, in many situations, the updated-date column is blank and so, I have to create a new column using the following code:
CASE WHEN UPDATED_DATE IS NULL THEN CREATED_DATE ELSE UPDATED_DATE END latest_date
Basically I made a new column for when updated_date is blank, use the created_date and now, my data looks like this:
updated_date
audit_id
created_date
latest_date
2021-04-02
006
2018-06-06
2021-04-02
NULL
006
2018-06-06
2018-06-06
2020-03-01
006
2018-06-06
2020-03-01
NULL
007
2018-07-07
2018-07-07
2020-04-01
007
2018-07-07
2020-04-01
2019-09-08
007
2018-07-07
2019-09-08
What I would like to retrieve is the latest info only:
updated_date
audit_id
created_date
latest_date
2021-04-02
006
2018-06-06
2021-04-02
2020-04-01
007
2018-07-07
2020-04-01
If someone could please let me know how this data can be retried, that would be great.
thank you
I assume your intention is to get the most recent row for each audit_id. If so, you can do something like
select *
from (select t.*,
row_number() over (partition by audit_id
order by coalesce( updated_date, created_date ) desc ) rn
from your_table t)
where rn = 1;
If you can have two rows with the same audit_id and latest_date, you could use the rank or dense_rank function rather than row_number to handle ties differently but I'm guessing that is not an issue you'll need to deal with and arbitrarily breaking the tie with row_number is sufficient.
You can use COALESCE and FETCH FIRST n ROWS ONLY (Oracle 12):
SELECT *
FROM table_name
ORDER BY COALESCE(UPDATED_DATE, CREATED_DATE) DESC
FETCH FIRST 2 ROWS ONLY;
According to the Oracle docs, TIMESTAMP WITH LOCAL TIMEZONE behaves as follows.
Data stored in the database is normalized to the database time zone, and the time zone information is not stored as part of the column data.
When a user retrieves the data, Oracle returns it in the user's local session time zone.
I have a question regarding the 2nd point i.e. retrieval.
That is, does this conversion to user's timezone happen by database itself or is the database client/driver responsible for doing that?
eg: Say I'm using Oracle JDBC driver. Does the driver convert the time value to user's timezone or is the database supposed to return the time value in the user's time zone? If the database is supposed to do the conversion the client should have provided an indication of his time zone to the database, shouldn't it?
The database does it. If you store the same nominal moment in time in timestamp, timestamp with time zone and timestamp with local time zone columns:
create table t42 (id number,
ts timestamp,
tstz timestamp with time zone,
tsltz timestamp with local time zone
);
insert into t42 (id, ts, tstz, tsltz)
values (1,
timestamp '2019-08-13 07:13:20 UTC',
timestamp '2019-08-13 07:13:20 UTC',
timestamp '2019-08-13 07:13:20 UTC'
);
insert into t42 (id, ts, tstz, tsltz)
values (2,
timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
timestamp '2019-08-13 12:34:56.789 Australia/Sydney'
);
alter session set time_zone = 'Europe/London';
select id, ts, tstz, tsltz from t42 order by id;
ID TS TSTZ TSLTZ
-- ----------------------- ---------------------------------------- -----------------------
1 2019-08-13 07:13:20.000 2019-08-13 07:13:20.000 UTC 2019-08-13 03:13:20.000
2 2019-08-13 12:34:56.789 2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY 2019-08-12 22:34:56.789
... you can use dump() to see how they are actually stored internally:
select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;
ID COL VALUE DUMPED
-- ----- ---------------------------------------- -------------------------------------------------------
1 TS 2019-08-13 07:13:20.000 Typ=180 Len=7: 120,119,8,13,8,14,21
1 TSTZ 2019-08-13 07:13:20.000 UTC Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4
1 TSLTZ 2019-08-13 08:13:20.000 EUROPE/LONDON Typ=231 Len=7: 120,119,8,13,8,14,21
2 TS 2019-08-13 12:34:56.789 Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64
2 TSTZ 2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
2 TSLTZ 2019-08-13 03:34:56.789 EUROPE/LONDON Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64
For the originally-UTC value, you can see that the TS and TSLTZ values are stored with exactly the same bytes, but a different type code; while the TSTZ is a third type, with the first 7 bytes the same (the stored date/time - see MoS Doc ID 69028.1, or here - those bytes are the same as type-12 dates) plus additional bytes for the time zone information (and zeros for fraction seconds here). The date/time bytes are all the same because they are all UTC. In my database, anyway, as that is my DBTIMEZONE:
select dbtimezone, sessiontimezone from dual;
DBTIME SESSIONTIMEZONE
------ ---------------------------------------------------------------------------
+00:00 Europe/London
For the originally-Sydney value the TS value is the local time, so the hour byte is 13 (12+1); for TSTZ and TSLTZ the date/time bytes are still UTC, so those have 3 (2+1); TSTZ also has the time zone information. TSLTZ has no time zone info because type 231 is always UTC. When it's converted to a string the session time zone is applied, so the stored hour byte 3 (2+1) becomes local hour 3.
In a different session time zone you see mostly the same:
alter session set time_zone = 'America/New_York';
select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;
ID COL VALUE DUMPED
-- ----- ---------------------------------------- -------------------------------------------------------
1 TS 2019-08-13 07:13:20.000 Typ=180 Len=7: 120,119,8,13,8,14,21
1 TSTZ 2019-08-13 07:13:20.000 UTC Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4
1 TSLTZ 2019-08-13 03:13:20.000 AMERICA/NEW_YORK Typ=231 Len=7: 120,119,8,13,8,14,21
2 TS 2019-08-13 12:34:56.789 Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64
2 TSTZ 2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
2 TSLTZ 2019-08-12 22:34:56.789 AMERICA/NEW_YORK Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64
The stored bytes are exactly the same, of course; but now the TSLTZ values are adjusted to New York time, so for Sydney the hour byte 3 (2+1) becomes local hour 22, and on a different date.
(Here the TSLTZ is being shown with the session time zone because of the way I formatted it; as you can see in the first query it doesn't actually have a time zone, so there is a further implicit conversion being done to enable that session value to be displayed).
You see the same values represented if you let the client format the underlying internal byte structure, as I did in the first query above. But using to_char() answers the main part of the your question; the database must be doing it as it is happening before the value is converted from its internal format to a string. If the client (or JDBC) as doing that adjustment to a local time then to_char() would only see the stored DBTIMEZONE-based bytes, and would always give you the DBTIMEZONE equivalent of the time.
Your client - or JDBC - is telling the database which local time zone to use. I've been overriding it with alter session, and you can do the same through JDBC. By default it's based on your Java locale and other application settings; but you can override it.
I am trying to convert the following Teradata SQL to Oracle. and the problem is that I am not able to find the equivalent time zone value in Oracle. In the below example, 'Europe Central' is not recognized in Oracle.
select hi.create_date
, to_char( hi.create_date,'YYYY-MM-DD HH24:MI:SS')
, ((CAST(to_char( hi.create_date,'YYYY-MM-DD HH24:MI:SS')||'+00:00' AS TIMESTAMP(0))) at time zone 'Europe Central')
from historical_info hi
This below code in Oracle throws an error:
SELECT create_date,
CAST( create_date
AS TIMESTAMP WITH TIME ZONE
) AT TIME ZONE 'Europe Central'
TZ_LOSANG
FROM historical_info
ORA-01878: specified field not found in datetime or interval.
01878. 00000 - "specified field not found in datetime or interval"
*Cause: The specified field was not found in the datetime or interval.
*Action: Make sure that the specified field is in the datetime or interval.
Can you please help me convert the Teradata time zones to Oracle recognized time zones.
You can get a list of supported time zones, as shown in the documentaion:
You can obtain a list of time zone names and time zone abbreviations from the time zone file that is installed with your database by entering the following statement:
SELECT TZNAME, TZABBREV
FROM V$TIMEZONE_NAMES
ORDER BY TZNAME, TZABBREV;
There isn't a simple built-in way to convert the Teradata name to an Oracle name, so you'll need to pick a suitable equivalent for each zone/region you have to deal with.
Oracle doesn't have a 'Europe/Central' time zone name, but does recognise the CET abbreviation, or any of the names which map to that abbreviation:
alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS TZR';
with historical_info(create_date) as (select sysdate from dual)
SELECT create_date,
CAST(create_date AS TIMESTAMP) AT TIME ZONE 'CET' TZ_AT_CET
FROM historical_info;
CREATE_DATE TZ_AT_CET
------------------- -----------------------
2015-08-17 11:59:29 2015-08-17 12:59:29 CET
But that is adjusting the time from my session time zone to CET, which may not be what you want. If you are saying that the stored time represents CET then you want the from_tz function:
SELECT create_date,
CAST(create_date AS TIMESTAMP) AT TIME ZONE 'CET' TZ_AT_CET,
FROM_TZ(CAST(create_date AS TIMESTAMP), 'CET') TZ_FROM_CET
FROM historical_info;
CREATE_DATE TZ_AT_CET TZ_FROM_CET
------------------- ----------------------- -----------------------
2015-08-17 12:01:40 2015-08-17 13:01:40 CET 2015-08-17 12:01:40 CET
From your column alias you may be trying to show that CET time in a different zone, which needs both steps:
SELECT create_date,
FROM_TZ(CAST(create_date AS TIMESTAMP), 'CET') TZ_FROM_CET,
FROM_TZ(CAST(create_date AS TIMESTAMP), 'CET')
AT TIME ZONE 'America/Los_Angeles' TZ_LOSANG
FROM historical_info
CREATE_DATE TZ_FROM_CET TZ_LOSANG
------------------- ----------------------- ---------------------------------------
2015-08-17 12:02:55 2015-08-17 12:02:55 CET 2015-08-17 03:02:55 AMERICA/LOS_ANGELES
If you have rows with different zones and those are stored as a separate column at the moment, you can use a case expression (or decode) to specify the Oracle equivalent for each one; but you'll still have to do this mapping yourself. You could put the translation into a look-up table if this isn't a one-off task.
with historical_info(create_date, orig_zone) as (
select sysdate, 'Europe Central' from dual
union all select sysdate, 'Europe Western' from dual
union all select sysdate, 'America Central' from dual
)
SELECT create_date,
FROM_TZ(CAST(create_date AS TIMESTAMP),
case orig_zone
when 'Europe Central' then 'CET'
when 'Europe Western' then 'WET'
when 'America Central' then 'US/Central'
-- when x then y for all other values you need
end) TZ_ADJUSTED
FROM historical_info
CREATE_DATE TZ_ADJUSTED
------------------- --------------------------------------
2015-08-17 12:16:13 2015-08-17 12:16:13 CET
2015-08-17 12:16:13 2015-08-17 12:16:13 WET
2015-08-17 12:16:13 2015-08-17 12:16:13 US/CENTRAL
You need to be careful to use time zones (or abbreviations) that adjust for daylight savings appropriately.
Here is one of our query in the database, i am trying to understand some UTC converstion in our query.
Can some one briefly explain, what the below query is doing?
SELECT
CAST (SYS_EXTRACT_UTC (CAST ( (BEGIN_DATE - (3 / 24)) AS TIMESTAMP)) AS DATE) BEGIN_DATE
FROM offer o
WHERE mask = 'OK'
AFTER CONVERSION OUTPUT : 06-SEP-11 04:00:00
SELECT
BEGIN_DATE
FROM offer o
WHERE mask = 'OK'
BEFORE CONVERSION OUTPUT : 06-SEP-11 00:00:00
SYS_EXTRACT_UTC extracts the UTC (Coordinated Universal Time—formerly Greenwich Mean Time) from a datetime value with time zone offset or time zone region name.
For example,
SQL> SELECT SYS_EXTRACT_UTC(TIMESTAMP '2014-11-05 12:00:00.00 -08:00') as dt
2 FROM DUAL;
DT
---------------------------------------------------------------------------
05-NOV-14 08.00.00.000000000 PM
SQL>
In your case, your timezone is -08:00. So, -3/24 is used.
For example, my timezone is +05:30. The current time is,
SQL> select to_char(sysdate, 'mm/dd/yyyy hh:mi:ss am') dt from dual;
DT
----------------------
11/04/2014 01:12:21 pm
SQL>
The UTC time equivalent is,
SQL> SELECT SYS_EXTRACT_UTC(TIMESTAMP '2014-11-05 01:12:21.00 +05:30') as dt
2 FROM DUAL;
DT
---------------------------------------------------------------------------
04-NOV-14 07.42.21.000000000 PM
SQL>