SQL cheetsheet

初步接触 DBMS: 不就是存个数据吗, 有什么难的. 增删改查, 我上我也行.

进一步了解 DBMS: 好像有点东西(全然余裕).

看完一本 DBMS 的书后: B+树索引结构, 关系模型和外键约束, 连接优化、投影消除...

记这么多DML作甚? 实际还不是用的GUI. 涉及复杂业务逻辑的时候, 写SQL语句照样烧脑. 设计表的时候还不是照样头疼. 不考虑优化, 结果性能巨差有谁来负责. 没个 Cheatsheet 还真没一下想不出来.

检索数据

基础操作: SELECT

  • 所有的 SQL 语句用 ; 结尾.
  • SQL 关键字大写, 变量名小写是好习惯.
  • 结果是未排序的.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 查找单列
SELECT course_id FROM courses;

-- 查找多列
SELECT course_id, course_name FROM courses;

-- 检索所有列
-- * 是个通配符. 检索不必要的列通常会降低性能.
SELECT * FROM courses;

-- 检索不同的行
-- 不能部分使用 DISTINCT
SELECT DISTINCT course_id FROM courses;

-- 限制结果
-- 返回结果不多于 5 行
SELECT course_id FROM courses
LIMIT 5;

-- 完全限定名称
-- 为了避免命名空间冲突
SELECT courses.course_id FROM courses;

排序检索: ORDER BY

SQL 语句由子句(clause)构成, 有些子句是必须的, 有些则不是.

ORDER BY 字句

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 按 course_name 排序
SELECT course_name FROM courses
ORDER BY course_name;

-- 按多列排序
SELECT course_id, course_name FROM courses
ORDER BY course_id, course_name;

-- 按指定排序方向, 关键字: DESC(ascending)
-- 默认是 ASC(ascending)
-- 本例按 id 高排序
SELECT course_id, course_name FROM courses
ORDER BY course_id DESC, course_name;

数据过滤: WHERE

  • 通常数据过滤在服务器而不是在客户端解决. 想想为什么?
  • ORDER BY 得放在 WHERE 后面.
  • 使用圆括号分组操作符是个好习惯.
  • MySQL 里的 NOT 和其他 SQL 语句略有不同. NOT IN, NOT BETWEEN 也支持.
操作符 说明
= 等于
<> 不等于1
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于
BETWEEN AND 在指定的两个值之间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- 按指定的搜索条件条件过滤
SELECT prod_name, prod_price
FROM products
WHERE prod_price = 2.50;

-- 空值检查
SELECT prod_name FROM products
WHERE prod_price IS NULL;

-- AND, OR
-- AND 运算优先级高于 OR
SELECT prod_name, prod_price
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;

-- IN
-- 像是个语法糖
-- 但比常规的 OR 要快
SELECT prod_name, prod_price
FROM products
WHERE vend_id IN (1002, 1003) AND prod_price >= 10;

-- NOT
-- 和 Python 里的一样
SELECT prod_name, prod_price
FROM products
WHERE vend_id NOT IN (1002, 1003)
ORDER BY prod_name;

通配符和 regex

  • 通配符(wildcard)用来匹配值的一部分的特殊字符.
  • 搜索模式(search pattern2)由字面值(literal), 通配符或两者组合构成的搜索条件.
  • 通配符就是 WHERE 字句中具有特殊含义的字符.
  • % 匹配不了 NULL.
  • 不要过度使用通配符, 注意通配符位置.
  • MySQL 只实现 REGEX 里的很小一块子集.
  • LIKEREGEXP3的区别: 完全匹配整个列和部分匹配.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- % 通配符: 任何字符出现次数
-- 'jet%' 匹配模式: 任何以词jet开头的产品
-- % 可以多次出现, 可以出现在任何位置
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE 'jet%';

-- _ 通配符: 只匹配单个字符
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE '_ ton anvil';

-- 使用正则表达式
-- '.000' 匹配诸如 `1000`, `2000`...
-- '1000|2000' 类似于 OR
-- '[1234]000' 几个字符之一 简写`[1-4]`
-- 剩下的 REGEX 就不介绍了
SELECT prod_name, prod_price
FROM products
WHERE prod_name REGEXP '.000'
ORDER BY prod_name;

-- 区分大小写: BINARY 关键字
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP 'JetPack .000';

计算字段

  • 字段(field)基本上就是列的意思.
  • 拼接字段得到一个导出列(derived column)
  • AS 关键字: 别名(alias)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 拼接字段
-- 示例输出:
/*
+----------------------+
| vend_title |
+----------------------+
| Vendor 1 (USA) |
| Vendor 2 (Germany) |
+----------------------+
*/
SELECT Concat(vend_name, ' (', vend_country, ')') AS vend_title
FROM vendors
ORDER BY vend_name;

-- 执行算术运算
-- 支持加减乘除
SELECT prod_id,
quantity,
item_price,
quantity * item_price AS expanded_price
FROM orderitems
WHERE order_num = 20005;

-- SELECT 可以拿来测试计算
SELECT 3 * 2
SELECT Trim('abc')
SELECT Now()

数据处理函数

文本处理函数:

函数名 描述
CONCAT 用于连接多个字符串
UPPER 将字符串转换为大写
LOWER 将字符串转换为小写
SUBSTRING 从字符串中提取子字符串
LENGTH 返回字符串的长度
TRIM 去除字符串首尾的空格
LTRIM 去除字符串开头的空格
RTRIM 去除字符串末尾的空格
LEFT 返回字符串左边指定长度的子字符串
RIGHT 返回字符串右边指定长度的子字符串
REPLACE 替换字符串中的指定子字符串
CHARINDEX 返回指定子字符串在字符串中第一次出现的位置
REVERSE 反转字符串
FORMAT 将数值或日期格式化为指定的字符串表示
COALESCE 返回第一个非空表达式的值
CASE 条件语句,用于在结果中根据条件返回不同的值
REGEXP_REPLACE 使用正则表达式替换字符串中的内容
TRANSLATE 替换字符串中的指定字符
SOUNDEX 返回字符串的 SOUNDEX 码,用于模糊匹配字符串的发音
CONCAT_WS 用指定的分隔符连接多个字符串,并去除空字符串
INITCAP 将字符串的首字母大写
STRPOS 返回指定子字符串在字符串中第一次出现的位置
LEFTPAD 在字符串的左侧填充指定字符
RIGHTPAD 在字符串的右侧填充指定字符

日期函数:

函数名 说明
AddDate 增加一个日期(天、周等)
AddTime 增加一个时间(时、分等)
CurDate 返回当前日期
CurTime 返回当前时间
Date 返回日期时间的日期部分
DateDiff 计算两个日期之差
Date_Add 高度灵活的日期运算函数
Date_Format 返回一个格式化的日期或时间串
Day 返回一个日期的天数部分
DayOfWeek 对于一个日期,返回对应的星期几
Hour 返回一个时间的小时部分
Minute 返回一个时间的分钟部分
Month 返回一个日期的月份部分
Now 返回当前日期和时间
Second 返回一个时间的秒部分
Time 返回一个日期时间的时间部分
Year 返回一个日期的年份部分

不同的 DBMS 对函数的实现和语法有些区别. 这么多谁记得住. 建议看文档.

1
2
3
4
-- 使用范例:
SELECT cust_id, order_num
FROM orders
WHERE Year(order_date) = 2005 AND Month(order_date) = 9;

汇总数据: 聚集函数

  • 汇总数据严格来说不是检索数据.
  • 聚集函数(aggregate function): 运行在行组上, 计算和返回单个值的函数.
  • COUNT(*): 行的值为空依然计数 COUNT(column): column 有值的计数
  • MAX() 要求指定列名
  • ALL 默认 DISTINCT 只包含不同的值
  • 可以组合聚集函数.

聚合函数:

函数名 说明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和
1
2
3
4
5
6
7
8
9
-- 聚集函数
-- 效率更高. 想想看和下面的差在哪?
SELECT MAX(column_name) AS max_value
FROM table_name;

-- 普通的查询
SELECT column_name FROM table_name
ORDER BY column_name
DESC LIMIT 1;

分组数据: GROUP BY 和 HAVING

  • GROUP BY(创建分组) 和 HAVING(过滤分组)
  • GROUP BY 支持多级分组. 数据将在最后规定的分组上进行汇总.
  • GROUP BYWHERE 后, ORDER BY
  • WHERE 过滤行, 而 HAVING 过滤分组(两者很相似, 操作符通用)
  • 可以这么想: WHERE 在分组前过滤, HAVING 在分组后过滤.
  • 而且 HAVING 可以和 WHERE 混着用
ORDER BY GROUP BY
输出 按指定列排序的结果 分组后的结果,但输出可能不按照分组的顺序
列的使用 任意列,甚至非选择的列也可以使用 只能使用选择列或表达式列,必须使用每个选择列表达式
必要性 不一定需要 如果与聚集函数一起使用列(或表达式),则必须使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
-- 创建分组
-- GROUP BY 按 vend_id 排序并分组数据
/*
| vend_id | num_prods |
|---------|-----------|
| 1 | 5 |
| 2 | 3 |
| 3 | 7 |
| 4 | 2 |
*/
SELECT vend_id, COUNT(*) AS num_prods
FROM products
GROUP BY vend_id;

-- WITH ROLLUP: 添加汇总行
/*
| vend_id | num_prods |
|---------|-----------|
| 1 | 5 |
| 2 | 3 |
| 3 | 7 |
| 4 | 2 |
| NULL | 17 |
*/
SELECT vend_id, COUNT(*) AS num_prods
FROM products
GROUP BY vend_id WITH ROLLUP;

-- GROUP BY 多级分组
/*
| customer_id | order_date | order_amount |
|-------------|------------|--------------|
| 1001 | 2023-01-01 | 50.00 |
| 1002 | 2023-01-01 | 75.00 |
| 1001 | 2023-01-02 | 30.00 |
| 1003 | 2023-01-02 | 20.00 |
| 1002 | 2023-01-03 | 45.00 |
| 1001 | 2023-01-03 | 60.00 |
| 1003 | 2023-01-03 | 35.00 |
*/
SELECT customer_id, order_date, SUM(order_amount) AS total_amount
FROM orders
GROUP BY customer_id, order_date;

-- 过滤分组
/*
| cust_id | orders |
|---------|--------|
| 1001 | 3 |
| 1003 | 2 |
| 1002 | 2 |
*/
SELECT cust_id, COUNT(*) AS orders
FROM orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;

在SQL查询中, SELECT 子句的一般顺序如下:

  • SELECT:指定要检索的列或表达式。
  • FROM:指定要从中检索数据的表或视图。
  • WHERE:应用条件来筛选满足特定条件的行。
  • GROUP BY:根据指定的列或表达式对结果进行分组。
  • HAVING:筛选分组后的结果集。
  • ORDER BY:指定结果集的排序顺序。
  • LIMIT / OFFSET(在某些数据库中可用):限制返回的行数或指定结果集的偏移量。

使用子查询

  • 子查询(subquery): 查找一遍又一遍
  • 子查询总是从内向外处理.
  • 相关子查询(correlated subquery): 涉及外部查询的子查询.
  • 逐渐增加子查询来建立查询. 也就是先分成多部查询, 确定正常后再写进子查询.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 用子查询
-- 括号里的先算
SELECT cust_id
FROM orders
WHERE order_num IN (SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2');

-- 其实你可以拆成两个查询. 最好先写成两步, 然后合并为子查询.

-- 作为计算字段使用子查询
/*
| cust_name | cust_state | orders |
|-----------|------------|--------|
| John | NY | 3 |
| Mary | CA | 2 |
| David | TX | 0 |
| Lisa | NY | 1 |
*/
SELECT cust_name,
cust_state,
(SELECT COUNT(*) FROM orders
WHERE orders.cust_id = customers.cust_id) AS orders
FROM customers
ORDER BY cust_name

  1. 我去, 这不是我们 PASCAL 的写法吗!↩︎

  2. schemapattern 都译作模式.↩︎

  3. MPE 的问题, REGEXP 代码块里应当高亮.↩︎