SQL修改笔试题备考:UPDATE、DELETE与UPSERT的条件边界和事务规范
简介面向SQL求职者与数据库开发初学者的经典面试题解析PDF覆盖查询、聚合、连接、子查询、排序等高频考点每题均附规范标准答案与可运行的SQL示例方便直接对照练习。资源共1个文件为PDF格式大小约1.38MB内容涵盖按部门统计平均工资并排除指定部门、不用聚合函数求最小值、多表连接计算每位客户收入总和、找出最高分记录、统计每科超过90分的人数等典型问题多数题目提供多种写法。已有190人学习/下载适合正在准备数据库笔试面试的求职者也可作为高校数据库课程的实操辅导材料。与零散代码合集不同这份资料按题目编号逐条展开既演示SELECT、GROUP BY、JOIN、子查询等核心语法也通过不同写法的比较解释空值处理、关联键缺失等边界条件能帮助读者理解SQL执行逻辑与常见陷阱在笔试或面试中更从容地写出可靠答案。1. 一份SQL数据库修改笔试题的备考笔记值不值得照着刷一遍很多准备后端开发面试的人容易在手写SQL环节翻车尤其是修改类笔试题。这类题目常被归为编辑/修改题考的并不是UPDATE、DELETE的语法背诵而是三样隐形的东西条件边界画得准不准、事务意识有没有、书写规不规范。市面上流传的SQL数据库经典编辑面试题docx版方便拆题改参数pdf版适合打印出来按笔试节奏计时但真正拉开分差的永远是答案里那几行条件和事务。这篇笔记我从题型拆解到避坑清单给你一条能照着练的完整路径。2. SQL修改笔试题的四种标准题型从单表UPDATE到UPSERT考的全是条件边界2.1 单表 UPDATEWHERE 才是拿分的第一道坎单表更新在修改笔试题里出镜率最高基本属于送分题但也是翻车重灾区。先看最基础的一条-- 题目employees 表中 dept_id 为 10 的员工工资统一上调 8% UPDATE employees SET salary salary * 1.08 WHERE dept_id 10;逻辑说明单表UPDATE的得分点全在WHERE。dept_id 10 圈定了目标行集SET 里对原值做乘法运算这两条缺一条都扣分。实际笔试里常见变形是入职满一年的员工或者姓名以张开头的员工本质是考察日期函数和模糊匹配怎么和UPDATE组合换汤不换药。参数说明salary * 1.08 直接在原值上运算不要先SELECT出来再改那不符合笔试对一条SQL的要求。如果题目要求保留两位小数写成 ROUND(salary * 1.08, 2) 就是加分点很多人想不到。这类题在各种java开发工程师面试题串里经常出现。后端开发平时写惯了MyBatis的updateById手写原生SQL反而生疏笔试题就是故意把框架自动拼接的SET和WHERE藏起来让你裸写SQL一写就露馅。所以哪怕你MyBatis面试题背得再熟修改笔试题也得单独练。2.2 多表关联 UPDATEJOIN、FROM、子查询三种数据库三种写法单表题区分度不够第二梯队就是多表关联更新。常见场景根据部门表的地区字段批量改员工表的补贴。-- MySQL 写法把华东地区部门的员工补贴设为 500 UPDATE employees e JOIN departments d ON e.dept_id d.dept_id SET e.allowance 500 WHERE d.region 华东;逻辑说明UPDATE ... JOIN 在MySQL里是合法语法它先把两张表按关联条件拼成中间结果再对满足WHERE的行执行SET。注意WHERE筛的是部门的region字段不是员工表字段如果写成 e.region 华东员工表里没这列直接报错。参数说明同样这道题SQL Server的写法是 UPDATE e SET e.allowance 500 FROM employees e JOIN departments d ON ...PostgreSQL则是 UPDATE employees SET allowance 500 FROM departments WHERE employees.dept_id departments.dept_id AND departments.region 华东。同一道题三种数据库三种答案这就是为什么看标准答案前要先确认题库标注的数据库环境。最稳妥的做法是在答案前写一行注释声明以下按 MySQL 8 语法后面规范部分会细说。2.3 存在即更新、不存在即插入UPSERT 的三条路编辑场景题里最高频的是保存订单、保存配置前端提交一条记录主键已存在就更新不存在就插入。笔试题会把背景写得很长但核心就是一条UPSERT。-- MySQL主键或唯一索引冲突时走更新分支 INSERT INTO product_stock (product_id, quantity, updated_at) VALUES (1001, 50, NOW()) ON DUPLICATE KEY UPDATE quantity VALUES(quantity), updated_at NOW();逻辑说明ON DUPLICATE KEY UPDATE是MySQL方言触发条件是目标表存在主键或唯一索引冲突。VALUES(quantity)引用的是INSERT里准备写入的那个值相当于把库存覆盖成最新提交的50。如果表上没有唯一约束这个语法永远不会走更新分支这也是题目里常埋的坑——题干一说product_id是主键你要能反应过来这是在给你触发条件。参数说明题目指定SQL Server时要写MERGEUSING 源表 WHEN MATCHED THEN UPDATE WHEN NOT MATCHED THEN INSERT代码量直接翻倍PostgreSQL用 INSERT ... ON CONFLICT (product_id) DO UPDATE SET ...。不要求三种全背但至少能认出哪些写法是MySQL独有、哪些是SQL Server独有。选择改错题非常喜欢把ON DUPLICATE KEY塞进SQL Server的题面里看一眼就要能识破。2.4 DELETE 题删重复数据、保留最新N条才是真正的高区分度修改笔试题里的DELETE很少直接写 DELETE FROM employees WHERE id 1。出题人更爱考删除每个部门工资最低的员工或者删除重复订单只保留最新一条这类题考的核心是先定义保留集再删除补集。-- MySQL 8删除每个部门工资最低的 1 名员工 DELETE FROM employees WHERE (dept_id, salary) IN ( SELECT dept_id, MIN(salary) FROM employees GROUP BY dept_id );逻辑说明先用分组聚合算出每个部门的最低工资再通过WHERE IN把命中的行删掉。隐藏细节是如果同部门有两个人都拿着最低工资这条SQL会把两人都删掉题目如果说只删一人就得改用窗口函数。参数说明窗口函数写法是先用 ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary) 给每个部门内部排序再删除序号大于1的行。MySQL 8开始支持窗口函数如果题目环境是5.7只能用JOIN自关联代码会长很多。笔试答题时在注释里注明基于MySQL 8窗口函数能让判卷人清楚你的环境假设这也是规范答案的一部分。3. 规范标准答案怎么写判卷人眼里 10 分和 8 分的差别3.1 手写SQL的格式规范大写关键字、分号与缩进很多人觉得笔试答案只要SQL能跑通就行但判卷人在一堆卷子里快速扫题时格式就是第一印象。我面试别人时看到关键字全小写、整条SQL挤在一行、没有分号的答案第一反应是这人不常手写SQL因为平时靠IDE插件格式化的人一上笔就原形毕露。规范的笔试题答案长这样-- 题目将 orders 表中 status 为 pending 且创建时间早于 2024-01-01 的记录status 改为 cancelled UPDATE orders SET status cancelled WHERE status pending AND create_time 2024-01-01;几条约定俗成的规范关键字统一大写表名字段名小写一眼能分清语法骨架和数据对象SET子句、WHERE子句各占一行多个条件换行且AND放行首对齐语句以分号结尾这是SQL脚本的常识好多人写着写着就丢允许注释时在SQL上方用一行注释写出解题思路比如先圈定待更新行集再改状态。这些不是SQL标准强制要求的但这就是标准答案里标准二字的含义。很多SQL数据库经典面试题题库在答案页会附一段解题思路说明因为判卷看的不只是SQL有没有写对还有你写SQL时的思考顺序。为什么手写SQL答案特别能暴露水平因为日常开发里SQL大都是ORM生成的例如MyBatis的XML里update语句的条件由标签自动拼接一旦笔试要求手写很多人连UPDATE的基本骨架都要想一会儿。修改题尤其如此因为它要同时处理列赋值和行筛选两层逻辑手一抖就漏WHERE。格式规范不是面子工程它是逼你把这两层逻辑分开的手段。3.2 标准答案不只一套MySQL / SQL Server / PostgreSQL 的方言对照标题写有规范标准答案但做过数据库sql面试题的人都知道所谓标准答案永远依赖前置条件数据库厂商、版本、表上有没有唯一索引。同一个场景三套数据库的推荐写法完全不同。笔试前建议把这张对照表过一遍场景MySQL 8SQL ServerPostgreSQL关联更新UPDATE ... JOINUPDATE ... FROMUPDATE ... FROM插入或更新INSERT ... ON DUPLICATE KEY UPDATEMERGEINSERT ... ON CONFLICT DO UPDATE更新前几行UPDATE ... ORDER BY ... LIMIT nUPDATE TOP (n) ...UPDATE ... WHERE ctid IN (子查询 LIMIT n)字符串拼接CONCAT(str1, str2)str1 str2str1 || str2表格说明判卷时如果你没声明环境通常默认你按MySQL写但题面明确说了SQL Server你还写ON DUPLICATE KEY UPDATE这空就白给了。不用把三种语法全背下来但一定要会认——改错题和选择题特别爱在方言差异上埋坑。比如给一段SQL Server的MERGE问你WHEN MATCHED分支里少了什么你至少能看出更新动作没写SET子句。怎么判断题库默认是哪个数据库看三处UPSERT的写法、分页或TOP关键词、字符串拼接符号。如果题面反复出现TOP和ROWCOUNT基本是SQL Server出现ON DUPLICATE、LIMIT、反引号基本是MySQL。笔试时遇到不指定环境的题我的习惯是在答案注释里写-- 默认按 MySQL 8 语法先把前提讲清楚比闷头写一个可能被当成方言错误的答案稳得多。3.3 答案末尾的半句事务包裹、影响行数和注释标准答案和拿满分的答案之间经常差一个动作把修改语句包进事务。比如3.1那条UPDATE满分答案会写成BEGIN; UPDATE orders SET status cancelled WHERE status pending AND create_time 2024-01-01; -- 影响行数异常时执行 ROLLBACK确认无误再 COMMIT COMMIT;逻辑说明手写SQL的笔试不会真的执行但写出BEGIN和COMMIT直接暴露你有没有生产环境改数据的习惯。有经验的判卷人看到裸UPDATE且没有WHERE基本划叉看到事务包裹至少确认你有安全意识。参数说明MySQL里可以用 ROW_COUNT() 拿到上一条语句影响行数SQL Server用 ROWCOUNTPG用 GET DIAGNOSTICS。笔试题不要求把各厂商的都写全但注释里写一句影响行数为0或异常时回滚是加分动作说明你考虑过幂等性。UPDATE语句里用到子查询时注释里标明子查询先执行、外层再更新的逻辑顺序也能拿到印象分。这些内容在标准答案页通常不会写但它们恰恰是区分背答案和真会写的地方。4. 把高频修改题在本地跑通建表、造数、8 道题的参考答案与执行验证4.1 搭建最小练习环境MySQL 8 本地库 员工表和部门表先搭一个干净的练习环境。常见做法是本地装MySQL 8社区版免费内存占用小。装完后用命令行或者图形客户端连上这里有一个血泪经验有同事用DBeaver连本地MySQL连接串没错左侧树就是不显示表十有八九是连接时选错了schema——默认连到了information_schema要在连接参数里把默认数据库指定成自己建的practice_db表才会列出来。CREATE DATABASE IF NOT EXISTS practice_db DEFAULT CHARACTER SET utf8mb4; USE practice_db; CREATE TABLE departments ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL, region VARCHAR(20) ); CREATE TABLE employees ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50) NOT NULL, dept_id INT, salary DECIMAL(10, 2), hire_date DATE, status VARCHAR(20), FOREIGN KEY (dept_id) REFERENCES departments(dept_id) );参数说明salary用DECIMAL(10,2)而非FLOAT金额计算不能引入浮点误差这也是规范答案的一种。hire_date用DATE类型涉及日期比较时比字符串严谨。dept_id允许NULL是为了复现无部门员工的边界场景后面的题会专门用到。INSERT INTO departments (dept_id, dept_name, region) VALUES (10, 研发部, 华东), (20, 市场部, 华北), (30, 销售部, 华南); INSERT INTO employees (emp_id, emp_name, dept_id, salary, hire_date, status) VALUES (1, 张伟, 10, 8000.00, 2021-03-15, active), (2, 李娜, 10, 9500.00, 2019-07-01, active), (3, 王强, 20, 7200.00, 2022-01-10, inactive), (4, 赵敏, 30, 6800.00, 2020-11-20, active), (5, 孙磊, NULL, 5000.00, 2023-05-06, active), (6, 周婷, 30, 7800.00, 2018-09-12, active);数据说明第5行dept_id是NULL专门给把无部门员工划入某部门这类题用第3行status是inactive给条件筛选题用。造数据时故意埋一两个边界值练习才有效果全是规规矩矩的正面数据反而练不出判断力。4.2 基础修改题四连单表UPDATE、条件UPDATE、NULL边界、关联UPDATE第1题将研发部dept_id10员工工资上调10%。UPDATE employees SET salary ROUND(salary * 1.10, 2) WHERE dept_id 10;逻辑说明WHERE dept_id 10 圈定目标行ROUND 对乘法结果做两位小数收尾避免浮点误差。参数说明1.10 是增长系数ROUND 的第二个参数 2 表示保留两位。验证方式SELECT emp_id, salary FROM employees WHERE dept_id 10; 张伟变成8800.00李娜变成10450.00能对上说明写对了。第2题把2022年1月1日前入职的员工状态改为active。UPDATE employees SET status active WHERE hire_date 2022-01-01;逻辑说明hire_date 2022-01-01 是日期范围条件DATE列直接和标准日期字面量比较没有走隐式转换。参数说明日期字面量务必写成年月日格式不要写20220101否则在不同数据库里的解析规则不一致。验证王强入职于2022-01-10不在范围内李娜和周婷会被改成active她们本来就是active重复执行不影响正确性只是影响行数会变少这正好体现UPDATE的幂等性。第3题把还没有部门的员工dept_id IS NULL划入研发部。UPDATE employees SET dept_id 10 WHERE dept_id IS NULL;逻辑说明判断NULL必须用IS NULL不能写 NULL这是修改题的第一陷阱。参数说明SET dept_id 10 是直接赋值没有做加减乘除注意赋值和运算在SET里的区别。验证先用 SELECT * FROM employees WHERE dept_id IS NULL; 预览执行后孙磊的dept_id从NULL变成10。第4题将华南地区region华南部门员工的工资下调5%。UPDATE employees e JOIN departments d ON e.dept_id d.dept_id SET e.salary ROUND(e.salary * 0.95, 2) WHERE d.region 华南;逻辑说明先通过JOIN把员工表和部门表按dept_id关联起来再对华南部门的员工做打折。参数说明0.95是折扣系数WHERE里筛选的是部门表的region字段如果写成 e.region 华南员工表没有region列直接报字段不存在。验证赵敏的6800变成6460.00周婷的7800变成7410.00两条结果都对就说明关联条件没写反。4.3 进阶修改题四连UPSERT、DELETE、子查询UPDATE、窗口函数DELETE第5题保存员工7的记录若emp_id7已存在则更新工资和入职日期不存在则插入。INSERT INTO employees (emp_id, emp_name, dept_id, salary, hire_date, status) VALUES (7, 陈晨, 10, 9000.00, 2024-06-01, active) ON DUPLICATE KEY UPDATE salary VALUES(salary), hire_date VALUES(hire_date);逻辑说明ON DUPLICATE KEY UPDATE在冲突时走更新分支VALUES()引用的是INSERT里准备写入的值。参数说明触发前提是emp_id有主键或唯一索引表结构里emp_id是PRIMARY KEY满足条件。验证第一次执行返回1行受影响再执行一次返回2行在MySQL客户端里看到这个数字变化就能确认两个分支都走到了。第6题删除状态为inactive的员工。DELETE FROM employees WHERE status inactive;逻辑说明删除动作只移除符合条件的行条件来自status字段。参数说明没有WHERE的DELETE会清空整表这条语句的WHERE不是可选项。验证只有王强被删。删除是修改操作里最危险的执行前先 SELECT * FROM employees WHERE status inactive; 确认行集再决定要不要跑DELETE。第7题把工资低于本部门平均工资的员工工资调整为部门平均工资。UPDATE employees e JOIN ( SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id ) t ON e.dept_id t.dept_id SET e.salary ROUND(t.avg_salary, 2) WHERE e.salary t.avg_salary;逻辑说明这里用派生表先聚合出各部门平均工资再JOIN回员工表做条件更新。参数说明AVG(salary)得到部门均值ROUND(..., 2)统一精度WHERE e.salary t.avg_salary 限定只抬高低工资行避免把所有行都改一遍。验证执行前先用等价的SELECT预览命中行执行后低于平均线的员工被抬高到平均线高于平均线的员工不受影响符合题目预期。第8题每个部门只保留工资最高的1名员工其余删除。DELETE FROM employees WHERE emp_id IN ( SELECT emp_id FROM ( SELECT emp_id, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM employees WHERE dept_id IS NOT NULL ) t WHERE rn 1 );逻辑说明内层先用窗口函数给每个部门按工资倒序编号外层只保留编号为1的行其余emp_id进入DELETE名单。参数说明PARTITION BY dept_id按部门分组ORDER BY salary DESC让工资最高的人排在第1位dept_id为NULL的员工不会出现在PARTITION里需要单独处理否则会漏删或误删。验证执行后每个部门只剩工资最高的人dept_id为NULL的员工不受影响。4.4 三道验证步骤SELECT预览、影响行数核对、事务回滚练习时不要写完SQL直接执行按三步走。第一步把UPDATE或DELETE里的WHERE原样复制到SELECT里先预览行集。比如第7题先跑 SELECT * FROM employees e JOIN (...) t ON ... WHERE e.salary t.avg_salary; 看命中哪些人确认行集对了再动UPDATE。第二步执行修改语句后核对客户端返回的受影响行数。MySQL命令行会打印 Query OK, 2 rows affected这个数字和你预览行集的条数一致才算真写对。第三步也是最重要的一步用事务包住练习SQL写错了直接回滚不用重建表。MySQL命令行里执行 BEGIN; 再执行修改语句验证无误后 COMMIT发现有错就 ROLLBACK。这个习惯练熟之后笔试答案里自然带出BEGIN/COMMIT成了加分项一举两得。5. SQL修改笔试题避坑清单5 个让面试官划叉的常见错误5.1 忘写 WHERE 条件全表 UPDATE 是最低级也最致命的事故**现象**题面要求把部门10的员工工资上调10%答案只写 UPDATE employees SET salary salary * 1.1;全表工资都被改。 **原因**把题面里的限定条件自动过滤掉了或者以为SET里写清楚涨工资就等于更新目标行。实际上没有WHERE数据库就认为你要更新全部行。 **解决**写完SQL把题面逐词对照一遍哪个表、哪些行、改成什么值。平时练习养成先SELECT COUNT(*)、再UPDATE的习惯。笔试卷上出现裸UPDATE印象分直接归零这在修改类题里是零容忍错误。判卷人会直接认定你缺乏生产环境的数据安全意识后面写得再好也难救回来。5.2 用 判断 NULL等出来的结果永远是空集**现象**题目写删除没有部门的员工答案写 DELETE FROM employees WHERE dept_id NULL;执行后影响0行你还以为符合预期。 **原因**SQL里判断空值必须用 IS NULL 或 IS NOT NULL NULL 的运算结果是UNKNOWN不匹配任何行。这和很多编程语言里 null null 的直觉正好相反是修改题第一陷阱。 **解决**凡是题面出现没有、为空、未知一律写 IS NULL不为空写 IS NOT NULL。如果考的是改错题看到 WHERE col NULL直接圈出来就是得分。NULL相关题目在数据库sql面试题里出现频率极高值得单独练透。5.3 同表子查询MySQL 报错PostgreSQL 却能跑**现象**写 DELETE FROM employees WHERE dept_id IN (SELECT dept_id FROM employees GROUP BY dept_id HAVING COUNT() 1);MySQL报错You cant specify target table employees for update in FROM clause同一个SQL在PostgreSQL里能正常执行。 **原因**MySQL实现上不允许修改目标表时在子查询里直接引用目标表PG通过子查询物化绕过了这个限制。同一段SQL换个数据库结果完全不同这就是为什么答题一定要声明环境。 **解决**MySQL里把子查询再包一层派生表SELECT dept_id FROM (SELECT dept_id FROM employees GROUP BY dept_id HAVING COUNT() 1) t。笔试时如果不确定判卷环境统一用嵌套派生表的写法在MySQL和PG下都能跑通这是最保险的兼容写法。5.4 日期和字符串的字面量比较看着对实际埋了隐式转换**现象**题目说2024年1月1日前创建的订单答案写 create_time 20240101在MySQL能跑换到PG直接报错 invalid input syntax for type timestamp。 **原因**不同数据库对字符串转日期的隐式规则不一致20240101 这种紧凑格式在PG里不被识别为标准日期格式。日期列和字符串字面量比较时数据库要做隐式转换转换规则一换就翻车。 **解决**日期比较全部写成显式的标准格式create_time 2024-01-01 00:00:00或者用 CAST(2024-01-01 AS DATE)。笔试题答案里写标准日期字面量最不容易被挑刺这也是规范标准答案里一条隐性规范。5.5 先删后插还是先查后改事务边界想不清楚一道题丢一半分**现象**题目要求把员工5的部门从NULL改为10有人写 DELETE FROM employees WHERE emp_id 5; 然后 INSERT 一条新记录还有人在多步骤场景题里先删旧数据再插新数据中间不包事务插入失败后数据直接消失。 **原因**把修改理解成编程语言里的删除重建忘了SQL里UPDATE才是改行的正解。DELETEINSERT两条语句之间有窗口期外键约束、唯一索引、下游读请求都会踩到数据不一致。 **解决**看到改先想UPDATE只有移除记录才用DELETE。所有多语句修改场景先写BEGIN最后写COMMIT中间一旦某步影响行数对不上就ROLLBACK。改错题里经常故意给先DELETE后INSERT的答案命题点就在事务边界和数据完整性上。如果你能一眼指出问题并补上事务这道题就拿稳了。6. 自测答案的硬核技巧EXPLAIN 看扫描范围事务回滚当后悔药练习时帮我最快的两个工具是EXPLAIN和ROLLBACK比参考答案还好使。先说EXPLAIN。对UPDATE或DELETE先把语句改成等价的SELECT在前面加EXPLAIN看type和rows两列。比如第4章的华南部门工资下调5%改成 EXPLAIN SELECT e.emp_id FROM employees e JOIN departments d ON e.dept_id d.dept_id WHERE d.region 华南;如果rows显示只有2行说明条件圈对了如果显示全表扫描且rows等于6说明WHERE没把行集收窄答案有问题。这个技巧在笔试里不直接考但练习时核验答案非常直观比肉眼盯着SQL猜正确率高得多。再说事务回滚。我现在所有修改类SQL的本地练习一律先BEGIN执行完看一眼受影响行数再决定COMMIT还是ROLLBACK。这个习惯是我在一次脏数据清洗里用血泪换来的当时跑一条批量UPDATE本预期影响200行结果返回2200行我下意识按了ROLLBACK查完才发现是漏了多表关联的JOIN条件一条数据都没丢。从那以后DELETE、UPDATE、DROP这类语句在我这里默认先上事务确认无误再执行。笔试卷上同样适用答案末尾补一行COMMIT题目本身是多步修改就补BEGIN这都是明明白白告诉判卷人我知道修改要可控。很多SQL面试题库的标准答案只给裸SQL你多写这一层就是10分和8分的差别。如果你手头正拿着那份SQL数据库经典编辑面试题(修改笔试题)别光背答案按这个顺序自己建库跑一遍把每个坑都踩一次印象比看十遍PDF都深。希望帮到你。本文还有配套的精品资源点击获取