SELECT
a.foo
b.bar
c.foobar
FROM tableOne AS a
INNER JOIN tableTwo AS b ON a.pk = b.fk
LEFT JOIN tableThree AS c ON b.pk = c.fk
WHERE a.foo = \'something\'
AND c.foo
The 'where' clause is performed after the join. This doesn't matter for inner joins but matters for outer joins.
Shorten Example
SELECT b.bar,c.foobar FROM tableTwo AS b LEFT JOIN tableThree AS c ON b.pk=c.fk WHERE c.foobar='somethingelse'
Raw data Outer Join Result Select Result
b.pk c.fk c.foorbar b.pk c.fk c.foorbar c.foorbar
1 1 something 1 1 something <not in result set>
1 1 somethingelse 1 1 somethingelse somethingelse
SELECT b.bar,c.foobar FROM tableTwo AS b LEFT JOIN tableThree AS c ON b.pk=c.fk AND c.foobar='somethingelse'
Raw data Outer Join Result Select Result
b.pk c.fk c.foorbar b.pk c.fk c.foorbar c.foorbar
1 1 something 1 null null null
1 1 somethingelse 1 1 somethingelse somethingelse
The joins are doing their work, then the where is removing the records where c.foobar <> 'somethingelse'.
The effect looks like an inner join but actually isn't.
The LEFT JOIN produces NULLs where there are no matching rows. In this case, c.foobar will be NULL for the non-matching rows. But your WHERE clause is looking for a specific value: 'somethingelse', and so will filter out all the NULL values. Since an INNER JOIN also produces no NULL values on the right side, the two look the same. You can add ' OR c.foobar IS NULL' to allow the null values back in.
When you move the condition to the ON clause, it becomes part of the JOIN row matching, rather than the final filter. The join match may fail, and the outer join then returns NULLs on cases where 'c.foobar' is NULL or not 'somethingelse'.
See
A Left Join returns everything from the left table (tableTwo in your example) and any matching rows from the table on the right (tableThree in your example). When you filter on something on the right side of the left join (i.e. tableThree) and you do not account for non-matching values you are effectively requiring that a value exist and that the value be 'something' which is the equivalent of an inner join. If what you are trying to do is to find all tableTwo rows which do not have a row in tableThree with a foobar value of 'something', you can move the filtering into the on clause:
Select a.foo, b.bar, c.foobar
From tableOne As a
Inner Join tableTwo as b
On b.fk = a.pk
Left Join tableThree as c
On c.fk = b.pk
And c.foobar = 'something'
Where a.foo = 'something'
And c.pk Is Null
The final addition, c.pk Is Null
filters for values that do not have a tableThree value with a foobar value of 'something'.If just want to see tableThree values when they have a foobar value of 'something' (and nulls otherwise), then remove the additional filter I added of c.pk Is Null
.
It doesn't turn a LEFT JOIN into an INNER JOIN, thought the effect may appear to be the same. When you set the WHERE condition
AND c.foobar = 'somethingelse'
you're getting rid of all the cases that allow a LEFT JOIN TO act as it does. In this case, some of the values for c.foobar will be NULL. Setting this on the JOIN condition still allows non-matching LEFT JOIN results, only restricting what is returned for C results.
The reason you're seeing this is because the left join sets all columns of c
to NULL for those rows that don't exist in c
(i.e. that can't be joined). This implies that the comparison c.foobar = 'somethingelse'
is not true, which is why those rows are not being returned.
In the case where you move the c.foobar = 'somethingelse'
into the join condition, that join is still returning those rows (albeit with NULL values) when the condition is not true.