I have a problem that I do not know how is the best way to solve it:
I have the following information:
PRODUCT
IS_BUY
DATE
A
1
01/01/2021
A
1
02/01/2021
A
1
03/01/2021
A
0
04/01/2021
A
1
05/01/2021
A
1
06/01/2021
B
1
01/01/2021
C
1
01/01/2021
C
0
02/01/2021
C
0
03/01/2021
C
1
04/01/2021
I need the following result to be left where for each product the date between where it exists or does not buy:
PRODUCT
IS_BUY
FROM_DATE
TO_DATE
A
1
01/01/2021
04/01/2021
A
0
04/01/2021
05/01/2021
A
1
05/01/2021
null
B
1
01/01/2021
null
C
1
01/01/2021
02/01/2021
C
0
02/01/2021
04/01/2021
C
1
04/01/2021
null
Thanks for your help!
You didn't tell us your Oracle version. Below I will show two solutions: first the more advanced one, using the match_recognize clause, added in Oracle 12.1, and then the older approach, using the Tabibitosan method for solving gaps-and-islands problems (the class that your problem belongs to).
Data setup:
create table my_data (product, is_buy, eff_date) as
select 'A', 1, to_date('01/01/2021', 'mm/dd/yyyy') from dual union all
select 'A', 1, to_date('02/01/2021', 'mm/dd/yyyy') from dual union all
select 'A', 1, to_date('03/01/2021', 'mm/dd/yyyy') from dual union all
select 'A', 0, to_date('04/01/2021', 'mm/dd/yyyy') from dual union all
select 'A', 1, to_date('05/01/2021', 'mm/dd/yyyy') from dual union all
select 'A', 1, to_date('06/01/2021', 'mm/dd/yyyy') from dual union all
select 'B', 1, to_date('01/01/2021', 'mm/dd/yyyy') from dual union all
select 'C', 1, to_date('01/01/2021', 'mm/dd/yyyy') from dual union all
select 'C', 0, to_date('02/01/2021', 'mm/dd/yyyy') from dual union all
select 'C', 0, to_date('03/01/2021', 'mm/dd/yyyy') from dual union all
select 'C', 1, to_date('04/01/2021', 'mm/dd/yyyy') from dual
;
(By the way, this is the preferred way to include sample data in your posts!)
Note that date is a reserved keyword; I changed the column name to eff_date.
First solution, using match_recognize to match patterns in the data:
select product, is_buy, eff_date, end_date
from my_data
match_recognize(
partition by product
order by eff_date
measures a.is_buy as is_buy,
a.eff_date as eff_date,
next(eff_date) as end_date
pattern ( a b* )
define b as is_buy = a.is_buy
);
PRODUCT IS_BUY EFF_DATE END_DATE
------- ------ ---------- ----------
A 1 01/01/2021 04/01/2021
A 0 04/01/2021 05/01/2021
A 1 05/01/2021
B 1 01/01/2021
C 1 01/01/2021 02/01/2021
C 0 02/01/2021 04/01/2021
C 1 04/01/2021
Second solution, using only analytic functions and aggregation (Tabibitosan method):
with prep as (
select product, is_buy, eff_date,
lead(eff_date) over (partition by product
order by eff_date) as next_date,
row_number() over (partition by product order by eff_date) -
row_number() over (partition by product, is_buy order by eff_date)
as grp
from my_data
)
select product, is_buy, min(eff_date) as eff_date,
max(next_date) keep (dense_rank last order by eff_date) as end_date
from prep
group by product, is_buy, grp
order by product, eff_date
;
Try this
Select
Product,
Is_Buy,
(select min(x.DATE) from products x where x.product=a.product and x.is_buy=a.is_buy) from_date,
(select max(x.DATE) from products x where x.product=a.product and x.is_buy=a.is_buy) to_date,
from products a
group by a.Product, a.Is_Buy
Related
I have data like this my table
2020-01-01 H
2020-01-02 B
2020-01-03 B
2020-01-04 B
.
2020-01-29 B
2020-01-30 H
2020-01-31 H
2020-01-02 H
2020-02-02 H
2020-02-03 B
2020-02-04 B
2020-02-05 B
.
now my problem is in the current month i need to check third business day i.e in this case 2020-02-05 i need to get last business day of last month. i.e.2020-01-29
By adding 2 columns:
row_number() over(partition by trunc(date_value,'MM'), day_type order by date_value) as rn_month_asc,
row_number() over(partition by trunc(date_value,'MM'), day_type order by date_value desc) as rn_month_desc
in a month the 3rd business day will have rn_month_asc=3 and day_type ='B' and the latest business day will have rn_month_desc=1 and day_type ='B', and easy to query other situations if you need to.
in the current month I need to check third business day
From Oracle 12, you can use:
SELECT date_value
FROM table_name
WHERE TRUNC(SYSDATE, 'MM') <= date_value
AND date_value < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1)
AND day_type = 'B'
ORDER BY date_value ASC
OFFSET 2 ROWS
FETCH NEXT ROW ONLY;
Which, for the sample data:
CREATE TABLE table_name (date_value, day_type) AS
SELECT DATE '2020-01-01', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-02', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-03', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-04', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-05', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-28', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-29', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-01-30', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-31', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-01-02', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-02-02', 'H' FROM DUAL UNION ALL
SELECT DATE '2020-02-03', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-02-04', 'B' FROM DUAL UNION ALL
SELECT DATE '2020-02-05', 'B' FROM DUAL;
If the current month was 2020-01 then the output is:
DATE_VALUE
04-JAN-20
I need to get last business day of last month
SELECT date_value
FROM table_name
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1) <= date_value
AND date_value < TRUNC(SYSDATE, 'MM')
AND day_type = 'B'
ORDER BY date_value DESC
FETCH FIRST ROW ONLY;
If the current month is 2020-02 then the output is:
DATE_VALUE
29-JAN-20
fiddle
I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>
I need to number the rows so that the row number with the same ID is the same. For example:
Oracle database. Any ideas?
Use the DENSE_RANK analytic function:
SELECT DENSE_RANK() OVER (ORDER BY id) AS row_number,
id
FROM your_table
Which, for the sample data:
CREATE TABLE your_table ( id ) AS
SELECT 86325 FROM DUAL UNION ALL
SELECT 86325 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86352 FROM DUAL UNION ALL
SELECT 86353 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL;
Outputs:
ROW_NUMBER
ID
1
86325
1
86325
2
86326
2
86326
3
86352
4
86353
5
86354
5
86354
db<>fiddle here
I have a situation where I need to split 'n' rows in to column group. For example, Below is dataset
COMMENT_TEXT
T1
T2
T3
T4
T5
T6
Expected Output:
SUN MON TUE
T1 T2 T3
T4 T5 T6
My Query:
SELECT htbp1.comment_text
FROM hxc_time_building_blocks htbp,
hxc_time_building_blocks htbp1
WHERE htbp1.parent_building_block_id = htbp.time_building_block_id
AND htbp1.parent_building_block_ovn = htbp.parent_building_block_ovn
AND htbp.parent_building_block_id = 116166
AND htbp.parent_building_block_ovn = 1
ORDER BY htbp1.time_building_block_id
Is there any way I can do PIVOT with a 'n' rows and without aggregate function?
Edit: T1/T2/T3 as sample data sets but in real it can be any random free text or null.
SELECT * FROM (SELECT htbp1.comment_text, TO_CHAR (htbp.start_time, 'DY') par_time,
trunc((rownum-1) / 7) buck
FROM hxc_time_building_blocks htbp,
hxc_time_building_blocks htbp1,
hxc_timecard_summary hts
WHERE hts.RESOURCE_ID = :p_resource_id
AND TRUNC(hts.STOP_TIME) = TRUNC(:p_wkend_date)
AND htbp1.parent_building_block_id = htbp.time_building_block_id
AND htbp1.parent_building_block_ovn = htbp.parent_building_block_ovn
AND htbp.parent_building_block_id = hts.timecard_id
AND htbp.parent_building_block_ovn = hts.timecard_ovn
ORDER BY htbp1.time_building_block_id ) PIVOT( max(comment_text) FOR par_time
IN ('SUN' AS "SUN",
'MON' AS "MON",
'TUE' AS "TUE",
'WED' AS "WED",
'THU' AS "THU",
'FRI' AS "FRI",
'SAT' AS "SAT"));
When I added the another table 'hxc_timecard_summary' which is parent then data is going crazy, but if I use the hardcoded parameters like the one in the first then the rows are showing up fine.
PIVOT also uses an aggregate function but you don't need a GROUP BY:
with tab as (
select sysdate - 7 date_col, 'T1' comment_text from dual
union all select sysdate - 6, 'T2' from dual
union all select sysdate - 5, 'T3' from dual
union all select sysdate - 4, 'T4' from dual
union all select sysdate - 3, 'T5' from dual
union all select sysdate - 2, 'T6' from dual
union all select sysdate - 1, 'T7' from dual
)
select * from (select to_char(date_col, 'D') day_of_week, comment_text from tab)
PIVOT (max(comment_text) for day_of_week in (7 as sun, 1 as mon, 2 as tue));
Also, I suppose you need the second column with a date to form your new columns.
And you cannot use expressions for FOR clause - this should be column(s). For example, this won't work:
select * from tab
PIVOT (max(comment_text) for to_char(date_col, 'D') in (7 as sun, 1 as mon, 2 as tue));
because of to_char(date_col, 'D')
Try using pivot.
It allows rows to be mapped to columns.
Its from 11g onwards I believe.
with tab as (
select 'T1' comment_text from dual
union all select 'T2' from dual
union all select 'T3' from dual
union all select 'T4' from dual
union all select 'T5' from dual
union all select 'T6' from dual
)
select regexp_substr(txt, '[^,]+', 1, 1) sun,
regexp_substr(txt, '[^,]+', 1, 2) mon,
regexp_substr(txt, '[^,]+', 1, 3) tue
from (
select buck, wm_concat(comment_text) txt
from (
select comment_text, trunc((rownum-1) / 3) buck
from (select comment_text from tab order by comment_text)
)
group by buck
);
wm_concat(comment_text) (Oracle 10g) =
listagg(comment_text, ',') within group(order by comment_text) (Oracle 11g)
But these two functions are both aggregate
My third try, no aggregate functions at all (works fine in Oracle 10g)
with tab as (
select 'T1' comment_text from dual
union all select 'T2' from dual
union all select 'T3' from dual
union all select 'T4' from dual
union all select 'T5' from dual
union all select 'T6' from dual
)
select regexp_substr(txt, '[^(#####)]+', 1, 1) sun,
regexp_substr(txt, '[^(#####)]+', 1, 2) mon,
regexp_substr(txt, '[^(#####)]+', 1, 3) tue
from (
select sys_connect_by_path(comment_text, '#####') txt, parent_id
from (
select rownum id, comment_text, mod(rownum-1, 3) parent_id
from (select comment_text from tab order by comment_text)
)
start with parent_id = 0
connect by prior id = parent_id
) where parent_id = 2;
This question is very much like my previous question, but a bit more complicated. Rob van Wijk's answer worked perfectly for my other question, and I've been using that as a starting point. My problem now is that I am pivoting dates for different fields. Whereas before I cared about getting all open_in and open_out values for a given id, now I want new_in, new_out, open_in, open_out, fixed_in, and fixed_out for each id. I have the following:
SELECT id,
state,
state_time,
MAX(new_row_num) OVER (PARTITION BY id ORDER BY state_time) AS new_row_group,
MAX(open_row_num) OVER (PARTITION BY id ORDER BY state_time) AS open_row_group,
MAX(fixed_row_num) OVER (PARTITION BY id ORDER BY state_time) AS fixed_row_group
FROM (
SELECT id,
state,
state_time,
CASE state
WHEN 'New'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS new_row_num,
CASE state
WHEN 'Open'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS open_row_num,
CASE state
WHEN 'Fixed'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS fixed_row_num
FROM ...
)
This gives me data like the following:
id state state_time new_row_group open_row_group fixed_row_group
1 New 2009-03-03 00:03:31 1
1 Closed 2009-03-04 04:15:27 1
2 New 2010-05-22 14:38:49 1
2 Open 2010-05-22 14:39:14 1 2
2 Fixed 2010-05-22 17:15:27 1 2 3
I would like data like the following:
id new_in new_out open_in open_out fixed_in fixed_out
1 2009-03-03 00:03:31 2009-03-04 04:15:27
2 2010-05-22 14:38:49 2010-05-22 14:39:14 2010-05-22 14:39:14 2010-05-22 17:15:27 2010-05-22 17:15:27
How can I pivot the data to get this date-pairing for each id?
Edit: to clarify, an id can enter and leave a state multiple times. For example, an id might go from New to Open to Fixed to Open to Fixed to Closed. In that case, there would need to be as many rows as is necessary to hold all the state times, e.g.:
id new_in new_out open_in open_out fixed_in fixed_out
4 2009-01-01 00:00:00 2009-01-02 00:00:00 2009-01-02 00:00:00 2009-01-03 00:00:00 2009-01-03 00:00:00 2009-01-04 00:00:00
4 2009-01-04 00:00:00 2009-01-05 00:00:00 2009-01-05 00:00:00 2009-01-06 00:00:00
Sarah,
Here is an example with your sample data:
SQL> create table yourtable (id,state,state_time)
2 as
3 select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all
4 select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
5 select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all
6 select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all
7 select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
8 select 3, 'New', date '2009-01-01' from dual union all
9 select 3, 'Open', date '2009-01-02' from dual union all
10 select 3, 'Fixed', date '2009-01-03' from dual union all
11 select 3, 'Open', date '2009-01-04' from dual union all
12 select 3, 'Fixed', date '2009-01-05' from dual union all
13 select 3, 'Closed', date '2009-01-06' from dual
14 /
Table created.
The query:
SQL> select id
2 , max(decode(state,'New',state_time)) new_in
3 , max(decode(state,'New',out_time)) new_out
4 , max(decode(state,'Open',state_time)) open_in
5 , max(decode(state,'Open',out_time)) open_out
6 , max(decode(state,'Fixed',state_time)) fixed_in
7 , max(decode(state,'Fixed',out_time)) fixed_out
8 from ( select id
9 , state
10 , state_time
11 , max(cnt) over (partition by id order by state_time) the_row
12 , lead(state_time) over (partition by id order by state_time) out_time
13 from ( select id
14 , state
15 , state_time
16 , count(*) over (partition by id,state order by state_time) cnt
17 from yourtable
18 )
19 )
20 group by id
21 , the_row
22 order by id
23 , the_row
24 /
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT FIXED_IN FIXED_OUT
---------- ------------------- ------------------- ------------------- ------------------- ------------------- -------------------
1 03-03-2009 00:03:31 04-03-2009 04:15:27
2 22-05-2010 14:38:49 22-05-2010 14:39:14 22-05-2010 14:39:14 22-05-2010 17:15:27 22-05-2010 17:15:27
3 01-01-2009 00:00:00 02-01-2009 00:00:00 02-01-2009 00:00:00 03-01-2009 00:00:00 03-01-2009 00:00:00 04-01-2009 00:00:00
3 04-01-2009 00:00:00 05-01-2009 00:00:00 05-01-2009 00:00:00 06-01-2009 00:00:00
4 rows selected.
To understand how it works, execute the query from the inside out and check the intermediate result sets. Please let me know if you need some additional explanation.
Regards,
Rob.
I'm not sure how you'd prefer to handle the situation where the same state is repeated more than once for an ID. The following answer takes the easy route, assuming that you would want the first time the state was set and the last time the state was replaced.
select id,
min(case state when 'New' then state_time else null end) as new_in,
max(case state when 'New' then out_state_time else null end) as new_out,
min(case state when 'Open' then state_time else null end) as open_in,
max(case state when 'Open' then out_state_time else null end) as open_out,
min(case state when 'Fixed' then state_time else null end) as fixed_in,
max(case state when 'Fixed' then out_state_time else null end) as fixed_out
from
(select id,
state,
state_time,
lead(state_time) over (partition by id
order by state_time) as out_state_time
from ...
)
group by id
The lead analytic function gets the next row described by the partition/order statement, so that's the easiest way to find out when the state changed. The middle query is a basic pivot query (transforming columns to rows).
select news.id, news.state_time as new_in, min(not_news.state_time) as new_out
, min(opens.state_time) as open_in
, min(not_opens.state_time) as open_out
, min(closes.state_time) as close_in
, min(not_closed.state_time) as close_out
from
(SELECT id,
state,
state_time
from mytable
where state = 'New' ) news
left join
(SELECT id,
state,
state_time
from mytable
where state <> 'New' ) not_news on news.id = not_news.id and news.state_time <= not_news.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Open' ) opens on news.id = opens.id and news.state_time <= opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('New', 'Open' )) not_opens on news.id = opens.id and news.state_time <= opens.state_time and opens.state_time <= not_opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Closed' ) closes on news.id = closes.id and news.state_time <= closes.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('Closed' )) not_closed on news.id = not_closed.id and news.state_time <= closes.state_time and closes.state_time <= not_closed.state_time
group by news.id, news.state_time
order by id, news.state_time
My test data (borrowed from Rob):
create table mytable (id,state,state_time)
as
select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all
select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
select 3, 'New', date '2009-01-01' from dual union all
select 3, 'Open', date '2009-01-02' from dual union all
select 3, 'Fixed', date '2009-01-03' from dual union all
select 3, 'Open', date '2009-01-04' from dual union all
select 3, 'Fixed', date '2009-01-05' from dual union all
select 3, 'Closed', date '2009-01-06' from dual
query results:
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT CLOSE_IN CLOSE_OUT
1 3/3/2009 12:03:31 3/4/2009 4:15:27 3/4/2009 4:15:27
2 5/22/2010 2:38:49 5/22/2010 2:39:14 5/22/2010 2:39:14 5/22/2010 5:15:27
3 1/1/2009 1/2/2009 1/2/2009 1/3/2009 1/6/2009
I hope you can read the above, I'm having trouble formatting it.