tags:

views:

83

answers:

2

Say I have these three tables:

Table: Baskets
id  |  name
1      Sale
2      Premium
3      Standard
4      Expired

Table: Fruit
id  |  name   | basketid
1      Apples    1
2      Oranges   2
3      Grapes    3
4      Apples    2
5      Apples    4

Table: Veggies
id  |  name   | basketid
1      Carrots     1
2      Peas        2
3      Asparagus   1

It may seem like the second two tables should have just been one table called produce, but in the real situation there is good reason for them to be different tables. I need to write a join that returns rows if the basket has rows in either the fruit or veggie tables. I thought I could accomplish this with two left joins like so:

Select Baskets.*, fruit.name as fruit,
veggies.name as veggies 
from Baskets
left join Fruit on Baskets.id =  Fruit.basketid 
left join veggies on Baskets.id = Veggies.basketid 
where Baskets.id = 2;

But this statement returns values in fields that I would like to be empty. The actual output:

id  | name     |  fruit   |  veggies
2     Premium    Oranges     Peas
2     Premium    Apples      Peas

The output I would like:

id  | name     |  fruit   |  veggies
2     Premium    Oranges     
2     Premium    Apples      
2     Premium               Peas

How can I accomplish this?

+2  A: 

This query will return no rows if there is nothing in the basket:

SELECT  baskets.*, fruit.name AS fruit, NULL AS veggie
FROM    baskets
JOIN    fruit
ON      fruit.basketId = baskets.id
WHERE   baskets.Id = 2
UNION
SELECT  baskets.*, NULL, veggie.name
FROM    baskets
JOIN    veggies
ON      veggies.basketId = baskets.id
WHERE   baskets.Id = 2

This query will return a single row with two NULLS if there is nothing in the basket:

SELECT  baskets.*, fruit.name AS fruit, NULL AS veggie
FROM    baskets
LEFT JOIN
        fruit
ON      fruit.basketId = baskets.id
WHERE   baskets.Id = 2
UNION
SELECT  baskets.*, NULL, veggie.name
FROM    baskets
LEFT JOIN
        veggies
ON      veggies.basketId = baskets.id
WHERE   baskets.Id = 2

This assumes that both fruit.name and veggie.name are not nullable and unique.

This query is the same as previous but can be more efficient:

SELECT  baskets.*, fruit.name AS fruit, NULL AS veggie
FROM    baskets
JOIN    fruit
ON      fruit.basketId = baskets.id
WHERE   baskets.Id = 2
UNION ALL
SELECT  baskets.*, NULL, veggie.name
FROM    baskets
JOIN    veggies
ON      veggies.basketId = baskets.id
WHERE   baskets.Id = 2
UNION ALL
SELECT  baskets.*, NULL, NULL
FROM    baskets
LEFT JOIN
        fruit
ON      fruit.basketId = baskets.id
LEFT JOIN
        veggies
ON      veggies.basketId = baskets.id
WHERE   baskets.Id = 2
        AND fruit.basketId IS NULL
        AND veggies.basketId IS NULL

, and does not assume anything.

Quassnoi
I don't think that this will act like a left join. I.e., if there are no entries in either fruit or veggies with the given basketId, you'll get no rows returned rather than one with null fruit and veggie fields.
ngroot
@ngroot: see the post update
Quassnoi
@ngroot: seems I mistook you for the @op :) Actually, the @op wanted "to write a join that returns rows if the basket has rows in either the fruit or veggie tables", which seems to be an `INNER JOIN`.
Quassnoi
Thanks for the examples. I don't want any rows if there is nothing in the basket, which leaves me with the first select which works very well for the provided example. However, the true situation involves a pretty complicated query which i think would be a waste of resources to force the server to do twice (not to mention I'd have to debug the code twice once for the top of the union and once for the bottom), so I'm going to give the check to ngroot for his nested union select, even though it didn't give the proper output. Thanks a bunch though, I learned a lot from reading your response.
Todd
A `LEFT OUTER JOIN` *does* return rows if the basket has rows in either the fruit or veggie tables. It also returns a row if it doesn't, which is the behavior I assumed that you wanted since you were using `LEFT OUTER JOIN`s rather than `INNER JOIN` s. Maybe "iff the basket has rows" would have been a better question? ;-)
ngroot
@ngroot: the title reads pretty clearly: "left join with empty results when no match is found" :)
Quassnoi
The "empty" part of the title refers to the fact that i didn't want repeat results in the empty table which is what I was getting when I used my original 2 left join query. The fact is, I didn't know about the UNION command. If I had, the question would have been different (if I even asked it at all). Again, thanks for all the excellent answers!
Todd
+1  A: 

You need to union the Fruits and Vegetables table into one relation that you join against, a la:

Select Baskets.*, produce.fruitname as fruit, produce.veggiename as veggies 
from Baskets
left join (SELECT basketid, name as fruitname, NULL as veggiename
           FROM fruit
           UNION
           SELECT basketid, NULL, name
           FROM veggies) produce
      ON baskets.id = produce.basketid
where Baskets.id = 2;
ngroot
Clever use of the union statement (which I'd not seen before). But this returns a row even if no matching record exists in either the fruit or veggies table. I think I solved this by changing from left join to regular inner join.
Todd
And use UNION ALL if there is no possibility a record could be in both.
HLGEM