[英]How to optimize this MySQL query? (CROSS JOIN, subquery)
對於MySQL專家,我有一個具有挑戰性的問題。
我有一個包含4個表的用戶權限系統:
users (id | email | created_at)
permissions (id | responsibility_id | key | weight)
permission_user (id | permission_id | user_id)
responsibilities (id | key | weight)
用戶可以分配任意數量的權限,並且可以將任意權限授予任意數量的用戶(很多)。 責任就像權限組,每個權限完全屬於一個職責。 例如,一種許可稱為對customers
負責的update
。 另一個將被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
責任 customers
責任update
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
我驚訝地發現,沒有GROUP BY
可以單獨進行HAVING
操作。
現在可以對其進行改進以獲得更好的性能嗎?
可能最有效的方法是:
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
,(可能)未使用索引,因此這也是次要的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.