阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数

本笔记为阿里云天池龙珠计划SQL训练营的学习内容,链接为:https://tianchi.aliyun.com/specials/promotion/aicampsql;

3.1 视图

3.1.1 什么是视图

视图是一个虚拟的表,不同于直接操作数据表,视图是依据SELECT语句来创建的,所以操作视图时会根据创建视图的SELECT语句生成一张虚拟表,然后在这张虚拟表上做SQL操作。

3.1.2 视图与表有什么区别

视图与表的区别—“是否保存了实际的数据”。 所以视图并不是数据库真实存储的数据表,它可以看作是一个窗口,通过这个窗口我们可以看到数据库表中真实存在的数据。所以我们要区别视图和数据表的本质,即视图是基于真实表的一张虚拟的表,其数据来源均建立在真实表的基础上。阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第1张图片
“视图不是表,视图是虚表,视图依赖于表”。

3.1.3 视图的作用

  1. 通过定义视图可以将频繁使用的SELECT语句保存以提高效率。
  2. 通过定义视图可以使用户看到的数据更加清晰。
  3. 通过定义视图可以不对外公开数据表全部字段,增强数据的保密性。
  4. 通过定义视图可以降低数据的冗余。

3.1.4 创建视图

CREATE VIEW <视图名称>(<列名1>,<列名2>,...) AS 

其中SELECT 语句需要书写在 AS 关键字之后。 SELECT 语句中列的排列顺序和视图中列的排列顺序相同, SELECT 语句中的第 1 列就是视图中的第 1 列, SELECT 语句中的第 2 列就是视图中的第 2 列,以此类推。而且视图的列名是在视图名称之后的列表中定义的。
需要注意的是视图名在数据库中需要是唯一的,不能与其他视图和表重名。

视图不仅可以基于真实表,我们也可以在视图的基础上继续创建视图。
阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第2张图片
虽然在视图上继续创建视图的语法没有错误,但是我们还是应该尽量避免这种操作。这是因为对多数 DBMS 来说, 多重视图会降低 SQL 的性能。

  • 注意事项
    需要注意的是在一般的DBMS中定义视图时不能使用ORDER BY语句。下面这样定义视图是错误的。
CREATE VIEW productsum (product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
  FROM product
 GROUP BY product_type
 ORDER BY product_type;

为什么不能使用 ORDER BY 子句呢?这是因为视图和表一样,数据行都是没有顺序的

在 MySQL中视图的定义是允许使用 ORDER BY 语句的,但是若从特定视图进行选择,而该视图使用了自己的 ORDER BY 语句,则视图定义中的 ORDER BY 将被忽略。

  • 基于单表的视图
    在product表的基础上创建一个视图,如下:
CREATE VIEW productsum (product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
  FROM product
 GROUP BY product_type ;

创建的视图如下图所示:
阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第3张图片

  • 基于多表的视图
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;

使用自然表连接或者join表连接

3.1.5 修改视图结构

ALTER VIEW <视图名> AS 

其中视图名在数据库中需要是唯一的,不能与其他视图和表重名

3.1.6 更新视图内容

因为视图是一个虚拟表,所以对视图的操作就是对底层基础表的操作,所以在修改时只有满足底层基本表的定义才能成功修改。

对于一个视图来说,如果包含以下结构的任意一种都是不可以被更新的:

  • 聚合函数 SUM()、MIN()、MAX()、COUNT() 等。
  • DISTINCT 关键字。
  • GROUP BY 子句。
  • HAVING 子句。
  • UNION 或 UNION ALL 运算符。
  • FROM 子句中包含多个表。

视图归根结底还是从表派生出来的,因此,如果原表可以更新,那么 视图中的数据也可以更新。反之亦然,如果视图发生了改变,而原表没有进行相应更新的话,就无法保证数据的一致性了。

语法:

UPDATE <视图名>
   SET ...
 WHERE ...;

因为我们刚刚修改的productSum视图不包括以上的限制条件,我们来尝试更新一下视图

UPDATE productsum
   SET sale_price = '5000'
 WHERE product_type = '办公用品';

此时我们再查看productSum视图,可以发现数据已经更新了
阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第4张图片
此时观察原表也可以发现数据也被更新了
阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第5张图片
不知道大家看到这个结果会不会有疑问,刚才修改视图的时候是设置product_type='办公用品’的商品的sale_price=5000,为什么原表的数据只有一条做了修改呢?

根据视图的定义,视图只是原表的一个窗口,所以它修改也只能修改透过窗口能看到的内容

注意:在创建视图时也尽量使用限制不允许通过视图来修改表

3.1.7 如何删除视图

DROP VIEW <视图名1> [ , <视图名2> …]

注意:需要有相应的权限才能成功删除。

3.2 子查询

3.2.1 什么是子查询

子查询指一个查询语句嵌套在另一个查询语句内部的查询。

在 SELECT 子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表。

3.2.2 子查询和视图的关系

子查询就是将用来定义视图的 SELECT 语句直接用于 FROM 子句当中。其中AS studentSum可以看作是子查询的名称,而且由于子查询是一次性的,所以子查询不会像视图那样保存在存储介质中, 而是在 SELECT 语句执行之后就消失了。

3.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;

虽然嵌套子查询可以查询出结果,但是随着子查询嵌套的层数的叠加,SQL语句不仅会难以理解而且执行效率也会很差,所以要尽量避免这样的使用。

3.2.4 标量子查询

  1. 概念
    标量就是要求我们执行的SQL语句只能返回一个值,也就是要返回表中具体的某一行的某一列。
  2. 作用
    将输出的结果作为其他语句的条件,比如这样的需求
    一、查询出销售单价高于平均销售单价的商品
    二、查询出注册日期最晚的那个商品
  3. 位置
    标量子查询可以在WHERE、SELECT 、GROUP BY 、HAVING ,ORDER BY 等语句中使用

3.2.5 关联子查询

  • 什么是关联子查询,比如
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);

子查询的WHERE p1.product_type = p2.product_type将内外两层的查询连接起来起到过滤数据的目的。

关联子查询的详细用法

简要的概括为:

  1. 首先执行不带WHERE的主查询
  2. 根据主查询讯结果匹配product_type,获取子查询结果
  3. 将子查询结果再与主查询结合执行完整的SQL语句

练习题-第一部分(答案)

3.1

CREATE VIEW ViewPractice5_1(product_name , sale_price , regist_date) 
AS select product_name , sale_price , regist_date
			from product
			where sale_price >= 1000 and regist_date = '2009-09-20';

3.2
解析:插⼊时将会报错。
视图插⼊数据时,原表也会插⼊数据,⽽原表数据插⼊时不满⾜约束条件,所以会报错。(因为
ViewPractice5_1 的原表有三个带有 NOT NULL 约束的字段)
3.3

select      product_id,
			product_name ,
			product_type , 
			sale_price , 
			(select avg(sale_price ) from product)as sale_price_all
from product;

3.4

CREATE VIEW AvgPriceByType 
AS select product_id ,
	product_name , 
	product_type, 
	sale_price , 
	(select avg(sale_price) 
		from product p2
		where = p1.product_type =p2.product_type 
		group by p1.product_type
		) as avg_sale_price
	from product as p1

3.3 各种各样的函数

函数的分类

  • 算术函数 (用来进行数值计算的函数)
  • 字符串函数 (用来进行字符串操作的函数)
  • 日期函数 (用来进行日期操作的函数)
  • 转换函数 (用来转换数据类型和值的函数)
  • 聚合函数 (用来进行数据聚合的函数)

3.3.1 算数函数

  • ABS – 绝对值
    语法:ABS( 数值 )

    ABS 函数用于计算一个数字的绝对值,表示一个数到原点的距离。

    当 ABS 函数的参数为NULL时,返回值也是NULL。

  • MOD – 求余数
    语法:MOD( 被除数,除数 )

    MOD 是计算除法余数(求余)的函数,是 modulo 的缩写。小数没有余数的概念,只能对整数列求余数。

    注意:主流的 DBMS 都支持 MOD 函数,只有SQL Server 不支持该函数,其使用%符号来计算余数。

  • ROUND – 四舍五入
    语法:ROUND( 对象数值,保留小数的位数 )

    ROUND 函数用来进行四舍五入操作。

    注意:当参数 保留小数的位数 为变量时,可能会遇到错误,请谨慎使用变量。

3.3.2 字符串函数

  • CONCAT – 拼接
    语法:CONCAT(str1, str2, str3)

    MySQL中使用 CONCAT 函数进行拼接。

  • LENGTH – 字符串长度
    语法:LENGTH( 字符串 )

  • LOWER – 小写转换
    语法:LOWER (字符串)

    LOWER 函数只能针对英文字母使用,它会将参数中的字符串全都转换为小写。该函数不适用于英文字母以外的场合,不影响原本就是小写的字符。
    类似的, UPPER 函数用于大写转换。

  • REPLACE – 字符串的替换
    语法:REPLACE( 对象字符串,替换前的字符串,替换后的字符串 )

  • SUBSTRING – 字符串的截取
    语法:SUBSTRING (对象字符串 FROM 截取的起始位置 FOR 截取的字符数)

    使用 SUBSTRING 函数 可以截取出字符串中的一部分字符串。截取的起始位置从字符串最左侧开始计算,索引值起始为1。

  • SUBSTRING_INDEX – 字符串按索引截取
    语法:SUBSTRING_INDEX (原始字符串, 分隔符,n)

    该函数用来获取原始字符串按照分隔符分割后,第 n 个分隔符之前(或之后)的子字符串,支持正向和反向索引,索引起始值分别为 1 和 -1。

3.3.3 日期函数

  • CURRENT_DATE – 获取当前日期
  • CURRENT_TIME – 当前时间
  • CURRENT_TIMESTAMP – 当前日期和时间
SELECT 
CURRENT_DATE,
CURRENT_TIME,
CURRENT_TIMESTAMP;
  • EXTRACT – 截取日期元素
    语法:EXTRACT(日期元素 FROM 日期)
    阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第6张图片

3.3.4 转换函数

  • CAST – 类型转换
    语法:CAST(转换前的值 AS 想要转换的数据类型)
-- 将字符串类型转换为数值类型
SELECT CAST('0001' AS SIGNED INTEGER) AS int_col;
+---------+
| int_col |
+---------+
|       1 |
+---------+
-- 将字符串类型转换为日期类型
SELECT CAST('2009-12-14' AS DATE) AS date_col;
+------------+
| date_col   |
+------------+
| 2009-12-14 |
+------------+
  • COALESCE – 将NULL转换为其他值
    语法:COALESCE(数据1,数据2,数据3……)

    COALESCE 是 SQL 特有的函数。该函数会返回可变参数 A 中左侧开始第 1个不是NULL的值。参数个数是可变的,因此可以根据需要无限增加。

    在 SQL 语句中将 NULL 转换为其他值时就会用到转换函数。

SELECT COALESCE(NULL, 11) AS col_1,
COALESCE(NULL, 'hello world', NULL) AS col_2,
COALESCE(NULL, NULL, '2020-11-01') AS col_3;
+-------+-------------+------------+
| col_1 | col_2       | col_3      |
+-------+-------------+------------+
|    11 | hello world | 2020-11-01 |
+-------+-------------+------------+

3.4 谓词

3.4.1 什么是谓词

谓词就是返回值为真值的函数。包括TRUE / FALSE / UNKNOW

主要有以下几个:

  1. LIKE
  2. BETWEEN
  3. IS NULL、IS NOT NULL
  4. IN
  5. EXISTS

3.4.2 LIKE谓词 – 用于字符串的部分一致查询

where子句的表达式中除了使用运算符来进行条件判断,还可以使用like操作符组合通配符进行模糊查询
通配符%用来匹配多个字符可以是零个、一个也可以是多个字符,_仅能用来匹配单个字符

like后面的字符和通配符的组合表达式需要用英文单引号’'包裹

例如:

-- 查找ddd开头的值
SELECT *
FROM 表
WHERE 列 LIKE 'ddd%';

-- 查找aaa结尾的值
SELECT *
FROM 表
WHERE 列 LIKE '%aaa';

-- 查找ddd开头aaa结尾的值
SELECT *
FROM 表
WHERE 列 LIKE 'ddd%aaa';

-- 查找中间是zzz的值
SELECT *
FROM 表
WHERE 列 LIKE '%zzz%';

3.4.3 BETWEEN谓词 – 用于范围查询

-- 选取销售单价为100~ 1000元的商品
SELECT product_name, sale_price
FROM product
WHERE sale_price BETWEEN 100 AND 1000;

BETWEEN 的特点就是结果中会包含 100 和 1000 这两个临界值,也就是闭区间。

3.4.4 IS NULL、 IS NOT NULL – 用于判断是否为NULL

为了选取出某些值为 NULL 的列的数据,不能使用 =,而只能使用特定的谓词IS NULL。
与此相反,想要选取 NULL 以外的数据时,需要使用IS NOT NULL。

SELECT product_name, purchase_price
FROM product
WHERE purchase_price IS NULL;

SELECT product_name, purchase_price
FROM product
WHERE purchase_price IS NOT NULL;

3.4.5 IN谓词 – OR的简便用法

多个查询条件取并集时可以选择使用or语句。

-- 通过OR指定多个进货单价进行查询
SELECT product_name, purchase_price
FROM product
WHERE purchase_price = 320
OR purchase_price = 500
OR purchase_price = 5000;

上述方法不够简练,可读性低,简便方法如下:

SELECT product_name, purchase_price
FROM product
WHERE purchase_price IN (320, 500, 5000);

需要注意的是,在使用IN 和 NOT IN 时是无法选取出NULL数据的。

3.4.6 使用子查询作为IN谓词的参数

SELECT product_name, sale_price
FROM product
WHERE product_id IN (SELECT product_id
  					 FROM shopproduct
                     WHERE shop_id = '000C');

3.4.7 EXIST 谓词

EXIST谓词的作用就是 “判断是否存在满足某种条件的记录”

如果存在这样的记录就返回真(TRUE),如果不存在就返回假(FALSE)。
EXIST(存在)谓词的主语是“记录”。

  • EXIST的参数
    EXIST 只需要在右侧书写 1 个参数,该参数通常都会是一个子查询。
SELECT product_name, sale_price
  FROM product AS p
 WHERE EXISTS (SELECT *
                 FROM shopproduct AS sp
                WHERE sp.shop_id = '000C'
                  AND sp.product_id = p.product_id);

上面这样的子查询就是唯一的参数。确切地说,由于通过条件“SP.product_id = P.product_id”将 product 表和 shopproduct表进行了联接,因此作为参数的是关联子查询。 EXIST 通常会使用关联子查询作为参数。

  • 子查询中的SELECT *
    由于 EXIST 只关心记录是否存在,因此返回哪些列都没有关系。 EXIST 只会判断是否存在满足子查询中 WHERE 子句指定的条件“商店编号(shop_id)为 ‘000C’,商品(product)表和商店

    商品(shopproduct)表中商品编号(product_id)相同”的记录,只有存在这样的记录时才返回真(TRUE)。

    因此,使用下面的查询语句,查询结果也不会发生变化。

SELECT product_name, sale_price
  FROM product AS p
 WHERE EXISTS (SELECT 1 -- 这里可以书写适当的常数
                 FROM shopproduct AS sp
                WHERE sp.shop_id = '000C'
                  AND sp.product_id = p.product_id);
  • 使用NOT EXIST替换NOT IN

    就像 EXIST 可以用来替换 IN 一样, NOT IN 也可以用NOT EXIST来替换。

    下面的代码示例取出,不在大阪门店销售的商品的销售单价。

SELECT product_name, sale_price
  FROM product AS p
 WHERE NOT EXISTS (SELECT *
                     FROM shopproduct AS sp
                    WHERE sp.shop_id = '000A'
                      AND sp.product_id = p.product_id);

NOT EXIST 与 EXIST 相反,当“不存在”满足子查询中指定条件的记录时返回真(TRUE)。

3.5 CASE 表达式

3.5.1 什么是 CASE 表达式?

CASE 表达式是函数的一种。是 SQL 中数一数二的重要功能,有必要好好学习一下。

CASE 表达式是在区分情况时使用的,这种情况的区分在编程中通常称为(条件)分支。

CASE表达式的语法分为简单CASE表达式和搜索CASE表达式两种。由于搜索CASE表达式包含简单CASE表达式的全部功能。本课程将重点介绍搜索CASE表达式。

语法:

CASE WHEN <求值表达式> THEN <表达式>
     WHEN <求值表达式> THEN <表达式>
     WHEN <求值表达式> THEN <表达式>
     .
     .
     .
ELSE <表达式>
END 

上述语句执行时,依次判断 when 表达式是否为真值,是则执行 THEN 后的语句,如果所有的 when 表达式均为假,则执行 ELSE 后的语句。
无论多么庞大的 CASE 表达式,最后也只会返回一个值。

3.5.2 CASE表达式的使用方法

假设现在 要实现如下结果:

A :衣服
B :办公用品
C :厨房用具  

因为表中的记录并不包含“A : ”或者“B : ”这样的字符串,所以需要在 SQL 中进行添加。并将“A : ”“B : ”“C : ”与记录结合起来。

  • 应用场景1:根据不同分支得到不同列值
SELECT  product_name,
        CASE WHEN product_type = '衣服' THEN CONCAT('A : ',product_type)
             WHEN product_type = '办公用品'  THEN CONCAT('B : ',product_type)
             WHEN product_type = '厨房用具'  THEN CONCAT('C : ',product_type)
             ELSE NULL
        END AS abc_product_type
  FROM  product;
+--------------+------------------+
| product_name | abc_product_type |
+--------------+------------------+
| T恤          | A : 衣服        |
| 打孔器       | B : 办公用品    |
| 运动T恤      | A : 衣服        |
| 菜刀         | C : 厨房用具    |
| 高压锅       | C : 厨房用具    |
| 叉子         | C : 厨房用具    |
| 擦菜板       | C : 厨房用具    |
| 圆珠笔       | B : 办公用品    |
+--------------+------------------+

ELSE 子句也可以省略不写,这时会被默认为 ELSE NULL,但是最好不要省略。

CASE 表达式最后的“END”是不能省略的,否则会报错。

  • 应用场景2:实现列方向上的聚合
    通常我们使用如下代码实现行的方向上不同种类的聚合(这里是 sum)
SELECT product_type,
       SUM(sale_price) AS sum_price
  FROM product
 GROUP BY product_type;  
+--------------+-----------+
| product_type | sum_price |
+--------------+-----------+
| 衣服         |      5000 |
| 办公用品      |       600 |
| 厨房用具      |     11180 |
+--------------+-----------+

假如要在列的方向上展示不同种类额聚合值,该如何写呢?

sum_price_clothes | sum_price_kitchen | sum_price_office
------------------+-------------------+-----------------
             5000 |             11180 |              600  

聚合函数 + CASE WHEN 表达式即可实现该效果

-- 对按照商品种类计算出的销售单价合计值进行行列转换
SELECT SUM(CASE WHEN product_type = '衣服' THEN sale_price ELSE 0 END) AS sum_price_clothes,
       SUM(CASE WHEN product_type = '厨房用具' THEN sale_price ELSE 0 END) AS sum_price_kitchen,
       SUM(CASE WHEN product_type = '办公用品' THEN sale_price ELSE 0 END) AS sum_price_office
  FROM product;
+-------------------+-------------------+------------------+
| sum_price_clothes | sum_price_kitchen | sum_price_office |
+-------------------+-------------------+------------------+
|              5000 |             11180 |              600 |
+-------------------+-------------------+------------------+
  • 应用场景3:实现行转列
    假设有如下图表的结构阿里天池AI训练营SQL笔记——Task03:复杂查询方法-视图、子查询、函数_第7张图片
    计划得到如下的图表结构
    在这里插入图片描述
    聚合函数 + CASE WHEN 表达式即可实现该转换
-- CASE WHEN 实现数字列 score 行转列
SELECT name,
       SUM(CASE WHEN subject = '语文' THEN score ELSE null END) as chinese,
       SUM(CASE WHEN subject = '数学' THEN score ELSE null END) as math,
       SUM(CASE WHEN subject = '外语' THEN score ELSE null END) as english
  FROM score
 GROUP BY name;
+------+---------+------+---------+
| name | chinese | math | english |
+------+---------+------+---------+
| 张三 |      93 |   88 |      91 |
| 李四 |      87 |   90 |      77 |
+------+---------+------+---------+

上述代码实现了数字列 score 的行转列,也可以实现文本列 subject 的行转列

-- CASE WHEN 实现文本列 subject 行转列
SELECT name,
       MAX(CASE WHEN subject = '语文' THEN subject ELSE null END) as chinese,
       MAX(CASE WHEN subject = '数学' THEN subject ELSE null END) as math,
       MIN(CASE WHEN subject = '外语' THEN subject ELSE null END) as english
  FROM score
 GROUP BY name;
+------+---------+------+---------+
| name | chinese | math | english |
+------+---------+------+---------+
| 张三 | 语文    | 数学 | 外语    |
| 李四 | 语文    | 数学 | 外语    |
+------+---------+------+---------+

总结:

  • 当待转换列为数字时,可以使用SUM AVG MAX MIN等聚合函数;
  • 当待转换列为文本时,可以使用MAX MIN等聚合函数

练习题-第二部分(答案)

3.5
正确
3.6
1.

product_name	purchase_price
打孔器	320
擦菜板	790

2.返回值为空
3.7

SELECT  
SUM(CASE WHEN sale_price <= 1000 THEN 1 ELSE 0 END) AS low_price ,
SUM(CASE WHEN sale_price BETWEEN 1001 AND 3000 THEN 1 ELSE 0 END ) AS mid_price ,
SUM(CASE WHEN sale_price >= 3001 THEN 1 ELSE 0 END ) AS high_price
FROM product

你可能感兴趣的:(阿里天池学习笔记,sql,数据库)