Hello Oracle Masters,
I'm trying to retrieve the column-table dependencies using the primary key/foreign key relations of a given table, but with another table as stop clause. I've been trying my luck with the recursive subquery factoring method mentioned here
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:5822332300346315797, but unfortunately without much success :(
Say I'm having the tables and test values of the linked LiveSQL Scheme, my goal would be to traverse all dependencies till I reach table A.
Precondition: All tables are in one way or another related to table A. Some are in direct parent-child relationships, others are in mapping tables.
Then I'd only want to see the path that lead me there (the reason for that will be explained later on).
Now when executing statement #33, instead of the achieved result I would have looked for something like this (first three cols omited):
LVL TABLE_NAME1 COLUMN_NAME1 TABLE_NAME2 COLUMN_NAME2
1 C C_B_ID B B_ID
2 B B_A_ID A A_ID
Unfortunately I'm not able to get that far.
Same goes for statement #34, with the mapping not making it easier, I'd like the result to be like this:
LVL TABLE_NAME1 COLUMN_NAME1 TABLE_NAME2 COLUMN_NAME2
1 D D_ID E E_D_ID
2 E E_A_ID A A_ID
Later on I plan to create dynamic update statements, using the results as my where clause, because the update will be on the A_ID value.
Is something like I'm looking for actually possible using the recursive subquery factoring method or did I use the wrong approach?
Hope you can help me out =)
Kind regards,
René
OK. So part of the problem here is when you have a chain of tables C -> B -> A.
When you follow the FK from C to B you get to B_PK. To then go onto A, you need to join back to the constraints on B to find its FKs.
So really your selects in the recursive with need to get both the source constraint (PK or FK) and the target (FK or PK). I've also included the columns in the recursive query.
This gives a much more complex base query along the lines of:
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
0 lvl ,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
ucc2.table_name path
from user_constraints uc
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
where uc.table_name = :tab
and uc.constraint_type in ('R', 'P')
And a similar one in the recursive branch. But rather than joining on constraint names, join user_constraints on the target table.
To stop processing when you get to A, include a check that the destination table isn't A. This gives a query like:
with constr ( src, dst, src_tab, dst_tab, lev, tp, col1, col2, cyc_tab, path) as (
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
0 lvl ,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
ucc2.table_name path
from user_constraints uc
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
where uc.table_name = :tab
and uc.constraint_type in ('R', 'P')
union all
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
lev + 1,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
path || ',' || ucc2.table_name path
from constr c
join user_constraints uc
on uc.table_name = c.dst_tab
and uc.constraint_type in ('R', 'P')
and uc.table_name <> 'A'
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
) search depth first by src_tab set tab_order
cycle cyc_tab set cycle to 1 default 0
select c.*,
case when lev < lead(lev) over (order by tab_order) then null
else path
end is_leaf
from constr c
where cycle = 0;
This will then work up and down the relationships. So if you start at a table with children that don't lead to A (such as B), you'll end up with extra rows:
SRC DST SRC_TAB DST_TAB LEV TP COL1 COL2 CYC_TAB PATH TAB_ORDER CYCLE IS_LEAF
B_A_FK A_PK B A 0 R B_A_ID A_ID A A 1 0 A
B_PK C_B_FK B C 0 P B_ID C_B_ID B C 2 0 C
To knock these out you need to check that your path has A as a leaf.
I've done this by returning the destination table if the current row is a leaf. Then "filling up" this leaf using last_value, walking back up the tree.
You then return just the rows where the result of this is A:
with constr ( src, dst, src_tab, dst_tab, lev, tp, col1, col2, cyc_tab, path) as (
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
0 lvl ,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
ucc2.table_name path
from user_constraints uc
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
where uc.table_name = :tab
and uc.constraint_type in ('R', 'P')
union all
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
lev + 1,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
path || ',' || ucc2.table_name path
from constr c
join user_constraints uc
on uc.table_name = c.dst_tab
and uc.constraint_type in ('R', 'P')
and uc.table_name <> 'A'
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
) search depth first by src_tab set tab_order
cycle cyc_tab set cycle to 1 default 0
select c.*,
case when lev < lead(lev) over (order by tab_order) then null
else path
end is_leaf
from constr c
where cycle = 0;
with constr ( src, dst, src_tab, dst_tab, lev, tp, col1, col2, cyc_tab, path) as (
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
0 lvl ,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
ucc2.table_name path
from user_constraints uc
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
where uc.table_name = :tab
and uc.constraint_type in ('R', 'P')
union all
select uc.constraint_name src,
nvl( ruc.constraint_name , uc.r_constraint_name) dst,
uc.table_name src_tab,
ucc2.table_name dst_tab,
lev + 1,
uc.constraint_type tp,
ucc1.column_name,
ucc2.column_name,
least(uc.table_name , ucc2.table_name) cyc_tab,
path || ',' || ucc2.table_name path
from constr c
join user_constraints uc
on uc.table_name = c.dst_tab
and uc.constraint_type in ('R', 'P')
and uc.table_name <> 'A'
join user_cons_columns ucc1
on ucc1.constraint_name = uc.constraint_name
left join user_constraints ruc
on ruc.r_constraint_name = uc.constraint_name
join user_cons_columns ucc2
on ucc2.constraint_name = case
when uc.constraint_type = 'R' then uc.r_constraint_name
when uc.constraint_type = 'P' then ruc.constraint_name
end
) search depth first by src_tab set tab_order
cycle cyc_tab set cycle to 1 default 0,
leaves as (
select c.*,
case when lev < lead(lev) over (order by tab_order) then null
else dst_tab
end is_leaf
from constr c
where cycle = 0
), rng as (
select l.*,
last_value(is_leaf) ignore nulls over (order by tab_order desc) lv
from leaves l
)
select * from rng
where lv = 'A'
order by lev;
Which gives the following results:
-- for D
SRC DST SRC_TAB DST_TAB LEV TP COL1 COL2 CYC_TAB PATH TAB_ORDER CYCLE IS_LEAF LV
D_PK E_D_FK D E 0 P D_ID E_D_ID D E 1 0 A
E_A_FK A_PK E A 1 R E_A_ID A_ID A E,A 2 0 A A
-- for C
SRC DST SRC_TAB DST_TAB LEV TP COL1 COL2 CYC_TAB PATH TAB_ORDER CYCLE IS_LEAF LV
C_B_FK B_PK C B 0 R C_B_ID B_ID B B 1 0 A
B_A_FK A_PK B A 1 R B_A_ID A_ID A B,A 2 0 A A
-- for B
SRC DST SRC_TAB DST_TAB LEV TP COL1 COL2 CYC_TAB PATH TAB_ORDER CYCLE IS_LEAF LV
B_A_FK A_PK B A 0 R B_A_ID A_ID A A 1 0 A A
For an explanation of how to find leaves using recursive with, read:
http://www.orafaq.com/node/2996 Note this includes a couple of assumptions which you may need to tweak:
- You have no cross-schema constraints. If you do, you'll need to use all* views and include the owner in tables
- All FKs point to PKs with none pointing to unique constraints
- You have no composite PKs (or at least, none with FKs referencing them!)