数据库:SQL从入门到入门

一些数据库知识,SQL操作和MySQL内容

Posted by BlackDn on July 26, 2022

“善良不纯,恶毒不够;冰酒燃火,寒山藏春。”

数据库:SQL 从入门到入门

前言

哥们哥们,来学一学数据库
绕的我有点晕,特别是连接 JOIN那一块
目录看着长,内容不长。

数据库简述

先说一些教材上的知识,数据库按照数据结构来组织、存储和管理数据,大体可分为三种模型:

  • 层次模型:以“上下级”的层次关系来组织数据的一种方式,其数据结构看起来就像一颗树
  • 网状模型:把每个数据节点和其他很多节点都连接起来,其数据结构像很多城市之间的路网
  • 关系模型:类似一个二维表格,任何数据都可以通过行号+列号来唯一确定

数据库软件又称数据库管理系统(DBMS,Database Management System),目前,市面上大部分使用的是基于关系模型的关系数据库,即关系型数据库
而主流的关系型数据库分为以下几类,他们又称为关系型数据库管理系统(RMDBS,Relational Database Management System)

  1. 商用数据库:OracleSQL ServerDB2
  2. 开源数据库:MySQLPostgreSQL
  3. 桌面数据库:微软的Access等,适合桌面应用程序使用
  4. 嵌入式数据库:Sqlite等,适合手机应用和桌面程序

SQL 语言

SQL(Structured Query Language),结构化查询语言,是一种用于操作关系型数据库的计算机语言。
虽然其是被ANSI(美国国家标准协会)标准化的语言,但是它有很多不同的实现版本。

SQL 语言定义了以下几种操作数据库的能力:

  • DDL,Data Definition Language:允许用户定义数据,也就是创建表、删除表、修改表结构这些操作。通常由管理员执行。
  • DML,Data Manipulation Language:为用户提供添加、删除、更新数据的能力,通常由应用程序对数据库进行操作。
  • DQL,Data Query Language:允许用户查询数据,这也是通常最频繁的数据库日常操作。

SQL 本身大小写不敏感,即不区分大小写,但是表名和列名是否大小写敏感由数据库决定。
顺便一提,数据库每一列的列头叫字段,一行数据叫记录,不会有人不知道吧 :>

其常用数据类型如下:

名称 类型 说明
INT 整型 4 字节整数类型,范围约+/-21 亿
BIGINT 长整型 8 字节整数类型,范围约+/-922 亿亿
REAL 浮点型 4 字节浮点数,范围约+/-1038
DOUBLE 浮点型 8 字节浮点数,范围约+/-10308
DECIMAL(M,N) 高精度小数 由用户指定精度的小数,例如,DECIMAL(20,10)表示一共 20 位,其中小数 10 位,通常用于财务计算
CHAR(N) 定长字符串 存储指定长度的字符串,例如,CHAR(100)总是存储 100 个字符的字符串
VARCHAR(N) 变长字符串 存储可变长度的字符串,例如,VARCHAR(100)可以存储 0~100 个字符的字符串
BOOLEAN 布尔类型 存储 True 或者 False
DATE 日期类型 存储日期,例如,2018-06-22
TIME 时间类型 存储时间,例如,12:20:59
DATETIME 日期和时间类型 存储日期+时间,例如,2018-06-22 12:20:59

此外,很多数据类型还有别名,如REAL可以写成FLOAT(24)。各数据库厂商还会支持特定的数据类型,例如JSON

SQL 基础操作

增删查改,又被称为CRUD
C: Create,U: Update,R: Retrive,D: Delete

SELECT - 查询

查询是 SQL 最为重要,也是功能最多、可以变得很复杂的操作。因此这里就不多说了,直接上一个模板:

SELECT [DISTINCT] * # *表示所有列,也可以指定一个或多个列,用逗号隔开
                    # DISTINCT 表示当出现相同记录的时候只显示一个
FROM <表名>
WHERE ... AND ... OR ...    # 指定条件
ORDER BY <1>, <2> ASC / DESC # 排序,ASC升序,DESC降序;
                                #可以指定一个或多个列,会先按<1>排序,若相同再按<2>排序
LIMIT 2    # 限制输出条数
OFFSET 3    # 跳过前三条记录,即输出第4,第5条记录(OFFSET 0显示第一条记录)

当指定一个或多个列而不是用*搜索所有列的时候,也被称为投影
而排序默认是升序,所以ASC可以省略;也可以若干列升序,若干列降序:

ORDER BY <1> ASC, <2> DESC

在 MySQL 中,LIMIT 15 OFFSET 30还可以简写成LIMIT 30, 15
如果OFFSET超过了查询的最大数量并不会报错,而是得到一个空的结果
注意OFFSET越大,查询效率也会越低

LIKE 操作和通配符

SQL 支持以下几种通配符(wildcard),用于在WHERE中作为条件,当使用通配符时,必须使用LIKE关键字操作

  • 百分号(%):表示任何字符出现任意次数。WHERE name LIKE 'A%'表示name字段以A开头
  • 下划线(_):表示单个任意字符。
  • 方括号([]):指定一个字符集。WHERE name LIKE '[AB]%'表示name字段以A开头或B开头

其实有点像正则表达式。注意LIKE和通配符操作只能用于文本字段

拼接和计算字段

在某些情况下我们需要将两个字段的内容进行拼接,可以用+
假设一个用户表users存有用户的first_namelast_name,我们将其拼接,并用AS设置新字段的别名:

SELECT first_name + ' ' + last_name AS full_name FROM users

在某些数据库中用||而非+,而在MySQL中拼接需要使用CONCAT()函数

此外,SELECT还可以进行算数计算
假设一个订单表orders存有商品单价single_price和商品数量quantity,我们需要计算一个订单的总价格,并用AS设置新字段的别名:

SELECT single_price * quantity AS total_price FROM orders

常用的加减乘除运算符都那样:(+,-,*,/)

联合查询

假设我们有以下两个表:

# 1: users 用户表
  (id, name, age)
  (1, '晓黑', 19)
  (2, 'BlackDawn', 22)
  ------------
  # 2: posts 文章表
  (post_id, title, poster_id)
  (1, 'title 1', 1)
  (2, 'title 2', 1)
  (3, 'hello', 2)

当我们联合查询两个表中的数据的时候,如果单单指定两个表,SQL 会将两个表的每一行两两拼在一起 :

SELECT * FROM users, posts;
# 结果如下:
  (id, name, age, post_id, title, poster_id)
  (1, '晓黑', 19, 1, 'title 1', 1)
  (1, '晓黑', 19, 2, 'title 2', 1)
  (1, '晓黑', 19, 3, 'hello', 1)
  (2, 'BlackDawn', 22, 1, 'title 1', 1)
  (2, 'BlackDawn', 22, 2, 'title 2', 1)
  (2, 'BlackDawn', 22, 3, 'hello', 1)

返回结果列数之和列数是两表列数之和,行数是两表行数之积(笛卡尔积
显然这种查询结果绝大部分是没有意义的,因此我们要加上WHERE进行筛选,就不演示了哈

子查询

所谓子查询,就是嵌套在其他查询语句中的查询,通常在WHERE中进行查询,从而实现两个表查询的结合
假设我们开店,有个订单表Orders和一个客户表Customers,我们想知道购买某一物品(ProductID='001')全部客户信息
以往我们需要两步来完成这个操作,先从订单表中筛选购买了这个物品的客户 ID;再通过客户 ID 在客户表中获取客户信息
但是通过子查询可以一步实现(当然也可以直接联合查询,条条大路通罗马嘛)

SELECT customerName, customerPhone... FROM Customers
WHERE customerID IN    (SELECT buyerID FROM Orders WHERE ProductID='001')

UNION:组合查询 / 复合查询

其实就是通过UNION关键字执行多条SELECT语句,并将结果合成为单个结果集。
比如下面的语句可以从学生表 students 中选出姓名以A开头,或者老师为'BlackDn'的学生(当然可以通过OR实现,条条大路通罗马嘛)

SELECT name, email, teacher FROM students
WHERE name LIKE 'A%'
UNION
SELECT name, email, teacher FROM students
WHERE teacher='BlackDn';

要注意的是,由UNION连接的若干条查询语句必须包含相同的列、表达式或聚集函数(简单来说就是结果集的字段要一样)
此外,在默认情况下,UNION会自动消除重复的行(相当于对最后的结果加上个 DISTINCT),改用UNION ALL就可以返回所有结果,包括重复的记录。

INSERT - 插入

INSERT INTO <表名> (字段1, 字段2, ...) VALUES (1, 2, ...);

注意字段要一一对应,就像键值对那样。不过字段的顺序和数据库表中的顺序可以不一样, SQL 会检索匹配字段插入数据。
讲道理,插入一条数据的时候要明确所有字段的值,不过当字段有默认值的时候可以不写(包括自增字段和用户设定默认值的字段)
此外, 可以在一条语句中插入多条数据:

INSERT INTO users (id, name, age) VALUES
  (1, '晓黑', 18),
  (2, 'BlackDawn', 22);

UPDATE - 更新

UPDATE <表名> SET 字段1=1, 字段2=2, ... WHERE ...;

我们可以在更新的时候进行一些简单的运算:

UPDATE users SET age=age+1 WHERE id=1;
------------
  (1, '晓黑', 19),
  (2, 'BlackDawn', 22)

值得注意的是,WHERE中的条件可以是一个范围,这允许我们批量修改多条数据:

UPDATE users SET name="嘿嘿" WHERE age>10;
------------
  (1, '嘿嘿', 19),
  (2, '嘿嘿', 22)

如果WHERE后面的条件没有匹配到任何记录,那么 SQL 不会报错,同样也不会有任记录被修改。
UPDATE语句不写WHERE时,SQL 会对表中所有记录执行更新此操作

MySQL中,UPDATE语句会返回更新的行数,比如上面两个例子会分别返回12

DELETE - 删除

DELETE FROM <表名> WHERE ...;

DELETE语句也可以通过WHERE筛选一次删除多条记录

DELETE FROM users WHERE id>0;
------------
# 直接全删了

如果WHERE没有匹配到任何记录,则不会报错,也不会有任何记录被删除。
不过要注意,如果没有WHERE,则会删掉整个表
UPDATE类似,在MySQL中,DELETE语句也会返回删除的行数

但是如果直接用DELETE自增的字段是不会重置的,还会继续自增。我们可以直接修改自增的字段,比如从 1 开始自增:

ALTER TABLE `table_name` AUTO_INCREMENT = 1;

当然还可以用TRUNCATE实现清空表的同时重置自增字段

TRUNCATE TABLE `table_name`

不过要注意TRUNCATE操作不可回滚

SQL 函数

文本处理函数

函数 说明
LEFT(<字段>, N) 返回字段左边 N 个字符
LENGTH(<字段>) 返回字段的字符长度
LOWER(<字段>) 转为小写
LTRIM(<字段>) 去掉左边的空格
RIGHT(<字段>, N) 返回字段右边 N 个字符
RTRIM(<字段>) 去掉右边的空格
SOUNDEX(<字段>) 返回字符串的 SOUNDEX 值
UPPER(<字段>) 转为大写

注意不是所有函数都是同一名称,比如在Access中转为小写的函数为LCASE(),转为大写的函数为
其中,SOUNDEX是一个为字符串计算发音的算法,从而实现比较发音类似的字符串,虽然其不是 SQL 特有的概念,但大部分 DBMS 都支持这个函数。

数值处理函数

进行代数、三角、几何运算等,在不同 DBMS 中这些函数名都比较统一,问题不大

函数 说明
ABS() 计算绝对值
SQRT() 计算平方根
EXP() 计算 e(自然对数的底)指数的幂值
PI() 返回圆周率
SIN() 计算正弦
COS() 计算余弦
TAN() 计算正切

聚集函数

所谓聚集函数/聚合函数(Aggregate Function),指的是那些运行在行组上(对多个记录的一个或多个列进行计算),最后返回单个值的函数
函数里要加参数的,通常是某个字段名,表里懒得写

函数 说明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列之和

AVG()MAX()MIN()SUM()忽略值为NULL的行
COUNT()有两种使用方式,如果是COUNT(*)则会对表中符合条件的所有记录计数,包括列为NULL的记录;而COUNT(column)对某特定列计数时,则会忽略改列为NULL值的记录
如果WHERE条件没有匹配到任何行,COUNT()会返回0,而SUM()AVG()MAX()MIN()会返回NULL

日期/时间处理函数

一般情况下,由于并非所有编程语言都有表示日期时间的数据结构,就算有也并非一样,所以对于日期时间的的处理通常由数据库完成,然后传回给应用程序进行显示。
但是由于不同 DBMS 处理日期和时间的方式不同,比如SQL ServerAccess等使用DATEPART()函数,这导致其可移植性很差,大家有需要用到的话就去搜一下吧嘻嘻。

SQL 更多概念

把一些稍难一点的概念和操作放在这里,比如连接,索引,事务等。

联结 / 连接:JOIN

因为现在多用关系型数据库,为了节省存储空间和提高查询的高效性,通常把信息分成很多个关系表存储
因此这就涉及到多表查询,上面用SELECTWHERE进行的联合查询就是最简单的多表查询,其也称等值连接(Equijoin)
连接大致可以分为以下几类:

JOIN

然后为了方便举例,这里给出简易的两个表:

# Table_A
+----+---------+
| KEY | Value   |
+----+---------+
|  1  | both ab |
|  2  | only a  |
+----+---------+
# Table_B
+----+---------+
| KEY | Value   |
+----+---------+
|  1  | both ab |
|  3  | only b  |
+----+---------+

最常用的是内连接 INNER JOIN,其将两个表进行关联并返回,不过需要注意两个表必须有可以关联的值(说白了就是取交集)
内连接也是默认的JOIN方式,如果我们看到 SQL 中没有写是什么JOIN,只是单纯的JOIN...ON,那就是内连接。

SELECT A.KEY AS A_KEY, B.KEY AS B_KEY, A.Value AS A_Value, B.Value AS B_Value
FROM Table_A A INNER JOIN Table_B B ON A.KEY = B.KEY;
+-------+-------+---------+---------+
| A_KEY | B_KEY | A_Value | B_Value |
|    1  |    1  | both ab | both ab |
+-------+-------+---------+---------+

其实就是把WHERE换了个语法,变成INNER JOIN ... ON

左连接 LEFT JOIN,也写作LEFT OUTER JOIN,会返回左表(表 A)的所有记录。如果右表(表 B)没有匹配,则会将其内容作为NULL,但仍会和表 A 拼接并返回。

SELECT A.KEY AS A_KEY, B.KEY AS B_KEY, A.Value AS A_Value, B.Value AS B_Value
FROM Table_A A LEFT JOIN Table_B B ON A.KEY = B.KEY;
+-------+-------+---------+---------+
| A_KEY | B_KEY | A_Value | B_Value |
|    1  |    1  | both ab | both ab |
|    2  | NULL  | only a  | NULL    |
+-------+-------+---------+---------+

同理,右连接 RIGHT JOIN,也写作RIGHT OUTER JOIN,本质和LEFT JOIN差别不大,只不过是以右表(表 B)为主,左表没有的话作为NULL
实际上,将表 A 和表 B 换个位置,等效于互换LEFT JOINRIGHT JOIN

FULL OUTER JOIN,外连接、全连接,返回左右表里的所有记录,其中左右表里能关联起来的记录被连接后返回。
MySQL不支持FULL OUTER JOIN,可以用UNION查询得到相同结果

SELECT A.KEY AS A_KEY, B.KEY AS B_KEY, A.Value AS A_Value, B.Value AS B_Value
FROM Table_A A FULL OUTER JOIN Table_B B ON A.KEY = B.KEY;
+-------+-------+---------+---------+
| A_KEY | B_KEY | A_Value | B_Value |
|    1  |    1  | both ab | both ab |
|    2  | NULL  | only a  | NULL    |
| NULL  |    3  |   NULL  | only b  |
+-------+-------+---------+---------+

简单来说,INNER JOIN只会返回两个表中能满足条件的记录;LEFT JOINRIGHT JOIN除了满足条件的记录之外,还会返回主表的记录,而另一个表的字段部分用NULL表示;而FULL OUTER JOIN就是返回两个表全部的记录,对不上的同样用NULL表示

当然还有一些延申用法,比如返回左表有但是右表没有的记录:
逻辑上就是LEFT JOIN减去INNER JOIN,这里额外加一个条件WHERE B.KEY IS NULL
我们称为LEFT JOIN EXCLUDING INNER JOIN,当然RIGHT JOIN EXCLUDING INNER JOIN同理,就不举例了。

SELECT A.KEY AS A_KEY, B.KEY AS B_KEY, A.Value AS A_Value, B.Value AS B_Value
FROM Table_A A LEFT JOIN Table_B B ON A.KEY = B.KEY WHERE B.KEY IS NULL;
+-------+-------+---------+---------+
| A_KEY | B_KEY | A_Value | B_Value |
|    2  | NULL  | only a  | NULL    |
+-------+-------+---------+---------+

当然还有FULL OUTER JOIN EXCLUDING INNER JOIN,表示左表和右表里没有相互关联的记录集,条件为WHERE A.KEY IS NULL OR B.KEY IS NULL
同样MySQL不支持,可以用UNION查询得到相同结果

SELECT A.KEY AS A_KEY, B.KEY AS B_KEY, A.Value AS A_Value, B.Value AS B_Value
FROM Table_A A FULL OUTER JOIN Table_B B ON A.KEY = B.KEY WHERE A.KEY IS NULL OR B.KEY IS NULL;
+-------+-------+---------+---------+
| A_KEY | B_KEY | A_Value | B_Value |
|    2  | NULL  | only a  | NULL    |
| NULL  |    3  |   NULL  | only b  |
+-------+-------+---------+---------+

索引

索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。
通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
就类似咱们习惯用数组存储,这样存取会比较慢,而索引就像个HashMap,能提高存取速度。

ALTER TABLE <表名>
ADD INDEX <索引名>(字段1, 字段2);

假设有表students有这几个字段:id, classID, name, gender, score
我们可以为单个或多个字段创建索引

ALTER TABLE students
ADD INDEX idx_name_score (name, score);

索引的效率取决于索引列的值是否散列,即该列的值如果越互不相同,那么索引效率越高。反过来,如果记录的列存在大量相同的值,那用索引就没啥意义。
对于主键,关系数据库会自动对其创建主键索引。使用主键索引的效率是最高的,因为主键会保证绝对唯一。
可以对一张表创建多个索引。索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引 。因此,如果索引太多,反而在增删查改的时候还要花时间更新索引,会适得其反让系统变得更慢。

而有些唯一的列,例如身份证号、邮箱地址等,具有具体业务含义,因此不宜作为主键,这种情况下可以为其添加一个唯一索引
假设students表的name不能重复,就可通过以下方法创建唯一索引,就是多了个UNIQUE关键字

ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);

事务

在某些情况下,一系列的 SQL 操作需要一口气完成,比如一个转账操作,需要从id=1的账户给id=2的账户转账 100 元:

-- 第一步:将id=1的A账户余额减去100
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 第二步:将id=2的B账户余额加上100
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

显然这个操作要么一口完成,要么就不执行。要是执行了第一步断网了,总不能 A 的账户少了 100,B 的账户啥也没变吧。

这种把多条语句作为一个整体进行操作的功能,被称为数据库事务
数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,就要撤销这些操作,不会对数据库数据有任何改动。
这就是教科书上说的,事务的ACID特性:

  • A:Atomic,原子性,将所有 SQL 作为原子工作单元执行,要么全部执行,要么全部不执行;
  • C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即本地和后端数据库保持一致;
  • I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
  • D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。

对于单条 SQL 语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务
此外,我们可以使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

隔离级别

为了处理两个并发执行的事务,尤其是这两个事务对同一条记录进行更改,SQL 定义里四种隔离级别

Isolation Level 出现脏读(Dirty Read) 出现不可重复读(Non Repeatable Read) 出现幻读(Phantom Read)
Read Uncommitted Yes Yes Yes
Read Committed - Yes Yes
Repeatable Read - - Yes
Serializable - - -

Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务可以读到另一个事务更新后但未提交的数据。缺点就在于,当第一个事务修改记录、被第二个事务读取后,如果第一个事务因意外回滚,第二事务所读取到的数据就和原数据不同,这种情况就是脏读
比如账户 A 中有 200 块钱,事务 A 要转 100 块给账户 B,事务 B 要转 100 块给账户 C。
事务 A 已经将账户 A 扣了 100 块,此时事务 B 启动,读取到账户 A 还有 100 元。结果事务 A 出现意外回滚,数据库中账户 A 变为 200 块。
但是这时候事务 B 正常进行,他将账户 A 扣了 100,变成了 0,并将结果存入数据库,则账户 A 就变为 0。
这就是脏读所带来的数据不一致的后果。

Read Committed则不允许其他事务读取当前事务所修改的数据,直到当前事务提交,但是这可能会出现不可重复读的问题,即在一个事务内多次读同一个数据,但是期间内另外一个事务修改了该数据从而导致两次读取的数据不一致的问题。
该级别也是许多数据库的默认设置级别,如 Oracle,SQL Server。

Repeatable Read规定一个事务BEGIN后所读取的多次数据都是启动时的状态,不会因为其他事务的修改而出现两次读取数据不一致的问题,因此不会出现脏读不可重复读的问题。
但是,当其他事务插入数据并COMMIT后,如果当前事务还没有COMMIT,那么当前事务是读取不到新的数据的(因为当前事务读取的数据状态是其BEGIN时的状态,那会其他事务还没COMMIT)。虽然读不到数据,但是如果有针对该条数据的修改操作,是可以成功执行的(因为数据库里确实有这条数据),这就是幻读,读不到数据,但是可以修改。
该级别是 MySQL 的默认隔离级别。

时刻 事务 A:(本来没有 id=99 的数据,这里插入这条数据) 事务 B:(一开始找不到 id=99 的数据,但是可以修改,且修改后就可以读取)
1 BEGIN; BEGIN;
2   SELECT * FROM students WHERE id = 99; – 找不到
3 INSERT INTO students (id, name) VALUES (99, ‘Bob’);  
4 COMMIT;  
5   SELECT * FROM students WHERE id = 99; – 找不到
6   UPDATE students SET name = ‘Alice’ WHERE id = 99; – 修改成功
7   SELECT * FROM students WHERE id = 99; – 返回 Alice
8   COMMIT;

Serializable是最严格的隔离级别。在 Serializable 隔离级别下,所有事务按照次序依次执行,因此,脏读不可重复读幻读都不会出现。
但是该级别下已经不存在事务的并行了,一个事务执行时其他事务必须阻塞等待,从而导致效率大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。

总的来说,级别越高,出现的错误越少,但同时事务并行的时间越少,效率越低。

MySQL 相关

因为我一开始学的时候是拿MySQL练手,所以下面这些比较复杂的语句都是在MySQL中可执行的,其他不知道=。=

MySQL 语句

在顾客表customers中,列出'France''UK' 的顾客的国家country和城市city的组合方式(不重复)

SELECT DISTINCT `country`, `city` FROM `customers` WHERE `country`='France' OR `country`='UK' ORDER BY `country`, `city`;

在顾客表customers中,列出所有顾客的state且不重复,结果按state排序

SELECT DISTINCT `state` FROM `customers` WHERE `state` IS NOT NULL ORDER BY `state`;

在订单明细表orderdetails中,列出总金额排名前五的明细记录

SELECT `orderNumber`, (`quantityOrdered` * `priceEach`) AS 'subtotal'
FROM `orderdetails` ORDER BY `subtotal` DESC LIMIT 5;

列出所有被取消(status='Cancelled')的订单以及其总金额

SELECT DISTINCT `orders`.`orderNumber`,
    sum(`orderdetails`.`quantityOrdered`*`orderdetails`.`priceEach`) AS 'totalPrice',
    count(`orderdetails`.`orderNumber`) AS 'detailsCount'
FROM `orders` INNER JOIN `orderdetails`
ON `orders`.`status`='Cancelled' AND `orders`.`orderNumber`=`orderdetails`.`orderNumber`
GROUP BY `orders`.`orderNumber` ORDER BY `orders`.`orderNumber`;

列出所有的员工employees的姓名及其管理者的姓名,如果员工没有管理者,则其管理者的姓名输出为 '(Top Manager)'

SELECT concat(E1.`lastName`,', ',E1.`firstName`) AS 'employee',
    IFNULL(concat(E2.`lastName`,', ',E2.`firstName`), '(Top Manager)') AS 'manager'
FROM `employees` AS E1 LEFT JOIN`employees` AS E2 ON E1.`reportsTo`=E2.`employeeNumber`
ORDER BY E2.`lastName`,E2.`firstName` , E1.`lastName`,E1.`firstName`;

列出所有的订单(orders)中每一种 status 的订单的总金额

SELECT `status`,
        SUM((SELECT SUM(`quantityOrdered` * `priceEach`) AS `orderPrice`
            FROM `orderdetails` WHERE `orderdetails`.`orderNumber`=`orders`.`orderNumber`)) AS 'totalPrice'
FROM `orders` GROUP BY `status` ORDER BY `status`;

列出每年的订单(orders)总额

SELECT YEAR(`orderDate`) AS 'year',
    SUM((SELECT SUM(`quantityOrdered`*`priceEach`)     FROM `orderdetails`
    WHERE `orderdetails`.`orderNumber`=`orders`.`orderNumber` GROUP BY `orderNumber`)) AS 'totalPrice'
FROM `orders` GROUP BY `year`;

列出有下任何订单的顾客(customers)的信息

SELECT `customerNumber`, `customerName` FROM `customers`
WHERE `customerNumber` NOT IN
    (SELECT DISTINCT `customerNumber` FROM `orders`    ORDER BY `customerNumber`)
ORDER BY `customerNumber`;

MySQL 常用函数

这里列出几个MySQL的常用函数,具体可见:MySQL 函数(经典收藏)
可能是MySQL特有,也有可能在其他数据库中也一样,这里不做区分,反正在MySQL里能用=。=

函数 说明 例子
TRUNCATE() 保留小数点,不四舍五入,不规定位数保留到整数 TRUNCATE(1.23456, 3) -> 1.234
ROUND() 保留小数点,四舍五入,不规定位数保留到整数 ROUND(1.23456, 3) -> 1.235
IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2 IF(1 > 0,’正确’,’错误’) -> 正确
IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2 IFNULL(null,’Hello Word’) -> Hello Word
CURDATE() 返回当前日期 自己试
CURTIME() 返回当前时间 自己试
NOW() 返回当前日期和时间 自己试
YEAR() 返回日期中的年份(参数需要是日期),同理还有 MONTH(),DAYOFWEEK(), DAYOFMONTH()等 自己试
HOUR() 返回时间中的小时(参数需要是时间),同理还有 MINUTE(),SECOND()等 自己试

参考

  1. 廖雪峰:SQL 教程
  2. SQL 是什么
  3. MySQL 函数(经典收藏)
  4. 图解 SQL 里的各种 JOIN
  5. 《SQL 必会知识》- Ben Forta 著,人民邮电出版社
  6. mysql 让自增的 id 重新从 0 开始的命令