After finding the shortest “foreign key” path between two tables, I’ll try to generate a SELECT
statement that joins all the related tables.
(Please don’t tell me that Oracle SQL Developer does this: I know it does, as long as you already know the path and select all the tables yourself! I find a path for you and make sure the column names are unique. Despite these differences, you will probably never need this code if you use SQL Developer.)
- The final
SELECT
starts with the bottom of the hierarchy and works its way up. - Table aliases are
T1
,T2
and so on. - All columns of all tables are selected (based on
ALL_TAB_COLUMNS
, notALL_TAB_COLS
). - To make columns unique, they are prefixed with the table alias:
T1_
* and so on. - Table names and column names are only enclosed in double quotes when they contain non-alphanumeric characters. Exceptionally, I always enclose the join columns in double quotes just to save myself some trouble…
- I use ANSI join syntax, and when I do the equality comparison I use parentheses like this:
on (T1.COL1, T1.COL2) = ((T2.COL1, T2.COL2))
It’s a bit easier to code. Don’t be surprised to see the parentheses even when there is only one column.
The code is more than 100 lines long. I’ll put my inadequate explanation first, then the code.
- search_for: the owners and tables I want to find the path between.
- pur: the constraints I need to start with.
- relations: add to each constraint row the remote or parent table.
- best_path: returns a string that contains all the data from all the rows in the path
- best_path_tables: breaks down the string into the rows and columns of the path I found. I use
REGEXP
functions instead of JSON functions so this should work from version 11.2. - final_select: get all the columns for the final
SELECT
, accessingALL_TAB_COLUMNS
- final_from: get the
FROM
andJOIN
clauses, accessingALL_CONS_COLUMNS
.
I’ll sign off before posting the code. I would say “hope this helps”, but I feel more like saying
Hope this works!
with search_for (owner, table_name) as ( select 'OBE', 'OEHR_REGIONS' from dual union all select 'OBE', 'OEHR_ORDER_ITEMS' from dual ) , pur as ( select OWNER, CONSTRAINT_NAME, TABLE_NAME, R_OWNER, R_CONSTRAINT_NAME from dba_constraints where constraint_type in('P','U','R') and owner in (select username from dba_users where oracle_maintained = 'N') ) , relations as ( select a.owner, a.table_name, a.constraint_name, a.r_owner, b.table_name r_table_name, a.r_constraint_name from pur a join pur b on (a.R_OWNER, a.R_CONSTRAINT_NAME) = ((b.OWNER, b.CONSTRAINT_NAME)) and (a.OWNER, a.table_name) != ((b.OWNER, b.table_name)) ) , best_path as ( select path from ( select row_number() over( order by case when (r_owner, r_table_name) != ((connect_by_root(owner), connect_by_root(table_name))) then level end ) rn, sys_connect_by_path( owner ||'/'||table_name ||'/'|| constraint_name||'/'|| r_owner||'/'||r_table_name||'/'||r_constraint_name,'<') path from relations a where (r_owner, r_table_name) in (select * from search_for) start with (owner, table_name) in (select * from search_for) connect by nocycle (owner, table_name) = ((prior r_owner, prior r_table_name)) and (owner, table_name) not in (select * from search_for) ) where rn = 1 ) , best_path_tables as ( select level lvl, 'T'||level al, 'T'||(level+1) r_al, regexp_substr(path, '<(.+?)/', 1, level,'i',1) owner, regexp_substr(path, '<(.+?/){1}(.+?)/', 1, level,'i',2) table_name, regexp_substr(path, '<(.+?/){2}(.+?)/', 1, level,'i',2) constraint_name, regexp_substr(path, '<(.+?/){3}(.+?)/', 1, level,'i',2) r_owner, regexp_substr(path, '<(.+?/){4}(.+?)/', 1, level,'i',2) r_table_name, regexp_substr(path, '<(.+?/){5}([^/<]+)', 1, level,'i',2) r_constraint_name from best_path connect by level <= regexp_count (path, '<') ) , final_select as ( select lvl, pref || listagg(al||'.'||qu||column_name||qu||' '||qu||al||'_'||column_name||qu,',') within group(order by column_id) str from ( select -99 lvl, 'select ' pref, al, column_name, column_id, case when not regexp_like (column_name, '^[A-Z][A-Z0-9$#_]*$') then '"' end qu from best_path_tables join all_tab_columns using(owner, table_name) where lvl = 1 union all select lvl-99, ',', r_al, column_name, column_id, case when not regexp_like (column_name, '^[A-Z][A-Z0-9$#_]*$') then '"' end qu from best_path_tables b join all_tab_columns a on (b.r_owner, b.r_table_name) = ((a.owner, a.table_name)) ) group by lvl, pref ) , final_from as ( select 0 lvl, 'from '||oq||owner||oq||'.'||tq||table_name||tq||' '|| al str from ( select a.*, case when not regexp_like (owner, '^[A-Z][A-Z0-9$#_]*$') then '"' end oq, case when not regexp_like (table_name, '^[A-Z][A-Z0-9$#_]*$') then '"' end tq from best_path_tables a where lvl = 1 ) a union all select lvl, 'join '||oq||r_owner||oq||'.'||tq||r_table_name||tq||' '|| r_al || ' on (' || ( select listagg(al||'."'||column_name||'"',',') within group(order by position) from all_cons_columns where (owner, table_name, constraint_name) = ((a.owner, a.table_name, a.constraint_name)) ) || ') = ((' || ( select listagg(r_al||'."'||column_name||'"',',') within group(order by position) from all_cons_columns where (owner, table_name, constraint_name) = ((a.r_owner, a.r_table_name, a.r_constraint_name)) ) || '))' from ( select a.*, case when not regexp_like (r_owner, '^[A-Z][A-Z0-9$#_]*$') then '"' end oq, case when not regexp_like (r_table_name, '^[A-Z][A-Z0-9$#_]*$') then '"' end tq from best_path_tables a ) a ) select str from ( select * from final_select union all select * from final_from ) order by lvl;
select T1.ORDER_ID T1_ORDER_ID,T1.LINE_ITEM_ID T1_LINE_ITEM_ID,T1.PRODUCT_ID T1_PRODUCT_ID,T1.UNIT_PRICE T1_UNIT_PRICE,T1.QUANTITY T1_QUANTITY,T1.ORDER_ITEM_ID T1_ORDER_ITEM_ID ,T2.ORDER_ID T2_ORDER_ID,T2.ORDER_DATE T2_ORDER_DATE,T2.ORDER_MODE T2_ORDER_MODE,T2.CUSTOMER_ID T2_CUSTOMER_ID,T2.ORDER_STATUS T2_ORDER_STATUS,T2.ORDER_TOTAL T2_ORDER_TOTAL,T2.SALES_REP_ID T2_SALES_REP_ID,T2.PROMOTION_ID T2_PROMOTION_ID ,T3.EMPLOYEE_ID T3_EMPLOYEE_ID,T3.FIRST_NAME T3_FIRST_NAME,T3.LAST_NAME T3_LAST_NAME,T3.EMAIL T3_EMAIL,T3.PHONE_NUMBER T3_PHONE_NUMBER,T3.HIRE_DATE T3_HIRE_DATE,T3.JOB_ID T3_JOB_ID,T3.SALARY T3_SALARY,T3.COMMISSION_PCT T3_COMMISSION_PCT,T3.MANAGER_ID T3_MANAGER_ID,T3.DEPARTMENT_ID T3_DEPARTMENT_ID ,T4.DEPARTMENT_ID T4_DEPARTMENT_ID,T4.DEPARTMENT_NAME T4_DEPARTMENT_NAME,T4.MANAGER_ID T4_MANAGER_ID,T4.LOCATION_ID T4_LOCATION_ID ,T5.LOCATION_ID T5_LOCATION_ID,T5.STREET_ADDRESS T5_STREET_ADDRESS,T5.POSTAL_CODE T5_POSTAL_CODE,T5.CITY T5_CITY,T5.STATE_PROVINCE T5_STATE_PROVINCE,T5.COUNTRY_ID T5_COUNTRY_ID ,T6.COUNTRY_ID T6_COUNTRY_ID,T6.COUNTRY_NAME T6_COUNTRY_NAME,T6.REGION_ID T6_REGION_ID ,T7.REGION_ID T7_REGION_ID,T7.REGION_NAME T7_REGION_NAME from OBE.OEHR_ORDER_ITEMS T1 join OBE.OEHR_ORDERS T2 on (T1."ORDER_ID") = ((T2."ORDER_ID")) join OBE.OEHR_EMPLOYEES T3 on (T2."SALES_REP_ID") = ((T3."EMPLOYEE_ID")) join OBE.OEHR_DEPARTMENTS T4 on (T3."DEPARTMENT_ID") = ((T4."DEPARTMENT_ID")) join OBE.OEHR_LOCATIONS T5 on (T4."LOCATION_ID") = ((T5."LOCATION_ID")) join OBE.OEHR_COUNTRIES T6 on (T5."COUNTRY_ID") = ((T6."COUNTRY_ID")) join OBE.OEHR_REGIONS T7 on (T6."REGION_ID") = ((T7."REGION_ID"))
Advertisements
Is another reason why someone can use this code. For example new workers in company who doasn’t know relation between tables. And writing his first sql can fast get knowledg about this relation. BDW great code!!!