Oracle and DateTime math - oracle

I'm writing a query to select some records. I have this data:
Event Table
------------------
Definition
EVE_RID NUMBER(10)
EVE_START_DATE Date
Data:
EVE_RID EVE_START_DATE
156891 11/1/2016
Agenda Table
------------------
Definition:
AGD_EVE_RID NUMBER(10)
AGD_DAY NUMBER(2)
AGD_START_TIME NUMBER(4)
Data:
AGD_EVE_RID AGD_DAY AGD_START_TIME
156891 1 1000
156891 1 1400
156891 8 1000
156891 8 1400
156891 15 1000
156891 15 1400
WAList Table
------------------
Definition:
WAL_STARTTIME DATE
WAL_KEY VARCHAR2(50)
Data:
WAL_STARTTIME WAL_KEY
11/1/2016 10:00:00 AM 6341371019318098180
11/1/2016 2:00:00 PM 7561779448126279684
11/8/2016 10:00:00 AM 6904435321948802820
11/8/2016 2:00:00 PM 7998296559469684996
11/15/2016 10:00:00 AM 4690144247933554180
11/15/2016 2:00:00 PM 7931460546152111876
What I need is some way to match records from the WAList table from the Agenda table.
How could I write my where clause to match the WALList records to the Agenda.AGD_DAY records and return the correct Key for the correct day? This would be result:
EVE_START_DATE AGD_DAY AGD_START_TIME WAL_KEY
11/1/2016 1 1000 6341371019318098180
11/1/2016 1 1400 7561779448126279684
11/1/2016 8 1000 6904435321948802820
11/1/2016 8 1400 7998296559469684996
11/1/2016 15 1000 4690144247933554180
11/1/2016 15 1400 7931460546152111876

Assuming datatypes as —
Sessions.startdate as varchar2 and WAL.startTime as date:
select s.startdate, s.sessionDay, s.startTime, w.key
from sessions s join WAL w
on to_date(to_char(to_date(s.startdate, 'mm/dd/yyyy') + s.sessionDay, 'mm/dd/yyyy')
||s.starttime,'mm/dd/yyyyhh24mi') = w.startTime;

Related

Oracle How to get the birthday list from last 15 days to next 15 days?

I've a table with employees and their birth date, in a column in a format string.
I cannot modify the table, so I created a view to get their birth date in a real date format (TO_DATE).
Now, I would like to get the list of the employees having theirs birthday in the last 15 days and the employees who'll have theirs birthday in the next 15 days.
So, just based with the Day and the month.
I successfully get for exemple all employees bornt in April with "Extract", but, I'm sure you've already understand, when I'll run the query the 25 April, I'd like the futures birthday in May.
How could I get that (oracle 12c)
Thank you 🙂
Using the hiredate column in table scott.emp for testing:
select empno, ename, hiredate
from scott.emp
where add_months(trunc(hiredate),
12 * round(months_between(sysdate, hiredate) / 12))
between trunc(sysdate) - 15 and trunc(sysdate) + 15
;
EMPNO ENAME HIREDATE
---------- ---------- ----------
7566 JONES 04/02/1981
7698 BLAKE 05/01/1981
7788 SCOTT 04/19/1987
This will produce the wrong result in the following situation: if someone's birthday is Feb. 28 in a non-leap year, their birthday in a leap year (calculated with the ADD_MONTHS function in the query) will be considered to be Feb. 29. So, they will be excluded if running the query on, say, Feb. 13 2024 (even though they should be included), and they will be included if running the query on March 14 (even though they should be excluded). If you can live with this - those people will be recognized in the wrong window, once every four years - then this may be all you need. Otherwise that situation will require further tweaking.
For people born on Feb. 29 (in a leap year, obviously), their birthday in a non-leap-year is considered to be Feb. 28. With this convention, the query will always work correctly for them. Whether this convention is appropriate in your locale, only your business users can tell you. (Local laws and regulations may matter, too - depending on what you are using this for.)
You can use ddd format model:
DDD - Day of year (1-366).
For example:
SQL> with v(dt) as (
2 select date'2020-01-01'+level-1 from dual connect by date'2020-01-01'+level-1<date'2021-01-01'
3 )
4 select *
5 from v
6 where
7 not abs(
8 to_number(to_char(date'&dt','ddd'))
9 -to_number(to_char(dt ,'ddd'))
10 ) between 15 and 350;
Enter value for dt: 2022-01-03
DT
-------------------
2020-01-01 00:00:00
2020-01-02 00:00:00
2020-01-03 00:00:00
2020-01-04 00:00:00
2020-01-05 00:00:00
2020-01-06 00:00:00
2020-01-07 00:00:00
2020-01-08 00:00:00
2020-01-09 00:00:00
2020-01-10 00:00:00
2020-01-11 00:00:00
2020-01-12 00:00:00
2020-01-13 00:00:00
2020-01-14 00:00:00
2020-01-15 00:00:00
2020-01-16 00:00:00
2020-01-17 00:00:00
2020-12-19 00:00:00
2020-12-20 00:00:00
2020-12-21 00:00:00
2020-12-22 00:00:00
2020-12-23 00:00:00
2020-12-24 00:00:00
2020-12-25 00:00:00
2020-12-26 00:00:00
2020-12-27 00:00:00
2020-12-28 00:00:00
2020-12-29 00:00:00
2020-12-30 00:00:00
2020-12-31 00:00:00
30 rows selected.
NB: This example doesn't analyze leap years.
Similar to mathguy's answer, but translating the current date back to the birth year (rather than translating the birth year forwards):
SELECT *
FROM employees
WHERE birth_date BETWEEN ADD_MONTHS(
TRUNC(SYSDATE),
ROUND(MONTHS_BETWEEN(birth_date, SYSDATE)/12)*12
) - INTERVAL '15' DAY
AND ADD_MONTHS(
TRUNC(SYSDATE),
ROUND(MONTHS_BETWEEN(birth_date, SYSDATE)/12)*12
) + INTERVAL '15' DAY;
Then, for the sample data:
CREATE TABLE employees (name, birth_date) AS
SELECT 'Alice', DATE '2020-02-28' FROM DUAL UNION ALL
SELECT 'Betty', DATE '2020-02-29' FROM DUAL UNION ALL
SELECT 'Carol', DATE '2021-02-28' FROM DUAL UNION ALL
SELECT 'Debra', DATE '2022-04-28' FROM DUAL UNION ALL
SELECT 'Emily', DATE '2021-03-30' FROM DUAL UNION ALL
SELECT 'Fiona', DATE '2021-03-31' FROM DUAL;
If today's date is 2022-04-16 then the output is:
NAME
BIRTH_DATE
Debra
28-APR-22
If today's date is 2022-03-15 then the output is:
NAME
BIRTH_DATE
Betty
29-FEB-20
Carol
28-FEB-21
Emily
30-MAR-21
And would get values from 28th February - 30th March in a non-leap-year and from 29th February - 30th March in a leap year.
db<>fiddle here

Oracle display timestamp as military time

I have a timestamp column in one of my tables. I am doing the following and its displayed as 12.00.00.100000 AM but when the time passes 12AM its displayed in military time as I want it.
How can I display the date as military time 12AM time as 00:00:00.100000
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF';
SEQ_NUM DT
1 01-JAN-22 12.00.00.000000 AM
2 01-JAN-22 12.05.00.100000 AM
3 01-JAN-22 12.10.00.200000 AM
4 01-JAN-22 12.15.00.300000 AM
5 01-JAN-22 12.20.00.400000 AM
6 01-JAN-22 12.25.00.500000 AM
7 01-JAN-22 12.30.00.600000 AM
8 01-JAN-22 12.35.00.700000 AM
9 01-JAN-22 12.40.00.800000 AM
10 01-JAN-22 12.45.00.900000 AM
11 01-JAN-22 12.50.00.000000 AM
12 01-JAN-22 12.55.00.100000 AM
13 01-JAN-22 01.00.00.200000 AM
14 01-JAN-22 01.05.00.300000 AM
If your data type is TIMESTAMP then use:
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF';
If your data type is TIMESTAMP WITH TIME ZONE then use:
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF TZR';
or
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM';
However, any user can change their own session parameters at any time so if you want a particular format then you are better to change from outputting a TIMESTAMP to outputting a string containing the value in the given format using TO_CHAR:
SELECT seq_num,
TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS.FF') AS dt
FROM table_name;
db<>fiddle here
You appear to have a plain TIMESTAMP column, not a TIMESTAMP WITH TIME ZONE column. (Or it could be TIMESTAMP WITH LOCAL TIME ZONE, if your session is set up as UTC/GMT.)
You are setting NLS_TIMESTAMP_TZ_FORMAT, but that applies to TIMESTAMP WITH TIME ZONE, not plain TIMESTAMP.
If you set the relevant NLS parameter instead (without the _TZ part):
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF';
then you get:
SEQ_NUM
DT
1
2022-01-01 00:00:00.000000
2
2022-01-01 00:05:00.100000
3
2022-01-01 00:10:00.200000
4
2022-01-01 00:15:00.300000
5
2022-01-01 00:20:00.400000
6
2022-01-01 00:25:00.500000
7
2022-01-01 00:30:00.600000
8
2022-01-01 00:35:00.700000
9
2022-01-01 00:40:00.800000
10
2022-01-01 00:45:00.900000
11
2022-01-01 00:50:01.000000
12
2022-01-01 00:55:01.100000
13
2022-01-01 01:00:01.200000
14
2022-01-01 01:05:01.300000
db<>fiddle with plain TIMESTAMP, or with TIMESTAMP WITH LOCAL TIMEZONE, which gets the same output as the session time zone is GMT.
Or you can use to_char() with the same format mask, so you aren't relying on session NLS settings.

Oracle INCREMENT fractional parts of a timestamp

I have a table, which contains a timestamp. I want each row an INTERVAL apart. In my example below I am using a15 minutes interval
My first solution appears to work perfectly except that the fractional part of the timestamp is always .000000, which I expect but don't want. I'd like it to contain some other numbers.
In my second attempt I'm trying to INCREMENT the fractional part of the timestamp by .100000 the problem with this solution is if I'm creating many rows (1344) in my example, the seconds part of the timestamp gets incremented by 1 second after 10 rows are inserted. See the second solution below. I don't want that either.
Thirdly, I thought perhaps a regexp_replace solution would work where I could chop off the integer part of the second solution (keep the fractional part) and then add that to my interval. That attempt failed with an error. See third attempt below.
Is there a way I can get this to work? Where I can change the fractional part of the timestamp without it affecting the MMDDYYYY HH24:MI:SS part of the date.
Below is my code and attempts along with an example of the sample output I'm looking to generate.
Attempt #1 fractional part always .000000
CREATE TABLE t3 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt TIMESTAMP );
/
INSERT into t3 (dt)
with dt (dt, interv) as (
select timestamp '2022-01-01 00:00:00',
numtodsinterval(15,'MINUTE') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-01-15')
select dt from dt;
/
SELECT * FROM T3 ORDER BY SEQ_NUM
SEQ_NUM DT
1 01-JAN-22 12.00.00.000000 AM
2 01-JAN-22 12.15.00.000000 AM
3 01-JAN-22 12.30.00.000000 AM
4 01-JAN-22 12.45.00.000000 AM
5 01-JAN-22 01.00.00.000000 AM
6 01-JAN-22 01.15.00.000000 AM
…
...
1342 14-JAN-22 11.15.00.000000 PM
1343 14-JAN-22 11.30.00.000000 PM
1344 14-JAN-22 11.45.00.000000 PM
Attempt #2 notice the seconds change at seq_num 1355. It went from :00 to :01
TRUNCATE TABLE T3;
/
INSERT into t3 (dt)
with dt (dt, interv) as (
select timestamp '2022-01-01 00:00:00',
numtodsinterval(15,'MINUTE') +
numtodsinterval( (rownum * .100000), 'SECOND') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-01-15')
select dt from dt;
/
SELECT * FROM T3 ORDER BY SEQ_NUM
SEQ_NUM DT
1345 01-JAN-22 12.00.00.000000 AM
1346 01-JAN-22 12.15.00.100000 AM
1347 01-JAN-22 12.30.00.200000 AM
1348 01-JAN-22 12.45.00.300000 AM
1349 01-JAN-22 01.00.00.400000 AM
1350 01-JAN-22 01.15.00.500000 AM
1351 01-JAN-22 01.30.00.600000 AM
1352 01-JAN-22 01.45.00.700000 AM
1353 01-JAN-22 02.00.00.800000 AM
1354 01-JAN-22 02.15.00.900000 AM
1355 01-JAN-22 02.30.01.000000 AM
1356 01-JAN-22 02.45.01.100000 AM
…
…
Attempt #3 failed
TRUNCATE TABLE T3;
/
INSERT into t3 (dt)
with dt (dt, interv) as (
select timestamp '2022-01-01 00:00:00',
numtodsinterval(15,'MINUTE') +
regexp_replace(
numtodsinterval( (rownum * .100000), 'SECOND'), '[^.]+\.(.*)$', '0.\1') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-01-15')
select dt from dt;
/
ORA-30081: invalid data type for datetime/interval arith
Desired output
SEQ_NUM DT
1345 01-JAN-22 12.00.00.000000 AM
1346 01-JAN-22 12.15.00.100000 AM
1347 01-JAN-22 12.30.00.200000 AM
1348 01-JAN-22 12.45.00.300000 AM
1349 01-JAN-22 01.00.00.400000 AM
1350 01-JAN-22 01.15.00.500000 AM
1351 01-JAN-22 01.30.00.600000 AM
1352 01-JAN-22 01.45.00.700000 AM
1353 01-JAN-22 02.00.00.800000 AM
1354 01-JAN-22 02.15.00.900000 AM
1355 01-JAN-22 02.30.00.000000 AM
1356 01-JAN-22 02.45.00.100000 AM
1357 01-JAN-22 03.00.00.200000 AM
…
…
You can use:
INSERT into t3 (dt)
SELECT TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '15' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND
FROM DUAL
CONNECT BY
TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '15' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND < DATE '2022-01-15';
or:
INSERT into t3 (dt)
SELECT TIMESTAMP '2022-01-01 00:00:00'
+ NUMTODSINTERVAL((LEVEL-1)*15*60 + MOD(LEVEL-1, 10)/10, 'SECOND')
FROM DUAL
CONNECT BY
TIMESTAMP '2022-01-01 00:00:00'
+ NUMTODSINTERVAL((LEVEL-1)*15*60 + MOD(LEVEL-1, 10)/10, 'SECOND')
< DATE '2022-01-15';
Which both give the values:
SEQ_NUM
DT
1
2022-01-01 00:00:00.000000
2
2022-01-01 00:15:00.100000
3
2022-01-01 00:30:00.200000
4
2022-01-01 00:45:00.300000
5
2022-01-01 01:00:00.400000
6
2022-01-01 01:15:00.500000
7
2022-01-01 01:30:00.600000
8
2022-01-01 01:45:00.700000
9
2022-01-01 02:00:00.800000
10
2022-01-01 02:15:00.900000
11
2022-01-01 02:30:00.000000
db<>fiddle here
It looks like you just want to add an interval of 15 minutes and 0.1 seconds
select level seq_num,
timestamp '2022-01-01 00:00:00' +
(level-1) * interval '15' minute +
(level-1) * interval '0.1' second dt
from dual
connect by level <= 10
Here's a dbfiddle that shows it producing the output you want.

How to convert Varchar2 to time in Oracle?

I have 2 columns in my table both of varchar2. I have a query like
SELECT MYCOLUMN_TIME||' '||MYCOLUMN_TIME_AMPM
FROM MYTABLE
I am getting output 0910 am. I have tried
SELECT TO_CHAR(TO_DATE(MYCOLUMN_TIME,'hh24miss'), 'hh24:mi:ss')||' '||MYCOLUMN_TIME_AMPM
FROM MYTABLE
With this query I am getting Output 09:10:00 pm.
I want Output like 21:10:00 pm. How can we achieve this? Please help.
When using HH24 format mask, 21 hours equals 9 PM. There's no point in having PM along with 21, is there?
When you convert a string (0910) concatenated with am/pm to a date, you use TO_DATE function with appropriate format mask. Date values - in Oracle - contain both date and time component (see datum_1 in the following example). Then apply TO_CHAR to such a result in order to display it as you want (again, by applying desired format mask) - that's datum_2.
For example:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh:mi pm';
Session altered.
SQL> with mytable (mycolumn_time, mycolumn_time_ampm) as
2 (select '0910', 'am' from dual union all
3 select '1150', 'pm' from dual
4 )
5 select mycolumn_time,
6 mycolumn_time_ampm,
7 to_date(mycolumn_time ||' '||mycolumn_time_ampm, 'hhmi pm') datum_1,
8 --
9 to_char(to_date(mycolumn_time ||' '||mycolumn_time_ampm, 'hhmi pm'), 'hh24:mi') datum_2
10 from mytable;
MYCO MY DATUM_1 DATUM_2
---- -- ------------------- -------
0910 am 01.09.2020 09:10 AM 09:10
1150 pm 01.09.2020 11:50 PM 23:50
SQL>
If you add PM format mask, you'd get
<snip>
9 to_char(to_date(mycolumn_time ||' '||mycolumn_time_ampm, 'hhmi pm'), 'hh24:mi pm') datum_2
10 from mytable; ^^
here
MYCO MY DATUM_1 DATUM_2
---- -- ------------------- --------
0910 am 01.09.2020 09:10 AM 09:10 AM
1150 pm 01.09.2020 11:50 PM 23:50 PM
SQL>
but - as I previously said - it doesn't make sense. There's no e.g. 23:50 AM, but it makes perfect sense in 11:50 AM or 11:50 PM.

How to query date differences between dates of same column?

I have a table with a date field and I need a query to return the ID of records that are on a certain day.
Example
ID UPDATED_DATE
42 31-DEC-19 12.00.00.000000000 AM
43 24-DEC-19 12.00.00.000000000 AM
44 03-DEC-19 12.00.00.000000000 AM
45 18-NOV-19 12.00.00.000000000 AM
46 08-NOV-19 12.00.00.000000000 AM
47 01-NOV-19 12.00.00.000000000 AM
48 26-OCT-19 12.00.00.000000000 AM
49 04-OCT-19 12.00.00.000000000 AM
50 20-SEP-19 12.00.00.000000000 AM
I need a query to find if the DAY part of the date is >= 1 and < 5.
In the example, I will get the ID as 44, 47 and 49 as output.
Can anyone help me for the query, please?
Use TO_CHAR with appropriate format mask:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with test as
2 (select 42 id, date '2019-12-31' updated_date from dual union all
3 select 43, date '2019-12-24' from dual union all
4 select 44, date '2019-12-03' from dual union all
5 select 45, date '2019-11-18' from dual union all
6 select 46, date '2019-11-08' from dual union all
7 select 47, date '2019-11-01' from dual union all
8 select 48, date '2019-10-26' from dual union all
9 select 49, date '2019-10-04' from dual union all
10 select 50, date '2019-09-20' from dual
11 )
12 select id, updated_date, to_char(updated_date, 'fmdd') dd
13 from test
14 where to_char(updated_date, 'fmdd') >= 1
15 and to_char(updated_date, 'fmdd') < 5
16 order by id;
ID UPDATED_DA DD
---------- ---------- --
44 03.12.2019 3
47 01.11.2019 1
49 04.10.2019 4
SQL>
If you want the difference (as per the original version of the question) between day-of-month of two rows to be between 1 and 4 days then use LAG/LEAD:
SELECT ID,
UPDATED_DATE
FROM (
SELECT ID,
UPDATED_DATE,
EXTRACT(DAY FROM updated_date) AS day,
LAG( EXTRACT(DAY FROM updated_date) )
OVER ( ORDER BY EXTRACT(DAY FROM updated_date) ) AS prev_day,
LEAD( EXTRACT(DAY FROM updated_date) )
OVER ( ORDER BY EXTRACT(DAY FROM updated_date) ) AS next_day
FROM table_name
)
WHERE day - prev_day BETWEEN 1 AND 4
OR next_day - day BETWEEN 1 AND 4
Which for your test data ouptuts:
ID | UPDATED_DATE
-: | :--------------------
47 | 01-NOV-19 12:00:00 AM
44 | 03-DEC-19 12:00:00 AM
49 | 04-OCT-19 12:00:00 AM
46 | 08-NOV-19 12:00:00 AM
45 | 18-NOV-19 12:00:00 AM
50 | 20-SEP-19 12:00:00 AM
43 | 24-DEC-19 12:00:00 AM
48 | 26-OCT-19 12:00:00 AM
If you want the day-of-month to be between 1 and 4 then use EXTRACT:
SELECT *
FROM table_name
WHERE EXTRACT(DAY FROM updated_date) BETWEEN 1 AND 4
Which outputs:
ID | UPDATED_DATE
-: | :--------------------
44 | 03-DEC-19 12:00:00 AM
47 | 01-NOV-19 12:00:00 AM
49 | 04-OCT-19 12:00:00 AM
db<>fiddle here

Resources