多表查询 : 连接查询-子查询
MySQL基础操作链接 ; 工具: SQLyog
1. 表结构
用户,角色,权限三张表(主表)及三者之间的关系通过两张 “第三张外键表”维护。“外键表”中的两个字段分别使用外键指向主表的主键。(一个用户可以有多个角色,一个角色可以有多个权限;正常来看是是一对多的关系,但是反过来 某个权限可以有多个角色拥有。 所以三者关系必须理解为多对多的关系,所以需要 “第三张外键表”去维护两张表之间的关系,同时保证实体表与实体表之间互相独立)
表名:
用户信息表user |
||
字段 |
类型 |
描述 |
id |
int(11) NOT NULL |
主键,自增 |
name |
varchar(255) NOT NULL |
名称 |
password |
varchar(255) NOT NULL |
密码 |
age |
int(11) NOT NULL |
年龄 |
gender |
char(1) NOT NULL |
性别 |
角色信息表role |
||
字段 |
类型 |
描述 |
id |
int(11) NOT NULL |
主键,自增 |
rolename |
varchar(255) NOT NULL |
名称 |
权限信息表permission |
||
字段 |
类型 |
描述 |
id |
int(11) NOT NULL |
主键,自增 |
name |
varchar(255) NOT NULL |
权限 |
用户角色表user_role |
||
字段 |
类型 |
描述 |
uid |
int(11) NOT NULL |
外键:user id |
rid |
int(11) NOT NULL |
外键:role id |
角色权限表role_permisson |
||
字段 |
类型 |
描述 |
rid |
int(11) NOT NULL |
外键:role id |
pid |
int(11) NOT NULL |
外键:permission id |
具体建表语句:
创建用户表:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,自增',
`name` varchar(255) NOT NULL COMMENT '名称',
`password` varchar(255) NOT NULL COMMENT '密码',
`age` int(11) NOT NULL COMMENT '年龄',
`gender` char(1) NOT NULL COMMENT '性别',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
创建角色表:
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,自增',
`rolename` varchar(255) NOT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
创建权限表:
CREATE TABLE `permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
创建用户角色表:
CREATE TABLE `user_role` (
`uid` int(11) DEFAULT NULL COMMENT '外键-userId',
`rid` int(11) DEFAULT NULL COMMENT '外键-roleId',
KEY `fk_ur_role_id` (`rid`),
KEY `fk_ur_user_id` (`uid`),
CONSTRAINT `fk_ur_user_id` FOREIGN KEY (`uid`) REFERENCES `user` (`id`),
CONSTRAINT `fk_ur_role_id` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建角色权限表:
CREATE TABLE `role_permission` (
`rid` int(11) DEFAULT NULL COMMENT '外键-roleId',
`pid` int(11) DEFAULT NULL COMMENT '外键-permissionId',
KEY `fk_rp_role_id` (`rid`),
KEY `fk_rp_permission_id` (`pid`),
CONSTRAINT `fk_rp_role_id` FOREIGN KEY (`rid`) REFERENCES `role` (`id`),
CONSTRAINT `fk_rp_permission_id` FOREIGN KEY (`pid`) REFERENCES `permission` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2. 查询操作
(三表联查)查询某个权限有哪些角色?(如:查询权限有哪些角色)
子查询
步骤:
- 1. 先从permission表中查询出拥有‘查询’权限的id
SELECT p.id, p.name FROM permission p WHERE p.name='查询';
- 2. 从role_permission表(中间表)中查询出拥有‘查询’(pid)权限的角色rid
SELECT rp.rid ,rp.pid FROM role_permission rp WHERE rp.pid = (SELECT p.id FROM permission p WHERE p.name='查询');
- 3. 最后从role表中获取信息:条件 role.id = role_permission.rid
注:通过子查询在role_permission表中查询出的rid有多个值(1,2,3),则需要在括号前面添加any字段。Any关键字 表示 where r.id = rp.rid1 or r.id = rp.rid2 or ... 。(扩展:all关键字用and替换or;some关键字和any关键字相同)。
否则报错:Subquery returns more than 1 row
SELECT r.id,r.rolename FROM role r WHERE r.id=ANY(
SELECT rp.rid FROM role_permission rp WHERE rp.pid = (SELECT p.id FROM permission p WHERE p.name='查询')
);
子查询--内连接join ... on ...
- 内连接:两张表合并,条件 rp.pid=p.id AND p.name='查询' 。
SELECT * FROM role_permission rp JOIN permission p ON rp.pid=p.id AND p.name='查询' ORDER BY rp.rid ASC;
- 子查询:
SELECT * FROM role r WHERE r.id=ANY(
SELECT rp.rid FROM role_permission rp JOIN permission p ON rp.pid=p.id AND p.name='查询'
);
内连接Join...on...
先“中间表”与permission表内连接 -查询出权限为‘查询’的字段,再与role表进行内连接 -查询出role表中的角色有哪些。(若数据量大时,不使用select *并添加limit限制行数)
SELECT * FROM role_permission rp JOIN permission p ON rp.pid=p.id AND p.name='查询' JOIN role r ON rp.rid=r.id;
(五表联查)查询某用户 有哪些角色并且有哪些权限?(如:A用户有哪些角色和拥有哪些权限)
内连接
SELECT * FROM USER u
JOIN user_role ur ON u.id=ur.uid AND u.name='管理员A'
JOIN role r ON ur.rid=r.id
JOIN role_permission rp ON r.id=rp.rid
JOIN permission p ON p.id=rp.pid ;
查询后的数据:角色及权限有重复,如图,
若想得到用户拥有的角色:
SELECT * FROM USER u JOIN user_role ur ON u.id=ur.uid AND u.name='管理员A' LEFT JOIN role r ON ur.rid=r.id ;
若想得到用户拥有的权限:在语句后面使用 分组: group by p.name
SELECT * FROM USER u
JOIN user_role ur ON u.id=ur.uid AND u.name='管理员A'
JOIN role r ON ur.rid=r.id
JOIN role_permission rp ON r.id=rp.rid
JOIN permission p ON p.id=rp.pid
GROUP BY p.name;
注: 去重可以使用distinct 和group by 。 显然关联查询中不能使用distinct。原因:distinct位置固定只能跟在select 后面如(select distinct name from...),且只适用于查询某个字段;若需要查询多个字段(如 select distinct name ,age from ...),则mysql会过滤(name字段并且age字段)也相同的数据。
后续...
附:查看mysql查询效率--explain的用法
explain语句用于查看一条SQL语句的查询执行计划,直接把explain放到要执行的SQL语句的前面即可。
例:explain select * from ...
explain extended和explain的输出结果一样,只是用explain extended语句后可以通过show warnings查看一条SQL语句的反编译的结果,让我们知道我们输入的一条SQL语句真正是怎么执行的。
对输入结果简单解释一下:
- select_type:表示select类型,常见的取值有SIMPLE(不使用表连接或子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的或者后面的查询语句)、SUBQUERY(子查询中的第一个select)等。
- table:输出结果集的表。
- type:表示表的连接类型,性能由好到差的连接类型为
- system(表中仅有一行,即常量表)、
- const(单表中最多有一个匹配行,例如PRIMARY KEY或者UNIQUE INDEX)、
- eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接中使用PRIMARYKEY或者UNIQUE INDEX)、
- ref(与eq_ref类似,区别在于不使用PRIMARYKEY或者UNIQUE INDEX,而是使用普通的索引)、
- ref_of_null(与ref类似,区别在于条件中包含对NULL的查询)
- index_merge(索引合并化)、
- unique_subquery(in的后面是一个查询主键字段的子查询)、
- index_subquery(与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询)、
- range(单表中的范围查询)、
- index(对于前面的每一行都通过查询索引来得到数据)、
- all(对于前面的每一行的都通过全表扫描来获得数据)。
结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null >index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
- possible_keys:表示查询时,可能使用到的索引。
- key:表示实际使用的索引
- key_len:索引字段的长度
- rows:扫描行的数量
- extra:执行情况的说明和描述。