On ORACLE 12C (this does not occur on 11g), the following SQL excludes rows from the tOwners table when it shouldnt:
SELECT *
FROM
(
SELECT 1 As OwnerId, 'Fred' As OwnerName FROM DUAL UNION
SELECT 2 As OwnerId, 'Tim' As OwnerName FROM DUAL
) tOwners
LEFT JOIN
(
SELECT 1 As PetId, 1 As OwnerId, 'Cat' As Pet FROM DUAL UNION
SELECT 2 As PetId, 1 As OwnerId, 'Mouse' As Pet FROM DUAL
) tPets
ON
tOwners.OwnerId = tPets.OwnerId
LEFT JOIN
(SELECT 2 As PetId, 'Treats' As Food FROM DUAL) tFoods
ON
tPets.Pet = 'Cat' AND
tPets.PetId = tFoods.PetId
Does not return a row for 'Tim':
1 Fred 1 1 Cat
1 Fred 2 1 Mouse
Execution Plan:
Plan hash value: 3529061095
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 10 (0)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 1 | 19 | 10 (0)| 00:00:01 |
|* 2 | HASH JOIN | | 1 | 17 | 8 (0)| 00:00:01 |
| 3 | VIEW | | 2 | 14 | 4 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 2 | | 4 (50)| 00:00:01 |
| 5 | UNION-ALL | | | | | |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | VIEW | | 2 | 20 | 4 (0)| 00:00:01 |
| 9 | SORT UNIQUE | | 2 | | 4 (50)| 00:00:01 |
| 10 | UNION-ALL | | | | | |
| 11 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 12 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 13 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("TPETS"."PET"=CASE WHEN (CASE WHEN ROWID(+) IS NOT NULL
THEN 2 ELSE NULL END IS NOT NULL) THEN 'Cat' ELSE 'Cat' END AND
"TPETS"."PETID"=CASE WHEN (ROWID(+) IS NOT NULL) THEN 2 ELSE NULL END )
2 - access("TOWNERS"."OWNERID"="TPETS"."OWNERID")
Those predicates look pretty suspect here, but why is it doing this?
However, if you change the "tPets.Pet = 'Cat'" join predicate to use a subquery, it works just fine:
SELECT *
FROM
(
SELECT 1 As OwnerId, 'Fred' As OwnerName FROM DUAL UNION
SELECT 2 As OwnerId, 'Tim' As OwnerName FROM DUAL
) tOwners
LEFT JOIN
(
SELECT 1 As PetId, 1 As OwnerId, 'Cat' As Pet FROM DUAL UNION
SELECT 2 As PetId, 1 As OwnerId, 'Mouse' As Pet FROM DUAL
) tPets
ON
tOwners.OwnerId = tPets.OwnerId
LEFT JOIN
(SELECT 2 As PetId, 'Treats' As Food FROM DUAL) tFoods
ON
tPets.Pet = (SELECT 'Cat' FROM DUAL) AND
tPets.PetId = tFoods.PetId
With:
1 Fred 1 1 Cat
1 Fred 2 1 Mouse
2 Tim
Execution Plan:
Plan hash value: 1713688406
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 56 | 16 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 2 | 56 | 16 (0)| 00:00:01 |
|* 2 | HASH JOIN OUTER | | 2 | 34 | 8 (0)| 00:00:01 |
| 3 | VIEW | | 2 | 14 | 4 (0)| 00:00:01 |
| 4 | SORT UNIQUE | | 2 | | 4 (50)| 00:00:01 |
| 5 | UNION-ALL | | | | | |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | VIEW | | 2 | 20 | 4 (0)| 00:00:01 |
| 9 | SORT UNIQUE | | 2 | | 4 (50)| 00:00:01 |
| 10 | UNION-ALL | | | | | |
| 11 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 12 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 13 | VIEW | VW_LAT_9BF0EE0C | 1 | 11 | 4 (0)| 00:00:01 |
|* 14 | FILTER | | | | | |
| 15 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 16 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("TOWNERS"."OWNERID"="TPETS"."OWNERID"(+))
14 - filter("TPETS"."PETID"=2 AND "TPETS"."PET"= (SELECT 'Cat' FROM
"SYS"."DUAL" "DUAL"))
It appears as if ORACLE is limiting the result set based on rows where there are no Foods for those Pets (only when using a literal), however, I would have thought it would always return rows form the tOwners table irrespective of the join predicates on the LEFT hanging joins, can someone explain this behaviour, or is actually a known bug of some sort?
NOTE: I have only tested in ORACLE version 11g and 12c, and it only occurs in 12c so far.