I have a tree structure as below.
Tree Structure
The requirement is to accumulate the Leadtimes of each parent and pass to child.
I currently have below query;
SELECT LEVEL AS struct_level
,t.part_no AS parent_part
,t.component_part AS comp_part_no
,NVL(t.lead_time ,0) AS comp_part_lead_time
,NVL(t.lead_time ,0) + NVL(PRIOR t.lead_time ,0) AS cumul_lead_time
FROM ifsapp.prod_structure_cfv t
START WITH (t.part_no) IN 'PART A'
CONNECT BY PRIOR (t.component_part) = (t.part_no)
ORDER SIBLINGS BY t.component_part;
This will give me a data set like below.
my data set
But the issue is, the lead time should be calculated from the whole branch. I.e. the correct data set should be,
correct data set
As per my understanding, I can only refer PRIOR record values and/or master record (PART A) data via ROOT.
Is there a way to accomplish this please? Maybe using a recursive function?
I tried to reuse the recursive function mentioned in this post, but couldn't get desired results.
You want to use a recursive sub-query factoring clause:
WITH rsqfc (part_no, component_part, lead_time, cumul_lead_time) AS (
SELECT part_no,
component_part,
COALESCE(lead_time, 0),
COALESCE(lead_time, 0)
FROM ifsapp.prod_structure_cfv
WHERE part_no = 'PART A'
UNION ALL
SELECT p.part_no,
p.component_part,
COALESCE(p.lead_time, 0),
r.cumul_lead_time + COALESCE(p.lead_time, 0)
FROM rsqfc r
INNER JOIN ifsapp.prod_structure_cfv p
ON (r.component_part = p.part_no)
)
SEARCH DEPTH FIRST BY component_part SET order_rn
SELECT part_no,
component_part,
lead_time,
cumul_lead_time
FROM rsqfc;
Which, for the sample data:
CREATE TABLE prod_structure_cfv (part_no, component_part, lead_time) AS
SELECT 'PART A', 'PART B', 0 FROM DUAL UNION ALL
SELECT 'PART A', 'PART C', 0 FROM DUAL UNION ALL
SELECT 'PART B', 'PART D', 1 FROM DUAL UNION ALL
SELECT 'PART B', 'PART E', 1 FROM DUAL UNION ALL
SELECT 'PART B', 'PART F', 1 FROM DUAL UNION ALL
SELECT 'PART C', 'PART G', 0 FROM DUAL UNION ALL
SELECT 'PART C', 'PART H', 0 FROM DUAL UNION ALL
SELECT 'PART D', 'PART P', 0 FROM DUAL UNION ALL
SELECT 'PART D', 'PART Q', 0 FROM DUAL UNION ALL
SELECT 'PART E', NULL, 2 FROM DUAL UNION ALL
SELECT 'PART F', 'PART R', 1 FROM DUAL UNION ALL
SELECT 'PART G', 'PART X', 3 FROM DUAL UNION ALL
SELECT 'PART G', 'PART Y', 3 FROM DUAL UNION ALL
SELECT 'PART H', NULL, 2 FROM DUAL UNION ALL
SELECT 'PART P', NULL, 2 FROM DUAL UNION ALL
SELECT 'PART Q', NULL, 1 FROM DUAL UNION ALL
SELECT 'PART R', 'PART S', 0 FROM DUAL UNION ALL
SELECT 'PART S', NULL, 3 FROM DUAL UNION ALL
SELECT 'PART X', NULL, 1 FROM DUAL UNION ALL
SELECT 'PART Y', NULL, 2 FROM DUAL;
Outputs:
PART_NO
COMPONENT_PART
LEAD_TIME
CUMUL_LEAD_TIME
PART A
PART B
0
0
PART B
PART D
1
1
PART D
PART P
0
1
PART P
null
2
3
PART D
PART Q
0
1
PART Q
null
1
2
PART B
PART E
1
1
PART E
null
2
3
PART B
PART F
1
1
PART F
PART R
1
2
PART R
PART S
0
2
PART S
null
3
5
PART A
PART C
0
0
PART C
PART G
0
0
PART G
PART X
3
3
PART X
null
1
4
PART G
PART Y
3
3
PART Y
null
2
5
PART C
PART H
0
0
PART H
null
2
2
db<>fiddle here
Related
I am looking for a oracle sql solution for the below problem:
Here's one option (as union of 3 different cases).
Sample data:
SQL> with test (col) as
2 (select 'A-D' from dual union all
3 select 'J-K' from dual union all
4 select 'X-20' from dual union all
5 select 'XX-20542' from dual union all
6 select 'A/B' from dual union all
7 select 'J/K' from dual
8 )
Query begins here:
9 -- A-D option:
10 select col,
11 chr(ascii(substr(col, 1, 1)) + column_value - 1) res
12 from test cross join
13 table(cast(multiset(select level from dual
14 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
15 ) as sys.odcinumberlist))
16 where regexp_like(col, '[[:alpha:]]-[[:alpha:]]')
17 -- XX-20542 option:
18 union all
19 select col, 'No action / ignore'
20 from test
21 where regexp_like(col, '[[:alpha:]]+-\d+')
22 -- A/B option:
23 union all
24 select col,
25 regexp_substr(col, '[[:alpha:]]', 1, column_value)
26 from test cross join
27 table(cast(multiset(select level from dual
28 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
29 ) as sys.odcinumberlist))
30 where regexp_like(col, '[[:alpha:]]/[[:alpha:]]');
Result:
COL RES
-------- ------------------
A-D A
A-D B
A-D C
A-D D
J-K J
J-K K
X-20 No action / ignore
XX-20542 No action / ignore
A/B A
A/B B
J/K J
J/K K
12 rows selected.
SQL>
You can use a single recursive query:
WITH ranges (value, min_value, skip, max_value) AS (
SELECT value,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 1),
CASE REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 2)
WHEN '-'
THEN 0
ELSE 1
END,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 3)
FROM table_name
UNION ALL
SELECT value,
CASE skip
WHEN 0
THEN CHR(ASCII(min_value) + 1)
ELSE max_value
END,
skip,
max_value
FROM ranges
WHERE min_value < max_value
)
SEARCH DEPTH FIRST BY value SET rn
SELECT value,
COALESCE(min_value, 'ignore') AS result
FROM ranges
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'A-D' FROM DUAL UNION ALL
SELECT 'J-K' FROM DUAL UNION ALL
SELECT 'XX-Y' FROM DUAL UNION ALL
SELECT 'Y-ZZ' FROM DUAL UNION ALL
SELECT 'B/C' FROM DUAL UNION ALL
SELECT 'E/E' FROM DUAL UNION ALL
SELECT 'F/H' FROM DUAL;
Outputs:
VALUE
RESULT
A-D
A
A-D
B
A-D
C
A-D
D
B/C
B
B/C
C
E/E
E
F/H
F
F/H
H
J-K
J
J-K
K
XX-Y
ignore
Y-ZZ
ignore
db<>fiddle here
I have this
RowNum
Value
1
X
2
X
3
Y
4
Z
5
Z
6
Z
7
V
and I want something like this
RowNum
Value
1
X
1
X
2
Y
3
Z
3
Z
3
Z
4
V
How can I do that in Oracle?
Thanks
Here is one way - using the match_recognize clause, available since Oracle 12.1. Note that rownum is a reserved keyword, so it can't be a column name; I changed it to rnum in the input and rn in the output, adapt as needed.
The with clause is not part of the query - it's there just to include your sample data. Remove it before using the query on your actual data.
with
inputs (rnum, value) as (
select 1, 'X' from dual union all
select 2, 'X' from dual union all
select 3, 'Y' from dual union all
select 4, 'Z' from dual union all
select 5, 'Z' from dual union all
select 6, 'Z' from dual union all
select 7, 'V' from dual
)
select rn, value
from inputs
match_recognize (
order by rnum
measures match_number() as rn
all rows per match
pattern ( a+ )
define a as value = first(value)
);
RN VALUE
-- -----
1 X
1 X
2 Y
3 Z
3 Z
3 Z
4 V
In Oracle 11.2 and earlier, you can use the start-of-group method (the flags created in the subquery and counted in the outer query):
select count(flag) over (order by rnum) as rn, value
from (
select rnum, value,
case lag(value) over (order by rnum)
when value then null else 1 end as flag
from inputs
)
;
Consider following table where I am doing row data multiplication:
with v1 (member_id, the_number) as
(
select 1, 3 from dual union all
select 1, 5 from dual union all
select 2, 2 from dual union all
select 2, 3 from dual union all
select 2, 4 from dual union all
select 3, 9 from dual union all
select 3, 3 from dual union all
select 3, 2 from dual
)
select member_id, EXP(SUM(LN(the_number))) from v1
GROUP BY member_id;
It gives the correct result as:
MEMBER_ID EXP(SUM(LN(THE_NUMBER)))
1 15
2 24
3 54
The moment I put a negative value in the the_number column, I get the following Oracle error: ORA-01428: argument 'x' is out of range This is because the range for LN () argument is > 0.
How can I modify the query so that I can have negative values as well in the_number column? I am using Oracle 11g.
Get the product of the absolute values of the numbers and finally multiply by -1 or 1 depending on whether there is an odd or even number of negative numbers:
select
member_id,
CASE WHEN MOD(SUM(CASE WHEN the_number < 0 THEN 1 ELSE 0 END), 2) = 1 THEN -1 ELSE 1 END *
EXP(SUM(LN(ABS(the_number)))) from v1
GROUP BY member_id;
See the demo.
I have a table that have date grouped.
I need to select only groups that have positive and negative value inside.
For example:
id value1
2 7
2 8
2 -1
3 3
3 4
4 -1
4 -2
5 7
5 -5
the result should be
id value1
2 7
2 8
2 -1
5 7
5 -5
because the group with id 3 just have positive number and the group with id 4 just have negative number.
any idea how can I do it using case (when then) in a select or using if else inside a function. Or any other idea?
Try this.
select id,value1 FROM
(
select t.*,
count( DISTINCT SIGN (value1 ) ) OVER (PARTITION BY id ) n
from yourtable t
) WHERE n = 2
;
The Sign() function gives 1 for positive and -1 for negative numbers.
DEMO
If you group by the ID, you can use the aggregate functions MIN and MAX to find out if there are both positive and negative values. You need to decide how to treat 0 though... I have treated it as positive below :)
with your_table as(
-- Your example data here, this is not really part of the solution
select 2 as id, 7 as value1 from dual union all
select 2 as id, 8 as value1 from dual union all
select 2 as id, -1 as value1 from dual union all
select 3 as id, 3 as value1 from dual union all
select 3 as id, 4 as value1 from dual union all
select 4 as id, -1 as value1 from dual union all
select 4 as id, -2 as value1 from dual union all
select 5 as id, 7 as value1 from dual union all
select 5 as id, -5 as value1 from dual
)
select *
from your_table
where id in(select id
from your_table
group by id
having min(value1) < 0
and max(value1) >= 0);
Column 'amount' has value 5 in first row, and value -10 in second row.
Is there a way to make oracle's sum(amount) over() function to return 0 instead of -5 for the second row?
Blatantly using Rajesh Chamarthi's example source: but altering to show more negative and positive... and showing how a case would change all the negative to zero while maintaining the other amounts...
with t as (
select 5 as x, 1 as id from dual
union all
select -10, 2 as id from dual
union all
select 7, 3 as id from dual
union all
select -5, 4 as id from dual
union all
select -2, 5 as id from dual
),
B as (select t.x,
case when sum(x) over (order by id) < 0 then 0
else sum(x) over (order by id)
end Amount
from t
order by id)
Select X, Case when amount < 0 then 0 else amount end as Amount from B;
T Amount
5 5
-10 0
7 2
-5 0
-2 0
----Attempt 2 (1st attempt preserved as comments below reference it)
I couldn't figure out how to interrupt the window function to reset the value to 0 when amount fell below 0... so I used a recursive CTE which gave me greater control.
If id's are not sequential, we could add a row_Number so we have an ID to join on... or we could use min() where > oldID. I assumed we have a single key unique ID or some way of "Sorting" the records in the order you want the sum to occur...
with aRaw as (
select 5 as x, 15 as id from dual
union all
select -10, 20 as id from dual
union all
select 7, 32 as id from dual
union all
select 2, 46 as id from dual
union all
select -15, 55 as id from dual
union all
select 3, 68 as id from dual
),
t as (Select A.*, Row_number() over (order by ID) rn from aRAW A),
CTE(RN, ID, x, SumX) AS (
Select T.RN, T.ID, x, X from t WHERE ID = (Select min(ID) from t)
UNION ALL
Select T.RN, T.ID, T.X, case when T.X+CTE.SumX < 0 then 0 else T.X+Cte.sumX end from T
INNER JOIN CTE
on CTE.RN+1=T.RN)
Select * from cte;
.
CTE: ARaw is just a sample data set
CTE: T adds a sequental row number incase there are gaps in the IDs allowing for a more simple joining approach on the recursive CTE.
CTE: CTE is the recursive CTE that keeps a running total and has a case statement to reset the running total to 0 when it falls below 0
You could use a case statement, but that would not be a true running total
with t as (
select 5 as x, 1 as id from dual
union all
select -10, 2 as id from dual
union all
select 20, 3 as id from dual
union all
select 30, 4 as id from dual
union all
select 10, 5 as id from dual
)
select t.x,
case when sum(x) over (order by id) < 0 then 0
else sum(x) over (order by id)
end running_total
from t
order by id;
X RUNNING_TOTAL
5 5
-10 0
20 15
30 45
10 55