In the year 2010 I would have joined the author of this questions and would have strongly voted for `JOIN`, but with much more experience (especially in MySQL) I can state: Yes subqueries can be better. I've read multiple answers here; some stated subqueries are faster, but it lacked a good explanation. I hope I can provide one with this (very) late answer:
First of all, let me say the most important: **There are different forms of sub-queries**
And the second important statement: **Size matters**
If you use sub-queries, you should **be aware** of how the DB-Server executes the sub-query. Especially **if the sub-query is evaluated once or for every row!**
On the other side, a modern DB-Server is able to optimize a lot. In some cases a subquery helps optimizing a query, but a newer version of the DB-Server might make the optimization obsolete.
## Sub-queries in Select-Fields ##
SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo
Be aware that a sub-query is executed for every resulting row from `foo`.
Avoid this if possible; it may drastically slow down your query on huge datasets. However, if the sub-query has no reference to `foo` it can be optimized by the DB-server as static content and could be evaluated only once.
## Sub-queries in the Where-statement ##
SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)
If you are lucky, the DB optimizes this internally into a `JOIN`. If not, your query will become very, very slow on huge datasets because it will execute the sub-query for every row in `foo`, not just the results like in the select-type.
## Sub-queries in the Join-statement
SELECT moo, bar
FROM foo
LEFT JOIN (
SELECT MIN(bar), me FROM wilco GROUP BY me
) ON moo = me
This is interesting. We combine `JOIN` with a sub-query. And here we get the real strength of sub-queries. Imagine a dataset with millions of rows in `wilco` but only a few distinct `me`. Instead of joining against a huge table, we have now a smaller temporary table to join against. This can result in much faster queries depending on database size. You can have the same effect with `CREATE TEMPORARY TABLE ...` and `INSERT INTO ... SELECT ...`, which might provide better readability on very complex queries (but can lock datasets in a repeatable read isolation level).
## Nested sub-queries ##
SELECT VARIANCE(moo)
FROM (
SELECT moo, CONCAT(roger, wilco) AS bar
FROM foo
HAVING bar LIKE 'SpaceQ%'
) AS temp_foo
GROUP BY moo
You can nest sub-queries in multiple levels. This can help on huge datasets if you have to group or change the results. Usually the DB-Server creates a temporary table for this, but sometimes you do not need some operations on the whole table, only on the resultset. This might provide a much better performance depending on the size of the table.
## Conclusion ##
Sub-queries are no replacement for a `JOIN` and you should not use them like this (although possible). In my humble opinion, the correct use of a sub-query is the use as a quick replacement of `CREATE TEMPORARY TABLE ...`. A good sub-query reduces a dataset in a way you cannot accomplish in an `ON` statement of a `JOIN`. If a sub-query has one of the keywords `GROUP BY` or `DISTINCT` and is preferably not situated in the select fields or the where statement, then it might improve performance a lot.