PL/SQL
Shyam Baviskar, December 14, 2018 - 7:20 am UTC
Thanks Chris!
But the first With clause of dates should be specific to ID.
It might be the case that set of dates will be different for other ID's, so we can't always rely on that.
Apologize for this but suppose If I have data like below:
CREATE TABLE PRDCT (ID NUMBER(10,0),
PRDCT_CD VARCHAR2(1),
LOB_CD VARCHAR2(2),
START_DT DATE,
END_DT DATE);
And data I have in this:
Insert Into PRDCT Values (1,'V',to_date('01-May-2017'),to_date('31-Jul-2017'),'DO')
Insert Into PRDCT Values (1,'D',to_date('01-May-2017'),to_date('31-Aug-2017'),'DO')
Insert Into PRDCT Values (1,'D',to_date('01-May-2017'),to_date('31-Dec-9999'),'DM')
Here start_date and end_date should be specific to the combination of ID and Consider LOB_CD only for 'D' and not for 'V'
And result should be like below
ID DO_IND DM_IND VS_IND START_DT END_DT
-------------------------------------------------
1 Y Y Y 01-MAY-17 31-JUL-17
1 N Y Y 01-Aug-17 31-Aug-17
1 N N Y 01-Sep-17 31-Dec-99
December 17, 2018 - 10:52 am UTC
if you need to consider ID values separately, take the distinct ID and date in the unpivot and partition by ID in the analytic functions.
PL/SQL
Shyam, December 17, 2018 - 6:46 am UTC
Hi Chris,
Thanks So Much! It is working as expected now.
I have another scenario like below
ID PRDCT_CD LOB_CD START_DT END_DT
---------------------------------------------
1 A DO 01-MAY-17 31-DEC-99
1 B DM 01-JUN-17 31-DEC-99
And expected result should be
ID DO_IND DM_IND VIS_IND START_DT END_DT
----------------------------------------------------
1 Y N N 01-MAY-17 31-JUL-17
1 Y Y N 01-AUG-17 31-DEC-99
Could you please help to solve this?
December 17, 2018 - 10:52 am UTC
What exactly have you tried? Why is this not working?
PL/SQL
Shyam, December 17, 2018 - 2:24 pm UTC
Hi Chris,
First scenario was working as expected but below scenario is not.
Scenario:
ID PRDCT_CD LOB_CD START_DT END_DT
---------------------------------------------
1 A DO 01-MAY-17 31-DEC-99
1 B DM 01-JUN-17 31-DEC-99
Expected result should be:
Scenario-1
ID A_IND B_IND C_IND START_DT END_DT
----------------------------------------------------
1 Y N N 01-MAY-17 31-JUL-17
1 Y Y N 01-AUG-17 31-DEC-99
Could you please help me to solve this? It's very urgent
PL/SQL
Shyam, December 17, 2018 - 4:22 pm UTC
Hi Chris,
For above scenario, I tried just static logic as below to achieve expected result.
ranges as (
select start_dt,
case when (extract day from end_dt)=1 then end_dt-1 else end_dt
from
(select dt start_dt,
lead ( dt ) over ( order by dt ) end_dt
from dates)
)
And then assigned rn = 1 when start_dt start from day 1 as below
rws as (
select gr.*,
case when extract (day from start_dt) = 1 then 1 else rn as rn
from
(select g.*,
row_number () over (
partition by prdct_cd
order by start_dt
) rn
from grps g
where end_dt is not null) gr
))
But this approach is static one and not correct as start_dt and end_dt can have any date in the table.
I tried with LAG/LEAD options as well but not sure how to achieve the expected result for scenario-2.
The logic should work for below scenarios
Scenario-1 : start_dt are same and end_dt are different
(This is working with the logic you provided)
Scenario-2 : start_dt are different and end_dt are same
Scenario-3 : start_dt and end_dt both are different.
It would be very appreciated if you help to solve for 2nd and 3rd scenarios as well within same query.
December 20, 2018 - 5:34 pm UTC
Does this work for you:
- Add the source column to the original unpivot
- Pass this through all the subqueries
- After the final pivot, adjust the start/end dates like so:
- If the source is END_DT, add one to the start date
- If the source for the next row is START_DT, subtract one from the end date
delete prdct;
Insert Into PRDCT Values (1,'A',to_date('01-May-2017'),to_date('31-dec-9999'));
Insert Into PRDCT Values (1,'B',to_date('01-Jun-2017'),to_date('31-dec-9999'));
with dates as (
select distinct col, dt
from prdct
unpivot (
dt for col in ( start_dt, end_dt )
)
), ranges as (
select col,
dt start_dt,
lead ( dt ) over ( order by dt ) end_dt
from dates
), grps as (
select distinct p.prdct_cd, p.id, d.start_dt, d.end_dt, col
from ranges d
left join prdct p
on p.start_dt <= d.start_dt
and p.end_dt >= d.end_dt
), rws as (
select g.*
from grps g
where end_dt is not null
), flags as (
select col, id,
start_dt, end_dt,
nvl ( a, 'N' ) a,
nvl ( b, 'N' ) b,
nvl ( c, 'N' ) c
from rws
pivot (
max ( case when prdct_cd is not null then 'Y' end )
for prdct_cd in ( 'A' a, 'B' b, 'C' c)
)
)
select col, id,
case
when col = 'END_DT' then start_dt + 1
else start_dt
end start_dt,
case
when lead ( col ) over (
partition by id
order by start_dt
) = 'START_DT'
then end_dt - 1
else end_dt
end end_dt,
a, b, c
from flags
order by start_dt;
COL ID START_DT END_DT A B C
START_DT 1 01-MAY-2017 00:00:00 31-MAY-2017 00:00:00 Y N N
START_DT 1 01-JUN-2017 00:00:00 31-DEC-9999 00:00:00 Y Y N
PL/SQL
Shyam, January 18, 2019 - 7:11 am UTC
Thanks so much Chris!
It's working as expected.
Sorry for delay in response.
PL/SQL
Shyam Baviskar, May 03, 2019 - 3:13 pm UTC
Hi Tom,
I am facing some performance issue and need your help to optimize below query:
I have audit table like below
CREATE AUDIT_TAB
(
HIST_ROW_ID NUMBER(10,0),
HIST_CREATE_DTM TIMESTAMP(3) DEFAULT SYSDATE,
HIST_IMAGE_CD VARCHAR2(1) NOT NULL,
PR_ID VARCHAR2(12) NOT NULL,
PR_LONG NUMBER(10,0) NOT NULL,
PR_LAT NUMBER(10,0) NOT NULL
)
And here are the index applied as below:
CREATE UNIQUE INDEX INDX_AUDIT_PRIM ON AUDIT_TAB (HIST_ROW_ID ASC)
CREATE NON UNIQUE INDEX INDX_HIST_CREATE_DTM ON AUDIT_TAB (HIST_CREATE_DTM ASC, HIST_IMAGE_CD ASC)
Insert Script:
INSERT INTO AUDIT_TAB VALUES (1, TO_TIMESTAMP('01-JAN-19 02.50.01 PM','DD-MON-RR HH.MI.SSXFF AM'),'B','PR1','112233','112234')
INSERT INTO AUDIT_TAB VALUES (2, TO_TIMESTAMP('02-JAN-19 03.20.31 PM','DD-MON-RR HH.MI.SSXFF AM'),'A','PR1','112235','112236')
INSERT INTO AUDIT_TAB VALUES (3, TO_TIMESTAMP('02-JAN-19 03.20.33 PM','DD-MON-RR HH.MI.SSXFF AM'),'B','PR1','112235','112236')
INSERT INTO AUDIT_TAB VALUES (4, TO_TIMESTAMP('03-JAN-19 01.15.10 PM','DD-MON-RR HH.MI.SSXFF AM'),'A','PR1','112233','112234')
Table Data:
SELECT * FROM AUDIT_TAB;
HIST_ROW_ID HIST_CREATE_DTM HIST_IMAGE_CD PR_ID PR_LONG PR_LAT
1 01-JAN-19 02.50.01 PM B PR1 112233 112234
2 02-JAN-19 03.20.31 PM A PR1 112235 112236
3 02-JAN-19 03.20.33 PM B PR1 112235 112236
4 03-JAN-19 01.15.10 PM A PR1 112233 112234
Requirement : Capture quarterly data from audit table and get the count for how many times Longitude (PR_LONG) and Latitude (PR_LAT) change against each PR_ID based on before and after.
Expected Result : For above example the count should written 2
For this I have written below query and it is working as expected, but it is taking 25-30 min to execute.
Note: This table has millions of data.
SELECT SUM(CASE WHEN PR_LONG = LEAD_LONG AND PR_LAT = LEAD_LAT
THEN 0
ELSE 1
END) - COUNT(DISTINCT PR_ID) CHG_CNT
FROM
(SELECT PR_ID,
PR_LONG,
PR_LAT,
HIST_IMAGE_CD,
NVL(LEAD(PR_LONG) OVER (PARTITION BY PR_ID ORDER BY HIST_ROW_ID),99999) LEAD_LONG,
NVL(LEAD(PR_LAT) OVER (PARTITION BY PR_ID ORDER BY HIST_ROW_ID),99999) LEAD_LAT
FROM AUDIT_TAB
WHERE HIST_CREATE_DTM BETWEEN ADD_MONTHS(TRUNC(SYSDATE, 'Q'),-3) AND TRUNC(SYSDATE, 'Q') -1 / 24*60*60);
Appreciate if you help to optimize above query.
May 10, 2019 - 8:28 am UTC
To help with performance questions we need to see the query's execution plan.
To get this run:
set serveroutput off
alter session set statistics_level = all;
<your query>
select *
from table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST'));
And submit a new question with the output of this.
If you want to know more about getting execution plans, see:
https://blogs.oracle.com/sql/how-to-create-an-execution-plan
with analytics
Rajeshwaran, Jeyabal, May 13, 2019 - 3:29 pm UTC
was reading the initial response.
- Unpivot the start/end dates to a single list of dates. Return the distinct dates
- Convert this back into start/end periods by returning the date and the next date (with lead)
- Outer join the products to this based on those that fall in each start/end period
- Pivot the rows based on the product codes you want to display. In the aggregate, return 'Y' if the code is non-null
- Map the null flags to N and do some date twiddling to get the ranges to be closed-closed intervals
rather than all these, how about doing a cross row comparision using analytics? something like this. Kindly advice.
demo@PDB1> select id,
2 case when end_dt <= flag_a then 'Y' else 'N' end a_prod_ind,
3 case when end_dt <= flag_b then 'Y' else 'N' end b_prod_ind,
4 case when end_dt <= flag_c then 'Y' else 'N' end c_prod_ind,
5 start_dt,end_dt
6 from (
7 select id,start_dt,end_dt,
8 greatest( last_value( a_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
9 last_value( a_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_a ,
10 greatest( last_value( b_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
11 last_value( b_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_b ,
12 greatest( last_value( c_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
13 last_value( c_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_c
14 from (
15 select *
16 from (
17 select t.*, start_dt x1,end_dt x2 from t
18 )
19 pivot(
20 max(x1) as new_start_dt,
21 max(x2) as new_end_dt
22 for prdct_cd in ('A' as a,'B' as b,'C' as c) )
23 )
24 )
25 /
ID A B C START_DT END_DT
---------- - - - ----------- -----------
1 Y Y Y 01-MAY-2017 31-JUL-2017
1 N Y Y 01-MAY-2017 31-AUG-2017
1 N N Y 01-MAY-2017 31-DEC-9999
demo@PDB1>
demo@PDB1> select * from t;
ID P START_DT END_DT
---------- - ----------- -----------
1 A 01-MAY-2017 31-JUL-2017
1 B 01-MAY-2017 31-AUG-2017
1 C 01-MAY-2017 31-DEC-9999
demo@PDB1>
demo@PDB1>
May 14, 2019 - 1:35 pm UTC
This doesn't generate all the date combinations. For example, if you move the start_date of B:
update t
set start_dt = date'2017-06-01'
where prdct_cd = 'B';
select id,
case when end_dt <= flag_a then 'Y' else 'N' end a_prod_ind,
case when end_dt <= flag_b then 'Y' else 'N' end b_prod_ind,
case when end_dt <= flag_c then 'Y' else 'N' end c_prod_ind,
start_dt,end_dt
from (
select id,start_dt,end_dt,
greatest( last_value( a_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
last_value( a_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_a ,
greatest( last_value( b_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
last_value( b_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_b ,
greatest( last_value( c_new_start_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ,
last_value( c_new_end_dt ignore nulls) over(order by end_dt rows between unbounded preceding and unbounded following) ) as flag_c
from (
select *
from (
select t.*, start_dt x1,end_dt x2 from t
)
pivot(
max(x1) as new_start_dt,
max(x2) as new_end_dt
for prdct_cd in ('A' as a,'B' as b,'C' as c) )
)
)
/
ID A_PROD_IND B_PROD_IND C_PROD_IND START_DT END_DT
1 Y Y Y 01-MAY-2017 31-JUL-2017 00:00:00
1 N Y Y 01-JUN-2017 31-AUG-2017 00:00:00
1 N N Y 01-MAY-2017 31-DEC-9999 00:00:00
There should be four rows in the output, covering the following date ranges:
1 May - 31 May
1 Jun - 31 Jul
1 Aug - 31 Aug
1 Sep - 31 Dec
PL/SQL
Shyam Baviskar, February 05, 2020 - 1:22 pm UTC
Hi Chris,
Thanks a lot for your continuous support!
Need one more help to get below result.
Q : How to adjust the string (after the specific patter of the original string) to 8 characters.
Ex.1. Input String = 'MFT123456' --9 Characters
Output String = 'MF123456' --restricted to 8 Characters after removing 'T' from original string.
Ex.2. Input String = 'MFT1234567' --9 Characters
Output String = 'M1234567' --restricted to 8 Characters after removing 'FT' from original string.
and so on...
Appreciate if you could help on this.
February 05, 2020 - 3:05 pm UTC
You'll need some combination of substr, replace, and/or regexp_substr. I'm not clear what your replacement rules are. So post a new question explaining what you're trying to do if you're stuck.
PL/SQL
Shyam Baviskar, February 06, 2020 - 7:31 am UTC
Hi Chris,
Apologize for the confusion.
Here are few examples. I want to restrict the output string to 8 characters by adjusting all the digits (after 'MFT' in below example) from Input string.
Ex.1. Input String is 'MFT123456'
Expected output String = 'MF123456'
Ex.2. Input String is 'LCC1234567'
Expected output String = 'L1234567'
I tried to achieve this using below logic. But it's not working for 2nd example and looking for dynamic solution for this.
SELECT CASE WHEN LENGTH('MFT123456') > 8
THEN CASE WHEN SUBSTR('MFT123456', 1,3) = 'MFT' THEN SUBSTR('MFT123456',1,2) || SUBSTR('MFT123456',4)
END
END AS PIN;
February 06, 2020 - 1:14 pm UTC
I'm still not clear on how you decide which characters to keep.
But in any case, this is unrelated to the original question. Please post this as a NEW question (not a review) when questions are open again.