Hints to control join order

This article presents how you can suggest join order for tables. It’s recommended to read following article:

Hints to control Join Operations 11g

Let’s prepare our data model and feed it with data

DROP TABLE master_tbl;
DROP TABLE child_tbl;
DROP TABLE child_tbl1;
DROP TABLE child_tbl2;
 
CREATE TABLE master_tbl
(master_id NUMBER);
 
CREATE TABLE child_tbl
(child_id NUMBER,
 master_id NUMBER);
 
CREATE TABLE child_tbl1
(child_id NUMBER,
 master_id NUMBER);
 
CREATE TABLE child_tbl2
(child_id NUMBER,
 master_id NUMBER);
 
BEGIN
  FOR i IN 1..1000
  LOOP
    INSERT INTO master_tbl VALUES(i);
  END LOOP;
  COMMIT;
END;
/
 
BEGIN
  FOR i IN 1..1000
  LOOP
    FOR j IN 1..2
    LOOP
      INSERT INTO child_tbl(child_id, master_id) VALUES(j, i);
      INSERT INTO child_tbl1(child_id, master_id) VALUES(j, i);
      INSERT INTO child_tbl2(child_id, master_id) VALUES(j, i);
    END LOOP;
  END LOOP;
  COMMIT;
END;
/
 
exec DBMS_STATS.gather_table_stats(USER, 'MASTER_TBL', cascade=>TRUE);
exec DBMS_STATS.gather_table_stats(USER, 'CHILD_TBL', cascade=>TRUE);
exec DBMS_STATS.gather_table_stats(USER, 'CHILD_TBL1', cascade=>TRUE);
exec DBMS_STATS.gather_table_stats(USER, 'CHILD_TBL2', cascade=>TRUE);
 
SELECT COUNT(*) FROM master_tbl;
SELECT COUNT(*) FROM child_tbl;
SELECT COUNT(*) FROM child_tbl1;
SELECT COUNT(*) FROM child_tbl2;

ORDERED – this hint instructs Oracle to join tables in the order in which they appear in the FROM clause.It’s very useful if you want to choose inner and outer table in explain plan.

In following example order of tables in FROM clause is following master_tbl, child_tbl so master_tbl is outer table and child_tbl is inner

explain plan FOR
SELECT /*+ ORDERED */ * 
FROM master_tbl m, child_tbl c
WHERE m.master_id=c.master_id;
 
SELECT * FROM TABLE(DBMS_XPLAN.display(NULL, NULL, 'basic'));
 
Plan hash VALUE: 1625664568
 
-----------------------------------------
| Id  | Operation          | Name       |
-----------------------------------------
|   0 | SELECT STATEMENT   |            |
|   1 |  HASH JOIN         |            |
|   2 |   TABLE ACCESS FULL| MASTER_TBL |
|   3 |   TABLE ACCESS FULL| CHILD_TBL  |
-----------------------------------------

to change it you need to change order in FROM clause to child_tbl, master_tbl. Now outer table is child_tbl and inner table is master_tbl

explain plan FOR
SELECT /*+ ORDERED */ * 
FROM child_tbl c, master_tbl m
WHERE m.master_id=c.master_id;
 
SELECT * FROM TABLE(DBMS_XPLAN.display(NULL, NULL, 'basic'));
 
Plan hash VALUE: 30615395
 
-----------------------------------------
| Id  | Operation          | Name       |
-----------------------------------------
|   0 | SELECT STATEMENT   |            |
|   1 |  HASH JOIN         |            |
|   2 |   TABLE ACCESS FULL| CHILD_TBL  |
|   3 |   TABLE ACCESS FULL| MASTER_TBL |
-----------------------------------------

Simple example for many tables. FROM together with ORDERED is forcing to make following order for joins:

1. child_tbl is joined with master_tbl

2. result from 1 step is joined with child_tbl2

3. result from step 2 is joined with child_tbl1

explain plan FOR
SELECT /*+ ORDERED */ * 
FROM child_tbl c, master_tbl m, child_tbl2 c2, child_tbl1 c1
WHERE m.master_id=c.master_id
  AND m.master_id=c1.master_id
  AND m.master_id=c2.master_id;
 
SELECT * FROM TABLE(DBMS_XPLAN.display(NULL, NULL, 'basic'));
 
Plan hash VALUE: 1057942285
 
-------------------------------------------
| Id  | Operation            | Name       |
-------------------------------------------
|   0 | SELECT STATEMENT     |            |
|   1 |  HASH JOIN           |            |
|   2 |   TABLE ACCESS FULL  | CHILD_TBL1 |
|   3 |   HASH JOIN          |            |
|   4 |    TABLE ACCESS FULL | CHILD_TBL2 |
|   5 |    HASH JOIN         |            |
|   6 |     TABLE ACCESS FULL| CHILD_TBL  |
|   7 |     TABLE ACCESS FULL| MASTER_TBL |
-------------------------------------------

Another example for many tables

1. child_tbl is joined with child_tbl2

2. result from step 1 is joined with child_tbl1

3. result from setp 2 is joined with master_tbl

explain plan FOR
SELECT /*+ ORDERED */ * 
FROM child_tbl c, child_tbl2 c2, child_tbl1 c1, master_tbl m
WHERE m.master_id=c.master_id
  AND m.master_id=c1.master_id
  AND m.master_id=c2.master_id;
 
SELECT * FROM TABLE(DBMS_XPLAN.display(NULL, NULL, 'basic'));
 
Plan hash VALUE: 3081111749
 
---------------------------------------------
| Id  | Operation              | Name       |
---------------------------------------------
|   0 | SELECT STATEMENT       |            |
|   1 |  HASH JOIN             |            |
|   2 |   TABLE ACCESS FULL    | MASTER_TBL |
|   3 |   MERGE JOIN CARTESIAN |            |
|   4 |    MERGE JOIN CARTESIAN|            |
|   5 |     TABLE ACCESS FULL  | CHILD_TBL  |
|   6 |     BUFFER SORT        |            |
|   7 |      TABLE ACCESS FULL | CHILD_TBL2 |
|   8 |    BUFFER SORT         |            |
|   9 |     TABLE ACCESS FULL  | CHILD_TBL1 |
---------------------------------------------

Oracle recommends that you use the LEADING hint, which is more versatile than the ORDERED hint.

LEADING – this hints instructs Oracle to use specified set of tables as first to join in order specified by this hint.

In this example we are trying to force first master_tbl with child_tbl and rest of the joins depend on optimizer choice. It’s very nice hint comparing to ORDERED because we don’t need to modify our query.

explain plan FOR
SELECT /*+ LEADING(m c) */ * 
FROM child_tbl c, child_tbl2 c2, child_tbl1 c1, master_tbl m
WHERE m.master_id=c.master_id
  AND m.master_id=c1.master_id
  AND m.master_id=c2.master_id;
 
SELECT * FROM TABLE(DBMS_XPLAN.display(NULL, NULL, 'basic'));
 
Plan hash VALUE: 588331670
 
-------------------------------------------
| Id  | Operation            | Name       |
-------------------------------------------
|   0 | SELECT STATEMENT     |            |
|   1 |  HASH JOIN           |            |
|   2 |   TABLE ACCESS FULL  | CHILD_TBL1 |
|   3 |   HASH JOIN          |            |
|   4 |    TABLE ACCESS FULL | CHILD_TBL2 |
|   5 |    HASH JOIN         |            |
|   6 |     TABLE ACCESS FULL| MASTER_TBL |
|   7 |     TABLE ACCESS FULL| CHILD_TBL  |
-------------------------------------------

This hint is ignored if the tables specified cannot be joined first in the order specified because of dependencies in the join graph. If you specify two or more conflicting LEADING hints, then all of them are ignored. If you specify the ORDERED hint, it overrides all LEADING hints.

Have a fun 🙂

Tomasz

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.