上一章,我们已经能用 SELECT 决定“结果里显示哪些列”,也能用表达式给列起别名、做简单计算。可真实的经营问题几乎从来不是“把整张表给我看看”,而是“只找出这一批”:成都会员、尚未付款的订单、库存低于安全线的商品、某个时间段内的交易。
这时,WHERE 就成了查询里最重要的闸门。每一行数据经过条件判断,满足要求才会留下;不满足的会被挡在结果集之外。
听起来很直白,直到你写出下面这条 SQL:
SELECT customer_id, customer_name, phone
FROM customers
WHERE phone <> '13800001001';你的本意是“找出电话号码不是苏小满这个号码的顾客”。结果一看,大部分顾客都有,那两位手机号尚未填写的顾客却不见了。你可能会想:手机号没填,当然也不是 13800001001,为什么没有被选中?
因为 SQL 并没有把 NULL 当成一个叫“空”的普通值。它表达的是“未知”:数据库现在不知道这位顾客的手机号。既然不知道,就无法断言它等于这个号码,也无法断言它不等于这个号码。于是 phone <> '13800001001' 对这行得到的不是 TRUE,也不是 FALSE,而是第三种结果 UNKNOWN。WHERE 只放行 TRUE,FALSE 和 UNKNOWN 都会被过滤。
本章就从这几行“悄悄消失”的数据开始,把 WHERE 条件真正讲透。你会学会精确比较、组合条件、集合与范围、模糊匹配、NULL 的三值逻辑,还会认识一个足以让整条查询返回零行的经典陷阱:NOT IN 的右侧只要混入一个 NULL,结果就可能完全变样。

我们继续使用“小满商店”。本章主要查看三张表:顾客 customers、订单 orders 与商品 products。字段仍然使用统一的英文名称,结果里的中文别名只负责让报表更好读。
为了让后面的每个条件都能观察到边界与 NULL,我们先截取一组代表性数据。
SELECT customer_id, customer_name, phone, city,
registered_at, referrer_id
FROM customers
ORDER BY customer_id;这张表里故意保留了缺失信息:白露和陆野的 phone 是 NULL,表示电话号码未知或尚未提供;苏小满的手机号则是一个普通字符串值。NULL、空字符串和数字 0 不是一回事。固定数据没有用空字符串冒充“未填写”,后面我们仍会专门比较三者的逻辑行为。
订单表的数据如下:
SELECT order_id, customer_id, order_date, status, total_amount
FROM orders
ORDER BY order_id;这些时间跨越 2025 与 2026 两年。等我们讨论范围边界时,它们会告诉你怎样完整取出一个自然年,又不把下一年的第一刻混进来。
本章的重点是条件,不是建表。你可以把上面的数据当作贯穿全章的一张对照表:先猜哪些行会留下,再看结果是否与猜测一致。每次猜错,都说明某条 SQL 规则还没有真正进入你的直觉。
一条常见查询可以先读成三个动作:
SELECT customer_id, customer_name, city
FROM customers
WHERE city = '杭州';FROM customers 指定从顾客表取数据。WHERE city = '杭州' 逐行判断条件。SELECT ... 决定通过筛选的行要显示哪些列。这不是要求数据库必须按这个物理顺序执行,而是一种可靠的阅读方式。数据库优化器可能选择更高效的访问路径,但查询的逻辑结果不能因此改变。
上面的查询结果是:
可以把 WHERE 想成一位只认 TRUE 通行证的门卫:
第三行正是 NULL 问题的根。很多初学者心里只有“真、假”两格,于是看到 UNKNOWN 被过滤时,会误以为数据库漏了数据。
SELECT ... WHERE ... 只是读取符合条件的行,并不会删除其他行。下面这条查询返回两位杭州顾客,但 customers 表仍然保存着全部顾客:
SELECT customer_name
FROM customers
WHERE city = '杭州';不过,UPDATE 和 DELETE 也能使用 WHERE。那时条件选中的行会真的被修改或删除,所以最好先把同样的条件放进 SELECT 检查:
-- 第一步:先确认命中范围
SELECT product_id, product_name, stock, status
FROM products
WHERE stock = 0
AND status = '缺货';
-- 第二步:确认无误后,再执行数据修改
UPDATE products
SET status = '下架'
WHERE stock = 0
AND status = '缺货';写 UPDATE 或 DELETE 时,不要只检查“WHERE 是否存在”,还要检查“WHERE 是否选中了正确的行数”。条件写错一个括号,危险程度和忘写 WHERE 并没有想象中那么远。
最常用的比较运算符并不多:
在可移植的 SQL 里,通常优先写标准形式 <>。!= 在 MySQL 和 PostgreSQL 中都很常见,但 <> 更能明确表达“这是 SQL 的不等于”。
SELECT order_id, status, total_amount
FROM orders
WHERE status = '已支付';'已支付' 是字符串字面量。不要写成 status = 已支付,否则数据库会把 已支付 当成标识符去解析,通常会报告“找不到这列”。
SELECT order_id, total_amount
FROM orders
WHERE total_amount >= 180;有些数据库会尝试把 '180' 转成数字,因此 total_amount >= '180' 也可能得到结果。但依赖隐式类型转换会把错误藏起来:一旦字符串里混入非数字字符,或者换了数据库与配置,行为可能变得反直觉。列是什么类型,就尽量拿同类型的值比较。
SELECT product_id, product_name
FROM products
WHERE status = '在售';大小写是否敏感、某些字符是否视为相同,不只由 = 决定,还取决于列的字符集与排序规则。比如商品编码、邮箱后缀或其他含拉丁字母的文本,不要凭编程语言中的字符串经验,武断地认定大小写一定相等或一定不等。业务需要区分大小写时,应在表结构和排序规则层面明确设计。
下面四个表达式看起来在问不同问题,碰到 NULL 时却都得不到 TRUE 或 FALSE:
SELECT
NULL = NULL AS equal_result,
NULL <> NULL AS not_equal_result,
NULL > 100 AS greater_result,
'成都' = NULL AS city_result;这里显示的 NULL 是逻辑上的 UNKNOWN。它不是在说“比较失败”,而是在说“现有信息不足以判断”。

单个条件往往回答不了真实问题。运营同学可能要的是“金额不低于 150 元并且已完成的订单”,这就需要 AND:
SELECT order_id, status, total_amount
FROM orders
WHERE status = '已完成'
AND total_amount >= 150;AND 要求两边都为 TRUE。一边为 FALSE,整行就不会通过。
如果目标改成“已完成或已发货的订单”,用 OR:
SELECT order_id, status, total_amount
FROM orders
WHERE status = '已完成'
OR status = '已发货';只要有一边为 TRUE,整行就能通过。
NOT 用来否定一个条件:
SELECT order_id, status
FROM orders
WHERE NOT (status = '已取消');对于 status 非 NULL 的行,它与 status <> '已取消' 等价。不过一旦条件涉及 NULL,不能简单把自然语言中的“不是”直接套进 SQL;UNKNOWN 被 NOT 之后仍然是 UNKNOWN。
看看这条查询:
SELECT order_id, status, total_amount
FROM orders
WHERE status = '已完成'
OR status = '已发货'
AND total_amount >= 180;它会被理解成:
WHERE status = '已完成'
OR (status = '已发货' AND total_amount >= 180)而不是:
WHERE (status = '已完成' OR status = '已发货')
AND total_amount >= 180第一种会保留全部已完成订单,再加上金额至少 180 元的已发货订单;第二种只保留金额至少 180 元且状态属于两者之一的订单。按固定数据,前者返回 101、102、104、106、108、109、111、113、114,后者返回 102、104、108、113。
数据库没有猜错,是条件没有把业务分组写出来。
即使你已经记住优先级,也建议在 AND 与 OR 混用时主动加括号。括号不只是给数据库看的,更是给三个月后的自己、代码评审者和接手同事看的。
需求:“找出已完成或已发货,并且金额不低于 180 元的订单。”
先切成两块:
再翻译:
SELECT order_id, status, total_amount
FROM orders
WHERE (status = '已完成' OR status = '已发货')
AND total_amount >= 180;结果为:
假设要排除“已取消或已退款”的订单:
WHERE NOT (status = '已取消' OR status = '已退款')对非 NULL 状态,它可以改写成:
WHERE status <> '已取消'
AND status <> '已退款'注意 OR 变成了 AND,每个比较也分别取反。如果把它误写成下面这样,含义就变了:
-- 错误:几乎所有非 NULL 状态都至少满足其中一边
WHERE status <> '已取消'
OR status <> '已退款';一条“已取消”订单确实不等于“已退款”,所以仍会通过;“已退款”同理。这个条件对任何单一状态几乎都为 TRUE。
当一个字段要匹配多个候选值时,连续写 OR 虽然正确,却很容易漏字段或复制出错:
WHERE status = '已支付'
OR status = '已发货'
OR status = '待支付'可以写成更紧凑的 IN:
SELECT order_id, status, total_amount
FROM orders
WHERE status IN ('已支付', '已发货', '待支付')
ORDER BY order_id;IN 表达的是集合成员关系:左边的值只要等于列表中的任意一个,就返回 TRUE。
-- 推荐:customer_id 是数字,候选值也都写数字
WHERE customer_id IN (1, 2, 5)不要把 IN (1, 2, '五号') 这样的混合类型交给隐式转换去猜。集合越长,隐藏的类型错误越难排查。
应用程序动态拼接查询时,候选数组可能为空。不要想当然地生成:
WHERE customer_id IN ()这在常见数据库中不是可靠的合法条件。业务上如果空集合意味着“没有任何行”,应用层可改为 WHERE 1 = 0;如果意味着“不启用这项过滤”,则应省略这一条件。两种语义完全不同,必须在拼 SQL 前决定。
SELECT order_id, status
FROM orders
WHERE status NOT IN ('已取消', '已退款');当 status 保证不为 NULL、列表里也没有 NULL 时,这条语句很直观:排除两种状态。但 NOT IN 对 NULL 极其敏感,我们会在后面用一整节拆开它。
运营要找 100 元到 500 元的订单,可以这样写:
SELECT order_id, total_amount
FROM orders
WHERE total_amount BETWEEN 100 AND 500
ORDER BY total_amount;BETWEEN 是闭区间,等价于:
WHERE total_amount >= 100
AND total_amount <= 500也就是说,恰好 100 和恰好 500 都会被保留。不要凭日常语言中的“100 到 500”猜边界是否包含,SQL 的 BETWEEN 明确包含两端。
-- 普通 BETWEEN 不会自动交换两端
WHERE total_amount BETWEEN 500 AND 100这相当于同时要求金额不小于 500 且不大于 100,通常没有任何行能满足。用户输入上下限时,应该在进入查询前校验顺序,而不是期待数据库替你纠正。
WHERE total_amount NOT BETWEEN 100 AND 500可读成:
WHERE total_amount < 100
OR total_amount > 500如果 total_amount 为 NULL,两边比较都是 UNKNOWN,最终不会被 WHERE 保留。“不在范围内”并不会自动包含“金额未知”。若业务想把未知金额也列出来,需要明确加上 OR total_amount IS NULL。

日期条件是范围查询里最容易悄悄漏数据的地方。
需求:“查询 2026 年的所有订单。”很多人第一反应是:
SELECT order_id, order_date
FROM orders
WHERE order_date BETWEEN '2026-01-01' AND '2026-12-31';如果 order_date 是 DATETIME 或 TIMESTAMP,字符串 '2026-12-31' 通常表示 2026-12-31 00:00:00。于是 12 月 31 日凌晨之后的订单都会被漏掉。固定数据暂时没有那一天的订单,不代表这个条件在未来新增数据后仍然正确。
你也许会把上界补成:
WHERE order_date BETWEEN '2026-01-01 00:00:00'
AND '2026-12-31 23:59:59';这比前一种好,却仍然把正确性绑在字段精度上。如果列能保存微秒,23:59:59.500000 仍然可能落在上界之外。
更稳定的写法是“左闭右开”:包含本年开始时刻,不包含下一年开始时刻。
SELECT order_id, order_date, status, total_amount
FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'
ORDER BY order_date;这六条正是固定数据里的全部 2026 年订单。将来即使新增 2026-12-31 23:59:59.500000 的订单,它也仍在这个范围里;2027-01-01 00:00:00 则会明确落到下一段。连续年份可以首尾相接,不重叠,也不留缝。
例如按天查询 2026 年 6 月 18 日:
WHERE order_date >= '2026-06-18'
AND order_date < '2026-06-19'查询某个价格档 [100, 500):
WHERE unit_price >= 100
AND unit_price < 500BETWEEN 适合两端都明确包含的业务范围;左闭右开适合时间切片、连续分桶和“下一段起点”很清楚的场景。选择写法前,先问清楚边界。
精确比较要求整个字符串符合条件。要按名称的一部分查商品,可以使用 LIKE。
SELECT product_id, product_name
FROM products
WHERE product_name LIKE '%杯%';在 LIKE 模式里:
想找名称中任意位置包含“收纳”的商品,需要两边都写 %:
SELECT product_id, product_name
FROM products
WHERE product_name LIKE '%灯%';只写 LIKE '收纳' 时,没有通配符,它的效果接近精确匹配,不会自动搜索子串。
% 和 _ 要转义假设商品名真的叫“100%纯棉帆布袋”。如果直接写:
WHERE product_name LIKE '%100%%'中间那个 % 仍是通配符,无法表达“名称里必须出现一个百分号字符”。推荐显式指定一个清楚的转义字符:
SELECT product_id, product_name
FROM products
WHERE product_name LIKE '%100!%%' ESCAPE '!';!% 表示字面量百分号,模式两端未转义的 % 仍表示任意字符。
同理,搜索名称中真的含下划线的商品:
SELECT product_id, product_name
FROM products
WHERE product_name LIKE '%!_%' ESCAPE '!';显式写 ESCAPE '!' 的好处是意图清楚,也避免把正确性寄托在反斜杠设置上。
如果搜索框里输入 _,用户很可能只是想找下划线,而数据库会把它当作“任意一个字符”。应用层构造模式时,需要先转义用户文本里的转义字符本身、% 和 _,再在外侧添加业务需要的通配符;同时应使用参数绑定,不要把输入直接拼进 SQL。
SELECT customer_id, customer_name, email
FROM customers
WHERE email NOT LIKE '%@example.com';如果某位顾客的 email 是 NULL,NULL NOT LIKE ... 仍然得到 UNKNOWN,因此不会出现。若需求是“邮箱不是 example.com,或者尚未填写邮箱”,要明确写出:
WHERE email NOT LIKE '%@example.com'
OR email IS NULL
现在回到开头那几位“消失”的顾客。
SELECT customer_id, customer_name, phone
FROM customers
WHERE phone <> '13800001001';结果为:
白露和陆野的手机号是 NULL。条件没有证明它们等于苏小满的号码,也没有证明它们不等于苏小满的号码,所以两行都被 WHERE 过滤。
如果业务真正想表达“不是这个号码,未填写手机号的也算”,就必须把未知情况写出来:
SELECT customer_id, customer_name, phone
FROM customers
WHERE phone <> '13800001001'
OR phone IS NULL;错误写法:
WHERE phone = NULL正确写法:
WHERE phone IS NULL查已知城市则用:
WHERE phone IS NOT NULLIS NULL 问的是“这里是否缺少一个已知值”,普通 = 问的是“左右两个已知值是否相等”。这是两类不同的问题,所以 SQL 使用不同语法。
SELECT
customer_id,
customer_name,
phone,
phone IS NULL AS is_null,
phone = '' AS is_empty_string
FROM customers
WHERE customer_id IN (1, 3, 7)
ORDER BY customer_id;白露和陆野的 phone = '' 不是 FALSE,而是 UNKNOWN,所以结果显示 NULL。固定数据没有把空字符串当成缺失手机号;如果一列真的存了 '',那么它的 IS NULL 为 FALSE、= '' 为 TRUE。数字 0 同样是一个已知数值,不是 NULL。
“未知”与“已知为空文本”是否都允许,应该由业务模型决定。如果系统把未填写电话有时存 NULL、有时存空字符串,后续每条统计与筛选都要兼顾两种状态,查询会变得更难维护。
普通布尔逻辑只有 TRUE 和 FALSE;SQL 条件还要容纳缺失信息,因此加入 UNKNOWN。
先看 NOT:
未知的信息取反后仍然未知。你不知道白露的手机号是否等于某个号码,也同样无法断言它“不等于”这个号码。
再看 AND:
FALSE AND UNKNOWN 可以确定为 FALSE,因为只要一个条件已经不成立,整体就不可能成立。TRUE AND UNKNOWN 仍无法确定。
最后看 OR:
TRUE OR UNKNOWN 可以确定为 TRUE,因为至少已经有一边成立;FALSE OR UNKNOWN 仍然未知。

条件如下:
WHERE phone = '13800001001'
OR referrer_id = 1对白露来说:
phone = '13800001001':手机号为 NULL,所以是 UNKNOWN。referrer_id = 1:确实等于 1,所以是 TRUE。UNKNOWN OR TRUE:结果为 TRUE,白露会留下。对陆野来说:
phone = '13800001001':UNKNOWN。referrer_id = 1:推荐人也为 NULL,因此仍是 UNKNOWN。UNKNOWN OR UNKNOWN:结果为 UNKNOWN,陆野被 WHERE 过滤。这说明 NULL 并不一定让一整行消失,最终还要看它与其他条件如何组合。
在支持这些谓词的数据库中,可以直接检查一个条件属于哪种状态。MySQL 中也可以把比较结果作为列观察:
SELECT
customer_name,
phone,
phone = '13800001001' AS raw_result,
(phone = '13800001001') IS TRUE AS is_true,
(phone = '13800001001') IS FALSE AS is_false,
(phone = '13800001001') IS UNKNOWN AS is_unknown
FROM customers
WHERE customer_id IN (1, 2这是一种很好用的排错方式:把原本藏在 WHERE 里的判断搬到 SELECT 列中,逐行看它到底得到了 1、0 还是 NULL。
现在要回答一个看似简单的问题:“哪些顾客没有推荐过其他顾客?”
customers.referrer_id 保存推荐人的 customer_id。先看看有哪些推荐人编号:
SELECT referrer_id
FROM customers;结果里有 1、2、3、4,也有多个 NULL。很自然地,你可能写出:
SELECT customer_id, customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT referrer_id
FROM customers
);结果却是:零行。
不是每个人都推荐过别人,为什么一个都没有?把子查询结果想象成一个列表:
WHERE customer_id NOT IN (NULL, 1, 1, 2, NULL, 3, NULL, 4, NULL, NULL)再以 customer_id = 5 为例。NOT IN 可以展开成一串“全部不等于”:
5 <> NULL
AND 5 <> 1
AND 5 <> 2
AND 5 <> 3
AND 5 <> 4这里省略了列表中的重复值,但逻辑没有变化:5 <> 1 到 5 <> 4 都是 TRUE,5 <> NULL 却是 UNKNOWN。于是整个条件是 TRUE AND UNKNOWN,结果仍是 UNKNOWN,WHERE 不会放行。
对顾客 1、2、3、4 来说,列表里能找到相等值,所以 NOT IN 明确为 FALSE;对其他顾客来说,没有相等值,却被列表里的 NULL 拖成 UNKNOWN。最终所有人都被过滤。
SELECT customer_id, customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT referrer_id
FROM customers
WHERE referrer_id IS NOT NULL
)
ORDER BY customer_id;SELECT c.customer_id, c.customer_name
FROM customers AS c
WHERE NOT EXISTS (
SELECT 1
FROM customers AS referred
WHERE referred.referrer_id = c.customer_id
)
ORDER BY c.customer_id;这个版本直接问:“是否不存在任何一位顾客,其 referrer_id 等于当前顾客编号?”子查询里某些行的 referrer_id 是 NULL,只会让那些行无法与当前编号匹配,不会污染整个“不存在”判断。
只要 NOT IN 的右侧来自表数据,就要主动问一句:这一列现在或将来可能出现 NULL 吗?如果答案不确定,优先考虑 NOT EXISTS,或者在子查询中明确排除 NULL。

正确性永远排在第一位。但当数据从十几行增长到数百万行,同样正确的条件,速度可能相差很大。
共享表在 orders(status, order_date) 上建立了复合 B-tree 索引。下面的条件先固定索引第一列 status,再让第二列 order_date 保持原样参与范围比较:
WHERE status = '已完成'
AND order_date >= '2026-01-01'
AND order_date < '2027-01-01'数据库更容易从索引里定位“2026 年的已完成订单”对应的一段连续范围。
而下面的写法先对每行的列值调用函数:
WHERE status = '已完成'
AND DATE(order_date) = '2026-04-01'或者:
WHERE status = '已完成'
AND YEAR(order_date) = 2026它们在很多情况下会让索引里的 order_date 部分难以直接用于范围定位,因为数据库需要先计算表达式,才能判断每行是否符合。status 这个左侧前缀仍可能有用,但日期范围的优势已经被削弱。某些数据库支持函数索引或表达式索引,那属于额外设计,不能假设普通索引会自动获得同样效果。
如果未来为 product_name 建立了合适的索引,并配合相应排序规则:
WHERE product_name LIKE '白瓷%'固定前缀“白瓷”可能帮助数据库缩小搜索范围;而:
WHERE product_name LIKE '%杯%'模式一开始就是任意字符,普通 B-tree 索引通常难以直接定位起点。后者并不是“不能写”,它可能正是业务需要;只是要知道数据量大时可能需要全文检索、专用索引或其他搜索方案。
这只是“可索引写法初识”,不是性能保证。索引是否真正使用,还受数据分布、返回比例、复合索引顺序、排序规则与优化器成本估算影响。后续遇到慢查询,应使用执行计划验证,而不是只看 SQL 外观下结论。
当查询结果“少了”“多了”或直接变成零行,不要立刻重写整条 SQL。按下面的顺序缩小问题,通常更快。
SELECT customer_id, customer_name, phone, referrer_id
FROM customers
ORDER BY customer_id;不要只看应用页面上显示的“暂无”。它可能对应 NULL、空字符串、0,甚至一个真实的文本“暂无”。回到数据库值本身。
SELECT
customer_id,
customer_name,
phone,
phone <> '13800001001' AS other_phone,
phone IS NULL AS phone_missing
FROM customers
ORDER BY customer_id;这样能直接看到每行条件是 1、0 还是 NULL,比盯着 WHERE 猜测更有效。
先执行:
WHERE status IN ('已完成', '已发货')确认行数,再加:
AND total_amount >= 180最后加时间范围。哪个步骤让行数异常变化,问题通常就在哪个条件块。
把:
WHERE A OR B AND C分别试成:
WHERE A OR (B AND C)和:
WHERE (A OR B) AND C不要只问“语法能不能运行”,要问“哪一组括号才对应业务原话”。
BETWEEN 包含两端。SELECT COUNT(*) AS null_count
FROM customers
WHERE referrer_id IS NULL;如果右侧来源列能产生 NULL,先决定使用 NOT EXISTS,还是明确增加 IS NOT NULL。
SELECT order_id, status
FROM orders
WHERE order_date < '2026-01-01'
AND status = '待支付';确认命中对象与数量后,再把 SELECT 改成 UPDATE 或 DELETE。若系统支持事务,还应在合适的事务与备份策略下操作。
现在把本章的条件组合起来。
需求如下:
找出 2026 年内,状态为“已完成”或“已发货”,金额不低于 150 元的订单;按金额从高到低查看。
先拆成三个条件块:
[2026-01-01, 2027-01-01)。SELECT order_id, customer_id, order_date, status, total_amount
FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'
AND status IN ('已完成', '已发货')
AND total_amount >= 150
ORDER BY total_amount DESC;订单 111 和 114 虽然也发生在 2026 年且已经完成,但金额分别只有 99.90 元与 86.00 元,没有达到 150 元;订单 110 金额为 99.00 元,状态也只是已支付。结果中的三行都能由明确的条件解释。
如果需求变成“只看手机号已填写、并且城市不是杭州的顾客订单”,单表已经回答不了了:订单表只有 customer_id,手机号与城市都在 customers 里。
你可能已经能写出两段各自正确的条件:
-- 订单条件
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'
AND status IN ('已完成', '已发货')
AND total_amount >= 150-- 顾客条件
WHERE phone IS NOT NULL
AND city <> '杭州'接下来的问题是:怎样让一行订单找到属于它的那位顾客,再同时应用两张表上的条件?这就是下一章的主题——多表连接。连接并没有创造新的比较规则,它仍然在使用本章学过的 =、AND、NULL 与 WHERE;只是比较的两边可以来自不同的表。
下面的练习都使用本章开头的数据。不要急着展开答案,先在纸上写出会留下哪些 customer_id 或 order_id,尤其要标出你认为会得到 UNKNOWN 的位置。
SELECT order_id, status, total_amount
FROM orders
WHERE status = '已完成'
OR status = '已发货'
AND total_amount >= 180;这条查询会返回哪些订单?怎样改成“状态属于已完成或已发货,并且金额至少 180 元”?
写出查询:找出手机号不是苏小满这个号码的顾客,并且把手机号未填写的顾客也包括进来。
下面两个条件哪个更稳定?为什么?
-- A
WHERE order_date BETWEEN '2026-01-01' AND '2026-12-31'
-- B
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'写出查询:找出名称中真的包含 _ 字符的商品,而不是匹配任意一个字符。
解释下面的查询为什么可能返回零行,并写出更稳妥的版本:
SELECT customer_id, customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT referrer_id
FROM customers
);1. 关于 WHERE 与三值逻辑,哪个说法正确?
2. 关于 BETWEEN,哪个说法正确?
3. 关于 LIKE,哪个说法正确?
4. 默认逻辑优先级从高到低是什么?
WHERE 并不难写,难的是把业务语义完整翻译成条件,尤其不要把“未知”擅自当成“不是”。
本章最值得带走的几条规则是:
IS NULL 或 IS NOT NULL,不要用 = NULL、<> NULL。IN 适合表达候选集合;NOT IN 的右侧若含 NULL,结果可能全部变成 FALSE 或 UNKNOWN。BETWEEN 包含两端;日期时间切片通常更适合左闭右开的范围。% 与 _ 是通配符,搜索字面量时要使用明确的 ESCAPE。到这里,你已经能够把单表中的目标行筛得相当精确。但小满商店的真实信息分散在多张表:订单只有 customer_id,顾客城市在 customers;订单明细只有 product_id,商品名称在 products。下一章,我们会把这些表通过共同编号连接起来。到那时,你会发现连接条件本质上仍是本章学过的比较,只是它把一张表的列与另一张表的列放在了等号两边。