🪟 第6章 SQL基础
📄 一句话概括

7 / 2 得 3 不是 3.5——两个整数相除,小数部分直接丢掉,这是算术最大的坑。 '1' + '1' 在 SQL Server 里得 '11'、在 SQLite 里得 2——同一个符号,两边含义不同。 混用 AND 和 OR 一定要加括号:NOT 比 AND 先算,AND 比 OR 先算。

📝 本章和前面几章的关系

运算符是 SQL 里最「看起来都会」的部分, 但它埋着两个不报错的坑:整数除法、加号的二义性。这两个的共同点是—— 代码不报错,结果不对。 教材 6.7 分五小节:算术、比较、逻辑、连接、优先级。前四节各有一两个坑,第五节是总结。 演示里真跑了整数除法、负数除法、'1' + '1' 和 '1' || '1' 的对比, 还有五组逻辑表达式看优先级怎么起作用。

一、算术运算符:整数除法丢小数

表达式 结果 说明
7 / 2 3 两个整数,结果取整
7.0 / 2 3.5 有一个是小数
7 % 2 1 取余
-7 / 2 -3 负数除法,向零取整

第一行是算术里最大的坑。 两个整数相除,结果还是整数, 小数部分直接丢掉——不是四舍五入,是直接砍掉。 这条在算比例的时候最容易出事。 总分 / 人数,两个都是整数, 结果也是整数——平均分 83.83 会变成 83。 解决办法有两种: 乘个 1.0(总分 * 1.0 / 人数), 或者转换类型(CAST(总分 AS FLOAT) / 人数)。前者短,后者清楚。 负数除法各家不一样。 演示里 SQLite 给 -3(向零取整), 有些产品给 -4(向下取整)——这一条别靠记忆,用到时实测。

二、比较运算符

运算符 含义 注意
= 等于 判断相等,不是赋值
<> 不等于 标准写法
!= 不等于 能用,但不是标准
>= <= > < 大小比较 字符串按排序规则比
IS NULL 是空值 不能用 = NULL

最后一行是 SQL 独有的规则。 = NULL 永远不成立——因为 NULL 表示「不知道」, 「不知道等于什么」无法判断。必须写 IS NULL。 字符串比较是按「排序规则」比的,不是按字典序。中文库里 '张' 和 '李' 谁大, 取决于按拼音还是按笔画排——这一点在 2.2b 讲排序规则时提过,那时讲配置,这里讲它影响结果。 大小写也是这样。 CI 的列里 'Apple' = 'apple' 为真,CS 的列里为假。 所以「同一个查询在两台机器上结果不同」,先查排序规则。

三、连接运算符:加号 vs 两条竖线

这是本节最值得看的一段。同一个 '1' + '1',两家给出完全不同的结果:

SQL Server:'1' + '1' = '11'    (加号当拼接)
SQLite:    '1' + '1' = 2       (加号当加法)
SQLite:    '1' || '1' = '11'   (两条竖线才是拼接)

为什么会这样? SQL Server 的加号是「多态」的:两边是数字就相加,两边是字符串就拼接。 SQLite 的加号永远当加法,字符串会自动转成数字。 所以 '1' + '1' 在 SQLite 里是 1 + 1 = 2——字符串被隐式转换成了数字。 这个过程不报错,也不警告。 跨库迁移时这类问题最难查。 代码不报错,结果不对,而且你得先想到「可能是这个原因」才能去查。 所以写跨库 SQL,一律用 || 或者 CONCAT(),别用 +。

四、逻辑运算符与优先级

表达式 结果 为什么
1=1 AND 1=2 假 真 AND 假
1=1 OR 1=2 真 真 OR 假
NOT 1=1 假 NOT 真
NOT 1=1 AND 1=2 假 (NOT 1=1) AND (1=2)
NOT (1=1 AND 1=2) 真 NOT(真 AND 假)

第四行和第五行是重点。 一个假一个真,只差一对括号——因为 NOT 比 AND 先算。 这两行的语义完全不同: 第四行是「不是 A,并且 B」,第五行是「不是(A 并且 B)」。 写条件的时候想清楚要哪个,然后加括号。 一个硬规则:只要一条 WHERE 里同时有 AND 和 OR,就加括号。不加的话, 别人读你的 SQL 要在脑子里跑一遍优先级——这是给自己和别人都添麻烦。

五、动手 Demo:整数除法与加号的二义性

这一段真跑三种算术、两种拼接、五组逻辑表达式。 重点是两个「不报错但结果不对」的地方:7 / 2 得 3,'1' + '1' 得 2。

import sqlite3

c = sqlite3.connect(u":memory:")

# 整除丢小数
for e in (u"7 / 2", u"7.0 / 2", u"7 % 2", u"-7 / 2"):
    print(u"%-10s = %s" % (e, c.execute(u"SELECT %s" % e).fetchone()[0]))

# 加号是加法,竖线才是拼接
print(c.execute(u"SELECT '1' + '1'").fetchone()[0])    # 2
print(c.execute(u"SELECT '1' || '1'").fetchone()[0])   # 11

# 优先级
for e in (u"NOT 1 = 1 AND 1 = 2", u"NOT (1 = 1 AND 1 = 2)"):
    v = c.execute(u"SELECT %s" % e).fetchone()[0]
    print(u"%-24s -> %s" % (e, u"真" if v else u"假"))

实跑输出:

--------------------------------------------------------------------
运算符与两处方言差异
--------------------------------------------------------------------


  ① 算术运算符:整数除法的坑
  --------------------------------------------------------------------
| 表达式 | 说明 | 结果 |
|---|---|---|
| 7 / 2 | 整数除整数 | 3 |
| 7.0 / 2 | 有一个是小数 | 3.5 |
| 7 % 2 | 取余 | 1 |
| -7 / 2 | 负数除法 | -3 |
| -7 % 2 | 负数取余 | -1 |

  7 / 2 得 3,不是 3.5——两个整数相除,结果是整数,小数部分直接丢掉。
  这条在算比例的时候最容易出事:总分 / 人数,两个都是整数,结果也是整数。
  解决办法是乘个 1.0,或者 CAST 成小数:CAST(总分 AS FLOAT) / 人数。
  负数除法是向下取整还是向零取整,各家不一样——这一条别靠记忆,用到时实测。

  ② 比较运算符
  --------------------------------------------------------------------
| 运算符 | 含义 | 注意 |
|---|---|---|
| = | 等于 | 判断相等,不是赋值 |
| <> | 不等于 | 标准写法 |
| != | 不等于 | 能用,但不是标准 |
| > >= < <= | 大小比较 | 字符串按排序规则比 |
| IS NULL | 是空值 | 不能用 = NULL |

  字符串比较是按「排序规则」比的,不是按字典序。
  中文库的默认排序规则下,'张' 和 '李' 谁大,取决于它按拼音还是按笔画排。
  这一点在 2.2b 讲排序规则时提过——那时讲的是配置,这里讲的是它影响比较结果。

  ③ 连接运算符:加号 vs 两条竖线
  --------------------------------------------------------------------
  SQLite:'1' + '1' = 2(类型 int)
  SQLite:'1' || '1' = 11(类型 str)
  同一个表达式,两种结果——因为加号在这里被当成算术加法,两条竖线是字符串拼接。
  SQL Server 里 '1' + '1' 得 '11':它的加号在遇到字符串时自动变成拼接。
  这就是「方言差异」最典型的一处:同一个符号,两边含义不同,而且不报错。
  跨库迁移时这类问题最难查——代码不报错,结果不对。

  ④ 逻辑运算符与优先级实测
  --------------------------------------------------------------------
| 表达式 | 说明 | 结果 |
|---|---|---|
| 1 = 1 AND 1 = 2 | 真 AND 假 | 假 |
| 1 = 1 OR 1 = 2 | 真 OR 假 | 真 |
| NOT 1 = 1 | NOT 真 | 假 |
| NOT 1 = 1 AND 1 = 2 | NOT 比 AND 先算 | 假 |
| NOT (1 = 1 AND 1 = 2) | 加了括号 | 真 |

  第四行和第五行结果一样,但含义完全不同——一个是「不是A 且 B」,一个是「不是(A且B)」。
  这两行放着对比,是因为它们恰好都得假。换个值就不一样了,所以别靠试。
  写条件时,只要混用了 AND 和 OR,就加括号。这是唯一能保证不出错的做法。

第一组四行里,第一行和第四行都是「整数除法」的结果。 7 / 2 得 3, -7 / 2 得 -3——向零取整,不是向下取整。 第二组是本节最该记住的对比。 '1' + '1' 得 2, '1' || '1' 得 '11'——同一个表里同一个字符串,两个运算符两种结果。 SQL Server 里 '1' + '1' 得 '11'。 所以这段代码搬到 SQL Server 上跑, 第一行输出就变了——而且不报错。这是迁移时最容易漏的地方。 第三组两行结果相反。 记不住优先级没关系,记住「混用 AND 和 OR 就加括号」就够了。

30 秒实验

  1. 算一下「总分 / 人数」,用一个能被整除的数和一个不能整除的数各试一次。 第二次你会发现结果少了小数——这就是整数除法。
  2. 把 '1' + '1' 换成 'abc' + 'def' 再试。 SQLite 里会得 0(字符串转数字失败当 0),SQL Server 里会得 'abcdef'——差距更明显。
  3. 写一条同时用 AND 和 OR 的 WHERE,然后加括号再跑一次。 如果两次结果一样,说明你是幸运的;换个条件就不一样了。

常见误区

  1. 「除法就是除法,不会丢精度」。两个整数相除,结果还是整数。 7 / 2 得 3,不是 3.5——算平均分的时候这一条会让你少一位小数。
  2. 「+ 能拼字符串,哪个库都一样」。SQL Server 能,SQLite 不能(它当加法)。 要拼字符串用 || 或 CONCAT(),别用 +。
  3. 「优先级我记住了,不用括号」。记住也会忘,而且别人未必记得。 混用 AND 和 OR 就加括号——这一条能省掉大量排查时间。
自测三问(点开对答案) 1. `7 / 2` 为什么得 3 不是 3.5? —— **因为两个操作数都是整数,结果也是整数,小数部分直接丢掉。**要得到 3.5, 得让其中一个变成小数:`7.0 / 2` 或 `CAST(7 AS FLOAT) / 2`。 2. `'1' + '1'` 在 SQL Server 和 SQLite 里分别得什么? —— **SQL Server 得 `'11'`(加号当拼接),SQLite 得 `2`(加号当加法)。** **两边都不报错**——这是迁移时最难的差异。写跨库 SQL 用 `||`。 3. `NOT A AND B` 和 `NOT (A AND B)` 有什么区别? —— **前者是「不是 A,并且 B」;后者是「不是(A 并且 B)」。**因为 `NOT` 比 `AND` 先算。 **混用 `AND` 和 `OR` 就加括号,这是唯一可靠的做法。**

下一篇 6.8 讲通配符,也是第 6 章的收尾。% 和 _ 是标准的, [] 和 [^] 是 SQL Server 的扩展——而且下划线在「想查字面量下划线」的时候会坑你一次。

相关笔记