Literally, you would need two different JOINs:
SELECT * FROM Site
JOIN SiteWidget AS mail ON (Site.id = mail.site_id AND mail.widget_id = 4)
JOIN SiteWidget AS comments ON (Site.id = comments.site_id AND comments.widget_id = 2);
If you are sure that the SiteWidget table has no duplicates, e.g. because (site_id, widget_id) is primary key as is usually done for MtM relations, then you can also use HAVING: this is MySQL syntax:
SELECT Site.* FROM Site
JOIN SiteWidget ON (SiteWidget.site_id = Site.id AND widget_id IN (2,4))
GROUP BY Site.id HAVING COUNT(*) = 2;
since, due to uniqueness, the only possibility for a site to appear twice is to have both widgets. Some believe this to be an abuse of GROUP BY
, and some SQLs (PostgreSQL, if I remember correctly) will require Site
's fields to appear in GROUP BY
or an aggregate functions in SELECT
even if they functionally depend from the group-by column Site.id
.
I find the first formula to be clearer and safer, and, I expect, more or less as fast as the second.
This is both because the many-to-many join table is very small (and index-covered to boot), and because this kind of operation was standard from day one, and is one of the most optimized. For example, I expect checks for widget_id 2 and 4 to run in parallel with a single logical read of the SiteWidget table in the join buffer. Even if they didn't, they would likely be loaded in parallel with a single physical read, the other hitting a SQL cache or, at the very least, the IOSS cache.
You might also try this slight variation, which should be the faster:
SELECT Site.* FROM Site
JOIN SiteWidget AS mail ON (Site.id = mail.site_id AND mail.widget_id = 4)
JOIN SiteWidget AS comments ON (mail.site_id = comments.site_id AND comments.widget_id = 2);
which ought to run the main JOIN against the smallest SiteWidget table, and come out with id lookups into Site. This is actually what is likely to get done even if you word the query as in the first instance.
The first formula is perhaps easier to extend by copying and pasting if you ever need to add, say, the polling
widget.