I’ve got two tables. One is users, which has fields uid and name. The other table is users_roles, which has fields uid and rid (role id).
I’d like to retrieve a list of users that don’t have any of a set of provided roles.
For example:
uid | name
----------
1 | BOB
2 | DAVE
3 | JOHN
USERS_ROLES:
uid | rid
---------
1 | 1
1 | 2
1 | 3
2 | 1
3 | 1
I want to be able to query for just users that don’t have a certain set of rids. For instance, a query that excludes rids 2 and 3 should return DAVE and JOHN, or a query that excludes rids (1,3) should return nobody.
A query using an anti-join pattern is sometimes the most efficient:
The anti-join pattern is do a LEFT [outer] JOIN the
user_rolestable to pull back all the matching rows, AND to get rows fromusersthat don’t have a matching row. The “trick” is to exclude all the matching rows with a predicate in the WHERE clause that eliminates all the rows from users that had a match.An equivalent resultset can be obtained using a NOT EXISTS correlated subquery:
Another approach is to use a
NOT IN, although that is sometimes less efficient. Performance depends on a whole host of factors. It’s possible for the optimizer to generate different execution plans for each of these queries.In any case, for best performance, you’ll need an index …
ON users_roles (uid)orON users_roles (uid,rid).Followup:
Performance testing on my MySQL 5.1.34 server reveals that an anti-join query is almost twice as fast as equivalent NOT EXISTS and NOT IN queries. (1.091 sec vs. 2.066 sec and 2.020 sec)