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' + '1'换成'abc' + 'def'再试。 SQLite 里会得 0(字符串转数字失败当 0),SQL Server 里会得 'abcdef'——差距更明显。 - 写一条同时用
AND和OR的WHERE,然后加括号再跑一次。 如果两次结果一样,说明你是幸运的;换个条件就不一样了。
常见误区
- 「除法就是除法,不会丢精度」。两个整数相除,结果还是整数。
7 / 2得 3,不是 3.5——算平均分的时候这一条会让你少一位小数。 - 「
+能拼字符串,哪个库都一样」。SQL Server 能,SQLite 不能(它当加法)。 要拼字符串用||或CONCAT(),别用+。 - 「优先级我记住了,不用括号」。记住也会忘,而且别人未必记得。
混用
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 的扩展——而且下划线在「想查字面量下划线」的时候会坑你一次。
- ☐ (本篇完,下一篇 6.8 通配符)