mysql专题07 子查询与连接
  热度 °
本文将以实践集合理论的角度深入学习总结mysql中有关子查询及连接的相关知识点;
数据准备
1 | source /sqls/tb10_ddl.sql; |
数据文件可参考 tb10_ddl.sql, tb10_dml.sql
子查询简介
子查询(subquery)是指出现在其它SQL语句内的SELECT子句;
eg.
SELECT * FROM tb1 WHERE col1= (SELECT col2 FROM t2);
其中SELECT * FROM tb1 称为Outer Query/Outer Statement,
而SELECT col2 FROM t2 称为subquery;子查询指嵌套在查询内部, 且必须始终出现在圆括号内;
- 子查询可以包含多个关键字或条件, 如DISTINCT,GROUP BY, ORDER BY, LIMIT, 函数等;
- 子查询的外层查询可以是SELECT, INSERT, UPDATE, SET 或DO;
- 子查询可以返回标量, 一行, 一列, 或子查询;
由比较运算符引发的子查询
使用比较运算符的子查询1
2
3=, >, <, >=, <=,
不等于
<>, !=, <=>
语法结构
operand comparison_operator subquery
计算平均值1
2
3
4
5
6
7SELECT ROUND(AVG(goods_price),2) FROM tdb_goods;
+---------------------------+
| ROUND(AVG(goods_price),2) |
+---------------------------+
| 5391.30 |
+---------------------------+
1 row in set (0.00 sec)
计算价格大于等于平均价格的商品1
2
3
4
5
6
7
8
9
10
11
12
13SELECT goods_id, goods_name, goods_price FROM tdb_goods WHERE goods_price >= (SELECT ROUND(AVG(goods_price), 2) FROM tdb_goods);
+----------+-----------------------------------------+-------------+
| goods_id | goods_name | goods_price |
+----------+-----------------------------------------+-------------+
| 3 | G150TH 15.6英寸游戏本 | 8499.000 |
| 7 | SVP13226SCB 13.3英寸触控超极本 | 7999.000 |
| 13 | iMac ME086CH/A 21.5英寸一体电脑 | 9188.000 |
| 17 | Mac Pro MD878CH/A 专业级台式电脑 | 28888.000 |
| 18 | HMZ-T3W 头戴显示设备 | 6999.000 |
| 20 | X3250 M4机架式服务器 2583i14 | 6888.000 |
| 22 | HMZ-T3W 头戴显示设备 | 6999.000 |
+----------+-----------------------------------------+-------------+
7 rows in set (0.00 sec)
当子查询结果返回多个值, 可用以下函数来修饰对返回结果的操作
- operand comparison_operator ANY(subquery)
- operand comparison_operator SOME(subquery)
operand comparison_operator ALL(subquery)
ANY 和SOME是等价的;
其计算原则如下,
ANY | SOME | ALL | |
---|---|---|---|
>, >= | 最小值 | 最小值 | 最大值 |
<,<= | 最大值 | 最大值 | 最小值 |
= | 任意值 | 任意值 | |
<>, != | 任意值 |
查询比所有超级本价格贵的笔记本;1
2
3
4
5
6
7
8
9SELECT goods_id, goods_name,goods_price from tdb_goods where goods_price > ALL(select goods_price from tdb_goods where goods_cate='超级本');
+----------+-----------------------------------------+-------------+
| goods_id | goods_name | goods_price |
+----------+-----------------------------------------+-------------+
| 3 | G150TH 15.6英寸游戏本 | 8499.000 |
| 13 | iMac ME086CH/A 21.5英寸一体电脑 | 9188.000 |
| 17 | Mac Pro MD878CH/A 专业级台式电脑 | 28888.000 |
+----------+-----------------------------------------+-------------+
3 rows in set (0.00 sec)
由[NOT]/IN/EXISTS引发的子查询
语法结构
- operand comparison_operator [NOT] IN (subquery)
- = ANY 运算符与IN等效;
- !=ALL或<>ALL 运算与NOT IN等效;
- 如果子查询返回任何行, EXISTS将返回TRUE, 否则返回FALSE;
1 | SELECT goods_id, goods_name,goods_price from tdb_goods where goods_price IN (select goods_price from tdb_goods where goods_cate='超级本'); |
使用INSERT…SELECT插入记录
当一张表中存在大量的重复值时, 就需要考虑将其拆分为多张表, 然后通过外键来关联, 以减少重复值占用空间, 如上述的tdb_goods表中, 其中的goods_cate就存在很多重复值, 当tdb_goods中存入100w+甚至更多数据时, 这些重复值势必将占用很多空间, 而这些空间其实可以省略的, 下面先建立一张tdb_goods_cate表, 然后通过INSERT… SELECT语句来讲tdb_goods表中存在的记录插入到新表中;
1 | show create table tdb_goods_cates \G |
多表更新
上述将分类值插入到分类表中, 但是商品表中的分类并没有通过外键关联到分类表中, 依然存储的是分类名称, 下面将参照分类表来更新商品表,
语法结构
1 | UPDATE table_references |
1 | update tdb_goods inner join tdb_goods_cates on goods_cate = cate_name set goods_cate = cate_id; |
- 上面通过内连接将tdb_goods 中的goods_cate 更新为tdb_goods_cates 表中的cate_id值;
- update 表a inner join 表b on 条件 set 设置值;
多表更新之一步到位
上述将tdb_goods中的goods_cate 值更新到tdb_goods_cates表中的goods_cate值, 大致进过了三个步骤,
- 创建tdb_goods_cates表;
- insert … select 更新 tdb_goods_cates表;
- update 表a inner join 表b on 条件 set 值 更新tdb_goods表;
是否一步到位完成上述三个操作? 当然可以,
语法结构
1 | CREATE TABLE [IF NOT EXISTS] tbl_name |
- CREATE…SELECT 在创建数据表的同时将查询结果写入到数据表
一步到位创建更新tdb_goods_brands 表1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65CREATE TABLE IF NOT EXISTS tdb_goods_brands(
brand_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
brand_name VARCHAR(40) NOT NULL
) SELECT brand_name FROM tdb_goods GROUP BY brand_name;
source /sqls/tb12.sql;
Query OK, 10 rows affected (0.04 sec)
Records: 10 Duplicates: 0 Warnings: 0
select * from tdb_goods_brands;
+----------+--------------+
| brand_id | brand_name |
+----------+--------------+
| 1 | 华硕 |
| 2 | 联想 |
| 3 | 雷神 |
| 4 | 索尼 |
| 5 | 苹果 |
| 6 | 戴尔 |
| 7 | 宏碁 |
| 8 | 惠普 |
| 9 | IBM |
| 10 | 九州风神 |
+----------+--------------+
10 rows in set (0.01 sec)
update tdb_goods as g inner join tdb_goods_brands as b on g.brand_name = b.brand_name
-> set g.brand_name = b.brand_id;
Query OK, 23 rows affected (0.09 sec)
Rows matched: 23 Changed: 23 Warnings: 0
将tdb_goods 中的brand_name 和goods_name 字段名称及类型进行修改
desc tdb_goods;
+-------------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------------+------+-----+---------+----------------+
| goods_id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| goods_name | varchar(150) | NO | | NULL | |
| goods_cate | varchar(40) | NO | | NULL | |
| brand_name | varchar(40) | NO | | NULL | |
| goods_price | decimal(15,3) unsigned | NO | | 0.000 | |
| is_show | tinyint(1) | NO | | 1 | |
| is_saleoff | tinyint(1) | NO | | 0 | |
+-------------+------------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
alter table tdb_goods
-> change goods_cate cate_id SMALLINT UNSIGNED NOT NULL,
-> CHANGE brand_name brand_id SMALLINT UNSIGNED NOT NULL;
Query OK, 23 rows affected (0.13 sec)
Records: 23 Duplicates: 0 Warnings: 0
desc tdb_goods;
+-------------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------------+------+-----+---------+----------------+
| goods_id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| goods_name | varchar(150) | NO | | NULL | |
| cate_id | smallint(5) unsigned | NO | | NULL | |
| brand_id | smallint(5) unsigned | NO | | NULL | |
| goods_price | decimal(15,3) unsigned | NO | | 0.000 | |
| is_show | tinyint(1) | NO | | 1 | |
| is_saleoff | tinyint(1) | NO | | 0 | |
+-------------+------------------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)
- 当两个表中存在同名字段时, 使用as来进行别名识别;
- 当需要同时修改列字段的名称及数据类型时建议用change 来修改
- alter table tbl_name change older_col, new_col_name, new_col_definition, [change older_col, new_col_name, new_col_definition] …
当需要将多张表的信息查询出来拼成一个完整的可读信息呈现给用户, 就需要用到连接 来操作多张表;
连接
Mysql 在SELECT语句, 多表更新, 多表删除语句中支持JOIN操作,
语法结构
1 | table_reference |
table_reference
tbl_name [[AS] alias] | table_subquery [AS] alias
数据表可以使用tbl_name AS alias_name 或tbl_name alias_name 赋予别名;
table_subquery 可以作为子查询使用在FROM子句中, 这样的子查询必须为其赋予别名;
内连接
Mysql中, JOIN, CROSS JOIN 和INNER JOIN 是等价的。
通常使用INNER JOIN;
使用ON 关键字来设定连接条件, 也可以使用WHERE 来代替;
通常使用ON关键字来设定连接条件, 使用WHERE 关键字进行结果集记录的过滤;
内连接, 即仅显示左表和右表符合连接条件的记录, 即结果是符合连接条件的左右表的交集;
1 | select goods_id, goods_name, cate_name from tdb_goods inner join tdb_goods_cates on tdb_goods.cate_id = tdb_goods_cates.cate_id; |
- 上面的结果是仅符合tdb_goods表中的cate_id 等于tdb_goods_cates中cate_id中的记录;
外连接
外连接又分为,
LEFT [OUTER] JOIN 左外连接
左外连接返回结果为 左表中的全部记录以及符合连接条件的右表中的记录的集合;
RIGHT [OUTER] JOIN 右外连接
右外连接返回结果为 右表中的全部记录以及符合连接条件的左表中的记录的集合;
示例说明内连接,左外,右外连接的区别,
1 |
|
多表连接
示例,1
2
3
4
5
6
7
8
9select goods_id, goods_name, cate_name, brand_name, goods_price from tdb_goods as g
-> inner join tdb_goods_cates as c on g.cate_id = c.cate_id
-> inner join tdb_goods_brands as b on g.brand_id = b.brand_id \G
*************************** 1. row ***************************
goods_id: 1
goods_name: R510VC 15.6英寸笔记本
cate_name: 笔记本
brand_name: 华硕
goods_price: 3399.000
可以说表的连接为外键的逆向操作;
关于连接的几点说明
A LEFT JOIN B join_condition
数据表B 的结果集依赖数据表A
数据表A的结果集根据左连接条件依赖所有数据表(B表除外);
左外连接条件决定如何检索数据表B(在没有指定WHERE条件的情况下);
如果数据表A的某条记录符合WHERE条件, 但是在数据表B中不存在, 符合连接条件的记录, 将生成一个所有列为空的额外的B行;
如果使用内连接查找的记录在连接数据表中不存在, 并且在WHERE子句中尝试如下操作: col_name IS NULL时, 如果col_name 被定义为NOT NULL, MySql 将在找到符合连接着条件的记录后停止搜索更多的行;
无限级分类表设计
一个商品在不同的集合中会可以归类为不同的分类,这种无限极的分类表改如何设计? 有多少个分类就设计多少张分类表? 显然不合理, 下面是一种无限极分类表的设计方案,
1 | CREATE TABLE tdb_goods_types( |
这种设计也是一种设计网站目录的方案,
1 |
|
多表删除
1 | DELETE tbl_name[.*] [, tbl_name[.*]] ... |
删除重复记录,保留id最小的记录;
1 | 准备重复记录 |
- 当sql_mode=only_full_group_by时 可以通过any_value(goods_id) as goods_id来临时解决;
总结
- 子查询多个实例分析学习; 主要有三种情况引发子查询:1.由比较运算符引发的子查询;2.由[NOT]IN/EXISTS引发的子查询;3.使用INSERT…SELECT插入记录;
- mysql连接分为内连接和外连接, 外连接又分为左外连接和右外连接;
- 多表更新及多表删除;
作者署名:朴实的一线攻城狮
本文标题:mysql专题07 子查询与连接
本文出处:http://researchlab.github.io/2018/09/01/mysql-07-subquery-and-join/
版权声明:本文由Lee Hong创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处, 否则保留追究法律责任的权利。