三、MySQL
1. 填空题
(1)计算行数并忽略 NULL 的函数
题目:使用 ______ 函数可以计算查询结果的行数,并忽略 NULL 值。
答案:COUNT()
展开讲:
SELECT COUNT(commission_pct) FROM employees;COUNT(列名)只统计该列非 NULL 的行。
SELECT COUNT(*) FROM employees;COUNT(*)统计所有行,不忽略 NULL。
区别:
COUNT(*):行数。COUNT(列):该列非 NULL 的个数。COUNT(DISTINCT 列):该列不重复非 NULL 值的个数。
题目说“忽略 NULL”,所以是COUNT(列名)。
易错点:
COUNT(1)和COUNT(*)在 MySQL 里基本等价,都统计行数。COUNT(NULL)永远是 0,因为 NULL 不算值。
(2)分组后过滤
题目:使用 ______ 子句可以在分组后对结果进行过滤。
答案:HAVING
展开讲:
SELECT department_id, AVG(salary) FROM employees GROUP BY department_id HAVING AVG(salary) > 5000;执行顺序大致:
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BYWHERE在分组前过滤行。HAVING在分组后过滤组。
易错点:
WHERE里不能用聚合函数,比如WHERE AVG(salary) > 5000是错的。HAVING可以用聚合函数,也可以用分组列。- 能写在
WHERE里的条件尽量写WHERE,提前过滤效率更高。
(3)去重
题目:在 SELECT 语句中使用 ______ 可以确保查询返回的结果不包含重复的行。
答案:DISTINCT
展开讲:
SELECT DISTINCT department_id FROM employees;DISTINCT作用于整个 SELECT 列表,不是只作用于某一列。
SELECT DISTINCT department_id, job_id FROM employees;是“部门 + 职位”组合去重。
注意:
DISTINCT可能影响性能,因为要排序或哈希去重。
易错点:
SELECT DISTINCT a, b不是只对a去重,是对(a, b)组合去重。DISTINCT和GROUP BY在只去重时效果类似,但语义不同。
(4)从一个表选数据插入另一个表
题目:使用 ______ 语句可以从一个表中选择数据并插入到另一个表中。
答案:INSERT INTO ... SELECT
展开讲:
INSERT INTO emp_backup(employee_id, first_name) SELECT employee_id, first_name FROM employees;- 列要对应。
- 可以加
WHERE过滤。
INSERT INTO emp_backup(employee_id, first_name) SELECT employee_id, first_name FROM employees WHERE department_id = 10;注意:
- 目标表要存在。
- 自增列、默认值要处理好。
- 列顺序要对应,不写列名时按表结构顺序。
(5)DELETE 中的条件子句
题目:使用 ______ 子句可以在 DELETE 语句中删除符合特定条件的行。
答案:WHERE
展开讲:
DELETE FROM employees WHERE employee_id = 100;不加WHERE会删全表:
DELETE FROM employees;注意:
DELETE可以回滚(在事务里)。TRUNCATE TABLE更快,但不能带WHERE,一般也不能回滚。- 生产环境删数据前先
SELECT确认条件。
(6)创建表时定义主键
题目:使用 ______ 子句可以在创建表时定义主键约束。
答案:PRIMARY KEY
展开讲:
列级写法:
CREATE TABLE t ( id INT PRIMARY KEY, name VARCHAR(20) );表级写法:
CREATE TABLE t ( id INT, name VARCHAR(20), PRIMARY KEY (id) );特点:
- 主键唯一。
- 主键不能为 NULL。
- 一个表只能有一个主键。
易错点:
- 主键和唯一索引不是一回事,主键不能 NULL,唯一索引可以。
- 复合主键要用表级写法。
(7)事务中临时保存但不提交
题目:使用 ______ 语句可以将当前事务的更改临时保存,但不提交。
答案:SAVEPOINT
展开讲:
SAVEPOINT sp1;回滚到保存点:
ROLLBACK TO SAVEPOINT sp1;严格说,SAVEPOINT是设置保存点,不是“保存更改”。事务里的更改在COMMIT前都还没真正提交。按试卷常见答案写SAVEPOINT。
补充:
COMMIT:提交事务。ROLLBACK:回滚整个事务。ROLLBACK TO SAVEPOINT sp1:只回滚到保存点。
(8)设置事务隔离级别
题目:使用 ______ 语句可以设置事务的隔离级别。
答案:SET TRANSACTION ISOLATION LEVEL
展开讲:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;常见隔离级别:
READ UNCOMMITTED:读未提交,脏读可能。READ COMMITTED:读已提交,避免脏读。REPEATABLE READ:可重复读,MySQL 默认。SERIALIZABLE:串行化,最严格。
注意:
SET TRANSACTION一般作用于下一个事务;全局设置用:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;(9)创建表时设置默认值
题目:使用 ______ 子句可以在创建表时为某个列设置默认值。
答案:DEFAULT
展开讲:
CREATE TABLE t ( id INT, status INT DEFAULT 0 );插入时不指定status,就是 0。
注意:
DEFAULT NULL是默认允许 NULL。NOT NULL DEFAULT 0是常见组合。- MySQL 8 支持表达式默认值,但一般列默认值用字面量。
(10)删除表上的索引
题目:使用 ______ 语句可以删除表上的一个索引。
答案:DROP INDEX
展开讲:
DROP INDEX idx_name ON table_name;也可以:
ALTER TABLE table_name DROP INDEX idx_name;注意:
- 主键索引用
DROP PRIMARY KEY。 - 删除索引前确认没有查询依赖它。
- 删除索引可能影响查询性能,但能减少写入开销。
2. 选择题
(1)创建存储过程
题目:
a) CREATE PROCEDURE proc_name() BEGIN ... END b) CREATE FUNCTION proc_name() RETURNS ... BEGIN ... END c) CREATE TRIGGER proc_name() AFTER ... BEGIN ... END答案:a
展开讲:
存储过程:
CREATE PROCEDURE proc_name() BEGIN -- SQL 语句 END;函数:
CREATE FUNCTION func_name() RETURNS INT BEGIN RETURN 1; END;触发器:
CREATE TRIGGER trig_name AFTER INSERT ON t FOR EACH ROW BEGIN -- ... END;三者区别:
- 存储过程:用
CALL调用,可以有输出参数。 - 函数:在 SQL 里使用,必须返回一个值。
- 触发器:在 INSERT/UPDATE/DELETE 时自动执行。
易错点:
- 存储过程名后面要带括号
()。 - 函数必须写
RETURNS类型。
(2)全文搜索索引类型
题目:
a) BTREE b) HASH c) FULLTEXT d) SPATIAL答案:c
展开讲:
BTREE:最常用,范围查询、排序。HASH:等值查询快,不支持范围。FULLTEXT:全文搜索,适合文本关键词。SPATIAL:空间数据,地理坐标。
全文索引示例:
CREATE FULLTEXT INDEX idx_content ON articles(content);查询:
SELECT * FROM articles WHERE MATCH(content) AGAINST('Linux');注意:
- InnoDB 从 MySQL 5.6 开始支持 FULLTEXT。
- 中文全文搜索需要分词插件,默认对中文支持有限。
(3)ACID 的 A
题目:
a) Atomicity b) Authenticity c) Authorization d) Aggregation答案:a
展开讲:
ACID:
- Atomicity:原子性,要么全成功,要么全失败。
- Consistency:一致性,事务前后数据满足约束。
- Isolation:隔离性,并发事务互不干扰。
- Durability:持久性,提交后不丢。
A 是 Atomicity。
(4)更改现有列的数据类型
题目:
a) MODIFY TABLE table_name column_name data_type b) ALTER TABLE table_name MODIFY column_name data_type c) CHANGE TABLE table_name column_name data_type d) UPDATE TABLE table_name column_name data_type答案:b
展开讲:
ALTER TABLE table_name MODIFY column_name data_type;MODIFY只改类型,不改列名。
CHANGE可以同时改列名和类型:
ALTER TABLE table_name CHANGE old_col new_col data_type;注意:
ALTER TABLE是改表结构。UPDATE是改数据。- 改类型时要注意数据兼容,比如字符串转数字可能失败。
(5)支持事务的存储引擎
题目:
a) MyISAM b) InnoDB c) MEMORY d) CSV答案:b
展开讲:
InnoDB:支持事务、行锁、外键、崩溃恢复,MySQL 默认。MyISAM:不支持事务,表锁,读多写少老场景。MEMORY:内存表,重启丢数据。CSV:以 CSV 文件存储,功能有限。
补充:
查看表的存储引擎:
SHOW TABLE STATUS LIKE 'employees';(6)导入 SQL 脚本
题目:
a) mysqlimport -u user -p database < script.sql b) mysql -u user -p database < script.sql c) source -u user -p database script.sql d) mysqldump -u user -p database < script.sql答案:b
展开讲:
mysql -u user -p database < script.sqlmysql客户端执行 SQL。<把脚本作为标准输入。
其他:
mysqlimport:导入文本数据,不是 SQL 脚本。source:MySQL 客户端内部命令,不是 shell 命令。mysqldump:导出,不是导入。
登录后也可以:
SOURCE /path/script.sql;易错点:
mysql后面跟数据库名,不是-D也行,但习惯写库名。- 脚本里如果有
USE语句,也可以不指定库名。
(7)添加外键约束
题目:原卷选项显示乱:
1) ADD CONSTRAINT FOREIGN KEY (column_name) REFERENCES table_name(column_name) 2) ALTER TABLE table_name ADD FOREIGN KEY (column_name) REFERENCES other_table(column_name) 3) CREATE FOREIGN KEY (column_name) REFERENCES table_name(column_name) 4) INSERT FOREIGN KEY (column_name) REFERENCES table_name(column_name)答案:2
展开讲:
ALTER TABLE table_name ADD FOREIGN KEY (column_name) REFERENCES other_table(column_name);带约束名:
ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id);注意:
- 外键列和引用列类型要一致。
- 存储引擎要 InnoDB。
- 被引用列一般是主键或唯一键。
- 外键会影响写入性能,互联网场景很多公司不用物理外键。
(8)优化表
题目:
a) OPTIMIZE TABLE table_name b) ANALYZE TABLE table_name c) CHECK TABLE table_name d) REPAIR TABLE table_name答案:a
展开讲:
OPTIMIZE TABLE:整理表空间,回收碎片。ANALYZE TABLE:更新统计信息,优化器用。CHECK TABLE:检查表是否有错误。REPAIR TABLE:修复表。
题目问“优化一个表”,选OPTIMIZE TABLE。
补充:
- InnoDB 的
OPTIMIZE TABLE实际是重建表,可能锁表,大表慎用。 ANALYZE TABLE在统计信息不准时也常用。
(9)查看数据库中的所有表
题目:
a) SHOW DATABASES b) SHOW TABLES c) SHOW COLUMNS d) SHOW INDEXES答案:b
展开讲:
SHOW TABLES;看当前数据库的表。
SHOW DATABASES;看有哪些数据库。
SHOW COLUMNS FROM table_name;看列。
SHOW INDEXES FROM table_name;看索引。
易错点:
SHOW TABLES要先USE某个库,或者写SHOW TABLES FROM db_name。SHOW DATABASES不是看表。
(10)创建新用户
题目:
a) CREATE USER 'username'@'host' IDENTIFIED BY 'password' b) ADD USER 'username'@'host' IDENTIFIED BY 'password' c) INSERT INTO mysql.user (...) VALUES (...) d) NEW USER 'username'@'host' IDENTIFIED BY 'password'答案:a
展开讲:
CREATE USER 'username'@'host' IDENTIFIED BY 'password';授权:
GRANT ALL PRIVILEGES ON db.* TO 'username'@'host'; FLUSH PRIVILEGES;注意:
'username'@'host'中 host 可以是localhost、%等。- 不推荐直接往
mysql.user插数据。 - MySQL 8 默认认证插件变了,老客户端可能连不上,需要
IDENTIFIED WITH mysql_native_password BY 'password'。
3. SQL 分析题
表结构里department_name原卷 OCR 粘连成department_na,下面按department_name写。四张表关系:
employees.department_id -> departments.department_id employees.job_id -> jobs.job_id departments.location_id -> locations.location_id employees.manager_id -> employees.employee_id(自连接)(1)员工姓名、邮箱、职位名称、部门名称
题目:查询每个员工的姓名、邮箱、职位名称以及所在部门名称。
答案:
-- 员工表关联职位表和部门表 SELECT e.first_name, e.last_name, e.email, j.job_title, d.department_name FROM employees e JOIN jobs j ON e.job_id = j.job_id JOIN departments d ON e.department_id = d.department_id;展开讲:
employees别名e。jobs别名j,通过job_id关联。departments别名d,通过department_id关联。
用JOIN默认是INNER JOIN,只返回能关联上的行。如果员工没有部门,就不会出现。题目说“每个员工”,严格说可以用LEFT JOIN保留没有部门的员工:
FROM employees e LEFT JOIN jobs j ON e.job_id = j.job_id LEFT JOIN departments d ON e.department_id = d.department_id;但考试一般写内连接即可。
易错点:
- 多表连接时字段名要加表别名,避免歧义。
first_name和last_name是两列,题目说“姓名”要都查出来。
(2)月薪最高的员工
题目:查询月薪最高的员工的姓名、职位名称以及月薪。
答案:
-- 先子查询找出最高月薪,再拿这个值去筛员工 SELECT e.first_name, e.last_name, j.job_title, e.salary FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.salary = (SELECT MAX(salary) FROM employees);展开讲:
子查询(SELECT MAX(salary) FROM employees)返回一个值,外层用=比较。
如果有多个人并列最高,这条会全部返回。
另一种写法:
SELECT e.first_name, e.last_name, j.job_title, e.salary FROM employees e JOIN jobs j ON e.job_id = j.job_id ORDER BY e.salary DESC LIMIT 1;这种只返回一个,并列时可能漏人。子查询写法更稳。
易错点:
- 子查询返回多行时不能用
=,要用IN。 MAX忽略 NULL,如果全是 NULL 返回 NULL。
(3)每个部门的平均月薪
题目:查询每个部门的平均月薪。
答案:
-- 按部门分组求平均工资 SELECT d.department_name, AVG(e.salary) AS avg_salary FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_id, d.department_name;展开讲:
LEFT JOIN保留没有员工的部门,平均值为 NULL。GROUP BY按部门分组。AVG(e.salary)自动忽略 NULL。
GROUP BY里带上d.department_name是为了兼容ONLY_FULL_GROUP_BY模式。
易错点:
GROUP BY只写department_id在某些模式下会报错,因为department_name不在聚合函数里。- 想保留没员工的部门用
LEFT JOIN,只看有员工的部门用JOIN。
(4)月薪高于 5000 的员工
题目:查询部门中员工月薪高于 5000 的所有员工的姓名、月薪以及所在部门名称。
答案:
-- 直接 where 过滤 salary SELECT e.first_name, e.last_name, e.salary, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE e.salary > 5000;展开讲:
题目里“部门中”只是描述,不是要求按部门分组,直接过滤工资即可。
如果要用LEFT JOIN保留没部门的员工,把JOIN换成LEFT JOIN。
易错点:
WHERE在GROUP BY之前执行,这里不需要分组。- 工资是
double,比较时注意精度,一般没问题。
(5)奖金率最高的员工
题目:查询奖金率最高的员工的姓名、职位名称以及奖金率。
答案:
-- 奖金率最高,注意 commission_pct 可能为 NULL SELECT e.first_name, e.last_name, j.job_title, e.commission_pct FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.commission_pct = (SELECT MAX(commission_pct) FROM employees);展开讲:
MAX(commission_pct)会忽略 NULL。
如果所有人都没有奖金率,子查询返回 NULL,= NULL不成立,结果为空。要处理可以用IS NULL判断,但题目一般不考虑。
易错点:
commission_pct是奖金率,不是奖金金额。- NULL 参与比较要用
IS NULL,不能用=。
(6)每个职位的最高和最低月薪
题目:查询每个职位的最高月薪和最低月薪。
答案:
-- 按职位分组,取 max 和 min SELECT j.job_title, MAX(e.salary) AS max_salary, MIN(e.salary) AS min_salary FROM jobs j LEFT JOIN employees e ON j.job_id = e.job_id GROUP BY j.job_id, j.job_title;展开讲:
用jobs左连接employees,没有员工的职位也会出现,MAX/MIN为 NULL。
如果只关心有员工的职位,用JOIN即可。
易错点:
MAX和MIN是聚合函数,必须配合GROUP BY。- 没员工的职位
MAX/MIN是 NULL,不是 0。
(7)入职早于 2000 年
题目:查询入职时间早于 2000 年的所有员工的姓名、入职日期以及职位名称。
答案:
-- hiredate 是 datetime,直接和 '2000-01-01' 比较 SELECT e.first_name, e.last_name, e.hiredate, j.job_title FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.hiredate < '2000-01-01';展开讲:
hiredate < '2000-01-01'表示 2000 年之前。
如果包含 2000 年当年,用:
WHERE e.hiredate < '2001-01-01'或者:
WHERE YEAR(e.hiredate) < 2000但YEAR()会导致索引失效,数据量大时少用。
易错点:
hiredate是datetime,字符串比较会隐式转换。BETWEEN包含边界,注意是否需要包含 2000-01-01。
(8)每个部门员工人数
题目:查询每个部门的员工人数。
答案:
-- count(e.employee_id) 只统计有员工的行 SELECT d.department_name, COUNT(e.employee_id) AS emp_count FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_id, d.department_name;展开讲:
COUNT(e.employee_id)只统计非 NULL,没员工的部门得到 0。
如果用COUNT(*),左连接后没员工的部门也会有一行 NULL,会被算成 1,这是常见坑。
易错点:
COUNT(*)和COUNT(列)在左连接时结果可能不同。- 统计人数用
COUNT(e.employee_id)更稳。
(9)每个部门月薪最高的员工
题目:查询在每个部门中月薪最高的员工的姓名、月薪以及部门名称。
答案:
-- 用部门 + 最高工资作为条件,找出每个部门工资最高的员工 SELECT e.first_name, e.last_name, e.salary, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE (e.department_id, e.salary) IN ( SELECT department_id, MAX(salary) FROM employees GROUP BY department_id );展开讲:
子查询按部门求最高工资,外层用(department_id, salary)元组匹配。
并列最高会全部返回。
MySQL 8 窗口函数写法:
SELECT first_name, last_name, salary, department_name FROM ( SELECT e.first_name, e.last_name, e.salary, d.department_name, ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS rn FROM employees e JOIN departments d ON e.department_id = d.department_id ) t WHERE rn = 1;考试写元组子查询即可。
易错点:
- 元组
IN要求两边字段顺序一致。 - 如果部门有多个最高工资并列,元组写法会全部返回;
ROW_NUMBER只返回一个,要用RANK才能并列。
(10)月薪超过部门平均月薪
题目:查询月薪超过部门平均月薪的员工的姓名、月薪以及所在部门名称。
答案:
-- 相关子查询:每个员工拿自己部门平均工资比较 SELECT e.first_name, e.last_name, e.salary, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE e.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e2.department_id = e.department_id );展开讲:
这是相关子查询:内层e2.department_id = e.department_id引用了外层当前行的部门。
每个员工都会执行一次子查询,算自己部门的平均工资。
注意:
- 别名要区分,内层用
e2。 - 部门平均工资可能为 NULL,比较不成立。
易错点:
- 相关子查询性能一般,数据量大时可以用派生表 join 优化。
- 子查询里不能直接用外层别名以外的同名列,要加表别名。
(11)每个职位员工人数
题目:查询每个职位的员工人数。
答案:
-- 按职位分组统计人数 SELECT j.job_title, COUNT(e.employee_id) AS emp_count FROM jobs j LEFT JOIN employees e ON j.job_id = e.job_id GROUP BY j.job_id, j.job_title;展开讲:
和部门人数类似,用jobs左连接,没人的职位显示 0。
易错点:
- 职位表是
jobs,员工表通过job_id关联。 COUNT(e.employee_id)不会把没人的职位算成 1。
(12)每个部门最高和最低月薪
题目:查询每个部门的最高月薪和最低月薪。
答案:
-- 部门分组后取 max/min SELECT d.department_name, MAX(e.salary) AS max_salary, MIN(e.salary) AS min_salary FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_id, d.department_name;展开讲:
MAX/MIN忽略 NULL,没员工的部门为 NULL。
易错点:
- 和第(6)题结构一样,只是分组字段从职位换成部门。
GROUP BY要带上department_name避免ONLY_FULL_GROUP_BY报错。
(13)员工及其上级领导姓名
题目:查询每个员工的姓名、邮箱、职位名称以及他们的上级领导的姓名。
答案:
-- employees 表自连接,m 表示 manager SELECT e.first_name, e.last_name, e.email, j.job_title, m.first_name AS manager_first_name, m.last_name AS manager_last_name FROM employees e JOIN jobs j ON e.job_id = j.job_id LEFT JOIN employees m ON e.manager_id = m.employee_id;展开讲:
e是员工。m是领导,通过e.manager_id = m.employee_id关联。- 用
LEFT JOIN保留没有领导的员工。
如果写JOIN,没有领导的员工(比如大老板)就查不出来。
易错点:
- 自连接必须用不同别名。
manager_id可能为 NULL,必须用LEFT JOIN。
(14)每个部门平均奖金率
题目:查询每个部门的员工平均奖金率。
答案:
-- 奖金率是 commission_pct,按部门求平均 SELECT d.department_name, AVG(e.commission_pct) AS avg_commission FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_id, d.department_name;展开讲:
AVG忽略 NULL。如果部门里只有部分人有奖金率,平均的是有值的那些。
如果想把 NULL 当 0 算,用:
AVG(IFNULL(e.commission_pct, 0))但题目一般按默认行为。
易错点:
AVG和COUNT一样忽略 NULL。- 奖金率是比例,不是金额,字段是
commission_pct。
(15)每个城市员工人数
题目:查询每个城市的员工人数。
答案:
-- locations -> departments -> employees 三表关联 SELECT l.city, COUNT(e.employee_id) AS emp_count FROM locations l LEFT JOIN departments d ON l.location_id = d.location_id LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY l.location_id, l.city;展开讲:
关系链:
locations.location_id <- departments.location_id departments.department_id <- employees.department_id用LEFT JOIN保留没有员工的城市,人数为 0。
易错点:
- 三表连接顺序要从
locations开始,再连departments,再连employees。 city可能重复,按location_id, city分组更准。
(16)每个部门职位种类数
题目:查询每个部门的职位种类数。
答案:
-- count(distinct job_id) 统计不重复的职位数 SELECT d.department_name, COUNT(DISTINCT e.job_id) AS job_kinds FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_id, d.department_name;展开讲:
COUNT(DISTINCT e.job_id)去重统计职位。
如果不用DISTINCT,统计的是员工人数,不是职位种类数。
易错点:
- 一个部门有多个员工同一职位,只算一种。
COUNT(DISTINCT ...)忽略 NULL。
(17)工资高于其职位平均工资
题目:查询工资高于其职位平均工资的员工姓名、职位名称以及月薪。
答案:
-- 相关子查询:按员工自己的 job_id 算该职位平均工资 SELECT e.first_name, e.last_name, j.job_title, e.salary FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e2.job_id = e.job_id );展开讲:
和第(10)题类似,只是内层按job_id分组,不是department_id。
内层e2.job_id = e.job_id引用外层当前员工的职位。
易错点:
- 相关子查询每行执行一次,数据量大时慢。
- 可以用
JOIN派生表先算每个职位平均工资再比较,性能更好。
(18)每个国家员工人数
题目:查询每个国家的员工人数。
答案:
-- locations 里有 country_id,按国家统计 SELECT l.country_id, COUNT(e.employee_id) AS emp_count FROM locations l LEFT JOIN departments d ON l.location_id = d.location_id LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY l.country_id;展开讲:
locations表有country_id,按国家分组。
如果还有国家名称表,可以再关联,但试卷只给了四张表,所以显示country_id。
易错点:
country_id是国家编号,不是国家名。- 按国家统计要经过
locations -> departments -> employees三表。
(19)没有领导的员工
题目:查询没有领导的员工的姓名以及职位名称。
答案:
-- manager_id 为 NULL 表示没有上级 SELECT e.first_name, e.last_name, j.job_title FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.manager_id IS NULL;展开讲:
manager_id IS NULL判断没有上级。
不能用= NULL,SQL 里 NULL 比较要用IS NULL。
易错点:
manager_id = NULL永远不成立。IS NULL和IS NOT NULL是专门判断 NULL 的。
(20)job_id 为 IT_PROG 的员工
题目:查询 job_id 为 "IT_PROG" 的员工的姓名、职位名称以及月薪。
答案:
-- 直接按 job_id 过滤 SELECT e.first_name, e.last_name, j.job_title, e.salary FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE e.job_id = 'IT_PROG';展开讲:
job_id是字符串,要用单引号。
关联jobs表是为了拿job_title。
注意:
MySQL 默认排序规则可能不区分大小写,'it_prog'也能匹配;如果要求严格区分,用BINARY或改排序规则。
易错点:
job_id是varchar,不是数字,别写成WHERE job_id = IT_PROG。- 字符串比较可能受排序规则影响。