SQL学习笔记-第一部分
written by SJTU-XHW
本部分文章是 SQL 的基础部分,纪念我丢掉的 SQL 笔记 😭
本人知识浅薄,文章难免有问题或缺漏,欢迎批评指正!
Reference: 《 SQL 必知必会 (第 5 版 ) 》
使用前须知
本系列文章的 “章末习题” 使用的数据库和表的关系如下图所示;
这里提供创建表和数据的 SQL 文本(以 MySQL 为例):generate.sql,可以在搭建好 MySQL 环境后直接导入它,以备后面章末习题使用;
Chapter 0 数据库的基本概念
数据库(从 SQL 角度看):以某种有组织的方式存储的数据集合;
关系型数据库 & 非关系型数据库
- 关系型数据库:采用关系模型、遵循关系代数模式来组织数据的数据库(例如 MySQL、PostgreSQL等);关系模型类似二维表,允许表间数据相互关联,支持多表查询;
- 非关系型数据库(No SQL):不依赖关系模型的、分布式的数据库(例如 MongoDB、Redis 等);以键值来存储,结构不稳定,每个元组的字段可以不相同,可以面向高并发key-value读写 / 面向文档;
表一 关系型数据库 & 非关系型数据库比项目 RDB NOSQL(Not Only SQL) 是否依赖关系模型 是,二维表容易理解 否,key-value 存储较为零散 是否有通用的 SQL 有,使用方便 无,无法提供WHERE字段查询 事务的一致性 强,ACID属性易于维护 弱,适宜面向网络/文档/高并发读写等场景 拓展能力 纵向,依赖处理能力提升 横向,天然分布式 存储数据 适宜结构化数据,例如账户、地址 适宜非结构化数据,例如文章、评论
注:一般生产环境会使用 RDB + NoSQL
组合方案,互相取长补短;
⚠ 本文章仅讨论关系型数据库中的 标准 SQL;
数据库管理系统(DBMS):俗称数据库管理系统;数据库是通过 DBMS 创建和操纵的容器;
MySQL、mariaDB、mongoDB、SQLite 等等都是 DBMS,而它们创建的数据库才是真正的 “数据库”;
表(table):某种特定类型数据的结构化清单;
根据定义,可以理解为:在数据库(相当于文件柜)中,存放文件需要放在特定的档案中,这个“文件柜”中的“档案”就是表;
表存储的是同一种类型的数据或清单,便于归类和查找
表名:表的唯一标识符(ID),在一个数据库中唯一,但不同数据库的表之间名字可以相同;
模式(schema):关于数据库和表的布局及特性的信息;
描述数据库中特定的表,或者描述整个数据库及表的关系;
包括:储存什么数据?数据如何分解(parse)?各部分信息如何命名?……
列(column):表中的一个字段,存储表的某一个部分/领域(field)的信息;
所有表都是由一个或多个列组成;
正确地按需求对数据列分解对于分类、过滤意义重大;例如,将国家和省市信息组合在一个列中,那么仅按省市查找的难度就比 “国家、省市信息分开在两列中” 的情况更大;
数据类型:每一列需要有相应的数据类型,限制该列存储的数据(以方便分类和查找);
对表的各列设计数据类型也是重要的:正确地设计数据类型不仅可以帮助正确分类和查找,还可以优化磁盘容量;
⚠ 因为有些 DBMS 中同一个数据类型的名称不一样(数据类型兼容问题),即便名称相同,定义可能不一样,所以应该总是查阅文档、学习和应用时需要注意;
行:表中的一个记录(从技术上说,行才是正确的术语);
主键(primary key):表中的每一行都应该有一列(或几列)可以唯一标识自己(ID);
没有主键,更新、删除数据库表的特定行就极其困难,不能保证没有误操作;
所以规范来说,表应该总是定义主键;
主键的要求:
- 互异性:表的任意两行都不具有相同主键值;
- 非空性:每一行必须具有一个主键值(不允许为 NULL 空值);
- 固定性:主键列中的值不允许修改和更新;
- 不可重用性:如果表的一行从表中被删除,那么它的主键不能赋给以后的新行;
SQL:
Structured Query Language
,结构化查询语言(专门用来与数据库沟通的语言);- 不是某个数据库厂商、某类 DBMS 特有的语言;
- 关键字数量极少,但功能强大;
本文章不考虑某些 DBMS 的 SQL 扩展(某个厂商独有、独自开发的),仅讨论标准 SQL(ANSI SQL);
SQL 的关键字:作为 SQL 组成部分的保留字,不能用作表 / 列的名字;会在以后章节中逐渐提及;
SQL 语言注意事项
SQL 语句的结束:以分号结尾;
SQL 语句不区分大小写,但习惯上约定俗成:关键字大写、列/表名小写;
在一个 SQL 语句未结束时,可以在两个词间添加换行,不会有影响,一般是方便阅读;
SQL 语言的索引从0开始,因此也是一种 0-index 语言;
注释方式:行内注释
--
(后面跟一个空格)、单行注释#
(某些 DBMS 不支持)、多行注释/* [contents] */
;好习惯:和 其他所有编程语言一样,必要时多加注释;
Chapter 1 数据检索
1.1 SELECT 语句
用途:从一个或多个表中检索信息;
语法
检索某一个表的 某一个列:
SELECT <column_name> FROM <table_name>;
同时检索 多个列:
SELECT <col_name1, [col_name2, ...]> FORM <table_name>;
检索 所有的的列:
SELECT * FORM <table_name>;
谨慎使用通配符(*),除非真的需要,或者不知道需要列的列名;
检索表中 某列不同的值(
DISTINCT
关键字):SELECT DISTINCT <col_name> FROM <table_name>;
⚠ 不可以部分使用
DISTINCT
关键字,例如:SELECT DISTINCT <col_name1, col_name2> FROM <table_name>;
因为
DISTINCT
作用于所有的列!部分使用会导致失去想要的结果(SQL 需要同时考虑其他列也有DISTINCT
,最后会出现组合的现象);(而且这么做不符合逻辑)ℹ 和
DISTINCT
相反的是ALL
,一般无需指定,因为这是默认行为;检索 结果限制(用来缩小范围找到想要的数据)
限制仅查询 N 行
1
2
3SELECT <col_name>
FROM <table_name>
LIMIT N;1
2
3SELECT <col_name>
FROM <table_name>
WHERE ROWNUM <= 5; -- 行计数器1
2SELECT TOP 5 <col_name>
FROM <table_name>;1
2
3SELECT <col_name>
FROM <table_name>
FETCH FIRST 5 ROWS ONLY;以下仅以常见的
MySQL
、MariaDB
为例,不再赘述;限制指定行:
M ~ M+N
⚠ SQL 语言 0-index;
1
2
3
4
5SELECT <col_name>
FROM <table_name>
LIMIT N OFFSET M; -- 即 0+M ~ 0+M+N
-- 或者
LIMIT M,N;
习题
从
customers
表中检索所有的cust_id
列的数据:1
2SELECT cust_id
FROM customers;已知表
OrderItems
(订单表)中存在一列prod_id
(产品 ID),问所有出现在订单中的产品 ID,要求每个产品 ID 仅显示一次:1
2SELECT DISTINCT prod_id
FROM OrderItems;检索
customers
表中所有的列,再检索其中列cust_id
、cust_name
、cust_country
的第 5~9 行:1
2
3
4SELECT * FROM customers;
SELECT cust_id, cust_name, cust_country
FROM customers
LIMIT 5 OFFSET 5;
1.2 排序已检索数据
主要内容:
SELECT
的ORDER BY
子句;
子句(clause)的定义:SQL 语句由子句构成;
- 有些子句是必需的,有些是可选的;
- 一个子句通常是由一个关键字 + 所提供的数据组成;
举例:SELECT 语句的
FROM
子句、LIMIT
子句、ORDER BY
子句等;语法
检索时 按某一列排序:
SELECT <col_name1[, col_name2, ...]> FROM <tb_name> ORDER BY <col_nameN>;
⚠ 必须保证
ORDER BY
子句为SELECT
语句的最后一句,否则会出错!ℹ 排序依据的列可以不是选择列(即
col_nameN
可以不在col_name1, col_name2,...
中);检索时 按多个列排序:
SELECT <col_name1[, col_name2, ...]> FROM <tb_name> ORDER BY <col_nameN1, col_nameN2, ...>;
含义:首先按
col_nameN1
排序,再按col_nameN2
排序,有点像高考录取排序方法;检索时 使用已选择列的列号排序(仅仅是一种简化方法):
SELECT <col_Name1[, ...]> FROM <tb_name> ORDER BY <col_num1[, ...]>;
检索时 指定排序方向:
SELECT <col_name1[, ...]> FROM <tb_name> ORDER BY <col_nameN1[ASC/DESC][, ...]>;
关键字
ASC
(Ascending,默认升序)、DESC
(Descending);
习题
从
Customers
表中检索所有的顾客名称(cust_names
),并按从 Z 到 A 的顺序显示结果:1
2
3SELECT cust_names
FROM Customers
ORDER BY cust_names DESC;从
Orders
表中检索顾客 ID(cust_id
)和订单号(order_num
),并先按顾客 ID 对结果进行排序,再按订单日期(order_date
)倒序排列:1
2
3SELECT cust_id, order_num
FROM Orders
ORDER BY cust_id ASC, order_date DESC;
1.3 过滤已检索数据
主要内容:
SELECT
的WHERE
子句、SELECT
的LIKE
子句(模糊过滤);
应用须知:对数据的过滤操作应该交给 SQL,而非上层的应用程序(开发语言);
原因:
- 数据库 SQL 对于数据过滤优化力度远远大于你自己在外层写的,造成性能资源浪费;
- 降低上层应用程序的可移植性、可读性、可维护性;
过滤条件:也称“搜索条件”,用于根据特定操作提取表数据的子句;
模糊过滤条件:使用类似正则式的方式确定过滤条件(仅适用于文本-string);
谓词(predicate):某些关键字不是操作符,而是作为谓词使用,例如
LIKE
;语法
- 在检索时 过滤条件:
SELECT <col_name[, ...]> FROM <tb_name> WHERE <cond>;
- 在检索时 使用模糊过滤条件:
SELECT <col_name[, ...]> FROM <tb_name> WHERE <col_str> LIKE <pattern>;
- 在检索时 过滤条件:
WHERE <cond>
子句判断操作符:小于(等于)、大于(等于)(与普通编程语言相同),等于(=
),不等于(!=
或<>
),不小于(!<
),不大于(!>
);另外:在指定两个值之间使用关键字操作符
BETWEEN...AND...
,判断是否为 NULLIS NULL
;以上操作符有冗余,有些 DBMS 不支持,请自行判断;
⚠ 注意1:NULL(空值)和字段包含0、空字符串、仅含空格的情况都不一样;
⚠ 注意2:只要使用了过滤条件,除了
IS NULL
操作符,其他的匹配无法选中匹配列记录为 NULL 的记录!⚠ 注意3:等于操作符和主流编程语言不一样,只有一个等号!
WHERE <cond>
子句条件操作符:AND
、OR
、NOT
、圆括号(改变运算顺序);运算顺序同主流编程语言;
WHERE <cond>
子句包含操作符:IN
、定元组(例如:('DLL01', 'BRS01')
);建议能用
IN
,就用它:(例如:
SELECT prod_name FROM products WHERE prod_id == 'DLL01' OR prod_id == 'BRS01';
不如SELECT prod_name FROM products WHERE prod_id in ('DLL01', 'BRS01');
)因为它有好处:
IN
操作符可读性更强;- 数据库底层优化
IN
可以使用一次 HASH 操作,效率高于OR
连接的运算; IN
操作符可以实现WHERE
子句的嵌套(后面讨论);
WHERE
子句的pattern
:表二 SQL pattern 和 正则式对比SQL pattern % (NULL excluded) _ [char set] [^char set] Regex .? . .[char set] .[^char set]
⚠ 注意1:在 LIKE
匹配时,pattern
区分大小写!
⚠ 注意2:在有些数据库的数据中,对于字符串数据类型按指定位存储时,不满位数的会填充以空格,因此 pattern 可能很难匹配到;正确做法是利用 SQL 自带函数去除字符串两边空格,后面讨论;、
⚠ 注意3:不宜过度使用通配符、不宜将通配符作为靠前的搜索条件;
习题
从Products 表中检索产品ID(
prod_id
)和产品名称(prod_name
),只返回价格(prod_price
)为 9 或更高的产品:1
2
3SELECT prod_id, prod_name
FROM Products
WHERE prod_price >= 9;从
OrderItems
表中检索出所有不同订单号(order_num
),其中包含产品个数(quantity
)在 100 个至 200 个之间:1
2
3
4
5SELECT DISTINCT order_num
FROM OrderItems
WHERE quantity BETWEEN 100 AND 200;
-- 或者
WHERE quantity >= 100 AND quantity <= 200;返回
Products
表中所有价格在 3 美元到 6 美元之间的产品的名称(prod_name
)和价格(prod_price
),然后先按价格、后按 ID (prod_id
)对结果都进行降序排序:1
2
3
4SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 3 AND 6
ORDER BY prod_name DESC, prod_id DESC;查找所有至少订购了总量(
quantity
) 100 个的,ID 为BR01
、BR02
或BR03
的订单。你需要返回OrderItems
表的订单号(order_num
)、产品ID(prod_id
)和数量,并按产品 ID 和数量进行升序排序:1
2
3
4SELECT order_num, prod_id
FROM OrderItems
WHERE quantity >= 100 AND prod_id IN ('BR01', 'BR02', 'BR03')
ORDER BY prod_id, quantity;从
Products
表中检索产品名称(prod_name
)和描述(prod_desc
),仅返回描述中未出现toy
一词的产品。要求按产品名称对结果进行排序:1
2
3
4SELECT prod_name, prod_desc
FROM Products
WHERE NOT (prod_desc LIKE '%toy%')
ORDER BY prod_name;从
Products
表中检索产品名称(prod_name
)和描述(prod_desc
),仅返回在描述
中以先后顺序同时出现toy
和carrots
的产品:1
2
3SELECT prod_name, prod_desc
FROM Products
WHERE prod_desc LIKE '%toy%carrots%';
Chapter 2 字段格式化输出
表中的列称为列,而在“计算字段”(格式化字段)中称为字段(field);
格式化输出的思想也遵循本思想🔗
2.1 字段拼接
1 | SELECT Concat(col_name1/'string1'[, ...]) |
1 | SELECT col_name1/'string1' + ... + col_nameN/'stringN' |
1 | SELECT col_name1/'string1' || ... || col_nameN/'stringN' |
2.2 简单算术运算和 strip
+-*/
和简单函数 LTRIM(str)
(去左空格)、RTRIM(str)
(去右空格)、TRIM(str)
(去两边空格);
常见数学函数几乎都与主流编程语言相同(SIN()
、EXP()
、ABS()
等);
常量:PI()
;
⚠ 以上描述都没有提及别名(alias,又称导出列,derived column),其实规范来说,对于所有的格式化输出(包括以后提及的),都应该设置别名,好处如下:
- 方便客户端引用。没有别名的格式化列无法被某些客户端使用,有些操作会变得困难;
- 重命名包含不合法字符的原列名;
- 提升可读性。在原来的列名含混、意义不清晰时扩充、具体化它;
2.3 普通函数处理
⚠ 本部分内容的可移植性会受到不同 DBMS 对函数名称和定义的影响;
⚠ 函数名称也不区分大小写;
函数用法 | 函数语法 | ||||
---|---|---|---|---|---|
MySQL | SQL Server | DB2 | SQLite | Oracle | |
字符串子串 | SUBSTRING() | SUBSTR() | |||
数据类型转换 | CONVERT() | CAST() | 因类型不同 | ||
取日期 | CURDATE() | GETDATE() | CURRENT_DATE | DATE() | SYSDATE |
格式化日期 | YEAR()等 | DATEPART(yy, DATE) | 同 MySQL | strftime(strf, DATE) | EXTRACT(year FROM DATE) |
去空格 | LTRIM()、RTRIM()、TRIM() | ||||
求字符串长度 | CHAR_LENGTH() | LEN() | STRING-LEGTH() | LENGTH() | |
求数据所占Byte | LENGTH() | DATALENGTH() | 同 MySQL | TYPEOF()对应的Byte | LENGTHB() |
tips 1. SUBSTRING(str, start, end)
,其中 end
包含,且此处函数面向用户,是 1-index;
tips 2. 还有些通用性比较好的函数:UPPER()/LOWER()
(取大/小写)、SOUNDEX()
(返回字符串的 SOUNDEX
值);
SOUNDEX()
适用于英文,实质是一种将单词转化为描述发音的字母数字算法,可以找出发音近似或相同的单词,在有一些误输入的查找中有用,比如顾客名 Michael
误输入为 Michelle
,但二者的 SOUNDEX()
相同,因此可以匹配;
2.4 聚集函数
定义:对表的某个列(某一行数据类型很可能不相同)运行的函数,仅计算返回一个值;
AVG()
平均值函数仅能针对单个列使用!!!如果需要多个列,请依次书写;
仅有一种特例(实质还是一个列——导出列):列以算术运算符连接;
忽略值为
NULL
的行;
COUNT()
计数函数COUNT(*)
:给表中的行计数,不忽略NULL
;COUNT(column)
:给特定列的行计数,忽略NULL
;
MAX()/MIN()
最值函数:返回升序 / 降序排序后的第一行;SUM()
求和函数;注意事项
- 允许列结合算数运算符;
- 聚集不同值:如果对某列要按不同值进行聚集,应该在列名前指定
DISTINCT
; - 使用聚集函数时强烈建议取别名;
2.5 数据分组
主要内容:
SELECT
的GROUP BY
子句 和HAVING
子句;
需求:在 2.4 中的聚集函数一般都是面向整个一列,或者一列的特定部分(如果使用了
DISTINCT
、WHERE
约束);但没法按照分类组来统计、聚集数据(例如按照date
时间段分组求最大值),所以引入了数据分组;创建分组:
SELECT <col_name-X, [AgFuctions]> FROM <tb_name> GROUP BY <col_name-X>;
以上语句的含义指:将
col_name-X
先DISTINCT
一下(所以上面的列名仅推荐使用col_name-X
,即选择的列),再按col_name-X
分组(内部完成),这时通配符*
指代的对象会变成:当前选择列中 各自分好的组(意味着col_name-X
只能有一个,否则不能使用通配符*
)!ℹ 你可以使用聚集函数的
*
来试一试上面的理论;分组的性质
数据列中
NULL
会被分为一组;GROUP BY
子句必须在WHERE
子句后,在ORDER BY
子句之后;GROUP BY
必须在WHERE
之后的原因:GROUP BY
产生内部分组,而WHERE
只能对所有行筛选,完全没有分组的概念,所以WHERE
不能筛选分组(否则造成意想不到的结果)!应该使用可以筛选分组的子句:
HAVING
(这个子句除了多了一个“可以筛选分组”的功能,其他使用与WHERE
几乎一致、可以替代;但标准来说只有GROUP BY
+ 需要筛选时才用到HAVING
)GROUP BY
子句允许嵌套分组;
过滤分组:
SELECT <col_name-X [AgFunctions]> FROM <tb_name> GROUP BY <col_name-X> HAVING <cond>;
排序分组:
SELECT <col_name-X [AgFunctions]> FROM <tb_name> GROUP BY <col_name-X> ORDER BY <col_name-X>;
⚠ 虽然你可能发现
GROUP BY
本身就可以按分组后的结果排序,但这个行为不在 SQL 规范中,所以如果有需要对分组进行排序,仍需要ORDER BY
子句,不能仅仅依赖GROUP BY
;
2.6 本章习题
从
Vendors
表中检索vend_id
、vend_name
、vend_address
和vend_city
,将vend_name
重命名为vname
,将vend_city
重命名为vcity
,将vend_address
重命名为vaddress
。再按供应商名称对结果进行排序。注意所有的字符串变量的存储都有定长、记录前可能有空格:1
2
3
4
5
6SELECT vend_id,
TRIM(vend_name) AS vname,
TRIM(vend_city) AS vcity,
TRIM(vend_address) AS vaddress
FROM Vendors
ORDER BY vname;商店正在进行打折促销,所有产品均降价 10%. 编写 SQL 语句,从
Products
表中返回prod_id
、prod_price
和sale_price
,其中sale_price
是一个包含促销价格的计算字段:1
2
3SELECT prod_id, prod_price,
prod_price*0.9 AS sale_price
FROM Products;假设商店已经上线了,正在创建顾客账户。所有用户都需要登录名,默认登录名是其名称和所在城市的组合。在表
Customers
中编写 SQL 语句,返回顾客 ID(cust_id
)、顾客名称(cust_name
)和登录名(user_login
),其中登录名全部为大写字母,并由顾客联系人的前两个字符(cust_contact
)和其所在城市的前三个字符(cust_city
)组成。例如,我的登录名是BEOAK
(Ben Forta
,居住在Oak Park
):1
2
3
4
5
6
7
8SELECT cust_id, cust_name,
UPPER(
CONCAT(
SUBSTRING(cust_contact, 1, 2),
SUBSTRING(cust_city, 1, 3)
)
) AS user_login
FROM Customers;编写 SQL 语句,返回 2020 年 1 月的所有订单(
Orders
表)的订单号(order_num
)和订单日期(order_date
),并按订单日期排序:1
2
3
4SELECT order_num, order_date
FROM Orders
WHERE date_format(order_date, '%Y-%m') = '2020-01'
ORDER BY order_date;编写 SQL 语句,找出
Products
表中的num_items
项的总个数(不包括NULL
)、值不同的个数、最小、最大、平均值,列在一个表格中:1
2
3
4
5
6SELECT COUNT(num_items) AS total_cnt,
COUNT(DISTINCT num_items) AS dist_cnt,
MIN(num_items) AS min_items,
MAX(num_items) AS max_items,
AVG(num_items) AS avg_items
FROM Products;计算
OrderItems
表中各行所要花费的金钱总和(物品数量quantity
* 物品价格item_price
再求和):1
SELECT SUM(quantity * item_price) AS total_cost FROM OrderItems;
编写 SQL 语句,确定
Products
表中价格不超过 10 美元的最贵产品的价格(prod_price
)。将计算所得的字段命名为max_price
:1
2
3SELECT MAX(prod_price) AS max_price
FROM Products
WHERE prod_price <= 10;编写 SQL 语句,要求统计
Products
表的供应商 IDvend_id
各自提供的商品个数(即每个供应商在Product
中含有的数据行数),按 “ID、个数(列名num_prods
)” 顺序输出:1
2
3SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;编写 SQL 语句,要求找出
Orders
表中订单数不少于 2 的顾客 ID(cust_id
),即每个cust_id
在表中含有的数据行数,按 “ID、订单数” 顺序输出:1
2
3
4SELECT cust_id, COUNT(*) AS num_orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;编写 SQL 语句,要求找出
Products
表中 “提供 2 个及以上的、价格(prod_price
)不少于4 的产品” 的供应商,按 “供应商ID(vend_id
)、满足条件的产品数(列名num_prods
)” 顺序输出:1
2
3
4
5SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4 -- 注意:在这里非分组筛选需要在分组前进行
GROUP BY vend_id
HAVING COUNT(*) >= 2;OrderItems
表包含每个订单的每个产品。编写SQL 语句,返回每个订单号(order_num
)各有多少行数(order_lines
),并按order_lines
对结果进行升序排序:1
2
3
4SELECT order_num, COUNT(*) AS order_lines
FROM OrderItems
GROUP BY order_num
ORDER BY order_lines;编写 SQL 语句,返回名为
cheapest_item
的字段,该字段包含每个供应商(vend_id
)成本最低的产品(使用Products
表中的prod_price
),然后从最低成本到最高成本对结果进行排序(按 “供应商 ID、cheapest_item
” 输出):1
2
3
4
5# 注意:此处 prod_price 已不再是整个列,而是各分组的 prod_price,对它用聚集函数会得到各列的结果
SELECT vend_id, MIN(prod_price) AS cheapest_item
FROM Products
GROUP BY vend_id
ORDER BY cheapeset_item;商店经营中,确定最佳顾客非常重要。有一个店主想出 2 种方法来确定他的最佳顾客:
Ⅰ. 找出满足 “所有订购产品的数量不少于 100” 的订单,至少持有一个这样订单的顾客是 “最佳顾客”;(注:在
OrderItems
表中特定的订单号order_num
、本订单订购商品名order_item
下的商品数量quantity
,是 “这个订单中 这个产品的数量”)所以请编写 SQL 语句,只需找出满足上述条件的这个订单,输出订单号即可;
1
2
3
4SELECT order_num
FROM OrderItems
GROUP BY order_num
HAVING SUM(quantity) >= 100;Ⅱ. 找出满足 “总价(包含不同的商品)至少为 1000” 的订单,至少持有一个这样订单的顾客是 “最佳顾客”;(注:对于一个订单中的一个商品而言,需要在
OrderItems
表中计算item_price * quantity
来获得订单中一类商品的总价格)所以请编写 SQL 语句,找出满足上述条件的订单,以降序输出订单号(
order_num
),按 “订单号、总价(列名total_price
)” 输出;1
2
3
4
5SELECT order_num, SUM(item_price * quantity) AS total_price
FROM OrderItems
GROUP BY order_num
HAVING total_price >= 1000
ORDER BY order_num DESC;在不运行下列语句的前提下,指出下列语句的错误:
1
2
3
4
5SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY items
HAVING COUNT(*) >= 3
ORDER BY items, order_num;Answer:
GROUP BY
语句不能对聚合函数分组!从分组聚合的含义上来说,COUNT(*)
是对分组后的组进行计数,这样是循环指代,从逻辑上行不通;改正方案:将
GROUP BY items
分组依据改成GROUP BY order_num
就正确了,如下:1
2
3
4
5SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;这段语句的含义是:找出所有满足 “包含不少于 3 种商品” 的订单,并按 “订单所含商品种类数、订单号” 输出(最后按种类数,再按订单号升序排序);
EX-1 第一部分语法总结
1 | SELECT <col_name1[, ...]> [AS new_name] |