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] |














