如何优化这个MySQL查询? (CROSS JOIN,子查询)

时间:2015-01-20 13:01:38

标签: mysql sql join subquery cross-join

我对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)
  5. 用户可以分配任意数量的权限,并且可以向任意数量的用户(多对多)授予任何权限。责任就像权限组,每个权限只属于一个责任。例如,一个权限称为update,责任为customers。另一个是delete orders责任。

    我需要获得每个用户的完整权限映射,但仅适用于至少拥有一个权限的用户。结果应按以下方式订购:

    1. 用户的权限数量从大多数到最少
    2. 用户的created_at列,最早的列
    3. 责任weight
    4. 权限weight
    5. 示例结果集:

      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. update customers责任
      3. create orders责任
      4. update orders责任。
      5. 这就是为什么我们在结果中有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的情况下单独行动。

        现在可以改进以获得更好的性能吗?

1 个答案:

答案 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,索引(可能)没有被使用,所以这也是次要的。