Select distinct column in sql group by - sql

I have the following rows:
DateTime Item_no Vo_No
2019-06-27 30322264 0118113
2017-12-27 00265929 0242712
2019-01-21 30322264 0515768
2017-12-27 00265929 0400026
2019-01-21 30322264 0569606
2018-09-25 00265929 0696864
2019-01-21 30317757 0696619
2019-06-27 30317757 0118113
2017-12-27 00265929 0242624
2017-01-24 00265929 0282971
2019-01-21 30317757 0386202
2019-01-21 30317757 0515706
2019-01-21 30322264 0696619
2017-12-27 00265929 0242625
2017-12-27 00265929 0395347
2017-12-27 00265929 0441449
2019-01-21 30317757 0569605
2017-12-27 00265929 0282972
2017-01-24 00265929 0282984
2019-01-21 30322264 0397256
I'm trying to get the distinct item no, with the latest date and the vo_no.
I have came up with the following query:
SELECT MAX(b.upd_dtime), b.item_no
FROM vo_item_t b JOIN (
SELECT a.vo_no, a.vo_state, a.vo_confirm_date, a.vo_order_date
FROM vo_order_t a) y ON y.vo_no = b.vo_no and b.item_No IN(30317757, 30322264, 00265929)
GROUP BY b.item_no
It works pretty well, I get the following result:
Date Item_No
2019-06-27 30322264
2019-06-27 30317757
2018-09-25 00265929
But as you can see, the vo_no is missing for each Item_No, so I tried the following:
SELECT MAX(b.upd_dtime), b.item_no, y.vo_no
FROM vo_item_t b JOIN (
SELECT a.vo_no, a.vo_state, a.vo_confirm_date, a.vo_order_date
FROM vo_order_t a) y ON y.vo_no = b.vo_no and b.item_No IN(30317757, 30322264, 00265929)
GROUP BY b.item_no, y.vo_no
As you can see, I added y.vo_no in the select and the group by. But the result from query is the following:
2019-06-27 30322264 0118113
2017-12-27 00265929 0242712
2019-01-21 30322264 0515768
2017-12-27 00265929 0400026
2019-01-21 30322264 0569606
2018-09-25 00265929 0696864
2019-01-21 30317757 0696619
2019-06-27 30317757 0118113
2017-12-27 00265929 0242624
2017-01-24 00265929 0282971
2019-01-21 30317757 0386202
2019-01-21 30317757 0515706
2019-01-21 30322264 0696619
2017-12-27 00265929 0242625
2017-12-27 00265929 0395347
2017-12-27 00265929 0441449
2019-01-21 30317757 0569605
2017-12-27 00265929 0282972
2017-01-24 00265929 0282984
2019-01-21 30322264 0397256
The distinct on item_no don't work any more.
So my question is: how can I also get the vo_no for the distinct item_no?

You could use an inner join to solve this. There could be an easier way but for now this should work
select x.*,y.vo_no from (
SELECT MAX(b.upd_dtime) as MaxT, b.item_no as Item_NO
FROM vo_item_t b JOIN (
SELECT a.vo_no, a.vo_state, a.vo_confirm_date, a.vo_order_date
FROM vo_order_t a) y ON y.vo_no = b.vo_no and b.item_No IN(30317757, 30322264, 00265929)
GROUP BY b.item_no
)x
INNER JOIN
vo_order_t y
ON x.MaxT = y.Date
AND x.Item_NO = y.Item_No

There's also this solution:
select item_no, max(datetime) mxdt, max(vo_no) keep(dense_rank first order by datetime desc) vo_no
from (select '2019-06-27' datetime, 30322264 item_no, 0118113 vo_no from dual union
select '2017-12-27', 00265929 , 0242712 from dual union
select '2019-01-21', 30322264 , 0515768 from dual union
select '2017-12-27', 00265929 , 0400026 from dual union
select '2019-01-21', 30322264 , 0569606 from dual union
select '2018-09-25', 00265929 , 0696864 from dual union
select '2019-01-21', 30317757 , 0696619 from dual union
select '2019-06-27', 30317757 , 0118113 from dual union
select '2017-12-27', 00265929 , 0242624 from dual union
select '2017-01-24', 00265929 , 0282971 from dual union
select '2019-01-21', 30317757 , 0386202 from dual union
select '2019-01-21', 30317757 , 0515706 from dual union
select '2019-01-21', 30322264 , 0696619 from dual union
select '2017-12-27', 00265929 , 0242625 from dual union
select '2017-12-27', 00265929 , 0395347 from dual union
select '2017-12-27', 00265929 , 0441449 from dual union
select '2019-01-21', 30317757 , 0569605 from dual union
select '2017-12-27', 00265929 , 0282972 from dual union
select '2017-01-24', 00265929 , 0282984 from dual union
select '2019-01-21', 30322264 , 0397256 from dual)
group by item_no
order by item_no
which gives this on your data:
ITEM_NO MXDT VO_NO
---------- ---------- ----------
265929 2018-09-25 696864
30317757 2019-06-27 118113
30322264 2019-06-27 118113
(look up this doc page )

Related

Selecting closest date relative to a a date in another table (Oracle SQL)

How do I select the closest (either before or after) received date from table2 relative to the requested date in table 1? Included the desired result at the end. I am using Oracle SQL.
Table1:
PO RequestedDate
14888 01/12/2018
14733 02/12/2018
14555 05/12/2018
Table2:
PO ReceivedDate
14888 01/11/2018
14888 03/12/2018
14733 12/12/2018
14555 07/23/2018
14555 09/23/2018
Expected Result:
PO RequestedDate NearestReceivedDate
14888 01/12/2018 01/11/2018
14733 02/12/2018 12/12/2018
14555 05/12/2018 07/23/2018
One option is to rank absolute value of date difference (when you subtract two date datatype values, result is number of days) and then fetch the ones that rank as the highest.
Sample data:
SQL> with
2 tab1 (po, requesteddate) as
3 (select 14888, date '2018-01-12' from dual union all
4 select 14733, date '2018-02-12' from dual union all
5 select 14555, date '2018-05-12' from dual
6 ),
7 tab2 (po, receiveddate) as
8 (select 14888, date '2018-01-11' from dual union all
9 select 14888, date '2018-03-12' from dual union all
10 select 14733, date '2018-12-12' from dual union all
11 select 14555, date '2018-07-23' from dual union all
12 select 14555, date '2018-09-23' from dual
13 ),
Query begins here:
14 temp as
15 (select a.po, a.requesteddate, b.receiveddate,
16 rank() over (partition by a.po order by abs(a.requesteddate - b.receiveddate)) rnk
17 from tab1 a join tab2 b on a.po = b.po
18 )
19 select t.po, t.requesteddate, t.receiveddate as nearestreceiveddate
20 from temp t
21 where t.rnk = 1;
PO REQUESTEDD NEARESTREC
---------- ---------- ----------
14555 05/12/2018 07/23/2018
14733 02/12/2018 12/12/2018
14888 01/12/2018 01/11/2018
SQL>
From Oracle 12, you can use a correlated sub-query and fetch only the closest row:
SELECT t1.*,
(
SELECT receiveddate
FROM table2 t2
WHERE t1.po = t2.po
ORDER BY ABS(t1.requesteddate - t2.receiveddate)
FETCH FIRST ROW ONLY
) AS receiveddate
FROM table1 t1;
Which, for the sample data:
CREATE TABLE table1 (po, requesteddate) AS
SELECT 14888, DATE '2018-01-12' FROM DUAL UNION ALL
SELECT 14733, DATE '2018-02-12' FROM DUAL UNION ALL
SELECT 14555, DATE '2018-05-12' FROM DUAL;
CREATE TABLE table2 (po, receiveddate) AS
SELECT 14888, DATE '2018-01-11' FROM DUAL UNION ALL
SELECT 14888, DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 14733, DATE '2018-12-12' FROM DUAL UNION ALL
SELECT 14555, DATE '2018-07-23' FROM DUAL UNION ALL
SELECT 14555, DATE '2018-09-23' FROM DUAL;
Outputs:
PO
REQUESTEDDATE
RECEIVEDDATE
14888
2018-01-12 00:00:00
2018-01-11 00:00:00
14733
2018-02-12 00:00:00
2018-12-12 00:00:00
14555
2018-05-12 00:00:00
2018-07-23 00:00:00
db<>fiddle here
WITH table1 (po, requesteddate)
AS
(
SELECT 14888, TO_DATE('01/12/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14733, TO_DATE('02/12/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14555, TO_DATE('05/12/2018','MM/DD/YYYY') FROM DUAL
),
table2 (po, receiveddate)
AS
(
SELECT 14888, TO_DATE('01/11/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14888, TO_DATE('03/12/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14733, TO_DATE('12/12/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14555, TO_DATE('07/23/2018','MM/DD/YYYY') FROM DUAL UNION ALL
SELECT 14555, TO_DATE('09/23/2018','MM/DD/YYYY') FROM DUAL
), sorted_results (po, requesteddate, receiveddate, rn)
AS
(
select t1.po,
t1.requesteddate,
t2.receiveddate,
row_number() over (partition by t1.po order by ABS(t1.requesteddate - t2.receiveddate)) as rn
from table1 t1 JOIN table2 t2 ON t1.po = t2.po
)
SELECT po, requesteddate, receiveddate as nearestreceiveddate FROM sorted_results WHERE rn = 1;
PO REQUESTEDDA NEARESTRECE
---------- ----------- -----------
14555 12-MAY-2018 23-JUL-2018
14733 12-FEB-2018 12-DEC-2018
14888 12-JAN-2018 11-JAN-2018

Merge Datetime Ranges Oracle SQL or PL/SQL

I have been struggling to merge datetime ranges in oracle SQL or PL/SQL (Database Standard Edition 11gR2).
I am trying to merge datetime ranges so that the following data
order_id start_date_time end_date_time
3933 04/02/2020 08:00:00 04/02/2020 12:00:00
3933 04/02/2020 13:30:00 04/02/2020 17:00:00
3933 04/02/2020 14:00:00 04/02/2020 19:00:00
3933 05/02/2020 13:40:12 05/02/2020 14:34:48
3933 05/02/2020 14:00:00 05/02/2020 18:55:12
3933 05/02/2020 14:49:48 05/02/2020 15:04:48
3933 06/02/2020 08:00:00 06/02/2020 12:00:00
3933 06/02/2020 13:30:00 06/02/2020 17:00:00
3933 06/02/2020 14:10:12 06/02/2020 18:49:48
3933 07/02/2020 08:00:00 07/02/2020 10:30:00
3933 07/02/2020 08:00:00 07/02/2020 12:00:00
3933 07/02/2020 13:30:00 07/02/2020 17:00:00
11919 14/05/2020 09:00:00 14/05/2020 17:00:00
11919 14/05/2020 09:00:00 14/05/2020 17:00:00
11919 14/05/2020 15:00:00 14/05/2020 16:30:00
11919 15/05/2020 08:40:12 15/05/2020 16:30:00
11919 15/05/2020 09:40:12 15/05/2020 16:30:00
11919 15/05/2020 10:15:00 15/05/2020 12:15:00
11919 15/05/2020 13:19:48 15/05/2020 16:00:00
11919 18/05/2020 08:49:48 18/05/2020 09:45:00
11919 18/05/2020 10:00:00 18/05/2020 17:00:00
11919 18/05/2020 10:00:00 18/05/2020 16:58:12
11919 18/05/2020 15:34:48 18/05/2020 16:10:12
11919 18/05/2020 16:30:00 18/05/2020 16:45:00
... ... ...
would transform into the following result set
--after merge (this is the result I am seeking)
order_id start_date_time end_date_time
3933 04/02/2020 08:00:00 04/02/2020 12:00:00
3933 04/02/2020 13:30:00 04/02/2020 19:00:00
3933 05/02/2020 13:40:12 05/02/2020 18:55:12
3933 06/02/2020 08:00:00 06/02/2020 12:00:00
3933 06/02/2020 13:30:00 06/02/2020 18:49:48
3933 07/02/2020 08:00:00 07/02/2020 12:00:00
3933 07/02/2020 13:30:00 07/02/2020 17:00:00
11919 14/05/2020 09:00:00 14/05/2020 17:00:00
11919 15/05/2020 08:40:12 15/05/2020 16:30:00
11919 18/05/2020 08:49:48 18/05/2020 17:00:00
... ... ...
The format of start_date_time and end_date_time is DAY/MONTH/YEAR HH24:MI:SS.
Any suggestion/solution on how to make that merge in Oracle SQL or PL/SQL?
I thought that was a trivial problem, however I was not able to find a solution on the internet yet.
Thanks in advance.
This is adapted from this answer which contains an explanation of the code. All that has changed is to add PARTITION BY order_id to calculate the date ranges for each order_id and then to return the ranges (rather than total the values, as per the linked answer):
SELECT order_id,
start_date_time,
end_date_time
FROM (
SELECT order_id,
LAG( dt ) OVER ( PARTITION BY order_id ORDER BY dt ) AS start_date_time,
dt AS end_date_time,
start_end
FROM (
SELECT order_id,
dt,
CASE SUM( value ) OVER ( PARTITION BY order_id ORDER BY dt ASC, value DESC, ROWNUM ) * value
WHEN 1 THEN 'start'
WHEN 0 THEN 'end'
END AS start_end
FROM table_name
UNPIVOT ( dt FOR value IN ( start_date_time AS 1, end_date_time AS -1 ) )
)
WHERE start_end IS NOT NULL
)
WHERE start_end = 'end';
From Oracle 12, you can use MATCH_RECONIZE to do row-by-row processing:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY order_id
ORDER BY start_date_time
MEASURES
FIRST(start_date_time) AS start_date_time,
MAX(end_date_time) AS end_date_time
ONE ROW PER MATCH
PATTERN (overlapping_rows* last_row)
DEFINE
overlapping_rows AS NEXT(start_date_time) <= MAX(end_date_time)
)
Which, for your test data:
CREATE TABLE table_name (
order_id NUMBER,
start_date_time DATE,
end_date_time DATE
);
INSERT INTO table_name ( order_id, start_date_time, end_date_time )
SELECT 3933, TIMESTAMP '2020-02-04 08:00:00', TIMESTAMP '2020-02-04 12:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-04 13:30:00', TIMESTAMP '2020-02-04 17:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-04 14:00:00', TIMESTAMP '2020-02-04 19:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-05 13:40:12', TIMESTAMP '2020-02-05 14:34:48' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-05 14:00:00', TIMESTAMP '2020-02-05 18:55:12' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-05 14:49:48', TIMESTAMP '2020-02-05 15:04:48' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-06 08:00:00', TIMESTAMP '2020-02-06 12:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-06 13:30:00', TIMESTAMP '2020-02-06 17:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-06 14:10:12', TIMESTAMP '2020-02-06 18:49:48' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-07 08:00:00', TIMESTAMP '2020-02-07 10:30:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-07 08:00:00', TIMESTAMP '2020-02-07 12:00:00' FROM DUAL UNION ALL
SELECT 3933, TIMESTAMP '2020-02-07 13:30:00', TIMESTAMP '2020-02-07 17:00:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-14 09:00:00', TIMESTAMP '2020-05-14 17:00:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-14 09:00:00', TIMESTAMP '2020-05-14 17:00:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-14 15:00:00', TIMESTAMP '2020-05-14 16:30:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-15 08:40:12', TIMESTAMP '2020-05-15 16:30:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-15 09:40:12', TIMESTAMP '2020-05-15 16:30:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-15 10:15:00', TIMESTAMP '2020-05-15 12:15:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-15 13:19:48', TIMESTAMP '2020-05-15 16:00:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-18 08:49:48', TIMESTAMP '2020-05-18 09:45:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-18 10:00:00', TIMESTAMP '2020-05-18 17:00:00' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-18 10:00:00', TIMESTAMP '2020-05-18 16:58:12' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-18 15:34:48', TIMESTAMP '2020-05-18 16:10:12' FROM DUAL UNION ALL
SELECT 11919, TIMESTAMP '2020-05-18 16:30:00', TIMESTAMP '2020-05-18 16:45:00' FROM DUAL;
Which both output:
ORDER_ID | START_DATE_TIME | END_DATE_TIME
-------: | :------------------ | :------------------
3933 | 2020-02-04 08:00:00 | 2020-02-04 12:00:00
3933 | 2020-02-04 13:30:00 | 2020-02-04 19:00:00
3933 | 2020-02-05 13:40:12 | 2020-02-05 18:55:12
3933 | 2020-02-06 08:00:00 | 2020-02-06 12:00:00
3933 | 2020-02-06 13:30:00 | 2020-02-06 18:49:48
3933 | 2020-02-07 08:00:00 | 2020-02-07 12:00:00
3933 | 2020-02-07 13:30:00 | 2020-02-07 17:00:00
11919 | 2020-05-14 09:00:00 | 2020-05-14 17:00:00
11919 | 2020-05-15 08:40:12 | 2020-05-15 16:30:00
11919 | 2020-05-18 08:49:48 | 2020-05-18 09:45:00
11919 | 2020-05-18 10:00:00 | 2020-05-18 17:00:00
db<>fiddle here
The solution below uses a common method known as the "start of group" method.
The idea is to order the intervals by start date (separately for each id), and to assign intervals to groups as follows. For each interval, check if its start time is strictly greater than the MAX of end times of all preceding intervals. If it is, that starts a new group. The rest is easy - just select the MIN start date and the MAX end date from each group.
Here is how this is implemented, using analytic functions:
with
has_sog_flags (order_id, start_date_time, end_date_time, flag) as (
select order_id, start_date_time, end_date_time,
case when start_date_time >
max(end_date_time) over (partition by order_id
order by start_date_time
rows between unbounded preceding and 1 preceding)
then 1 end
from table_name
)
, has_groups (order_id, start_date_time, end_date_time, grp) as (
select order_id, start_date_time, end_date_time,
sum(flag) over (partition by order_id order by start_date_time)
from has_sog_flags
)
select order_id, min(start_date_time) as start_date_time,
max(end_date_time) as end_date_time
from has_groups
group by order_id, grp
order by order_id, start_date_time
;
An interesting question is how to handle open-ended intervals (where for example null for end_date_time means "open ended into the future". The query can be adapted relatively easily to cover such extensions to the problem statement.
This is how I solved. Imagine an ORDERS table with ORDERID, DATE_START and DATE_END. The innermost query (A) gets the previous end date, the second innermost subquery (B) detects overlaps (or better nooverlaps), the third sums nooverlap to create groups (GID). Finally the outermost query aggregates these groups to create the final intervals for each ORDERID.
SELECT C.ORDERID, MIN(C.DATE_START ) DATE_START , MAX(C.DATE_END) DATE_END
FROM
(
SELECT B.*,SUM(B.NOOVERLAP) over(PARTITION BY B.ORDERID order by B.DATE_START ) as GID
FROM
(
SELECT A.*,
CASE WHEN A.PREV_DATE_END >= A.DATE_START THEN 0 ELSE 1 END NOOVERLAP
FROM
(
SELECT ORDERID,DATE_START ,DATE_END,
LAG(DATE_END) OVER(PARTITION BY ORDERID ORDER BY DATE_START ) PREV_DATE_END
FROM ORDERS
) A
) B
) C
GROUP BY C.ORDERID , C.GID

Appending Name based on Min and Max datetime

I have Event_No, Events, Date Range in my table like below.
[Event_No, Events, Date Range
1 PR 2/6/2018 12:01:00 AM
1 PR 2/6/2018 12:02:00 AM
1 PR 2/6/2018 12:03:00 AM
1 RR 2/6/2018 12:04:00 AM
1 RR 2/6/2018 12:05:00 AM
1 RR 2/6/2018 12:06:00 AM
1 SR 2/6/2018 12:07:00 AM
1 SR 2/6/2018 12:08:00 AM
1 SR 2/6/2018 12:09:00 AM
2 PR 2/6/2018 01:01:00 AM
2 PR 2/6/2018 01:02:00 AM
2 PR 2/6/2018 01:03:00 AM
2 RR 2/6/2018 01:04:00 AM
2 RR 2/6/2018 01:05:00 AM
2 RR 2/6/2018 01:06:00 AM
2 SR 2/6/2018 01:07:00 AM
2 SR 2/6/2018 01:08:00 AM
2 SR 2/6/2018 01:09:00 AM
I need to show Min datetime respective Event name with 'IN'(a concatenation of (Event-'IN'))and Max datetime respective Event with Out(a concatenation of (Event-'Out')). I need My Final Output like below
Event_No Events Date Range EventInOut
1 PR 2/6/2018 12:01:00 AM PR-IN
1 PR 2/6/2018 12:03:00 AM PR-OUT
1 RR 2/6/2018 12:04:00 AM RR-IN
1 RR 2/6/2018 12:06:00 AM RR-OUT
1 SR 2/6/2018 12:07:00 AM SR-IN
1 SR 2/6/2018 12:09:00 AM SR-OUT
2 PR 2/6/2018 01:01:00 AM PR-IN
2 PR 2/6/2018 01:03:00 AM PR-OUT
2 RR 2/6/2018 01:04:00 AM RR-IN
2 RR 2/6/2018 01:06:00 AM RR-OUT
2 SR 2/6/2018 01:07:00 AM SR-IN
2 SR 2/6/2018 01:09:00 AM SR-OUT
Thanks
This is a gaps and islands problem.
select event_no, date,
min(date), max(date)
from (select t.*,
row_number() over (partition by event_no order by date) as seqnum,
row_number() over (partition by event_no, event order by date) as seqnum_e
from t
) t
group by event_no, event;
This puts the values on one row, which might meet your needs.
You can also use lead() and lag():
select t.*,
(event || '-' || (case when prev_event is null then 'IN' else 'OUT' end))
from (select t.*,
lag(event) over (partition by event_no order by date) as prev_event,
lead(event) over (partition by event_no order by date) as next_event
from t
) t
where prev_event is null or next_event is null;
The following should get you the results....
create table events(event_no int, events varchar(10),date_range timestamp)
insert into events
select 1,'PR',timestamp '2/6/2018 12:01:00 AM' union all
select 1,'PR',timestamp '2/6/2018 12:02:00 AM' union all
select 1,'PR',timestamp '2/6/2018 12:03:00 AM' union all
select 1,'RR',timestamp '2/6/2018 12:04:00 AM' union all
select 1,'RR',timestamp '2/6/2018 12:05:00 AM' union all
select 1,'RR',timestamp '2/6/2018 12:06:00 AM' union all
select 1,'SR',timestamp '2/6/2018 12:07:00 AM' union all
select 1,'SR',timestamp '2/6/2018 12:08:00 AM' union all
select 1,'SR',timestamp '2/6/2018 12:09:00 AM' union all
select 2,'PR',timestamp '2/6/2018 01:01:00 AM' union all
select 2,'PR',timestamp '2/6/2018 01:02:00 AM' union all
select 2,'PR',timestamp '2/6/2018 01:03:00 AM' union all
select 2,'RR',timestamp '2/6/2018 01:04:00 AM' union all
select 2,'RR',timestamp '2/6/2018 01:05:00 AM' union all
select 2,'RR',timestamp '2/6/2018 01:06:00 AM' union all
select 2,'SR',timestamp '2/6/2018 01:07:00 AM' union all
select 2,'SR',timestamp '2/6/2018 01:08:00 AM' union all
select 2,'SR',timestamp '2/6/2018 01:09:00 AM'
with data
as (
select *
,row_number() over(partition by event_no,events order by date_range) as rnk
,date_range - (row_number() over(partition by event_no,events order by date_range)* interval '1 minute' ) as col_range
from events
)
,iterim_data
as(
select *
,row_number() over(partition by col_range order by date_range asc) as rnk_asc
,row_number() over(partition by col_range order by date_range desc) as rnk_desc
from data
)
select event_no,events,date_range
,case when rnk_asc=1 then concat(events,'-IN')
when rnk_desc=1 then concat(events,'-OUT')
end
from iterim_data
where (rnk_asc=1 or rnk_desc=1)
db fiddle link
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=00304c5f260a199a99c8d43bd351c3a1

Pivot: Create column to capture duplicate cycle dates

I have the below tables. The join from cycle to program is date based.
There are millions of PGMID entries, so I was thinking about pivoting feature but I can't hard code the PGMID. Any thoughts/help would be appreciated.
I do have the ability to edit tables in the db.
Table: Cycle
ID START_CYCLE END_CYCLE
4400 7/22/2018 8/3/2018
4400 8/4/2018 8/5/2018
4400 8/6/2018 8/6/2018
4400 8/7/2018 8/9/2018
4400 8/10/2018 9/6/2018
4400 9/7/2018 9/7/2018
4400 9/8/2018 9/9/2018
4400 9/10/2018 12/31/9999
Table: Program
PGMID START_DT END_DT
101 8/4/2018 9/10/2018
102 9/8/2018 9/8/2018
103 9/10/2018 NULL
Output:
ID START_CYCLE END_CYCLE PGMID
4400 7/22/2018 8/3/2018
4400 8/4/2018 8/5/2018 101
4400 8/6/2018 8/6/2018 101
4400 8/7/2018 8/9/2018 101
4400 8/10/2018 9/6/2018 101
4400 9/7/2018 9/7/2018 101
4400 9/8/2018 9/9/2018 101
4400 9/8/2018 9/9/2018 102
4400 9/10/2018 12/31/9999 103
There are duplicate cycle entries, I do NOT want any repeat dates.
4400 9/8/2018 9/9/2018 101
4400 9/8/2018 9/9/2018 102
Expected output:
ID START_CYCLE END_CYCLE PROGRAM1 PROGRAM2
4400 7/22/2018 8/3/2018
4400 8/4/2018 8/5/2018 101
4400 8/6/2018 8/6/2018 101
4400 8/7/2018 8/9/2018 101
4400 8/10/2018 9/6/2018 101
4400 9/7/2018 9/7/2018 101
4400 9/8/2018 9/9/2018 101 102
4400 9/10/2018 12/31/9999 103
select *
from (
select id, start_cycle, end_cycle, pgmid, case when rn > 5 then 0 else rn end rn
from (
select id, start_cycle, end_cycle, pgmid,
row_number() over (partition by id, start_cycle, end_cycle order by pgmid) rn
from cycle c
left join program p on p.start_dt <= c.end_cycle and c.start_cycle <= p.end_dt ))
pivot (listagg(pgmid, ',') within group (order by pgmid)
for rn in (1 program1, 2 program2, 3 program3, 4 program4, 5 program5, 0 others))
order by id, start_cycle
left join data as you did,
add row_number() partitioned by each cycle and ordered by pgmid,
if this number exceeds some number (in my case it is 5) then assign 0 instead,
make pivot using this column. First five columns are built as always, last which may contain more program is called others
instead of typically used in pivot min or max use listagg
These steps were needed to show all programs if there are more than 5 of them per cycle. All the rest are in others. If you know that there can be no more than, let's say 3 programs, then you can simplify this query.
If you want each program in different column and number of maximum columns is unknown then it's dynamic pivot problem. There are some solutions already described on Stack Overflow, but these are mostly workarounds.
Here is an example where we have up to 8 programs in one cycle:
with
cycle(id, start_cycle, end_cycle) as (
select 4400, date '2018-07-22', date '2018-08-03' from dual union all
select 4400, date '2018-08-04', date '2018-08-05' from dual union all
select 4400, date '2018-08-06', date '2018-08-06' from dual union all
select 4400, date '2018-08-07', date '2018-08-09' from dual union all
select 4400, date '2018-08-10', date '2018-09-06' from dual union all
select 4400, date '2018-09-07', date '2018-09-07' from dual union all
select 4400, date '2018-09-08', date '2018-09-09' from dual union all
select 4400, date '2018-09-10', date '9999-12-31' from dual ),
program(pgmid, start_dt, end_dt) as (
select 101, date '2018-08-04', date '2018-09-10' from dual union all
select 104, date '2018-08-06', date '2018-08-07' from dual union all
select 105, date '2018-08-06', date '2018-08-07' from dual union all
select 106, date '2018-08-06', date '2018-08-07' from dual union all
select 107, date '2018-08-06', date '2018-08-07' from dual union all
select 108, date '2018-08-06', date '2018-08-07' from dual union all
select 109, date '2018-08-06', date '2018-08-07' from dual union all
select 110, date '2018-08-07', date '2018-08-07' from dual union all
select 102, date '2018-09-08', date '2018-09-08' from dual union all
select 103, date '2018-09-10', null from dual )
select *
from (
select id, start_cycle, end_cycle, pgmid, case when rn > 5 then 0 else rn end rn
from (
select id, start_cycle, end_cycle, pgmid,
row_number() over (partition by id, start_cycle, end_cycle order by pgmid) rn
from cycle c
left join program p on p.start_dt <= c.end_cycle and c.start_cycle <= p.end_dt ))
pivot (listagg(pgmid, ',') within group (order by pgmid)
for rn in (1 program1, 2 program2, 3 program3, 4 program4, 5 program5, 0 others))
order by id, start_cycle
Result:
ID START_CYCLE END_CYCLE PROGRAM1 PROGRAM2 PROGRAM3 PROGRAM4 PROGRAM5 OTHERS
----- ----------- ----------- --------- --------- --------- --------- --------- ------------
4400 2018-07-22 2018-08-03
4400 2018-08-04 2018-08-05 101
4400 2018-08-06 2018-08-06 101 104 105 106 107 108,109
4400 2018-08-07 2018-08-09 101 104 105 106 107 108,109,110
4400 2018-08-10 2018-09-06 101
4400 2018-09-07 2018-09-07 101
4400 2018-09-08 2018-09-09 101 102
4400 2018-09-10 9999-12-31 101
dbfiddle demo
1- You must be add "group by START_CYCLE, END_CYCLE"
2- In select section must be add group_concat(PGMID separator ',')
I don't knowledge of oracle for above but in mysql is :
select ..., group_concat(PGMID separator ',') as PGMIDs, ...
from ...
join ...
where ...
group by START_CYCLE, END_CYCLE
I hope to help you.

effective_end_date not 31-dec-4712 in sql query

I have a table tab_assignment_xx
date_from date_end action person_number
01-Apr-2014 31-Jul-2014 HIRE 050498
01-Aug-2014 31-Jan-2015 OTHERS 050498
01-Feb-2015 30-Jun-2015 OTHERS 050498
01-Jul-2015 15-Nov-2015 OTHERS 050498
16-Nov-2015 31-Dec-2015 OTHERS 050498
01-Jan-2016 30-JAN-2016 OTHERS 050498
01-APR-2016 31-JUL-2016 hire 83982
01-jan-2015 31-dec-4712 Others 6447
Now i want to check for all those particular employees with effective_start_date and effective_End_date that it should have 31-dec-4712 as max(effective_end_date) for example : for 050498 the max(effective_end_date) is not '31-dec-4712' and same goes for 83982. 6447 is correct.
for this i made :
select * from (
select T.*,
max(EFFECTIVE_START_DATE) over (partition by PERSON_NUMBER order by EFFECTIVE_START_DATE) MAX_FROM
from tab_assignment_xx T
where 1=1
--T.PERSON_NUMBER = '093343'
AND ASSIGNMENT_TYPE='E'
)
where MAX_FROM <> to_date('31-DEC-4712')
;
But this is not working in the sense that it is both the rows with and without 31-dec-4712 max eff end date
Oracle Setup:
CREATE TABLE table_name ( date_from, date_end, action, person_number ) AS
SELECT DATE '2014-04-01', DATE '2014-07-31', 'HIRE', '050498' FROM DUAL UNION ALL
SELECT DATE '2014-08-01', DATE '2015-01-31', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-02-01', DATE '2015-06-30', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-07-01', DATE '2015-11-15', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2015-11-16', DATE '2015-12-31', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2016-01-01', DATE '2016-01-30', 'OTHERS', '050498' FROM DUAL UNION ALL
SELECT DATE '2016-04-01', DATE '2016-07-31', 'hire', '83982' FROM DUAL UNION ALL
SELECT DATE '2015-01-01', DATE '4712-12-31', 'Others', '6447' FROM DUAL;
Query:
SELECT Date_From, Date_To, action, person_number
FROM (
SELECT t.*,
MAX( date_end )
OVER ( PARTITION BY person_number
ORDER BY ROWNUM
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS mx
FROM table_name t
)
WHERE mx <> DATE '4712-12-31';
Output:
DATE_FROM DATE_END ACTION PERSON_NUMBER
------------------- ------------------- ------ -------------
2014-04-01 00:00:00 2014-07-31 00:00:00 HIRE 050498
2014-08-01 00:00:00 2015-01-31 00:00:00 OTHERS 050498
2015-02-01 00:00:00 2015-06-30 00:00:00 OTHERS 050498
2015-07-01 00:00:00 2015-11-15 00:00:00 OTHERS 050498
2015-11-16 00:00:00 2015-12-31 00:00:00 OTHERS 050498
2016-01-01 00:00:00 2016-01-30 00:00:00 OTHERS 050498
2016-04-01 00:00:00 2016-07-31 00:00:00 hire 83982
Please check if this works(I din't try). Comment if anything wrong.
select * from tab_assignment_xx a where to_date('31-DEC-4712')<>(select max(date_end) from tab_assignment_xx b where a.person_number=b.person_number);