简体   繁体   中英

MySQL query which has multiple subqueries, each with different join types

Let me start by giving you a diagram of the relationships between the tables used in my mysql query:

表关系
(source: r717.net )

I have a query that looks like this:

SELECT  * 
FROM    `permissions`
WHERE   `id` IN (
    SELECT      pr.perm_id 
    FROM        `user_roles` as ur 
    LEFT JOIN   `permissions_role` as pr 
    ON          ur.role_id = pr.role_id
    WHERE       ur.user_id = '$userid'
)
OR      `id` IN (
    SELECT      `perm_id` 
    FROM        `permissions_user` 
    WHERE       `user_id` = '$userid'
)

$userid is the id from the user table for the current user. I store the permission name from the result into an array which represents all permissions assigned to the user based on his/her role and his/her id:

<?php
$user_perms = array();
if(mysql_num_rows($query) > 0):
    while($result = mysql_fetch_array($query):
        $user_perms[] = $result('name');
    endwhile;
endif;
?>

print_r($user_perms); produces an output that looks like:

Array ( 
    [0] => ACCESS_TELEPHONELIST_PAGE
    [1] => ACCESS_VACATIONSCHED_PAGE
    [2] => ACCESS_TOURSCHED_PAGE
    [3] => ACCESS_WORKSCHED_PAGE
    [4] => ACCESS_RESOURCES_LINKS
    [5] => ACCESS_PMTOOL_PAGE
    [6] => ACCESS_TOOLSTOOL_PAGE
    [7] => ACCESS_SHOPTOOLLIST_PAGE
    [8] => ACCESS_TOOLINVENTORY_PAGE
    [9] => ACCESS_MANAGETOOLLIST_PAGE
    [10] => ACCESS_TOOLREPORTS_PAGE
    [11] => ACCESS_JOBSLIST_LINKS
    [12] => MAIN_TAB_TOOLSTOOL
    [13] => ADMIN_TAB_PODMANAGEMENT
    [14] => TOOL_TAB_SHOPTOOLLIST
)

What I want to do is to also store all of the user's roles into another array without making a second query. I thought I needed to use aliases for the sub queries, so I tried this query:

SELECT      permissions.*, usersroles.role_id 
FROM        `permissions`
INNER JOIN  (
    SELECT      ur.user_id, pr.perm_id, ur.role_id
    FROM        `user_roles` as ur
    LEFT JOIN   `permissions_role` as pr 
    ON          ur.role_id = pr.role_id
    WHERE       ur.user_id = '$userid'
) AS usersroles ON usersroles.perm_id = permissions.id
INNER JOIN  (
    SELECT      `perm_id`, `user_id` 
    FROM        `permissions_user` 
    WHERE       `user_id` = '$userid'
) AS userperms ON userperms.user_id = usersroles.user_id
              AND userperms.perm_id = permissions.id

and, using code similar to the code above...

<?php
$user_perms = array();
$user_roles = array();
if(mysql_num_rows($query) > 0):
    while($result = mysql_fetch_array($query):
        $user_perms = $result('name');
        $user_roles = $result('role_id');
    endwhile;
endif;
?>

... I get this warning:

Warning: mysql_num_rows() expects parameter 1 to be resource, boolean given

But, I would like to print_r($user_roles); and produce an output that looks like this:

Array (
    [0] => administrator
    [1] => humanresources
    [2] => podmanager
)

Can anyone tell me what I am doing wrong, or suggest a better way to get the data I need from one query into 2 arrays?

EDIT : After careful consideration, I changed my code to use 2 queries as ImreL suggested. The resulting code works nicely and executes quickly. I have edited my answer to show the final code I used and added supporting code to demonstrate how I am using the 2 queries. Big thanks to ImreL!

The query will be required to run on every page the user loads and we have over 30,000 permissions, and 3,000 roles. I am just trying very hard to keep my number of queries to a minimum. We also host our 7 sites on a server in our office, and the server doesn't seem to be capable of handling the amount of traffic we generate (unfortunately I have no control over this)

I see that your intentions are good, but let me tell you this:

The "number of queries" is not the right way to measure your site's performance.

Many times 2 simple queries use less resources than 1 complex.

Also there are other ways to speed up your sites:

  • Assess if you really need all those roles and permissions loaded on each request. Maybe querying for only the role/permission needed is enough.
  • Have proper indexes
  • Utilize caching techniques to reduce the load (site content)

So finally, I tried to come up with query to satisfy what was asked:

select * from (
    select ur.role_id, p.*
    from user_roles ur 
    left join permissions_role pr on ur.role_id =  pr.role_id
    left join permissions p on p.id = pr.perm_id
    where ur.user_id = '$userid'
  union all
    select null as role_id, p.*
    from permissions_user pu
    join permissions p on p.id = pu.perm_id
    where pu.user_id = '$userid'
) sub
group by ifnull(name,role_id) -- group by to eliminate duplicates

But this is not good on performance. You are much better off with 2 queries: 1st to get all permissions for user

select p.* from permissions p
join permissions_role pr on pr.perm_id = p.id
join user_roles ur on ur.role_id =  pr.role_id and ur.user_id = '$userid'
union
select p.* from permissions p
join  permissions_user pu on pu.perm_id = p.id and pu.user_id = '$userid';

and 2nd to get all roles.

EDIT : Here are the final working queries. Also, I thought it might be useful for others to see how the queries are used, so a full explanation is included.

//build the array(s)
$userperms = array();
$userroles = array();
if(isset($_SESSION['userid'])):
    //get the userid
    $thisuserid = $_SESSION['userid'];
    //get the permissions for each of the user's roles
    $Cpermissions_role = mysql_query(" 
    SELECT      r.type, ur.user_id, pr.perm_id, ur.role_id, p.name
    FROM        user_roles ur
    LEFT JOIN   permissions_role pr ON ur.role_id = pr.role_id
    LEFT JOIN   roles r ON ur.role_id = r.id
    LEFT JOIN   permissions p ON p.id = pr.perm_id
    WHERE       ur.user_id = '$userid'") or die(mysql_error());
    //get the extra permissions for the user
    $Cpermissions_user = mysql_query("
    SELECT      pu.user_id, pu.perm_id, p.name
    FROM        permissions_user pu
    LEFT JOIN   permissions p ON p.id = pu.perm_id
    WHERE       pu.user_id = '$userid'") or die(mysql_error());
    //build an array of the user's roles & an array of the user's permissions
    if(mysql_num_rows($Cpermissions_role) > 0):
        while($Rpermissions_role = mysql_fetch_array($Cpermissions_role)):
            if(empty($userperms)):
                $userperms[] = $Rpermissions_role['name'];
            elseif(!in_array($Rpermissions_role['name'],$userperms)):
                $userperms[] = $Rpermissions_role['name'];
            endif;
            if(empty($userroles)):
                $userroles[] = $Rpermissions_role['type'];
            elseif(!in_array($Rpermissions_role['type'],$userroles)):
                $userroles[] = $Rpermissions_role['type'];
            endif;
        endwhile;
    endif;
    if(mysql_num_rows($Cpermissions_user) > 0):
        while($Rpermissions_user = mysql_fetch_array($Cpermissions_user)):
            if(empty($userperms)):
                $userperms[] = $Rpermissions_user['name'];
            elseif(!in_array($Rpermissions_user['name'],$userperms)):
                $userperms[] = $Rpermissions_user['name'];
            endif;
        endwhile;
    endif;
endif;
/**
 * Determines if the user has permission for the page or parts of page
 * @param string $perm the permission constant
 * @return boolean true if user has access, false if not
 */
function hasPermission($perm){
    global $userperms;
    if(empty($userperms)):
        return false;
    else:
        if(is_array($userperms)):
            if(in_array($perm,$userperms)):
                return true;
            else:
                return false;
            endif;
        else:
            if($perm == $userperms):
                return true;
            else:
                return false;
            endif;
        endif;
    endif;
}

Then, I use the following decision statement:

if(hasPermission("ACCESS_HOME_PAGE")):
    //perform circus tricks here
endif;

So, now print_r($userperms); outputs this:

Array ( 
    [0] => ACCESS_TELEPHONELIST_PAGE
    [1] => ACCESS_VACATIONSCHED_PAGE
    [2] => ACCESS_TOURSCHED_PAGE
    [3] => ACCESS_WORKSCHED_PAGE
    [4] => ACCESS_RESOURCES_LINKS
    [5] => ACCESS_PMTOOL_PAGE
    [6] => ACCESS_TOOLSTOOL_PAGE
    [7] => ACCESS_SHOPTOOLLIST_PAGE
    [8] => ACCESS_TOOLINVENTORY_PAGE
    [9] => ACCESS_MANAGETOOLLIST_PAGE
    [10] => ACCESS_TOOLREPORTS_PAGE
    [11] => ACCESS_JOBSLIST_LINKS
    [12] => MAIN_TAB_TOOLSTOOL
    [13] => ADMIN_TAB_PODMANAGEMENT
    [14] => TOOL_TAB_SHOPTOOLLIST
    [15] => ACCESS_HOME_PAGE
)

And, print_r($userroles); outputs this:

Array ( 
    [0] => administrator
    [1] => humanresourcees
    [2] => podmanager
)

And, just to be thorough, here is the JavaScript I use to access the php array:

var js_userperms = new Array();
js_userperms = ["<?php echo join("\", \"", $userperms); ?>"];

And, the jQuery decision statement:

if(jQuery.inArray("ACCESS_HOME_PAGE", js_userperms) != -1){
    //perform circus tricks here
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM