文章目录
前言
复杂的MySQL查询使用多个参数来搜索数据,并且可能包含多个表之间的多个联接以及相当多的嵌套子查询(嵌套在另一查询中 的查询)的组合。复杂查询还经常涉及大量使用AND和OR子句。
复杂查询通常用于从多个表中检索复杂数据。高级查询还可以用于报告,联接多个表,嵌套查询和事务锁定。
1. 视图
先来看一个查询语句:
SELECT stu_name FROM view_students_info;
单从表面上看起来这个语句是和正常的从数据表中查询数据是完全相同的,但其实我们操作的是一个视图。所以从SQL的角度来说操作视图与操作表看起来是完全相同的。
Q:那么为什么还会有视图的存在呢?视图到底是什么?视图与表有什么不同呢?
1.1 什么是视图
视图是一个虚拟的表,不同于直接操作数据表,视图是依据SELECT语句来创建的,所以操作视图时会根据视图的SELECT语句生成一张虚拟表,然后在这张虚拟表上做SQL操作。
1.2 视图与表有什么区别
有一句话非常凝练的概括了视图与表的区别——“是否保存了实际的数据”。所以视图并不是数据库真实存储的数据表。它可以看作是一个窗口,通过这个窗口我们可以看到数据库表中真实存在的数据。所以我们要区别视图和数据表的本质,即视图是基于真实表的一张虚拟的表,其数据来源均建立在真实表的基础上。
1.3 为什么会存在视图
主要有以下几点原因:
- 通过定义视图可以将频繁使用的SELECT语句保存以提高效率;
- 通过定义视图可以使用户看到的数据更加清晰;
- 通过定义视图可以不对外公开数据表全部字段,增强数据的保密性;
- 通过定义视图可以降低数据的冗余。
1.4 如何创建视图
创建视图的基本语法如下:
CREATE VIEW <视图名称>(<列名1>,<列名2>,...) AS <SELECT语句>
其中SELECT 语句需要书写在AS 关键字之后。SELECT 语句中列的排列顺序和视图中列的排列顺序相同,SELECT 语句中的第1 列就是视图的第 1 列,SELECT 语句中的第 2 列就是视图中的第 2 列,以此类推。而且视图的列名是在视图名称之后的列表中定义的。
注意: 视图名称是在数据库中是唯一的,不能与其他视图和表重名。
视图不仅可以基于真实表,也可以在视图基础上继续创建视图。
虽然在视图上继续创建视图的语法没有错误,但是还是应该尽量避免这种操作。这是因为对多数DBMS 来说,多重视图会降低SQL 的性能。
注意事项:
需要注意的是在一般的DBMS中定义视图时不能使用ORDER BY 语句。下面这样定义视图时错误的。
CREATE VIEW product_sum(product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type
ORDER BY product_type;
Q:为什么不能使用ORDER BY子句?
A:因为视图和表是一样的,数据行都是没有顺序的。
基于单表查询的视图
在product表的基础上创建一个视图,代码如下:
CREATE VIEW productsum(product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type;
创建的视图如下图所示:
基于多表的视图
为了学习多表视图,再创建一张表,相关代码如下:
-- 创建一张shop_product 表
create table shop_product
(shop_id char(4) NOT NULL,
shop_name varchar(200) NOT NULL,
product_id char(4) NOT NULL,
quantity INTEGER NOT NULL,
primary key(shop_id, product_id));
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000A', '东京', '0001', 30);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000A', '东京', '0002', 50);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000A', '东京', '0003', 15);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000B', '名古屋', '0002', 30);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000B', '名古屋', '0003', 120);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000B', '名古屋', '0004', 20);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000B', '名古屋', '0006', 10);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000B', '名古屋', '0007', 40);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000C', '大阪', '0003', 20);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000C', '大阪', '0004', 50);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000C', '大阪', '0006', 90);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000C', '大阪', '0007', 70);
INSERT INTO shop_product (shop_id, shop_name, product_id, quantity) values ('000D', '福冈', '0001', 100);
在product表和shop_product表的基础上创建视图。
create view view_shop_product(product_type, sale_price, shop_name)
as
select product_type, sale_price, shop_name
from product,
shop_product
where product.product_id = shop_product.product_id;
创建的视图如下:
在这个视图的基础上进行查询:
select sale_price, shop_name
from view_shop_product
where product_type = '衣服';
1.5 如何修改视图结构
修改视图结构的基本语法如下:
ALTER VIEW<视图名> AS <SELECT语句>
当然视图名在数据库中需要是唯一的,不能与其他视图和表重名。
当然也可以通过将当前视图删除然后重新创建的方式达到修改的效果。(对于数据库底层是不是也是这样的呢,可以自己探索一下。)
修改视图
修改上方的productsum视图为
ALTER VIEW productsum
AS
SELECT product_type, sale_price
FROM product
WHERE regist_date > '2009-09-11';
此时productsum视图内容如下图所示
1.6 如何更新视图内容
因为视图是一个虚拟表,所以对视图的操作就是对底层基础表的操作,所以在修改时只有满足底层基本表的定义才能成功修改。
对于一个视图来说,如果包含以下结构的任意一种都是不可以被更新的:
- 聚合函数SUM()、MIN()、MAX()、COUNT()等
- DISTINCT关键字
- GROUP BY 子句
- HAVING 子句
- UNION 或 UNION ALL 运算符
- FROM 子句中包含多个表
视图归根结低还是从表派生出来的,因此,如果原表可以更新,那么视图中的数据也可以更新。反之亦然,如果视图发生了改变,而原表没有进行相应更新的话,就无法保证数据的一致性了。
更新视图
因为我们刚刚修改的productsum视图不包括以上的限制条件,我们来尝试更新一下视图。
UPDATE productsum
SET sale_price = '5000'
WHERE product_type = '办公用品';
未执行update语句前的productsum视图:
执行update语句之后,再来查看productsum视图,可以发现数据以及更新了:
注意: 如若mysql执行update语句会报错,报错如下:
Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences
这是因为MySQL运行在 safe-updates 模式下,该模式会导致非主键条件下无法执行 update 或者 delete 命令。
可以通过以下SQL进行状态查询:
show variables like 'SQL_SAFE_UPDATES';
这里显示sql_safe_updates 模式为打开状态。
解决办法:
- 执行下面的SQL,关闭 safe-updates 模式:
set sql_safe_updates = 0;
-- 或者
set sql_safe_updates = false;
- 执行下面的SQL 语句,打开 safe-updates 模式:
set sql_safe_updates = 1;
-- 或者
set sql_safe_updates = true;
2. 子查询
代码如下(示例):
SELECT stu_name
FROM (
SELECT stu_name, COUNT(*) AS stu_cnt
FROM students_info
GROUP BY stu_age) AS studentsum;
这个语句看起来很好理解,其中使用括号括起来的SQL语句首先执行,执行成功后再执行外面的SQL语句。上一节提到的视图也是根据SELECT语句创建视图然后在这个基础上在进行查询。
Q:什么是子查询呢?子查询和视图又有什么关系呢?
2.1 什么是子查询
子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从 MySQL4.1开始引入,在SELECT 子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表。
2.2 子查询和视图的关系
子查询就是将用来定义视图的 SELECT语句直接用于 FROM子句当中。其中AS studentsum可以看作是子查询的名称,而且由于子查询是一次性的,所以子查询不会像视图那样保存在存储介质中,而是在 SELECT 语句执行之后就消失了。
2.3 嵌套子查询
与在视图上再定义视图类似,子查询也没有具体的限制,例如:
SELECT product_type, cnt_product
FROM (SELECT *
FROM (SELECT product_type,
COUNT(*) AS cnt_product
FROM product
GROUP BY product_type) AS productsum
WHERE cnt_product = 4) AS productsum2;
其中最内层的子查询我们将其命名为productsum,这条语句根据product_type分组并查询个数,第二层查询中将个数为4的商品查询出来,最外层查询product_type和cnt_product两列。
虽然嵌套子句可以查询出结果,但是随着子查询嵌套层数的叠加,SQL语句不仅会难以理解而且执行效率也会很差,所以要尽量避免这样的使用。
2.4 标量子查询
标量就是单一的意思,那么标量子查询也就是单一的子查询。
Q:那什么叫做单一的子查询呢?
A:所谓单一就是要求我们执行的SQL语句只能返回一个值,也就是要返回表中具体的某一行的某一列。
例如,我们有下面这有一张表:
product_id | product_name | sale_price |
---|---|---|
0003 | 运动T恤 | 4000 |
0004 | 菜刀 | 3000 |
0005 | 高压锅 | 6800 |
那么我们执行一次标量子查询后是要返回类似于,“0004”,“菜刀”这样的结果。
2.5 标量子查询有什么用
那知道了标量子查询可以返回一个值了,那么它有什么作用呢?
先来看几个具体的需求:
- 查询出销售单价高于平均销售单价的商品
- 查询出注册日期最晚的那个商品
Q:看如何通过标量子查询语句查询出销售单价高于平均销售单价的商品?
代码如下:
SELECT product_id, product_name, sale_price
FROM product
WHERE sale_price > (SELECT AVG(sale_price) FROM product);
运行结果如图:
上面的这条语句首先后半部分查询出product表中的平均售价,前面的sql语句在根据WHERE条件挑出合适的商品。
由于标量子查询的特性,导致标量子查询不仅仅局限于 WHERE 子句中,通常任何可以使用单一值的位置都可以使用。也就是说,能够使用常数或者列名的地方,无论是 SELECT 子句、GROUP BY 子句、HAVING 子句,还是 ORDER BY 子句,几乎所有的地方都可以使用。
还可以这样使用标量子查询:
SELECT product_id,
product_name,
sale_price,
(SELECT AVG(sale_price)
FROM product) AS avg_price
FROM product;
运行结果如下图:
2.6 关联子查询
什么是关联子查询
关联子查询既然包含关联两个字那么一定意味着查询与子查询之间存在这联系。这种联系是如何建立起来的呢?
先来看一个例子:
SELECT product_type, product_name, sale_price
FROM product AS p1
WHERE sale_price > (SELECT AVG(sale_price)
FROM product AS p2
WHERE p1.product_type = p2.product_type
GROUP BY product_type);
运行结果:
通过上面的例子,我们知道关联子查询就是通过一些标志将内外两层的查询连接起来起到过滤数据的目的,接下来我们就一起看一下关联子查询的具体内容吧。
关联子查询与子查询的联系
查询出销售单价高于平均销售单价的商品的SQL语句如下:
SELECT product_id, product_name, sale_price
FROM product
WHERE sale_price > (SELECT AVG(sale_price) FROM product);
运行结果如图:
再来看一下这个需求:选取出各商品种类中高于该商品种类的平均销售单价的商品的SQL语句如下:
SELECT product_type, product_name, sale_price
FROM product AS P1
WHERE sale_price > (SELECT AVG(sale_price)
FROM product AS p2
WHERE p1.product_type = p2.product_type
GROUP BY product_type);
运行结果如图:
上面这两个语句的区别:
在第二条SQL语句也就是关联子查询我们将外面的product表标记为p1,将内部的product设置为p2,而且通过WHERE 语句连接了两个查询。
但是如果刚接触的话一定会比较疑惑关联查询的执行过程,可以通过这篇文章了解。在这里可以简要的概况为:
- 首先执行不带WHERE的主查询;
- 根据主查询的结果匹配product_type,获取子查询的结果;
- 将子查询结果再与主查询结合执行完整的SQL语句。
总结
视图和子查询是数据库操作中较为基础的内容,对于一些复杂的查询需要使用子查询加一些条件语句组合才能得到正确的结果。但是无论如何对于一个SQL语句来说都不应该设计的层数非常深且特别复杂,不仅可读性差而且执行效率也难以保证,所以尽量有简洁的语句来完成需要的功能。