List of dates between two tables in oracle - sql

Problem
Input:
START_DATE END_DATE
01-FEB-16 03-FEB-16
01-FEB-16 02-FEB-16
10-FEB-16 11-FEB-16
I want to generate all the dates between the start_day and end_day as
Output
01-FEB-16
02-FEB-16
03-FEB-16
10-FEB-16
11-FEB-16

You could do it using Row Generator technique.
Setup
SQL> CREATE TABLE t
2 (START_DATE DATE, END_DATE DATE
3 );
Table created.
SQL> INSERT INTO t VALUES(DATE '2016-02-01', DATE '2016-02-03');
1 row created.
SQL> INSERT INTO t VALUES(DATE '2016-02-01', DATE '2016-02-02');
1 row created.
SQL> INSERT INTO t VALUES(DATE '2016-02-10', DATE '2016-02-11');
1 row created.
SQL> COMMIT;
Commit complete.
Query
SQL> SELECT DISTINCT TO_CHAR(START_DATE+LEVEL-1, 'DD-MON-YYYY') the_date
2 FROM t
3 CONNECT BY LEVEL <= END_DATE-START_DATE+1
4 ORDER BY the_date
5 /
THE_DATE
-----------
01-FEB-2016
02-FEB-2016
03-FEB-2016
10-FEB-2016
11-FEB-2016
SQL>

Related

Oracle copying rows into same table with different dates

I have a schedule where dates can cross midnight. In the example you can see dates for 08/23 & 08/24.
How can I copy these rows into the same table but with different dates.
For example the row with start_date 08/23 I want to be 08/28. The row with start_date 08/24 should be the next day 08/29.
Note the situation where the start_date=08/23 and end_date=08/24
Thanks in advance to all that answer.
Desired output
SCHEDULE_ID LOCATION_ID START_DATE END_DATE
22 1 08232021 23:50:00 08232021 23:54:00
22 1 08232021 23:56:00 08242021 00:02:00
22 2 08242021 00:05:00 08242021 00:09:00
22 1 08282021 23:50:00 08282021 23:54:00
22 1 08282021 23:56:00 08292021 00:02:00
22 2 08292021 00:05:00 08292021 00:09:00
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table schedule(
schedule_id NUMBER(4),
location_id number(4),
start_date DATE,
end_date DATE,
check (start_date=trunc(start_date,'MI')),
check (end_date=trunc(end_date,'MI'))
);
insert into schedule(
schedule_id,
location_id,
start_date,
end_date
)
VALUES (22,1,TO_DATE('2021/08/23 23:50:00', 'yyyy/mm/dd hh24:mi:ss'),TO_DATE('2021/08/23 23:54:00', 'yyyy/mm/dd hh24:mi:ss'));
insert into schedule(
schedule_id,
location_id,
start_date,
end_date
)
VALUES (22,1,TO_DATE('2021/08/23 23:56:00', 'yyyy/mm/dd hh24:mi:ss'),TO_DATE('2021/08/24 00:02:00', 'yyyy/mm/dd hh24:mi:ss'));
insert into schedule(
schedule_id,
location_id,
start_date,
end_date
)
VALUES (22,2,TO_DATE('2021/08/24 00:05:00', 'yyyy/mm/dd hh24:mi:ss'),
TO_DATE('2021/08/24 00:09:00', 'yyyy/mm/dd hh24:mi:ss'));
Are you doing this to generate more data, perhaps for testing?
It should be something like
insert into schedule
select schedule_id, location_id, start_date + 5, end_date + 5
from schedule
;
The fact that some date intervals straddle over midnight is irrelevant.

how can we group 5 pm yesterday to 5 pm today records to todays date

oracle table
id timestamp status
1 2019-10-20 12:34:56.000 approved
1 2019-10-22 12:34:56.000 approved
2 2019-10-20 17:34:56.000 approved
2 2019-10-21 12:34:56.000 approved
3 2019-10-23 18:10:10.000 mod_in_ip
3 2019-10-24 11:10:10.000 approved
3 2019-10-24 12:10:10.000 approved
4 2019-10-25 12:10:10.000 approved
4 2019-10-25 18:10:10.000 approved
I want to label id's as new or edited. problem is from the records from 5pm to 5pm is considered working period i.e
'2019-10-25 17:00:00' to '2019-10-26 17:00:00' would be considered for a working period or
yesterday's 5 pm to today's 5 pm is a working period.
For example: an id record with yesterday 6:00PM record and today 11AM record should be labeled new
if you look at the table and expected outcome you can get the idea
the expected result/outcome should be
1 edited
2 new
3 new
4 edited
initally I tried this but it fails to address the above point
select id,
case
when count(id)<=1 then 'New'
else 'Edited' End AS prefix
from(select id,status ,trunc(timestamp) from table
where
status='approved' and id in (1,2,3,4)
group by id,status,trunc(timestamp))
group by id
the result is
1 Edited
2 Edited
3 new
4 new
but the expected result is
1 edited
2 new
3 new
4 edited
I am looking for solutions such as the innerquery groups records from 5pm to 5pm such that outer query can work fine
or an entirely different solution is also feasible
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=abd90416004000043c85316423d64b17
If you want to treat 5pm to 5pm as the "same" day, it is easy to shift an Oracle date either forward or backward with fractions of day, (eg 5pm can be shifted 7 hours forward to become the start of the 'next' day)
SQL> create table ora_table (id number, time_data timestamp, status varchar2(30));
Table created.
SQL> insert into ora_table values (1 , to_timestamp('2019-10-20 12:34:56.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (1 , to_timestamp('2019-10-22 12:34:56.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (2 , to_timestamp('2019-10-20 17:34:56.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (2 , to_timestamp('2019-10-21 12:34:56.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (3 , to_timestamp('2019-10-23 18:10:10.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'mod_in_ip');
1 row created.
SQL> insert into ora_table values (3 , to_timestamp('2019-10-24 11:10:10.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (3 , to_timestamp('2019-10-24 12:10:10.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (4 , to_timestamp('2019-10-25 12:10:10.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL> insert into ora_table values (4 , to_timestamp('2019-10-25 18:10:10.000', 'yyyy-mm-dd hh24:mi:ss.ff'), 'approved');
1 row created.
SQL>
SQL> select id, time_data, trunc(time_data) true_date, trunc(time_data+7/24) mapped_date
2 from ora_table;
ID TIME_DATA TRUE_DATE MAPPED_DA
---------- ---------------------------------- --------- ---------
1 20-OCT-19 12.34.56.000000 PM 20-OCT-19 20-OCT-19
1 22-OCT-19 12.34.56.000000 PM 22-OCT-19 22-OCT-19
2 20-OCT-19 05.34.56.000000 PM 20-OCT-19 21-OCT-19 <===
2 21-OCT-19 12.34.56.000000 PM 21-OCT-19 21-OCT-19
3 23-OCT-19 06.10.10.000000 PM 23-OCT-19 24-OCT-19 <===
3 24-OCT-19 11.10.10.000000 AM 24-OCT-19 24-OCT-19
3 24-OCT-19 12.10.10.000000 PM 24-OCT-19 24-OCT-19
4 25-OCT-19 12.10.10.000000 PM 25-OCT-19 25-OCT-19
4 25-OCT-19 06.10.10.000000 PM 25-OCT-19 26-OCT-19
9 rows selected.
This should give you what you're looking for:
WITH cte(id, ts, NewCutoff, Row_Num) AS (
select
id,
ts,
MIN(
trunc(ts) +
CASE
WHEN EXTRACT(HOUR FROM ts) < 17 THEN INTERVAL '00 17' DAY TO HOUR
ELSE INTERVAL '01 17' DAY TO HOUR -- If time is after 5pm, add an extra day
END
) OVER(PARTITION BY id) AS NewCutoff, -- Determine cut-off TS for "new" entries
ROW_NUMBER() OVER(
PARTITION BY id ORDER BY ts DESC
) AS Row_Num -- Order entries by date within "id" group
FROM mytable
)
SELECT id, CASE WHEN ts < NewCutoff THEN 'new' ELSE 'edited' END AS prefix
FROM cte
WHERE Row_Num = 1 -- Only look at latest entry per id
;
You may need to check the end conditions -- i.e. how to handle a ts value with exactly 5pm.
SQL Fiddle

Column with exact date and time of recording

How to Add to a Database Table the Exact Date and Time, Adding a New Record to this Table
SELECT * FROM test;
ID NAME DT
---------- ---------- ----------
1 Ana 01.01.2019 00:00:00
2 Ina 01.01.2019 00:00:00
I want the exact time when this one was created
example:
ID NAME DT
---------- ---------- ----------
1 Ana 01.01.2019 10:41:22
2 Ina 01.01.2019 10:45:17
CREATE TABLE table
(
Id NUMBER(10),
Name varcahar2(10),
DT date
);
CREATE TABLE test
(
Id NUMBER(10),
Name varchar2(10),
DT date
);
Insert data:
INSERT INTO test values (1, 'Ana', sysdate);
INSERT INTO test values (2, 'Ina', sysdate);
COMMIT;
Query results:
SELECT id, name, TO_CHAR(dt, 'dd.mm.yyyy hh24:mi:ss') FROM test;
ID NAME TO_CHAR(DT,'DD.MM.Y
---------- ---------- -------------------
1 Ana 02.09.2019 10:07:18
2 Ina 02.09.2019 10:07:18
Oracle Setup:
CREATE TABLE table_name(
Id NUMBER(10),
Name VARCHAR2(10),
DT DATE
);
Option 1:
Use a DATE and INTERVAL literals:
INSERT INTO table_name ( id, name, dt )
VALUES ( 1, 'Ana', DATE '2019-01-01' + INTERVAL '10:41:22' HOUR TO SECOND );
Option 2:
Use TO_DATE and convert from a string:
INSERT INTO table_name ( id, name, dt )
VALUES ( 2, 'Ina', TO_DATE( '2019-01-01 10:45:17', 'YYYY-MM-DD HH24:MI:SS' ) );
Option 3:
Use a TIMESTAMP literal:
INSERT INTO table_name ( id, name, dt )
VALUES ( 3, 'Ona', TIMESTAMP '2019-01-01 10:49:12' );
Option 4:
If you want the current date & time then use SYSDATE:
INSERT INTO table_name ( id, name, dt )
VALUES ( 4, 'Una', SYSDATE );
or CURRENT_DATE:
INSERT INTO table_name ( id, name, dt )
VALUES ( 4, 'Una', CURRENT_DATE );
Output:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT * FROM table_name;
or
SELECT id, name, TO_CHAR( dt, 'YYYY-MM-DD HH24:MI:SS' ) AS dt FROM table_name;
Outputs:
ID | NAME | DT
-: | :--- | :------------------
1 | Ana | 2019-01-01 10:41:22
2 | Ina | 2019-01-01 10:45:17
3 | Ona | 2019-01-01 10:49:12
4 | Una | 2019-09-02 09:22:28
db<>fiddle here
use TIMESTAMP
CREATE TABLE table
(
Id NUMBER(10),
Name varchar2(10),
DT TIMESTAMP
);
Insert current date with your data, and use analytical function to compare time since last event.
CREATE TABLE so_test
(
Id NUMBER(10),
Name varchar2(10),
DT date
);
insert into so_test values (1, 'Scott', sysdate);
insert into so_test values (2, 'Andrii', sysdate);
insert into so_test values (3, 'Zaynul', sysdate);
select id, name, dt
,round((dt-LAG(dt) OVER (ORDER BY id))*86400) seconds_diff
from so_test
ID NAME DT SECONDS_DIFF
---------- ---------- ------------------- ------------
1 Scott 02-09-2019 16:02:47
2 Andrii 02-09-2019 16:02:58 11
3 Zaynul 02-09-2019 16:03:12 14

Get Max Date for prev months based on a given date

I am trying to write a query that will give me the max date form Table_A based on the following condition.
If the given date (input parameter) falls between 01 April and 30 Sept of that year then return MAX(date) prior to April 1.
OR
If the given date (input parameter) falls between 01 Oct and 31 March of the following year then return MAX(date) prior to Oct 1.
For example :
Given date is 27th Dec, 2012 (date between 01 Oct and 31 March of the following year)
return 29th Sept, 2012
Given Date is 17th Aug, 2012 (date between 01 April and 30 Sept of the following year)
return date (MAX DATE prior to is 01 April is 29th March)
try this:
select max(case
when trunc(the_date) between to_date('01-apr-'||the_year, 'dd-mon-yyyy')
and to_date('30-sep-'||the_year, 'dd-mon-yyyy')
and dte < to_date('01-apr-'||the_year, 'dd-mon-yyyy')
then
dte
when trunc(the_date) between to_date('01-oct-'||the_year, 'dd-mon-yyyy')
and to_date('31-mar-'||(the_year+1), 'dd-mon-yyyy')
and dte < to_date('01-oct-'||the_year, 'dd-mon-yyyy')
then
dte
end) max_date
from table_a a
cross join (select v_inp_date the_date,
to_char(add_months(v_inp_date,-3), 'yyyy') the_year
from dual) dte;
so you input date is in the dte part
v_inp_date
and
to_char(add_months(v_inp_date,-3), 'yyyy') the_year
eg a small test:
SQL> create table table_a(dte date);
Table created.
SQL> insert into table_a values(to_date('28-sep-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('29-sep-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('28-mar-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('29-mar-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('28-sep-2011', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('27-mar-2011', 'dd-mon-yyyy'));
1 row created.
SQL> commit;
Commit complete.
SQL> var inpdate varchar2(20);
SQL> exec :inpdate := '27-dec-2012';
PL/SQL procedure successfully completed.
SQL> select max(case
2 when trunc(the_date) between to_date('01-apr-'||the_year, 'dd-mon-yyyy')
3 and to_date('30-sep-'||the_year, 'dd-mon-yyyy')
4 and dte < to_date('01-apr-'||the_year, 'dd-mon-yyyy')
5 then
6 dte
7 when trunc(the_date) between to_date('01-oct-'||the_year, 'dd-mon-yyyy')
8 and to_date('31-mar-'||(the_year+1), 'dd-mon-yyyy')
9 and dte < to_date('01-oct-'||the_year, 'dd-mon-yyyy')
10 then
11 dte
12 end) max_date
13 from table_a a
14 cross join (select to_date(:inpdate,'dd-mon-yyyy') the_date,
15 to_char(add_months(to_date(:inpdate,'dd-mon-yyyy'), -3), 'yyyy') the_year
16 from dual) dte;
MAX_DATE
---------
29-SEP-12
SQL> exec :inpdate := '17-aug-2012';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
29-MAR-12
SQL> exec :inpdate := '01-oct-2011';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
28-SEP-11
SQL> exec :inpdate := '01-apr-2011';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
27-MAR-11
SQL>
The end date in my example is Dec-31-2012, you can extend it to 2013+. I'm building table on the fly-the max_date is printed for every row. Also, I hardcode max dates. You do not need to do any of this, this is just for example. Replace &1 and &2 with values w/out quotes...:
SELECT date_table --, start_date, end_date
, (CASE WHEN date_table BETWEEN To_Date('01-APR-2012') And To_Date('30-SEP-2012') Then max_apr_date
WHEN date_table BETWEEN To_Date('01-OCT-2012') And To_Date('31-DEC-2012') Then max_oct_date
END) max_date
FROM
(
SELECT *
FROM
(
SELECT TRUNC(SYSDATE,'Y')+LEVEL-1 date_table
, To_Date('&1') start_date
, To_Date('&2') end_date
, '29-MAR-2012' max_apr_date
, '29-Sep-2012' max_oct_date
FROM dual
CONNECT BY LEVEL <= (ADD_MONTHS(TRUNC(SYSDATE,'Y'),12)-TRUNC(SYSDATE,'Y') )
)
WHERE date_table BETWEEN start_date AND end_date
)
/

Finding percentage time by hour from irregular dates

I have a table like the following, each entry a change in STATUS on the given time.
The status can be repeated because other columns have sub-status information.
How can I get a percentage time for each status by, say, hour?
NAME STATUS_CHANGE_TIME STATUS
foo 15-MAY-11 18:52 A
foo 15-MAY-11 18:38 A
foo 15-MAY-11 18:33 B
foo 15-MAY-11 16:53 A
foo 15-MAY-11 16:47 B
foo 15-MAY-11 13:37 A
foo 15-MAY-11 13:33 C
foo 15-MAY-11 10:23 C
foo 15-MAY-11 10:17 A
foo ...
Desired return:
HH24 STATUS PERCENT
10 ...
11 C 100 (No entries; last change was to C)
12 C 100 "" ""
13 C 62
13 A 38 (From C to A at :37 with 23 mins left; 23/60 ~ 38%)
14 A 100
15 A 100
16 A 90 (= A for first 47 minutes, then for another 7)
16 B 10 (16:53 - 16:47 = 6 minutes or 10% of an hour)
17 A 100
18 ... etc.
Great question, this was an interesting challenge!
What you need is an ancillary table to store each time division (in this case, hours), then join to it where the status updates overlap. LEAD() can grab the next status entry to check when it was, and GREATEST() and LEAST() can figure out which time is applicable for the start/end of the status for each hour.
Of course, this is much easier explained in an example. Here is the HOURS table needed:
SQL> CREATE TABLE hours (HOUR NUMBER(2), start_m date, end_m date);
Table created.
SQL> BEGIN
2 FOR i IN 0..23 LOOP
3 INSERT INTO hours VALUES(i, to_date(lpad(i, 2, '0')||':00:00', 'HH24:MI:SS')
4 , to_date(lpad(i, 2, '0')||':59:59', 'HH24:MI:SS'));
5 END loop;
6 COMMIT;
7 END;
8 /
PL/SQL procedure successfully completed.
The following is just population of your test data from your question.
SQL> CREATE TABLE status_updates (NAME VARCHAR2(3), status_change_time DATE, status CHAR(1));
Table created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 18:52', 'DD-MON-RR HH24:MI:SS'), 'A');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 18:38', 'DD-MON-RR HH24:MI:SS'), 'A');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 18:33', 'DD-MON-RR HH24:MI:SS'), 'B');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 16:53', 'DD-MON-RR HH24:MI:SS'), 'A');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 16:47', 'DD-MON-RR HH24:MI:SS'), 'B');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 13:37', 'DD-MON-RR HH24:MI:SS'), 'A');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 13:33', 'DD-MON-RR HH24:MI:SS'), 'C');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 10:23', 'DD-MON-RR HH24:MI:SS'), 'C');
1 row created.
SQL> INSERT INTO status_updates VALUES ('foo',TO_DATE('15-MAY-11 10:17', 'DD-MON-RR HH24:MI:SS'), 'A');
1 row created.
SQL> commit;
Commit complete.
Now here is the select statement to get the required percentages.
SELECT t.NAME, t.HOUR, t.status, sum(round((status_end_h-start_status_h)*24*100)) per_cent
FROM (
SELECT A.NAME
, A.status
, A.status_change_time
, A.next_change_time
, b.HOUR
, greatest(status_change_time, trunc(status_change_time)+(b.start_m-trunc(b.start_m))) start_status_h
, least(next_change_time, trunc(next_change_time)+(b.end_m-trunc(b.end_m))) status_end_h
FROM (
SELECT NAME
, status
, status_change_time
, lead(status_change_time) OVER (ORDER BY NAME, status_change_time) next_change_time
FROM status_updates
) A, hours b
WHERE TO_CHAR(b.start_m, 'HH24:MI:SS') BETWEEN TO_CHAR(A.status_change_time, 'HH24:MI:SS') AND TO_CHAR(A.next_change_time, 'HH24:MI:SS')
OR TO_CHAR(b.end_m, 'HH24:MI:SS') BETWEEN TO_CHAR(A.status_change_time, 'HH24:MI:SS') AND TO_CHAR(A.next_change_time, 'HH24:MI:SS')
OR (TO_CHAR(A.status_change_time, 'HH24:MI:SS') BETWEEN TO_CHAR(b.start_m, 'HH24:MI:SS') AND TO_CHAR(b.end_m, 'HH24:MI:SS')
AND TO_CHAR(A.next_change_time, 'HH24:MI:SS') BETWEEN TO_CHAR(b.start_m, 'HH24:MI:SS') AND TO_CHAR(b.end_m, 'HH24:MI:SS'))
) t
GROUP BY t.NAME, t.HOUR, t.status
ORDER BY t.HOUR;
NAM HOUR S PER_CENT
--- ---------- - ----------
foo 10 A 10
foo 10 C 62
foo 11 C 100
foo 12 C 100
foo 13 A 38
foo 13 C 62
foo 14 A 100
foo 15 A 100
foo 16 A 90
foo 16 B 10
foo 17 A 100
NAM HOUR S PER_CENT
--- ---------- - ----------
foo 18 A 78
foo 18 B 8
13 rows selected.