I have two tables (listed only fields important for the question):
t_groups
- INT groupId PRIMARY
- VARCHAR(255) grname
t_goods
- INT goodId PRIMARY
- INT groupId
- INT price
- VARCHAR(255) name
Now I need a query, which selects group names and name of the cheapest good in each group. Tried doing it this way:
SELECT gr.groupId, grname, g.name
FROM t_groups AS gr
LEFT JOIN (SELECT * FROM t_goods ORDER BY PRICE ASC LIMIT 1) AS g
ON g.groupId = gr.groupId
but it doesn’t work — returns NULLs in g.name field. It could be easily explained:
SELECT within JOIN statement selects cheapest good first, and then tries to “filter it” by groupId. Obviously, it’ll only work for the group cheapest good belongs to.
How do I solve the task?
Why your query does not work
The inner query selects the absolutely cheapest good (irrespective of group) in your database. Therefore, when you
LEFT JOINthe groups to this result set, only the group which actually includes the universally cheapest good has a matching row (that group should get theg.namecolumn filled properly). However, due to the wayLEFT JOINworks all other groups will getNULLas the value of all columns ing.The correct solution
First, you need to select the cheapest price in each group. This is easy:
However the cheapest price is not useful without the associated
goodId. The problem is that it’s not meaningful to write something like:The reason is that you cannot select a non-grouped column (i.e.
goodId) unless you wrap it in an aggregate function (such asMIN): we don’t know whichgoodIdyou want from among those that share the samegroupId.The correct, portable way to get the
goodIdof the cheapest goods in each group isThe above query first finds out the cheapest price per group, and then joins to the goods table again to find the
goodIds of the goods having that price inside that group.Important: if multiple goods have an equal cheapest price in a group, this query will return all of them. If you only want one result per group you have to specify the tiebreaker, for example:
With this query in hand, you can then find the name and price of the single cheapest good in each group (lowest
goodIdwill be used as tiebreaker):This final query performs two joins at its “outer” level:
goodIdand cheapest price for each group” table to get thegoodIdand cheapest pricegoodIdIt will produce only one good per group, even if multiple goods are tied for cheapest.