子查询
子查询(Subquery)是指出现在其它SQL语句内的SELECT语句.
SELECT * FROM t1 WHERE col1=(SELECT col2 FROM t2);
其中,SELECT * FROM t1 称为Outer Query / Outer Statement, SELECT col2 FROM t2 称为SubQuery.
- 子查询必须始终出现在圆括号内
- 子查询可以包含多个关键字或条件,如DISTINCT, ORDER BY, GROUP BY, LIMIT, 函数等
- 子查询的外部查询可以是SELECT, INSERT, UPDATE, SET或DO.
- 子查询可以返回标量,一行,一列或子查询
使用比较运算符的子查询
- 语法结构:operand comparision_operator ANY | SOME | ALL (subquery)
- 使用比较运算符的子查询,若比较运算符后跟的子查询结果有多个值时,用要用关键字ANY, SOME, ALL来做修饰
练习1
//求所有电脑产品的平均价格,并且保留两位小数,AVG,MAX,MIN、COUNT、SUM为聚合函数
SELECT ROUND(AVG(goods_price),2) AS avg_price FROM tdb_goods;
//查询所有价格大于平均价格的商品,并且按价格降序排序
SELECT goods_id, goods_name, goods_price FROM tdb_goods WHERE goods_price>5845.10 ORDER BY goods_price DESC;
使用子查询来实现
SELECT goods_id, goods_name, goods_price FROM tdb_goods
WHERE goods_price>(SELECT ROUND(AVG(goods_price),2) FROM tdb_goods)
ORDER BY goods_price DESC;
使用 [NOT] IN 的子查询
- 语法结构:operant [NOT] IN (SubQuery)
- =ANY, =SOME 等价于 IN; !=ALL,<>ALL d等价与NOT IN
练习2
//查询价格大于或等于"超级本"价格的商品,并且按价格降序排列
SELECT goods_id, goods_name, goods_price FROM tdb_goods
WHERE goods_price=ANY(SELECT goods_price FROM tdb_goods WHERE goods_cate='超级本')
ORDER BY goods_price DESC;
= ANY 或 = SOME 等价于 IN
SELECT goods_id,goods_name,goods_price FROM tdb_goods
WHERE goods_price IN (SELECT goods_price FROM tdb_goods WHERE goods_cate = '超级本')
ORDER BY goods_price DESC;
使用 [NOT] EXISTS 的子查询
- 如果子查询返回任何行,EXISTS将返回TRUE, 否则返回FALSE
连接
- MySQL在SELECT语句,多表更新,多表删除中支持JOIN操作
- 基本语法结构:table_reference {[INNER | CROSS] JOIN | {LEFT | RIGHT} [OUTER] JOIN} table_reference ON conditional_expr;
table_reference
table_name [[AS] alias_name] | table_subquery [AS] alias_name
table_subquery 可以作为子查询使用在FROM子句中,这样的子查询必须为其赋予别名.
连接类型
内连接( [INNER | CROSS] JOIN): 仅显示左表和右表中符合连接条件的记录
左外连接(LEFT [OUTER] JOIN): 显示左表的全部记录以及右表符合连接条件的记录
右外连接(RIGHT [OUTER] JOIN): 显示右表的全部记录以及左表符合连接条件的记录
连接条件
使用ON关键字来设定连接条件,也可以使用WHERE来代替
通常,使用ON关键字在设定连接条件,使用WHERE关键字进行结果集记录的过滤
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将在找到符合连接条件的记录后停止搜索更多的行
SELECT语句中的多表连接
//SELECT 表tdb_goods中的goods_id,goods_name,goods_price 表tdb_goods_cates中的cate_name 以及 表tdb_goods_brands中的brand_name字段
SELECT 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;
无限级分类表的设计
在一个分类下又分出许多子类,在子类下再分出小的子类,一直分下去,这样无限级的表该如何设计呢.
我们可以在一张表中添加一个parent_id字段,该字段的值就是其父类的id,这样通过一张表中的自身连接就可以实现无限子类表的设计
CREATE TABLE tdb_goods_types(
type_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
type_name VARCHAR(20) NOT NULL,
parent_id SMALLINT UNSIGNED NOT NULL DEFAULT 0
);
插入记录并显示,"家用电器","电脑 办公"两项的parent_id为0, 表示它们为最初始的父类,"大家电","生活电器"的parent_id为1, 表示它们是"家用电器下的子类"
root@localhost goods_test>SELECT * FROM tdb_goods_types;
+---------+-----------------+-----------+
| type_id | type_name | parent_id |
+---------+-----------------+-----------+
| 1 | 家用电器 | 0 |
| 2 | 电脑、办公 | 0 |
| 3 | 大家电 | 1 |
| 4 | 生活电器 | 1 |
| 5 | 平板电视 | 3 |
| 6 | 空调 | 3 |
| 7 | 电风扇 | 4 |
| 8 | 饮水机 | 4 |
| 9 | 电脑整机 | 2 |
| 10 | 电脑配件 | 2 |
| 11 | 笔记本 | 9 |
| 12 | 超级本 | 9 |
| 13 | 游戏本 | 9 |
| 14 | CPU | 10 |
| 15 | 主机 | 10 |
+---------+-----------------+-----------+
15 rows in set (0.00 sec)
对于这样的数据表,在查找时可以通过自身连接来实现,即对同一个数据表使用连接操作.
通过自身连接查找父类
SELECT s.type_id, s.type_name, p.type_name FROM tdb_goods_types AS s LEFT JOIN tdb_goods_types AS p ON s.parent_id=p.type_id;
+---------+-----------------+-----------------+
| type_id | type_name | type_name |
+---------+-----------------+-----------------+
| 1 | 家用电器 | NULL |
| 2 | 电脑、办公 | NULL |
| 3 | 大家电 | 家用电器 |
| 4 | 生活电器 | 家用电器 |
| 5 | 平板电视 | 大家电 |
| 6 | 空调 | 大家电 |
| 7 | 电风扇 | 生活电器 |
| 8 | 饮水机 | 生活电器 |
| 9 | 电脑整机 | 电脑、办公 |
| 10 | 电脑配件 | 电脑、办公 |
| 11 | 笔记本 | 电脑整机 |
| 12 | 超级本 | 电脑整机 |
| 13 | 游戏本 | 电脑整机 |
| 14 | CPU | 电脑配件 |
| 15 | 主机 | 电脑配件 |
+---------+-----------------+-----------------+
通过自身连接查找子类
SELECT p.type_id, p.type_name, s.type_name FROM tdb_goods_types AS p LEFT JOIN tdb_goods_types AS s ON p.type_id=s.parent_id;
+---------+-----------------+--------------+
| type_id | type_name | type_name |
+---------+-----------------+--------------+
| 1 | 家用电器 | 大家电 |
| 1 | 家用电器 | 生活电器 |
| 3 | 大家电 | 平板电视 |
| 3 | 大家电 | 空调 |
| 4 | 生活电器 | 电风扇 |
| 4 | 生活电器 | 饮水机 |
| 2 | 电脑、办公 | 电脑整机 |
| 2 | 电脑、办公 | 电脑配件 |
| 9 | 电脑整机 | 笔记本 |
| 9 | 电脑整机 | 超级本 |
| 9 | 电脑整机 | 游戏本 |
| 10 | 电脑配件 | CPU |
| 10 | 电脑配件 | 主机 |
| 5 | 平板电视 | NULL |
| 6 | 空调 | NULL |
| 7 | 电风扇 | NULL |
| 8 | 饮水机 | NULL |
| 11 | 笔记本 | NULL |
| 12 | 超级本 | NULL |
| 13 | 游戏本 | NULL |
| 14 | CPU | NULL |
| 15 | 主机 | NULL |
+---------+-----------------+--------------+
多表删除
查询数据表中重复记录
SELECT min(goods_id), goods_name FROM tdb_goods GROUP BY goods_name HAVING count(goods_name)>=2;
+---------------+-----------------------------+
| min(goods_id) | goods_name |
+---------------+-----------------------------+
| 18 | HMZ-T3W 头戴显示设备 |
| 19 | 商务双肩背包 |
+---------------+-----------------------------+
通过子查询和连接实现多表删除(删除一张表中的重复记录)
DELETE t1 FROM tdb_goods AS t1 LEFT JOIN
(SELECT any_value(goods_id) AS goods_id, goods_name FROM tdb_goods
GROUP BY goods_name HAVING count(goods_name)>=2) AS t2
ON t1.goods_name=t2.goods_name
WHERE t1.goods_id>t2.goods_id;
多表更新
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;