我对MySQL专家提出了一个具有挑战性的问题。
我有一个包含4个表的用户权限系统:
users (id | email | created_at)
permissions (id | responsibility_id | key | weight)
permission_user (id | permission_id | user_id)
responsibilities (id | key | weight)
用户可以分配任意数量的权限,并且可以向任意数量的用户(多对多)授予任何权限。责任就像权限组,每个权限只属于一个责任。例如,一个权限称为update
,责任为customers
。另一个是delete
orders
责任。
我需要获得每个用户的完整权限映射,但仅适用于至少拥有一个权限的用户。结果应按以下方式订购:
created_at
列,最早的列weight
weight
示例结果集:
user_id | responsibility | permission | granted
-----------------------------------------------
5 | customers | create | 1
5 | customers | update | 1
5 | orders | create | 1
5 | orders | update | 1
2 | customers | create | 0
2 | customers | delete | 0
2 | orders | create | 1
2 | orders | update | 0
假设我在数据库中有10个用户,但只有两个用户拥有任何权限。总共有4个权限:
create
customers
责任update
customers
责任create
orders
责任update
orders
责任。这就是为什么我们在结果中有8条记录(2个用户拥有任何权限×4权限)。首先显示id = 5的用户,因为他有更多权限。如果有任何抽奖,那么created_at
日期较早的抽奖活动将首先出现。权限总是按照责任的权重排序,然后按自己的权重排序。
我的问题是,如何为此案例撰写最佳查询?我自己已经制作了一个并且效果很好:
SELECT `users`.`id` AS `user_id`,
`responsibilities`.`key` AS `responsibility`,
`permissions`.`key` AS `permission`,
!ISNULL(`permission_user`.`id`) AS `granted`
FROM `users`
CROSS JOIN `permissions`
JOIN `responsibilities`
ON `responsibilities`.`id` = `permissions`.`responsibility_id`
LEFT JOIN `permission_user`
ON `permission_user`.`user_id` = `users`.`id`
AND `permission_user`.`permission_id` = `permissions`.`id`
WHERE (
SELECT COUNT(*)
FROM `permission_user`
WHERE `user_id` = `users`.`id`
) > 0
ORDER BY (
SELECT COUNT(*)
FROM `permission_user`
WHERE `user_id` = `users`.`id`
) DESC,
`users`.`created_at` ASC,
`responsibilities`.`weight` ASC,
`permissions`.`weight` ASC
问题是我使用了两次相同的子查询。
我可以做得更好吗?我依靠你,MySQL专家!
---编辑---
感谢Gordon Linoff的评论我使用了HAVING
条款:
SELECT `users`.`email`,
`responsibilities`.`key`,
`permissions`.`key`,
!ISNULL(`permission_user`.`id`) as `granted`,
(
SELECT COUNT(*)
FROM `permission_user`
WHERE `user_id` = `users`.`id`
) AS `total_permissions`
FROM `users`
CROSS JOIN `permissions`
JOIN `responsibilities`
ON `responsibilities`.`id` = `permissions`.`responsibility_id`
LEFT JOIN `permission_user`
ON `permission_user`.`user_id` = `users`.`id`
AND `permission_user`.`permission_id` = `permissions`.`id`
HAVING `total_permissions` > 0
ORDER BY `total_permissions` DESC,
`users`.`created_at` ASC,
`responsibilities`.`weight` ASC,
`permissions`.`weight` ASC
我很惊讶地发现HAVING
可以在没有GROUP BY
的情况下单独行动。
现在可以改进以获得更好的性能吗?
答案 0 :(得分:1)
执行此操作的最有效方法可能是:
SELECT u.email, r.`key`, r.`key`,
!ISNULL(pu.id) as `granted`
FROM (SELECT u.*,
(SELECT COUNT(*) FROM `permission_user` pu WHERE pu.user_id = u.id
) AS `total_permissions`
FROM `users` u
) u CROSS JOIN
permissions p JOIN
responsibilities r
ON r.id = p.responsibility_id LEFT JOIN
permission_user pu
ON pu.user_id = u.id AND
pu.permission_id = p.id
WHERE u.total_permissions > 0
ORDER BY `total_permissions` DESC,
`users`.`created_at` ASC,
`responsibilities`.`weight` ASC,
`permissions`.`weight` ASC;
这将为每个用户运行一次子查询,而不是每个用户/权限组合运行一次(因为修改后的查询和原始查询都在进行)。这有两个成本。第一个是子查询的实现,因此必须再次读取和写入users表中的数据。考虑到查询中的其他内容,可能不是什么大问题。第二个是users
表上的索引丢失。再一次,使用cross join
,索引(可能)没有被使用,所以这也是次要的。