3.2 Outer JoinsSometimes while performing a join between two tables, you need to return all the rows from one table even when there are no corresponding rows in the other table. Consider the following two tables, SUPPLIER and PART: SELECT * FROM SUPPLIER; SUPPLIER_ID NAME ----------- ------------------------------ 101 Pacific Disks, Inc. 102 Silicon Valley MicroChips 103 Blue River Electronics SELECT * FROM PART; PART_NBR NAME SUPPLIER_ID STATUS INVENTORY_QTY UNIT_COST RESUPPLY_DATE -------- ------------------ ----------- ------ ------------- --------- ------------- HD211 20 GB Hard Disk 101 ACTIVE 5 2000 12-DEC-00 P3000 3000 MHz Processor 102 ACTIVE 12 600 03-NOV-00 If you want to list all the suppliers and all the parts supplied by them, it is natural to use the following query: SELECT S.SUPPLIER_ID, S.NAME SUPPLIER_NAME, P.PART_NBR, P.NAME PART_NAME FROM SUPPLIER S, PART P WHERE S.SUPPLIER_ID = P.SUPPLIER_ID; SUPPLIER_ID SUPPLIER_NAME PART_NBR PART_NAME ----------- ------------------------------ ---------- ------------------- 101 Pacific Disks, Inc. HD211 20 GB Hard Disk 102 Silicon Valley MicroChips P3000 3000 MHz Processor Note that even though we have three suppliers, this query lists only two of them, because the third supplier (Blue River Electronics) doesn't currently supply any part. When Oracle performs the join between SUPPLIER table and PART table, it matches the SUPPLIER_ID from these two tables (as specified by the join condition). Since SUPPLIER_ID 103 doesn't have any corresponding record in the PART table, that supplier is not included in the result set. This type of join is the most natural, and is known as an inner join. However, we want to see all the suppliers even if they don't supply any parts. Oracle provides a special type of join to include rows from one table that don't have matching rows from the other table. This type of join is known as an outer join. An outer join allows us to return rows for all suppliers, and also for parts in cases where a supplier currently supplies parts. In cases where a supplier doesn't supply parts, NULLs are returned for the PART table columns in the result set. The syntax of the outer join is a bit different from that of the inner join, because it includes a special operator called the outer join operator. The outer join operator is a plus sign enclosed in parentheses, i.e., (+). This operator is used in the join condition in the WHERE clause following a field name from the table that you wish to be considered the optional table. In our suppliers and parts example, the PART table doesn't have information for one supplier. Therefore, we will simply add a (+) operator to the join condition on the side of the PART table. The query and the result set look as follows: SELECT S.SUPPLIER_ID, S.NAME SUPPLIER_NAME, P.PART_NBR, P.NAME PART_NAME FROM SUPPLIER S, PART P WHERE S.SUPPLIER_ID = P.SUPPLIER_ID (+); SUPPLIER_ID SUPPLIER_NAME PART_NBR PART_NAME ----------- ------------------------------ ---------- ------------------- 101 Pacific Disks, Inc. HD211 20 GB Hard Disk 102 Silicon Valley MicroChips P3000 3000 MHz Processor 103 Blue River Electronics Note the (+) operator following P.SUPPLIER_ID. That makes PART the optional table in this join. If a supplier does not currently supply any parts, Oracle will fabricate a PART record with all NULLs for that supplier. Thus, the query results can include all suppliers, regardless of whether they currently supply parts. You can see that the PART columns for supplier 103 in this example all have NULL values. The outer join operator (+) can appear on either the left or the right side of the join condition. However, make sure you apply this operator to the appropriate table in the context of your query. For example, it makes no difference to the result if you switch the two sides of the equality operator in the previous example: SELECT S.SUPPLIER_ID, S.NAME SUPPLIER_NAME, P.PART_NBR, P.NAME PART_NAME FROM SUPPLIER S, PART P WHERE P.SUPPLIER_ID (+) = S.SUPPLIER_ID; SUPPLIER_ID SUPPLIER_NAME PART_NBR PART_NAME ----------- ------------------------------ ---------- -------------------- 101 Pacific Disks, Inc. HD211 20 GB Hard Disk 102 Silicon Valley MicroChips P3000 3000 MHz Processor 103 Blue River Electronics However, if you associate the (+) operator with the wrong table, you may get unexpected results. For example: SELECT S.SUPPLIER_ID, S.NAME SUPPLIER_NAME, P.PART_NBR, P.NAME PART_NAME FROM SUPPLIER S, PART P WHERE P.SUPPLIER_ID = S.SUPPLIER_ID (+); SUPPLIER_ID SUPPLIER_NAME PART_NBR PART_NAME ----------- ------------------------------ ---------- -------------------- 101 Pacific Disks, Inc. HD211 20 GB Hard Disk 102 Silicon Valley MicroChips P3000 3000 MHz Processor Here, the outer join operator is placed on the side of the SUPPLIER table in the join condition. By doing this, you are asking Oracle to print the parts and their corresponding suppliers, as well as the parts without a supplier. However, in our example data, all the parts have a corresponding supplier. Therefore, the results are the same as if we had done an inner join. 3.2.1 Restrictions on Outer JoinsThere are some rules and restrictions on how you can use an outer join query. When you perform an outer join in a query, Oracle doesn't allow you to perform certain other operations in the same query. We discuss these restrictions and some of the work-arounds in this list.
3.2.2 Full Outer JoinsAn outer join extends the result of an inner join by including rows from one table (table A, for example) that don't have corresponding rows in another table (table B, for example). An important thing to note here is that the outer join operation will not include the rows from table B that don't have corresponding rows in table A. In other words, an outer join is unidirectional. There are situations when you may want a bidirectional outer join, i.e., you want to include all the rows from A and B that are:
Let's take an example to understand this further. Consider the following two tables: LOCATION and DEPARTMENT: DESC LOCATION Name Null? Type ------------------------------- -------- ---- LOCATION_ID NOT NULL NUMBER(3) REGIONAL_GROUP VARCHAR2(20) DESC DEPARTMENT Name Null? Type ------------------------------- -------- ---- DEPT_ID NOT NULL NUMBER(2) NAME VARCHAR2(14) LOCATION_ID NUMBER(3) Assume there are locations in the LOCATION table that don't have corresponding departments in the DEPARTMENT table, and that at the same time there are departments in the DEPARTMENT table without a LOCATION_ID pointing to corresponding LOCATION rows. If you perform an inner join of these two tables, you will get only the departments and locations that have corresponding rows in both the tables. SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID = L.LOCATION_ID; DEPT_ID NAME REGIONAL_GROUP ------------- -------------- -------------------- 10 ACCOUNTING NEW YORK 20 RESEARCH DALLAS 30 SALES CHICAGO 40 OPERATIONS BOSTON 12 RESEARCH NEW YORK 13 SALES NEW YORK 14 OPERATIONS NEW YORK 23 SALES DALLAS 24 OPERATIONS DALLAS 34 OPERATIONS CHICAGO 43 SALES BOSTON 11 rows selected. There are locations that don't have any departments. To include those locations in this list, you have to perform an outer join with the (+) operator on the department side, making the DEPARTMENT table the optional table in the query. Notice that Oracle supplies NULLs for missing DEPARTMENT data. SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID (+) = L.LOCATION_ID; DEPT_ID NAME REGIONAL_GROUP ------------- -------------- -------------------- 10 ACCOUNTING NEW YORK 12 RESEARCH NEW YORK 14 OPERATIONS NEW YORK 13 SALES NEW YORK 30 SALES CHICAGO 34 OPERATIONS CHICAGO 20 RESEARCH DALLAS 23 SALES DALLAS 24 OPERATIONS DALLAS SAN FRANCISCO 40 OPERATIONS BOSTON 43 SALES BOSTON 12 rows selected. There are departments that don't belong to any location. If you want to include those departments in the result set, perform an outer join with the (+) operator on the location side. SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID = L.LOCATION_ID (+) ; DEPT_ID NAME REGIONAL_GROUP ------------- -------------- -------------------- 10 ACCOUNTING NEW YORK 20 RESEARCH DALLAS 30 SALES CHICAGO 40 OPERATIONS BOSTON 12 RESEARCH NEW YORK 13 SALES NEW YORK 14 OPERATIONS NEW YORK 23 SALES DALLAS 24 OPERATIONS DALLAS 34 OPERATIONS CHICAGO 43 SALES BOSTON 50 MARKETING 60 CONSULTING 13 rows selected. However, the previous query excluded any location that doesn't have a department. If you want to include the departments without a location as well as the locations without a department, you will probably try to use a two-sided outer join, correctly termed a full outer join, like the following: SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID (+) = L.LOCATION_ID (+); WHERE D.LOCATION_ID (+) = L.LOCATION_ID (+) * ERROR at line 3: ORA-01468: a predicate may reference only one outer-joined table As you can see, a two-sided outer join is not allowed. A UNION of two SELECT statements is a work around for this problem. In the following example, the first SELECT represents an outer join in which DEPARTMENT is the optional table. The second SELECT has the LOCATION table as the optional table. Between the two SELECTS, you get all locations and all departments. The UNION operation eliminates duplicate rows, and the result is a full outer join: SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID (+) = L.LOCATION_ID UNION SELECT D.DEPT_ID, D.NAME, L.REGIONAL_GROUP FROM DEPARTMENT D, LOCATION L WHERE D.LOCATION_ID = L.LOCATION_ID (+) ; DEPT_ID NAME REGIONAL_GROUP ------------- -------------- -------------------- 10 ACCOUNTING NEW YORK 12 RESEARCH NEW YORK 13 SALES NEW YORK 14 OPERATIONS NEW YORK 20 RESEARCH DALLAS 23 SALES DALLAS 24 OPERATIONS DALLAS 30 SALES CHICAGO 34 OPERATIONS CHICAGO 40 OPERATIONS BOSTON 43 SALES BOSTON 50 MARKETING 60 CONSULTING SAN FRANCISCO 14 rows selected. As you can see, this UNION query includes all the rows you would expect to see in a full outer join. UNION queries are discussed in more detail in Chapter 7.
|