在带数据库实训的那些日子里我反复见到这样一幕学生能照着示例敲出SELECT * FROM student但一旦要求写成“查询成绩高于80且作业缺交次数不超过2次的学生”就开始犯迷糊。卡壳的原因真不在 SELECT 本身而在两个最容易被低估的零件——算术运算符和比较运算符。单表数据记录查询看似基础恰恰是这两个运算符把 SQL 从“抄句子”变成了“算逻辑”。这篇分享就围绕“单表数据记录查询 算术运算符 比较运算符”这个实训项目展开把设计思路、操作步骤、常见问题一次性讲透。无论你是刚开始学 SQL 的新手还是准备给学生布置实训的带教者都可以拿这份总结当一份能照着复现的参考。1. 项目思路拆解单表查询到底在练什么1.1 这个项目背后的三层学习目标提到“单表数据记录查询”不少新手的反应是不就是一个 SELECT 吗确实从语句形态看它比多表连接、子查询简单得多。但请注意项目标题里还有后半句——算术运算符和比较运算符。这说明它要考的并不是“会不会拼写 SELECT”而是“会不会用运算符构造查询条件”。我在带班时习惯把这个项目目标拆成三层第一层能正确写出单表查询语句知道 SELECT、FROM、WHERE、ORDER BY 分别承担什么职责第二层理解算术运算符和比较运算符在 SELECT 子句、WHERE 子句中的生效范围和优先级第三层能把现实问题翻译成运算符表达式比如“平时分乘以0.3再与卷面分相加”应该写成ROUND(score * 0.3, 2)还是直接在括号里做运算。很多同学一上来就盯着 SQL 语句本身却忽略了“运算符位置”这个关键点。SELECT score 5 FROM student和SELECT score FROM student WHERE score 5 90虽然都用了加法但前者是改变显示结果后者是改变筛选逻辑两者在实际业务里的含义完全不是一回事。这个项目的核心价值就是帮你把“在哪一层做运算”这件事理清楚。1.2 为什么拿单表场景练运算符最合适有人会问既然迟早要学多表查询为什么不直接上 JOIN我的看法是单表场景恰恰是观察运算符行为最干净的实验场。多表查询里你要先判断字段属于哪张表、连接条件是否成立、笛卡尔积是否失控干扰项太多。单表查询只有一张表的数据流动你写WHERE homework attendance时能立刻通过结果集推断出运算符的执行逻辑不需要考虑外键映射关系。打个比方学算术运算符和比较运算符就像是学“加减乘除和大小比较”单表就是一张白纸让你专心练口算多表则是应用题既考口算又考阅读理解。先把口算练扎实后面读应用题自然不慌。也正因如此我强烈建议新手不要跳过单表查询直接练 JOIN。我自己带过的学员里凡是单表条件查询写得磕磕绊绊的人到多表阶段几乎都会陷入同一个困局WHERE 条件里连接条件写错、字段归属不明确、优先级问题被放大十倍。反过来单表练得熟练的人遇到多表时往往只需要额外关注“连接条件”一个新概念就行了。1.3 模拟项目X的整体结构设计既然是“项目7_1”说明它通常是一系列实训项目中的一个环节。我常用的模拟项目X是给学员准备的一个学生信息管理库围绕它设计了三个表student、course、score。虽然本节只操作 score 单表但表结构从一开始就按多表联动的思路设计方便后续项目扩展。score 表的核心字段有这么几个id主键流水号student_id学生编号course_id课程编号total_score总成绩DECIMAL(5,2) 类型homework_count作业提交次数attendance_count出勤次数is_pass是否通过用 0 和 1 表示。为什么用DECIMAL(5,2)而不是 FLOAT因为成绩通常需要保留两位小数DECIMAL 是精确数值类型不会出现 0.1 0.2 不等于 0.3 的浮点误差。这一点在实训中特别值得提醒很多同学到后面学 GROUP BY 做统计时遇到求和值奇怪回头才发现是字段类型一开始就选错了。2. 核心细节解析与实操要点2.1 算术运算符不只是“加减乘除”标准 SQL 里的算术运算符包括、-、*、/、%取模。在单表查询中它们有两个主战场一个是 SELECT 子句用于对查询结果做列运算另一个是 WHERE 子句用于构造比较条件。先说 SELECT 子句里的用法。假设我们想给每个学生的总成绩加 5 分作为平时奖励分语句可以写成SELECT student_id, total_score, total_score 5 AS bonus_score FROM score;这里的AS bonus_score是给计算结果起别名。新手经常忽略的是加了别名之后外层查询如果有的话不能直接引用别名这是另一个常见的坑后续会专门讲。再说 WHERE 子句里的用法。假设我们想找“作业次数少于出勤次数一半”的学生因为这种情况说明出勤了却经常不交作业很可疑SELECT student_id FROM score WHERE homework_count attendance_count / 2;这里/运算符的作用是把出勤次数折半再拿作业次数和它比大小。有些学员会写成homework_count / attendance_count 0.5逻辑上也对但注意当 attendance_count 为 0 时会出现除零错误所以更稳妥的写法是像前一条语句那样把除数放在条件右侧。取模运算符%在单表查询里同样有妙用。比如我们要给学号尾数为偶数的学生做某个活动标记SELECT student_id FROM score WHERE student_id % 2 0;这个操作虽然简单却是理解“模运算”与“等值判断”结合的好例子。很多学校在做赛制分组、周期排班、数据抽样时都会用到取模思路。2.2 比较运算符家族从等值到范围比较运算符是 WHERE 子句的灵魂、!或、、、、还有一组容易被忽略的特殊成员BETWEEN AND、IN、LIKE、IS NULL。正常情况下大家都会用和但选哪种组合却有讲究。比如“成绩在 80 到 90 之间”有人写total_score 80 AND total_score 90有人写total_score BETWEEN 80 AND 90。两者结果一致但 BETWEEN 的边界是包含的这一点必须讲清楚。如果需求变成“80 到 90 不含端点”那 BETWEEN 就不适用了。再看IN的用法。要查课程编号为 1、3、5 的记录SELECT * FROM score WHERE course_id IN (1, 3, 5);它等价于course_id 1 OR course_id 3 OR course_id 5。IN 的可读性更好但很多初学者会误以为 IN 后面可以接一个“运算表达式”比如IN (12, 3*3)这在多数数据库里并不推荐因为可读性差且依赖数据库实现。稳妥做法是直接写常量集合。LIKE主要用于字符串模糊匹配比如查学生姓名带“林”字的记录。单表查询里若没有关联字符串表也可以对课程名做模糊匹配用%和_通配符。需要强调的是%代表任意长度字符串_代表单个字符这两个符号的使用频率在实训中极高后面实操环节会展示具体例子。2.3 NULL比较运算符的盲区这是整个项目里最容易阴沟翻船的地方。NULL 在 SQL 中表示“未知”它不是数值 0也不是空字符串而是一个“不确定”的状态。正因如此total_score NULL永远不成立total_score ! NULL也永远不成立NULL 80同样不成立因为“未知”和任何值比较结果都是未知。判断某个字段是否为 NULL只能用IS NULL或IS NOT NULLSELECT student_id FROM score WHERE total_score IS NULL;这个细节几乎每年都会出现大量错误。有个学员曾经写WHERE total_score NULL结果查出 0 条记录他以为是数据问题其实是把“未知”当成了普通值去比较。理解 NULL 的传播性是理解后续连接查询、聚合函数的重要前提所以在这个阶段必须讲透。3. 完整实操过程从建表到复杂查询3.1 准备阶段建表与造数据下面给出可直接运行的示例为了方便起见这里以 MySQL 为例但语法在其他主流数据库上略作调整即可复用。CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, total_score DECIMAL(5,2), homework_count INT DEFAULT 0, attendance_count INT DEFAULT 0, is_pass TINYINT DEFAULT 0 );然后插入一组测试数据注意特意包含几条 NULL 和边界值方便后面观察运算符行为INSERT INTO score (student_id, course_id, total_score, homework_count, attendance_count, is_pass) VALUES (101, 1, 92.50, 10, 10, 1), (102, 1, 78.00, 8, 9, 0), (103, 1, 85.00, 7, 9, 1), (104, 2, 45.50, 5, 6, 0), (105, 2, NULL, 9, 10, 0), (106, 3, 88.00, 6, 10, 1), (107, 3, 65.00, 4, 8, 0), (108, 1, 73.50, 8, 8, 1);注意第 7 条记录homework_count为 4第 6 条记录total_score为 NULL。这些数据不是随手写的而是刻意为了覆盖“边界条件”“NULL 值”“运算结果超界”等场景。3.2 第一个查询纯算术运算的 SELECT我们要查每名学生的“成绩平均分转换分数”假设该课程总评由卷面成绩占70%和平时作业分占30%组成作业分按照作业次数和满分 100 分折算。简化起见直接算一个模拟的加权分SELECT student_id, total_score * 0.7 homework_count * 3 AS weighted_score FROM score;这里我把作业次数乘以 3 当作作业分近似值纯粹是为了演示乘法。执行后会发现id 为 105 的那条记录加权分是 NULL因为 total_score 为 NULL任何包含 NULL 的算术表达式结果都是 NULL。这一点非常值得在练习后复盘如果你不想让计算结果“被 NULL 传染”就得提前用IFNULL(total_score, 0)处理。3.3 带 WHERE 的比较运算符查询接下来我们要筛出“成绩达到 80 分并且作业提交次数不少于 8 次”的记录SELECT student_id, total_score, homework_count FROM score WHERE total_score 80 AND homework_count 8;结果会排除成绩为 NULL 的记录。初学者可能觉得奇怪NULL 不是“未知”吗未知和“ 80”比较不成立所以确实不会出现在结果里。这也是我建议在测试数据里加入一条 NULL 成绩的原因只有看到结果才知道比较运算符对 NULL 的真实反应。再看一个BETWEEN的例子查成绩在 70 到 85 之间含端点的记录SELECT student_id, total_score FROM score WHERE total_score BETWEEN 70 AND 85;执行后可以看到 108 号学生的 73.50 和 103 号学生的 85.00 都被选中。这里提醒一句BETWEEN 的先后顺序必须是“小值 AND 大值”写成BETWEEN 85 AND 70虽然不会报语法错但结果永远是空集因为语法上不检查逻辑合理性。3.4 运算符组合和优先级实战现在进入本项目的核心进阶同时使用算术和比较运算符。需求是“找出作业次数低于出勤次数 60% 的未通过学生”。翻译成运算符表达式就是homework_count attendance_count * 0.6同时is_pass 0SELECT student_id, homework_count, attendance_count, is_pass FROM score WHERE homework_count attendance_count * 0.6 AND is_pass 0;这里有一个典型陷阱如果不加括号把条件写成homework_count attendance_count * 0.6 AND is_pass 0 AND total_score 5 70逻辑就会变得很难维护。在 SQL 中运算符优先级从高到低的规律是括号 算术运算 比较运算 NOT AND OR。这和我们小学学“先乘除后加减”的道理类似但多了“先 AND 后 OR”这条规则。举一个优先级导致错误的实例。有学员写SELECT * FROM score WHERE is_pass 0 OR total_score 80 AND attendance_count 8;他原本想表达“筛选未通过的学生或者成绩大于 80 且出勤好的学生”。因为 AND 优先级高于 OR这条语句实际含义是“筛选出is_pass 0的全部学生以及成绩大于 80 且出勤大于等于 8 的学生”结果范围比他预想的大。如果想强制先算 OR必须写成WHERE (is_pass 0 OR total_score 80) AND attendance_count 8;这个细节既是考试高频点也是实际开发中排查数据异常的一把钥匙。我自己排查线上 SQL 时第一眼永远是看 WHERE 条件里有没有 AND/OR 混用却不用括号经验里一半的“查询结果数量不对”都跟它有关。3.5 给查询排序ORDER BY 与运算符的结合单表查询不只有过滤还有排序。用 ORDER BY 对计算结果排序也很常见。比如按“总成绩加平时奖励分后的值”从高到低排列SELECT student_id, total_score 5 AS adjusted_score FROM score WHERE total_score IS NOT NULL ORDER BY total_score 5 DESC;注意 ORDER BY 子句里可以重新写运算表达式也可以直接使用别名例如ORDER BY adjusted_score DESC。两种写法大多数数据库都支持但如果表达式比较长建议使用别名可读性更好。这里再次提醒WHERE 子句里不能使用 SELECT 中定义的别名比如上面这条语句如果改成WHERE adjusted_score 80在标准 SQL 里是不合法的那是另一种容易踩的坑。4. 常见问题与排查技巧实录4.1 运算符优先级导致的“数据莫名其妙变多”这种情况我在实训中见过太多次。通常学员的反馈是明明逻辑是对的怎么查出来的行数比预想多很多这时我会先让他检查 AND 和 OR 的排列顺序必要时给每个条件都加上括号。宁可多写几个括号也不要依赖记忆运算符优先级。举个例子在带班时有人为了查“成绩大于 90 或作业缺交”的学生写了SELECT student_id FROM score WHERE total_score 90 OR homework_count 8 AND attendance_count 8;实际结果把出勤小于 8 的所有人全查出来了因为他漏掉了对成绩条件的括号包裹。处理这种问题的排查技巧只有一条把 SQL 按条件拆解成多个“只带一个判断”的小语句逐一验证结果再拼接起来。拆开执行后很快就能定位是哪两个条件的组合逻辑出了偏差。4.2 NULL 造成的“查不到数据”“为什么我明明把条件写成total_score 80却查不到总成绩为空的记录”这个问题的根因就是 NULL 不能参与普通比较运算。NULL 80的结果是“未知”而 WHERE 子句只保留结果为真的记录所以 NULL 成绩的记录会被过滤掉。解决办法是明确地把“NULL 是否属于筛选范围”考虑进去SELECT student_id FROM score WHERE (total_score 80 OR total_score IS NULL);在真实项目中很多报表执行出来数据比预期少常常就是某个可空字段的 NULL 记录被意外排除。做数据核查时最有效的方法是用COUNT(*)对比COUNT(total_score)如果两个计数不一致说明存在 NULL 记录然后再决定是否需要校正。4.3 除数和数据类型的坑运算中除以 0 或者整数除法结果异常是另一类高发问题。在 MySQL 中10 / 4的结果是 2.5因为 MySQL 的默认行为是浮点数除法但在 SQL Server 中如果两个操作数都是整数10 / 4会得到 2因为整数除法会截断小数。这种差异非常折磨人尤其是从一种数据库换到另一种数据库做实训时。解决办法有两种一是明确把小数的参与放进去比如WHERE homework_count * 1.0 / attendance_count 0.5二是提前做好字段类型转换使用CAST(homework_count AS DECIMAL(5,2))。无论哪种都要记住宁可多用一次转换也不要依赖数据库默认行为。关于除零我建议在涉及时用NULLIF或 CASE 先判断分母SELECT student_id, CASE WHEN attendance_count 0 THEN NULL ELSE homework_count / attendance_count END AS hw_ratio FROM score;这里顺便解释NULLIF(attendance_count, 0)的常见用法如果 attendance_count 为 0则返回 NULL从而避免除零错误。不过 CASE 写法更直观教学时更容易讲清楚。4.4 类型不一致引发的隐式转换单表查询里还经常出现字符串和数值混着比较的情况。比如某表中类型为 VARCHAR 的学号字段和数值类型常量比较时数据库会做隐式转换。这种做法有时会把索引“吃掉”导致慢查询而且结果还可能出乎意料数字 105 和字符串 105 比较虽然等于但语义上完全是两码事。在实际操作中遇到这类情况我通用的建议是遵循“类型对类型”原则比较时把字段类型先说明白不要依赖数据库隐式处理。你可以在字段旁写清楚类型注释或者在查询前用CAST(字段 AS 目标类型)显式转换。虽说单表项目里很少出现性能瓶颈但培养这种习惯对日后写大查询非常有帮助。4.5 高频率出错点速查表为了便于大家自查我把自己这几年带实训和实际开发中踩过的坑整理成一张速查表错误类型常见写法正确思路等值判断 NULLtotal_score NULL用IS NULL不等值判断 NULLtotal_score 80查不到 NULL补OR total_score IS NULLAND/OR 优先级A OR B AND C显式加括号不靠记优先级BETWEEN 顺序写反BETWEEN 85 AND 70必须小值在大值前整数除法10 / 4结果变 2转成 DECIMAL 或乘 1.0WHERE 用别名WHERE adjusted_score 80大部分数据库不允许需重写表达式除零错误分母可能为 0 时直接除用NULLIF或 CASE 处理隐式转换VARCHAR 和数值直接比较显式CAST保持类型一致这张表其实就是一份单项查询的正确性检查清单。我在做项目评审时也会要求学生提交的 SQL 里优先排查这些条目。很多看起来“莫名其妙”的查询结果都是从这里钻出来的。5. 单表之后的学习路径扩展5.1 把单表查询能力往多表迁移单表查询不是终点它是后续学习的分水岭。学完算术运算符和比较运算符之后紧接着要面对的是 GROUP BY 聚合、HAVING 过滤、子查询、多表连接。你会发现单表阶段掌握的“运算符位置”“NULL 处理”“优先级意识”这些基本功几乎全部会被复用。举个最简单的例子多表连接里常写WHERE a.total_score b.avg_score本质还是比较运算符只是把值从常量换成了另一张表的字段。如果你在单表阶段就习惯思考“运算符左边是什么类型、右边是什么类型”到多表阶段就不会犯跨表比较不同类型字段的低级错误。5.2 给学员或自己设计验收标准如果你是在带一个实训项目我建议按可量化的标准验收而不是只看有没有写对几条 SQL。我的做法是设计 8 到 10 个查询题目每个题目绑定一个技能点用错误率统计来定位薄弱环节。比如可以设计这样几道验收题查询成绩大于平均分的学生考察子查询加比较运算符查询作业次数除以出勤次数小于 0.5 的学生考察算术运算和比较运算的组合查询成绩为空且状态为未通过的学生考察 NULL 处理查询学号是偶数且成绩在前三名区间的学生考察取模加 BETWEEN。如果学员在这些题上反复出错我一般会让他回炉单表模块尤其是把NULL和优先级两张速查表重做一遍。经验告诉我单表模块看似简单但它决定了一个人后续写复杂查询的“手感”。5.3 几个后续可以继续挖掘的方向如果你学完这个项目还想再做点有意思的练习我建议往这 3 个方向扩展一是给同一个表加时间字段练习日期函数与运算符的结合比如“查最近 30 天内有成绩记录的学员”二是把CASE WHEN应用到查询结果集里做成绩分档比如 90 分以上为优秀、60 分以下为不及格三是把单表查询结果导出到报表工具理解“查询驱动报表”这个通用链路。这些方向都不是跳跃性内容而是在吃透当前项目基础上的自然延伸。建议你按顺序手敲一遍 SQL不要复制粘贴敲的过程中你的手指会比脑子更早记住运算符的用法。6. 最后再分享一点我的实际体会踩过这么多次坑之后我现在带班也好、自己写查询也罢都坚持一条规矩任何涉及多条件组合的查询先缩进美化再加括号在 WHERE 条件里每一条 AND/OR 都单独一行。这样做的好处是不仅自己不易错别人 review 的时候也省力。你在单表阶段养成的这个习惯会一路带着你走过 GROUP BY、JOIN、窗口函数受益非常明显。还有一个小技巧值得多说一句单表查询阶段尽量养成“先查 COUNT(*)再查明细”的习惯。比如执行一个复杂条件查询之前先跑SELECT COUNT(*) FROM score WHERE ...如果数字和你的预期不一致就别急着往下看数据先回头调整运算条件。很多看似难缠的运算逻辑错误其实在计数阶段就暴露了。这个项目本身不大但它是你在 SQL 这条路上打下的第一批地基。把算术运算符和比较运算符练到条件反射后续面对任何“让人头秃”的查询你都多了一层底气。