views:

130

answers:

6

I'd like to know if having to conditionals when using a JOIN keyword is a good practice.

I'm trying to filter this resultset by date but I'm unable to get all the branches listed even if there's no expense or income for a date using a WHERE clause. Is there a better way of doing this, if so how?

SELECT
  Branches.Name
  ,SUM(Expenses.Amount) AS Expenses
  ,SUM(Incomes.Amount) AS Incomes
FROM
  Branches
  LEFT JOIN Expenses
    ON Branches.Id = Expenses.BranchId AND Expenses.Date = '3/11/2010'
  LEFT JOIN Incomes
    ON Branches.Id = Incomes.BranchId AND Incomes.Date = '3/11/2010'
GROUP BY Branches.Name
+2  A: 

That is the correct way to condition an outer join.

You should not have any performance problems, as long as your query can use indexes for both the BranchId and Date fields of the Expenses and Incomes tables.

Daniel Vassallo
+4  A: 

Why not? Even more! OUTER JOIN has very specific trick about these two conditions! INNER JOIN is tolerant to recombination, for example following are equivalent:

INNER JOIN Expenses
    ON Branches.Id = Expenses.BranchId
WHERE
    Expenses.Date = '3/11/2010'

via:

INNER JOIN Expenses
    ON Branches.Id = Expenses.BranchId AND Expenses.Date = '3/11/2010'

BUT!!! For OUTER JOIN you MUST specify exactly two conditions inside ON, since WHERE would treat result as INNER JOIN

Dewfy
Well you could easily say `Expenses.Date = '3/11/2010' OR Expenses.Date IS NULL`. This isn't really a trick, it's the logical behaviour.
Paul Creasey
Absolutely right, there is subtle difference for outer joins between `ON` and `WHERE`; +1
incarnate
@Paul: only if Expenses.Date isn't nullable.
Peter
A: 

You should separate the join logic and the filtering in my opinion.

SELECT
  Branches.Name
  ,SUM(Expenses.Amount) AS Expenses
  ,SUM(Incomes.Amount) AS Incomes
FROM
  Branches
  LEFT JOIN Expenses
    ON Branches.Id = Expenses.BranchId
  LEFT JOIN Incomes
    ON Branches.Id = Incomes.BranchId
WHERE ISNULL(Expenses.Date,'3/11/2010') = '3/11/2010'
AND  ISNULL(Incomes.Date,'3/11/2010') = '3/11/2010'
GROUP BY Branches.Name
Paul Creasey
In many cases when doing a left join, moving your conditional from the JOIN to the WHERE section of the query will provide very different results. Be careful when advising to make this change.
Scott Ivey
That changes the left join to an inner join and gives the wrong result set. Very poor idea.
HLGEM
fair enough, you are absolutely correct, i totally missed the fact it was a left join, despite typing it! I've modified it now. I still think that i am right in principle, it is unclear that the above query can still match NULL dates, i'd much rather make it explicit.
Paul Creasey
A: 

You are doing it correctly. In your case, you are expecting to filter the right side of the join, so the date condition belongs in the join. It says "give me all branches, and only those income & expenses that match my condition."

When you move your condition to the WHERE clause, you are changing the entire meaning of the query to be "give me all branches and their income & expenses, but only where everything matches my condition."

This is not a matter of good practice, but is by design as to how your database will evaluate your queries.

Scott Ivey
A: 

This is the best way to do this when using outer joins. Moving the additional condition to the where clause will change the join to an inner join as it has to meet the where condition in all the records.

HLGEM
A: 

Your above query is correct. Here is how I keep it straight:

  • When a criteria is in your WHERE clause, it's a criteria that has to be satisfied in the result set, meaning that NULL rows (in your LEFT JOIN, for example) won't match, and will be excluded.
  • Where a criteria is in your JOIN clause (either LEFT or RIGHT), it's criteria that has to be satisfied in the join, but not in the results. If a row exists, it must satisfy the criteria, but SQL will allow an OUTER JOIN to count as a match, even though there's no value on the other side to actually evaluate.

I'm not sure if that makes more sense, but in your case of wanting to return the row even if there are no matches, your criteria must be in the JOIN (as you've done it above).

rwmnau