How can I combine records on the basis of some conditions - oracle

I have following table,
id id_concept start_date end_date
----------------------------------------------
100 282 20/06/2016 24/06/2016
100 282 15/07/2016 18/07/2016
300 282 01/09/2016 02/09/2016
I need to combine the records which has same id,id_concept and the time between END_DATE of one record and START_DATE of the next is 30 days or less (<=30)
Also for the combined records, I need to take the start_date as the very first start_date of the record and end _date as the end_date of the last record
The o/p should be like,
id id_concept start_date end_date count
---------------------------------------------------
100 282 20/06/2016 18/07/2016 2
300 282 01/09/2016 02/09/2016 1

SQL Fiddle
PostgreSQL 9.3 Schema Setup:
CREATE TABLE table_name ( id, id_concept, start_date, end_date ) AS
SELECT 100, 282, DATE '2016-06-20', DATE '2016-06-24' UNION ALL
SELECT 100, 282, DATE '2016-07-15', DATE '2016-07-18' UNION ALL
SELECT 300, 282, DATE '2016-09-01', DATE '2016-09-02';
Query 1:
SELECT id,
id_concept,
MIN( start_date ) AS start_date,
MAX( end_date ) AS end_date,
COUNT(*) AS "count"
FROM (
SELECT id,
id_concept,
start_date,
end_date,
SUM( diff ) OVER (
PARTITION BY id, id_concept
ORDER BY start_date, end_date
) AS grp
FROM (
SELECT t.*,
CASE
WHEN LAG( end_date ) OVER (
PARTITION BY id, id_concept
ORDER BY start_date, end_date
) >= start_date - INTERVAL '30' DAY
THEN 0
ELSE 1
END AS diff
FROM table_name t
) t
) t
GROUP BY id, id_concept, grp
Results:
| id | id_concept | start_date | end_date | count |
|-----|------------|-----------------------------|-----------------------------|-------|
| 300 | 282 | September, 01 2016 00:00:00 | September, 02 2016 00:00:00 | 1 |
| 100 | 282 | June, 20 2016 00:00:00 | July, 18 2016 00:00:00 | 2 |

Related

Separating Overlapping Date Ranges in Oracle

I have data with overlapping data ranges. Example below
Customer_ID
FAC_NUM
Start_Date
End_Date
New_Monies
12345
ABC1234
26/NOV/2014
26/MAY/2015
100000
12345
ABC1234
12/DEC/2014
12/JUN/2015
200000
12345
ABC1234
15/JUN/2015
15/DEC/2015
500000
12345
ABC1234
20/DEC/2015
20/JUN/2016
600000
I want to convert this table into data with non overlapping ranges such that for each overlapping period, the New_Monies column is summed together and shown as a new row. For the example above, I want the output to be as follows
Customer_ID
FAC_NUM
Start_Date
End_Date
New_Monies
12345
ABC1234
26/NOV/2014
11/DEC/2014
100000
12345
ABC1234
12/DEC/2014
26/MAY/2015
300000
12345
ABC1234
27/MAY/2015
12/JUN/2015
200000
12345
ABC1234
15/JUN/2015
15/DEC/2015
500000
12345
ABC1234
20/DEC/2015
20/JUN/2016
600000
Row 2 above being the overlapping period of 12 Dec 2014 to 26 May 2015 showing the total New_Monies as 300000 (100000+200000)
What would be the best way to do this in Oracle?
Thanks in advance for your support.
Regards,
Ani
with
prep (customer_id, fac_num, dt, amount) as (
select t.customer_id, t.fac_num,
case h.col when 's' then t.start_date else t.end_date + 1 end as dt,
case h.col when 's' then t.new_monies else - t.new_monies end as amount
from sample_data t
cross join
(select 's' as col from dual union all select 'e' from dual) h
)
, cumul_sums (customer_id, fac_num, dt, amount) as (
select distinct
customer_id, fac_num, dt,
sum(amount) over (partition by customer_id, fac_num order by dt)
from prep
)
, with_intervals (customer_id, fac_num, start_date, end_date, amount) as (
select customer_id, fac_num, dt,
lead(dt) over (partition by customer_id, fac_num order by dt) - 1,
amount
from cumul_sums
)
select customer_id, fac_num, start_date, end_date, amount
from with_intervals
where end_date is not null
order by customer_id, fac_num, start_date
;
The prep subquery unpivots the inputs, while at the same time changing the "end date" to the "start date" of the following interval and assigning a positive amount to the "start date" and the negative of the same amount to the following "start date". cumul_sums computes the cumulative sums; note that if two or more intervals begin on the same date (so the same date from prep appears multiple times for a customer and fac_num), the analytic sum will include the amounts from ALL the rows up to that date - the default windowing clause is range between...... After the cumulative sums are computed, this subquery also de-duplicates the output rows (to handle precisely that complication, of multiple intervals starting on the same date). with_intervals recovers the "start date" - "end date" intervals, and the final step simply removes the last interval ("to infinity") which would have an "amount" of zero.
EDIT This solution answers the OP's original question. After posting the solution, the OP changed the question. The solution can be changed easily to address the new formulation. I'm not going to chase shadows though; the solution will remain as is.
Here is an way to do this.
with all_data
as (select Customer_ID,FAC_NUM,start_date as dt,new_monies as calc_monies
from t
union all
select Customer_ID,FAC_NUM,end_date as dt,new_monies*-1 as calc_monies
from t
)
select x.customer_id
,x.fac_num
,x.start_date
,case when row_number() over(order by end_date desc)=1 then
x.end_date + 1
else x.end_date
end as new_end_date
from (
select t.customer_id
,t.fac_num
,t.dt as start_date
,lead(dt) over(order by dt)-1 as end_date
,sum(calc_monies) over(order by dt) as new_monies
from all_data t
)x
where end_date is not null
order by 3
db fiddle link
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=856c9ac0954e45429994f4ac45699e6f
+-------------+---------+------------+--------------+------------+
| CUSTOMER_ID | FAC_NUM | START_DATE | NEW_END_DATE | NEW_MONIES |
+-------------+---------+------------+--------------+------------+
| 12345 | ABC1234 | 26-NOV-14 | 11-DEC-14 | 100000 |
| 12345 | ABC1234 | 12-DEC-14 | 25-MAY-15 | 300000 |
| 12345 | ABC1234 | 26-MAY-15 | 12-JUN-15 | 200000 |
+-------------+---------+------------+--------------+------------+

ROW_NUMBER over PARTITION BY restart row counter between breaks [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a list of activities that is currently ordered by user, date and time of activity, and ID. I want to generate numbers for each group set by those same fields. Using the following code, I achieve considerable accuracy. However, there's a problem when the same ID is repeated at a later time and I need the row number count to restart instead of continuing from the previous iteration.
Here's my code:
ROW_NUMBER() OVER (PARTITION BY USER_ID, foc_id ORDER BY USER_ID, to_char(activity_date, 'MM/DD/YYYY HH24:MI:SS'), foc_id) seq_nbr
In the image below, we see that FOC_ID "A240" had activity around 2:20PM. Then FOC_ID "B410" had activity around 3:19PM, lastly the user returned to "A240" for additional activity around 3:20. Because there was activity between the first and second sequence of events of "A240," I need the row number (seq_nbr) to restart instead of continuing from the previous activity.
You can use MATCH_RECOGNIZE:
SELECT user_id,
activity_date,
foc_id,
ROW_NUMBER() OVER ( PARTITION BY user_id, mno ORDER BY activity_date ) AS seq_num
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY user_id
ORDER BY activity_date
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
PATTERN ( same_foc_id* last_row )
DEFINE
same_foc_id AS FIRST( foc_id ) = NEXT( foc_id )
)
or, multiple ROW_NUMBERs:
SELECT user_id,
activity_date,
foc_id,
ROW_NUMBER() OVER ( PARTITION BY user_id, foc_id, grp ORDER BY activity_date ) AS seq_num
FROM (
SELECT user_id,
activity_date,
foc_id,
ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY activity_date )
- ROW_NUMBER() OVER ( PARTITION BY user_id, foc_id ORDER BY activity_date ) AS grp
FROM table_name
)
ORDER BY user_id, activity_date
Which, for the sample data:
CREATE TABLE table_name ( user_id, activity_date, foc_id ) AS
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '14:20:34' HOUR TO SECOND, 'A240' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '14:21:23' HOUR TO SECOND, 'A240' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '14:21:23' HOUR TO SECOND, 'A240' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '14:21:23' HOUR TO SECOND, 'A240' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:19:39' HOUR TO SECOND, 'B410' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:19:44' HOUR TO SECOND, 'B410' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:19:58' HOUR TO SECOND, 'B410' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:20:11' HOUR TO SECOND, 'B410' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:22:16' HOUR TO SECOND, 'A240' FROM DUAL UNION ALL
SELECT 'UVAC3', DATE '2020-11-04' + INTERVAL '15:22:33' HOUR TO SECOND, 'A240' FROM DUAL;
Both output:
USER_ID | ACTIVITY_DATE | FOC_ID | SEQ_NUM
:------ | :------------------ | :----- | ------:
UVAC3 | 2020-11-04 14:20:34 | A240 | 1
UVAC3 | 2020-11-04 14:21:23 | A240 | 2
UVAC3 | 2020-11-04 14:21:23 | A240 | 3
UVAC3 | 2020-11-04 14:21:23 | A240 | 4
UVAC3 | 2020-11-04 15:19:39 | B410 | 1
UVAC3 | 2020-11-04 15:19:44 | B410 | 2
UVAC3 | 2020-11-04 15:19:58 | B410 | 3
UVAC3 | 2020-11-04 15:20:11 | B410 | 4
UVAC3 | 2020-11-04 15:22:16 | A240 | 1
UVAC3 | 2020-11-04 15:22:33 | A240 | 2
db<>fiddle here

Want to ROUND the Data according to DAY difference

Query :
select
TO_CHAR((to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+ (level-1)),'DD-MM-YYYY'),
TO_CHAR(to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS') + level,'DD-MM-YYYY') ,
to_number(regexp_substr(IP_PLAN_CONSUMPTION, '^\d+'))/(TO_DATE(IP_END_DATE, 'DD-MM-YYYY HH24:MI:SS') - TO_DATE(IP_START_DATE, 'DD-MM-YYYY HH24:MI:SS')) || regexp_substr(IP_PLAN_CONSUMPTION, '[A-Z]') as IP_PLAN_CONSUMPTION
FROM
dual
CONNECT BY
level <= to_date(IP_END_DATE,'DD-MM-YYYY HH24:MI:SS')-to_date(IP_START_DATE,'DD-MM-YYYY HH24:MI:SS')+1;
-> Data in Query :
select
TO_CHAR((to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS')+ (level-1)),'DD-MM-YYYY'),
TO_CHAR(to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS') + level,'DD-MM-YYYY'),
to_number(regexp_substr('4000 T', '^\d+'))/(TO_DATE('18-07-2018 00:00', 'DD-MM-YYYY HH24:MI:SS') - TO_DATE('16-07-2018 11:02', 'DD-MM-YYYY HH24:MI:SS')) || regexp_substr('4000 T', '[A-Z]') as IP_PLAN_CONSUMPTION
FROM
dual
CONNECT BY
level <= to_date('18-07-2018 00:00','DD-MM-YYYY HH24:MI:SS')-to_date('16-07-2018 11:02','DD-MM-YYYY HH24:MI:SS')+1;
Output will Be :
But its should be 2000 T
Not : If Start Date: 16-07-2018 00:00 & End Date : 19-07-2018 00:00 then Day Difference is 3 Days & Consumption is 4000 T then Inserted Consumption Should be 1333.333333333333 T ~ 1334 T in each date.
If you are storing dates, you should store them in your table as the DATE data type (and not as strings).
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE your_table( id, ip_start_date, ip_end_date, ip_plan_consumption ) AS
SELECT 1,
DATE '2018-07-16' + INTERVAL '11:02' HOUR TO MINUTE,
DATE '2018-07-18' + INTERVAL '00:00' HOUR TO MINUTE,
'4000 T'
FROM DUAL
UNION ALL
SELECT 2,
DATE '2018-07-16' + INTERVAL '11:02' HOUR TO MINUTE,
DATE '2018-07-16' + INTERVAL '23:08' HOUR TO MINUTE,
'3000 T'
FROM DUAL
UNION ALL
SELECT 3,
DATE '2018-07-10' + INTERVAL '00:00' HOUR TO MINUTE,
DATE '2018-07-13' + INTERVAL '23:59' HOUR TO MINUTE,
'15000 U'
FROM DUAL
;
Query 1:
WITH data ( id, start_dt, end_dt, consumption, units ) AS (
SELECT id,
TRUNC( IP_START_DATE ),
GREATEST( TRUNC( IP_START_DATE ) + 1, TRUNC( IP_END_DATE ) ),
TO_NUMBER( REGEXP_SUBSTR( IP_PLAN_CONSUMPTION, '^\d+' ) ),
REGEXP_SUBSTR( IP_PLAN_CONSUMPTION, '\S+$' )
FROM your_table
)
SELECT id,
t.column_value AS start_dt,
t.column_value + 1 AS end_dt,
consumption / ( end_dt - start_dt ) || units AS IP_PLAN_CONSUMPTION
FROM data d
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT d.start_dt + LEVEL - 1
FROM DUAL
CONNECT BY d.start_dt + LEVEL - 1 < d.end_dt
)
AS SYS.ODCIDATELIST
)
) t
Results:
| ID | START_DT | END_DT | IP_PLAN_CONSUMPTION |
|----|----------------------|----------------------|---------------------|
| 1 | 2018-07-16T00:00:00Z | 2018-07-17T00:00:00Z | 2000T |
| 1 | 2018-07-17T00:00:00Z | 2018-07-18T00:00:00Z | 2000T |
| 2 | 2018-07-16T00:00:00Z | 2018-07-17T00:00:00Z | 3000T |
| 3 | 2018-07-10T00:00:00Z | 2018-07-11T00:00:00Z | 5000U |
| 3 | 2018-07-11T00:00:00Z | 2018-07-12T00:00:00Z | 5000U |
| 3 | 2018-07-12T00:00:00Z | 2018-07-13T00:00:00Z | 5000U |

Finding no. of working days in a month till sysdate/ today's date is it is current month

I am using the following query to find out the number of working days in a month excluding the weekends
select payroll_id as payrollId,
(select count(*)
from ( select rownum rnum
from all_objects
where rownum <= to_date(to_char(last_day(to_date('01-'||'MAY'||'2017','DD-MM-YYYY')),'DD')||'MAY'||'2017','DD-MM-YYYY') - to_date('01-'||'MAY'||'2017','DD-MM-YYYY')+1 )
where to_char( to_date('01-'||'MAY'||'2017','DD-MM-YYYY')+rnum-1, 'DY' )
not in ( 'SAT', 'SUN' )) - (select count(*) from admin_holiday where to_char(holiday_date,'DD-MON-YYYY') like '%-MAY-2017%' and holiday_type_id=1) days
from employee where DEL_FLAG=1 order by payrollId
Here, payrollId is the employee_id in employee table and admin_holiday is a table that contains information about the national holidays in a month
My requirement is that if the month is current month then the working days should be upto today's date. For example the current month is MAY/2017 and till today(i.e 22/05/2017) the working days count excluding weekends is 15 (10/05/2017 is a national holiday according to Hindu calendar.
How do I obtain the required result.
NOTE this action has to be performed in a single select statement and no other pl/sql blocks, etc.
Here is how I would approach the equivalent query. By placing the date range calculation into a small cross joined query you can access the result easily throughout the remainder of the query.
SELECT
e.payroll_id AS payrollId
, cj. working_days
FROM employee e
cross join (
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
FROM (
select
to_date('20170501','yyyymmdd') as start_date
, to_date('20170522','yyyymmdd') as end_date
from dual
)
) cj
WHERE e.DEL_FLAG = 1
ORDER BY e.payrollId
The working days logic is:
Calculate leading days in first week + (Count the number on mon/fri periods) * 5 - number of trailing days in final week - the additional holidays in a table:
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
, start_date
, end_date
, holidays
, end_date - start_date
FROM (
select
trunc(sysdate,'MM') as start_date
, trunc(sysdate) as end_date
from dual
)
example (not there is zero holidays here):
+--------------+--------------+-------------+------------+-------------------------+
| WORKING_DAYS | START_DATE | END_DATE | HOLIDAYS | END_DATE-START_DATE |
+--------------+--------------+-------------+------------+-------------------------+
| 16 | 01.05.2017 | 22.05.2017 | 0 | 21 |
+--------------+--------------+-------------+------------+-------------------------+
To manually set the dates you can do this:
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
FROM (
select
to_date('20170501','yyyymmdd') as start_date
, to_date('20170522','yyyymmdd') as end_date
from dual
)

Convert columns to rows in oracle [duplicate]

This question already has answers here:
Oracle SQL pivot query
(4 answers)
Closed 8 years ago.
this is my table in oracle 11g:
**date qty1 qty2 qty3 qty4**
2-Feb-14 61 64 52 54
2-Mar-14 124 130 149 156
i want to convert it into the following table. i.e. add 7 days to the date and transpose the qty. And i have till qty52 such metrics
***date qty***
**2-Feb-14 61**
9-Feb-14 64
16-Feb-14 52
23-Feb-14 54
**2-Mar-14 124**
9-Mar-14 130
16-Mar-14 149
23-Mar-14 156
have a try:
WITH t(my_date, val, val2, val3, val4)
AS (
SELECT to_date('01/01/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 1,2,3,4 from dual
UNION ALL
SELECT to_date('01/02/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 5,6,7,8 FROM dual
)
SELECT (my_date-7) + (row_number() OVER (partition by my_date ORDER BY my_date)*7) my_date, value as qty
FROM (
( SELECT my_date, val, val2, val3, val4 FROM t
) unpivot ( value FOR value_type IN (val, val2, val3, val4) ) );
output:
MY_DATE QTY
----------------------- ----------
01/01/2014 12:00:00 AM 1
08/01/2014 12:00:00 AM 2
15/01/2014 12:00:00 AM 3
22/01/2014 12:00:00 AM 4
01/02/2014 12:00:00 AM 5
08/02/2014 12:00:00 AM 6
15/02/2014 12:00:00 AM 7
22/02/2014 12:00:00 AM 8
select date,qty from
(select date,qty1 as qty
from tbl
union
select date+7 as date,qty2 as qty
from tbl
union
select date+14 as date,qty3 as qty
from tbl
union
select date+21 as date,qty4 as qty
from tbl)
order by date
If you've got Oracle 11g, I'd look at doing it with UNPIVOT.
select
start_date + to_number(week_number) * 7,
qty
from (
select *
from quantity_data
unpivot (qty for week_number
in (qty1 as '0', qty2 as '1', qty3 as '2', qty4 as '3'))
)
This is an alternative to the example from ajmalmhd04, using to_number instead of the row_number analytic function. The answer from ajmalmhd04 is probably more generic though
If you haven't got Oracle 11g then try this for an option:
with pivot_data as (
select 0 as pivot_col from dual union all
select 1 from dual union all
select 2 from dual union all
select 3 from dual
)
select
start_date + (7 * pivot_col) as start_date,
case
when pivot_col = 0 then qty1
when pivot_col = 1 then qty2
when pivot_col = 2 then qty3
when pivot_col = 3 then qty4 end as qty
from
quantity_data cross join pivot_data
order by 1
Try this
with tab(date_d,qty1,qty2,qty3,qty4) as (
select '2-Feb-14',61,64,52,54 from dual union all
select '2-Mar-14',124,130,149,156 from dual),
tab2(dd, ss) as (select date_d, qty1||','||qty2||','||qty3||','||qty4 from tab)
select to_date(dd) + ((level-1) * 7) "DATE", regexp_substr(ss, '[^(,)]+', 1, level) "QTY"
from tab2
connect by level <= length(ss) - length(replace(ss, ',')) + 1
and prior ss = ss
and prior sys_guid() is not null
output
| DATE | QTY |
|---------------------------------|-----|
| March, 02 2014 00:00:00+0000 | 124 |
| March, 09 2014 00:00:00+0000 | 130 |
| March, 16 2014 00:00:00+0000 | 149 |
| March, 23 2014 00:00:00+0000 | 156 |
| February, 02 2014 00:00:00+0000 | 61 |
| February, 09 2014 00:00:00+0000 | 64 |
| February, 16 2014 00:00:00+0000 | 52 |
| February, 23 2014 00:00:00+0000 | 54 |
Let me know if it meets your requirement.

Resources