簡體   English   中英

如何優化此MySQL查詢? (交叉聯接,子查詢)

[英]How to optimize this MySQL query? (CROSS JOIN, subquery)

對於MySQL專家,我有一個具有挑戰性的問題。

我有一個包含4個表的用戶權限系統:

  1. users (id | email | created_at)
  2. permissions (id | responsibility_id | key | weight)
  3. permission_user (id | permission_id | user_id)
  4. responsibilities (id | key | weight)

用戶可以分配任意數量的權限,並且可以將任意權限授予任意數量的用戶(很多)。 責任就像權限組,每個權限完全屬於一個職責。 例如,一種許可稱為對customers負責的update 另一個將被delete並承擔orders責任。

我需要獲得每個用戶的權限的完整地圖,但僅適用於已授予至少一個權限的用戶。 結果應按以下順序排序:

  1. 用戶的權限數(從最大到最小)
  2. 用戶的created_at列,最舊的優先
  3. 責任weight
  4. 權限的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個權限:

  1. create customers責任
  2. customers責任update
  3. create orders責任
  4. 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM