[英]MYSQL: Excluding several rows if one joined row matches a condition
I've got two tables. 我有两张桌子。 One is users, which has fields uid and name. 一个是用户,其中包含字段uid和名称。 The other table is users_roles, which has fields uid and rid (role id). 另一个表是users_roles,其中包含字段uid和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: 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. 我希望能够只查询没有一组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. 例如,排除rid 2和3的查询应该返回DAVE和JOHN,或者排除rids(1,3)的查询应该返回nobody。
A query using an anti-join pattern is sometimes the most efficient: 使用反连接模式的查询有时是最有效的:
SELECT u.uid
, u.name
FROM users u
LEFT
JOIN users_roles r
ON r.uid = u.uid
AND r.rid IN (2,3)
WHERE r.uid IS NULL
The anti-join pattern is do a LEFT [outer] JOIN the user_roles
table to pull back all the matching rows, AND to get rows from users
that don't have a matching row. 反连接模式是LEFT [outer] JOIN user_roles
表以拉回所有匹配的行,AND来从没有匹配行的users
获取行。 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. “技巧”是在WHERE子句中使用谓词排除所有匹配行,从而消除具有匹配项的用户的所有行。
An equivalent resultset can be obtained using a NOT EXISTS correlated subquery: 使用NOT EXISTS相关子查询可以获得等效的结果集:
SELECT u.uid
, u.name
FROM users u
WHERE NOT EXISTS
( SELECT 1
FROM users_roles r
WHERE r.uid = u.uid
AND r.rid IN (2,3)
)
Another approach is to use a NOT IN
, although that is sometimes less efficient. 另一种方法是使用NOT IN
,尽管有时效率较低。 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)
or ON users_roles (uid,rid)
. 在任何情况下,为了获得最佳性能,您需要一个索引... ON users_roles (uid)
或ON users_roles (uid,rid)
。
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. 我的MySQL 5.1.34服务器上的性能测试表明,反连接查询的速度几乎是等效的NOT EXISTS和NOT IN查询的两倍。 (1.091 sec vs. 2.066 sec and 2.020 sec) (1.091秒对2.066秒和2.020秒)
-- setup and populate test tables
CREATE TABLE t_users
( uid INT UNSIGNED NOT NULL PRIMARY KEY
, `name` VARCHAR(50)
) ENGINE=INNODB DEFAULT CHARSET=latin1 ;
CREATE TABLE t_users_roles
( uid INT UNSIGNED NOT NULL
, rid INT UNSIGNED NOT NULL
, PRIMARY KEY (uid,rid)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
ALTER TABLE t_users_roles ADD CONSTRAINT FK_t_users_roles_t_users FOREIGN KEY (uid) REFERENCES t_users (uid);
CREATE INDEX t_users_ix1 ON t_users (uid,`name`);
CREATE INDEX t_users_roles_ix1 ON t_users_roles (rid,uid);
INSERT INTO t_users (uid,`name`)
SELECT d.d*100000+e.d*10000+u.d*1000+h.d*100+t.d*10+o.d+1 AS uid
, CONCAT('NAME',LPAD(d.d*100000+e.d*10000+u.d*1000+h.d*100+t.d*10+o.d+1,8,'-')) AS `name`
FROM (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) o
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) h
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) u
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) e
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
;
-- 1000000 row(s) affected.
INSERT INTO t_users_roles (uid,rid)
SELECT d.d*100000+e.d*10000+u.d*1000+h.d*100+t.d*10+o.d+1 AS uid
, r.rid
FROM (SELECT 1 AS rid UNION ALL SELECT 2 UNION ALL SELECT 3) r
CROSS
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) o
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) h
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) u
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) e
JOIN (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
WHERE (d.d*100000+e.d*10000+u.d*1000+h.d*100+t.d*10+o.d+1) % 100000 <> 0
;
-- 2999970 row(s) affected
OPTIMIZE TABLE t_users;
OPTIMIZE TABLE t_users_roles;
SHOW STATUS LIKE 'Qcache_hits' ;
-- Variable_name Value
-- ------------- ---------
-- Qcache_hits 1117342
SHOW VARIABLES LIKE 'version' ;
-- Variable_name Value
-- ------------- ------------
-- version 5.1.53-log
-- table size from the file system
$ du -sh DATA/test/t_users*.ibd
72M DATA/test/t_users.ibd
133M DATA/test/t_users_roles.ibd
-- anti-join query
SELECT SQL_NO_CACHE u.uid
, u.name
FROM t_users u
LEFT
JOIN t_users_roles r
ON r.uid = u.uid
AND r.rid IN (2,3)
WHERE r.uid IS NULL ;
-- Exec: 1.095 sec
-- Exec: 1.090 sec
-- Exec: 1.091 sec
-- Exec: 1.087 sec
-- Exec: 1.090 sec
-- avg 5 executions: 1.091 sec
-- not exists query
SELECT SQL_NO_CACHE u.uid
, u.name
FROM t_users u
WHERE NOT EXISTS
( SELECT 1
FROM t_users_roles r
WHERE r.uid = u.uid
AND r.rid IN (2,3)
) ;
-- Exec: 2.071 sec
-- Exec: 2.066 sec
-- Exec: 2.059 sec
-- Exec: 2.065 sec
-- Exec: 2.070 sec
-- avg 5 executions: 2.066 sec
-- not in query
SELECT SQL_NO_CACHE u.uid
, u.name
FROM t_users u
WHERE u.uid NOT IN
( SELECT r.uid
FROM t_users_roles r
WHERE r.uid IS NOT NULL
AND r.rid IN (2,3)
) ;
-- Exec: 2.022 sec
-- Exec: 2.023 sec
-- Exec: 2.014 sec
-- Exec: 2.026 sec
-- Exec: 2.016 sec
-- avg 5 executions: 2.020 sec
SHOW STATUS LIKE 'Qcache_hits' ;
-- Variable_name Value
-- ------------- ---------
-- Qcache_hits 1117342
EXPLAIN output for three statements:
-- ANTI JOIN
id select_type table type possible_keys key key_len ref rows filtered Extra
-- ------------------ ------ -------------- ------------------------- ----------- ------- ----- ------- -------- ------------------------------------
1 SIMPLE u index t_users_ix1 57 1000423 100.00 Using index
1 SIMPLE r ref PRIMARY,t_users_roles_ix1 PRIMARY 4 u.uid 1 100.00 Using where; Using index; Not exists
-- NOT EXISTS
id select_type table type possible_keys key key_len ref rows filtered Extra
-- ------------------ ------ -------------- ------------------------- ----------- ------- ----- ------- -------- --------------------------
1 PRIMARY u index t_users_ix1 57 1000423 100.00 Using where; Using index
2 DEPENDENT SUBQUERY r ref PRIMARY,t_users_roles_ix1 PRIMARY 4 u.uid 1 100.00 Using where; Using index
-- NOT IN
id select_type table type possible_keys key key_len ref rows filtered Extra
-- ------------------ ------ -------------- ------------------------- ----------- ------- ------ ------- -------- --------------------------
1 PRIMARY u index t_users_ix1 57 1000423 100.00 Using where; Using index
2 DEPENDENT SUBQUERY r index_subquery PRIMARY,t_users_roles_ix1 PRIMARY 4 func 1 100.00 Using index; Using where
You're looking for the NOT IN query: 您正在寻找NOT IN查询:
MySQL "NOT IN" query MySQL“NOT IN”查询
SELECT name
FROM users
WHERE uid NOT IN (SELECT uid FROM Users_roles WHERE rid = 2 OR rid = 3)
The query will give you all the complete list of names with their missing rid
. 查询会给你他们失踪的名字都完整列表rid
。
SELECT a.*, b.rid
FROM users a CROSS JOIN
(SELECT DISTINCT rid FROM users_roles) b
LEFT JOIN USERS_ROLES c
ON a.uid = c.uid AND
b.rid = c.rid
WHERE c.rid IS NULL
-- AND other conditions
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.