Oracle SQL Query rewrite: Separate outer joined inline view using UNION ALL or add hint for the inline view
Separate outer joined inline view using UNION ALL or add hint for the inline view.
This is an article of Oracle SQL Query rewrite serials.
If outer joined inline view has more than 2 tables in the FROM clause, It has to read all the data in the view, because the view is non-mergeable view due to outer join. So, we have to re-write the statement to remove outer join using UNION ALL.
Take this SQL for example:
SELECT a.emp_id, a.emp_name, b.salary_amt FROM emp a, (SELECT emp_id, SUM(salary) salary_amt FROM salary a, dept b -- two tables joined WHERE a.dept_id = b.dept_id AND b.location = 'US' GROUP BY emp_id ) b WHERE emp.date_entered >= to_date('20080101','yyyymmdd') AND a.emp_id = b.emp_id (+);
Solution #1:
Outer join means that ‘EXISTS’ or ‘NOT EXISTS’. ‘EXISTS’ means equi-join, so we removed outer symbol (+) to make it equi-join and added NOT EXISTS sub-query to the equi-join using UNION ALL.
SQL will be translated into:
SELECT a.emp_id, a.emp_name, b.salary_amt FROM emp a, (SELECT emp_id, SUM(salary) salary_amt FROM salary a, dept b WHERE a.dept_id = b.dept_id AND b.location = 'US' GROUP BY emp_id) b WHERE emp.date_entered >= To_date('20080101', 'yyyymmdd') AND a.emp_id = b.emp_id UNION ALL SELECT a.emp_id, a.emp_name, NULL salary_amt FROM emp a WHERE emp.date_entered >= To_date('20080101', 'yyyymmdd') AND NOT EXISTS (SELECT 1 FROM salary epi001, dept b WHERE a.dept_id = b.dept_id AND b.location = 'US' AND a.emp_id = epi001.emp_id)
Solution #2:
Add hint in the SELECT clause
SQL will be translated into
SELECT /*+ NO_MERGE(b) USE_HASH(b) */ a.emp_id, a.emp_name, b.salary_amt FROM emp a, (SELECT emp_id, SUM(salary) salary_amt FROM salary a, dept b WHERE a.dept_id = b.dept_id AND b.location = 'US' GROUP BY emp_id) b WHERE emp.date_entered >= To_date('20080101', 'yyyymmdd') AND a.emp_id = b.emp_id (+)
or
SELECT /*+ PUSH_PRED(b) */ a.emp_id, a.emp_name, b.salary_amt FROM emp a, (SELECT emp_id, SUM(salary) salary_amt FROM salary a, dept b WHERE a.dept_id = b.dept_id AND b.location = 'US' GROUP BY emp_id) b WHERE emp.date_entered >= To_date('20080101', 'yyyymmdd') AND a.emp_id = b.emp_id (+)
Download this demo: C# version