当前位置: 首页 > news >正文

数据库系统原理实战:从经典SQL考题到高频面试题精解

1. SQL查询基础:从排序到模糊匹配

SQL查询是数据库操作中最基础也是最常用的功能。我们先来看几个简单的查询例子,这些例子在期末考试和面试中经常出现。

升序排列查询是最基础的排序操作:

SELECT * FROM afinfo ORDER BY birth;

这条语句会按照birth字段的值从小到大排列结果。在实际工作中,我们经常需要对结果进行排序,比如按照时间先后显示订单,或者按照销售额高低排列产品。

模糊查询在需要匹配部分字符串时非常有用:

SELECT * FROM afinfo WHERE name LIKE '徐%';

这里的LIKE '徐%'表示查找所有姓"徐"的人员。百分号(%)是通配符,代表任意数量的任意字符。这种查询在搜索功能中很常见,比如电商网站的商品搜索。

数据更新操作需要注意条件准确性:

UPDATE afinfo SET age=45 WHERE name="陈晓";

这个例子中,我们更新了"陈晓"的年龄为45岁。在实际操作中,一定要确保WHERE条件准确,否则可能会误改大量数据。我曾经在一次项目中就因为漏写了WHERE条件,导致整张表的数据都被更新,造成了严重的数据事故。

删除操作更要谨慎:

DELETE FROM afinfo WHERE name="王芳芳";

删除是不可逆的操作,执行前最好先做备份。在生产环境中,我们通常会采用逻辑删除(标记删除状态)而不是物理删除。

2. 多表连接查询实战

实际业务中,数据往往分散在多个表中,这就需要我们掌握多表连接查询的技巧。让我们看一个学生-社团数据库的例子。

表结构定义是理解查询的基础:

CREATE TABLE 社团 ( 编号 NUMBER(4) PRIMARY KEY, 名称 VARCHAR(10), 负责人 NUMBER(4), 办公地点 VARCHAR(20), CONSTRAINT FK_FZR FOREIGN KEY (负责人) REFERENCES 学生(学号) );

这里定义了社团表的主键和外键约束。外键确保了数据的完整性,防止出现无效的负责人学号。

视图创建可以简化复杂查询:

CREATE VIEW 社团负责人 AS SELECT 社团.编号, 名称, 负责人, 姓名, 性别 FROM 学生, 社团, 参加 WHERE 学生.学号=参加.学号 AND 社团.编号=参加.编号;

视图就像是一个虚拟表,存储了查询定义而不是数据本身。合理使用视图可以简化应用程序代码。

多表连接查询有多种写法:

-- 查询参加科协的学生信息 SELECT 学号, 姓名, 性别 FROM 学生, 社团, 参加 WHERE 学生.学号=参加.学号 AND 社团.编号=参加.编号 AND 名称='科协';

这种写法使用WHERE子句进行表连接,是SQL-92标准之前的语法。现在更推荐使用JOIN语法:

SELECT 学号, 姓名, 性别 FROM 学生 JOIN 参加 ON 学生.学号=参加.学号 JOIN 社团 ON 社团.编号=参加.编号 WHERE 名称='科协';

JOIN语法更清晰,也更容易理解各表之间的关系。

分组聚合是统计分析的基础:

SELECT 编号, COUNT(学号) FROM 参加 GROUP BY 编号;

这个查询计算每个社团的参加人数。GROUP BY子句指定分组依据,COUNT是聚合函数。其他常用聚合函数还包括SUM、AVG、MAX、MIN等。

3. 权限管理与复杂查询

权限控制是数据库安全的重要部分:

GRANT INSERT, DELETE ON 社团 TO 李平 WITH GRANT OPTION;

这条语句将社团表的插入和删除权限授予用户李平,并允许他将这些权限授予其他用户。在实际项目中,权限管理需要非常谨慎,遵循最小权限原则。

复杂条件查询需要灵活运用SQL语法:

-- 查找所有女科长 SELECT * FROM 职工 WHERE 性别='女' AND 职务='科长';

多表连接查询的两种写法

-- 查找办公室的科长姓名和地址(多表连接) SELECT 姓名, 家庭地址 FROM 职工, 部门 WHERE 职工.部门编号=部门.部门编号 AND 部门名称='办公室' AND 职务="科长"; -- 使用子查询 SELECT 姓名, 家庭地址 FROM 职工 WHERE 职务='科长' AND 部门编号 IN ( SELECT 部门编号 FROM 部门 WHERE 部门名称='办公室' );

两种写法结果相同,但执行计划可能不同。在数据量大时,性能差异会很明显。

视图的创建与使用

-- 创建健康状况为差的职工视图 CREATE VIEW VW AS SELECT * FROM 职工 WHERE 职工号 IN ( SELECT 职工号 FROM 保健 WHERE 健康状况='差' );

视图可以简化复杂查询,也可以用于权限控制,只允许用户访问视图而不是基表。

表结构修改

ALTER TABLE 保健 ADD 备注 CHAR(20);

这条语句为保健表增加了一个备注字段。在生产环境修改表结构需要谨慎,可能影响正在运行的应用程序。

4. 高级查询技巧:子查询与关系代数

关系代数与SQL的对应是理解复杂查询的关键。让我们看一个教学数据库的例子。

检索至少选修C02和C06课程的学生学号

-- 关系代数表达式 πsno(σ1=4∧2='C02'∧5='C06'(elective×elective)) -- SQL实现 SELECT FIRST.sno FROM elective FIRST, elective SECOND WHERE FIRST.sno=SECOND.sno AND FIRST.cno='C02' AND SECOND.cno='C06';

这个查询需要同一个学生选修两门指定课程,因此需要自连接elective表。

检索没有选修C06课程的学生

SELECT sname, class FROM student WHERE NOT EXISTS ( SELECT * FROM elective WHERE sno=student.sno AND cno='C06' );

NOT EXISTS是处理"不存在"条件的强大工具。这种查询在业务中很常见,比如"查找从未下单的客户"。

检索学习全部课程的学生

SELECT sname FROM student WHERE NOT EXISTS ( SELECT * FROM course WHERE NOT EXISTS ( SELECT * FROM elective WHERE sno=student.sno AND cno=course.cno ) );

这是典型的"除"操作,找出满足"对于所有课程,该学生都选修了"条件的学生。这种查询在面试中经常出现。

关系除法的另一种写法:

SELECT sname FROM student WHERE (SELECT COUNT(*) FROM elective WHERE sno=student.sno) = (SELECT COUNT(*) FROM course);

这种写法假设没有重复选课,且只关心选课数量是否等于课程总数。

视图的应用

CREATE VIEW 客户产品 AS SELECT 客户号, 产品号 FROM 订单, 订单明细 WHERE 订单明细.订单号=订单.订单号;

视图可以简化后续查询,比如查询至少购买了01号客户购买的所有产品的客户号:

SELECT 客户号 FROM 客户产品 客户产品1 WHERE NOT EXISTS ( SELECT * FROM 客户产品 客户产品2 WHERE 客户号='01' AND NOT EXISTS ( SELECT * FROM 客户产品 客户产品3 WHERE 客户产品1.客户号=客户产品3.客户号 AND 客户产品2.产品号=客户产品3.产品号 ) );

这种嵌套NOT EXISTS的写法是解决"所有-都"类问题的标准模式。

5. 综合应用:电商场景SQL实战

让我们看一个电商数据库的实战例子,包含客户、产品、订单和订单明细表。

查询一次订购产品号为02且数量大于10的客户号

SELECT 客户号 FROM 订单, 订单明细 WHERE 订单明细.订单号=订单.订单号 AND 产品号='02' AND 数量>10;

这是典型的多表连接查询,在电商后台管理系统中很常见。

按客户购买总额降序输出

SELECT 客户.客户名, SUM(金额) AS 总额 FROM 客户, 订单 WHERE 客户.客户号=订单.客户号 GROUP BY 客户.客户号 ORDER BY 总额 DESC;

这个查询使用了GROUP BY分组和SUM聚合函数,是销售分析的基础。

查询购买了特定产品的客户邮件

SELECT cust_email FROM Customers WHERE cust_id IN ( SELECT cust_id FROM Orders WHERE order_num IN ( SELECT order_num FROM OrderItems WHERE prod_id='BR01' ) );

这种嵌套子查询的写法逻辑清晰,但性能可能不如连接查询:

SELECT c.cust_email FROM OrderItems a JOIN Orders b ON a.order_num=b.order_num JOIN Customers c ON b.cust_id=c.cust_id WHERE a.prod_id='BR01';

在数据量大时,连接查询通常性能更好。

计算每个客户的总订单金额

SELECT b.cust_id, SUM(a.quantity*a.item_price) AS total_ordered FROM OrderItems a JOIN Orders b ON a.order_num=b.order_num GROUP BY cust_id ORDER BY total_ordered DESC;

这个查询计算了每个客户的消费总额,是客户价值分析的基础。

使用HAVING筛选分组结果

SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3 ORDER BY items, order_num;

HAVING类似于WHERE,但它作用于分组后的结果而不是单行记录。

6. 常见面试题解析

查找最晚入职员工是经典面试题:

SELECT * FROM employees ORDER BY hire_date DESC LIMIT 1;

这种写法简单直接,但如果有多人同一天最晚入职,只会返回其中一个。

查找薪水第二高的员工

SELECT emp_no, salary FROM salaries ORDER BY salary DESC LIMIT 1 OFFSET 1;

或者使用子查询:

SELECT emp_no, salary FROM salaries WHERE salary = ( SELECT DISTINCT salary FROM salaries ORDER BY salary DESC LIMIT 1 OFFSET 1 );

第二种写法能处理多人薪水相同的情况。

统计各部门工资记录数

SELECT dept_no, COUNT(*) FROM dept_emp JOIN salaries ON dept_emp.emp_no=salaries.emp_no GROUP BY dept_no;

这类分组统计问题在业务分析中非常常见。

使用JOIN查询找出没有分类的电影

SELECT film.film_id, film.title FROM film LEFT JOIN film_category ON film.film_id=film_category.film_id WHERE film_category.category_id IS NULL;

LEFT JOIN配合IS NULL是查找"不存在"关系的标准模式。

批量插入数据

INSERT INTO actor (actor_id, first_name, last_name) VALUES (1, 'PENELOPE', 'GUINESS'), (2, 'NICK', 'WAHLBERG'), (3, 'ED', 'CHASE');

批量插入比单条插入效率高很多,特别是在数据量大时。

创建唯一索引

CREATE UNIQUE INDEX uniq_idx_firstname ON actor(first_name);

唯一索引确保字段值不重复,同时也能提高查询速度。

7. 性能优化与最佳实践

EXISTS与IN的选择

-- 使用EXISTS SELECT * FROM employees e WHERE EXISTS ( SELECT 1 FROM titles t WHERE t.emp_no=e.emp_no AND t.title='Manager' ); -- 使用IN SELECT * FROM employees WHERE emp_no IN ( SELECT emp_no FROM titles WHERE title='Manager' );

在大多数情况下,EXISTS性能优于IN,特别是当子查询结果集大时。

避免在WHERE子句中使用函数

-- 不推荐 SELECT * FROM orders WHERE YEAR(order_date)=2023; -- 推荐 SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';

在字段上使用函数会导致索引失效。

合理使用索引

-- 创建复合索引 CREATE INDEX idx_name ON employees(last_name, first_name); -- 索引使用情况 EXPLAIN SELECT * FROM employees WHERE last_name='Smith' AND first_name='John';

复合索引的顺序很重要,应该将选择性高的字段放在前面。

分页查询优化

-- 简单分页 SELECT * FROM products ORDER BY price DESC LIMIT 10 OFFSET 20; -- 大数据量分页优化 SELECT * FROM products WHERE id > 1000 ORDER BY id LIMIT 10;

OFFSET在大数据量时性能很差,使用WHERE条件过滤更高效。

事务处理

START TRANSACTION; UPDATE accounts SET balance=balance-100 WHERE user_id=1; UPDATE accounts SET balance=balance+100 WHERE user_id=2; COMMIT;

事务确保多个操作要么全部成功,要么全部失败,保证数据一致性。

8. 实战案例:图书馆管理系统

图书馆数据库典型查询

-- 查询借阅次数最多的图书 SELECT book_id, title, COUNT(*) AS borrow_count FROM books JOIN borrow_records ON books.id=borrow_records.book_id GROUP BY book_id, title ORDER BY borrow_count DESC LIMIT 10;

查询逾期未还的图书

SELECT b.title, r.borrower_name, r.due_date FROM borrow_records r JOIN books b ON r.book_id=b.id WHERE r.return_date IS NULL AND r.due_date < CURRENT_DATE;

查询热门图书类别

SELECT c.name, COUNT(*) AS borrow_count FROM categories c JOIN books b ON c.id=b.category_id JOIN borrow_records r ON b.id=r.book_id GROUP BY c.id, c.name ORDER BY borrow_count DESC;

使用存储过程处理借书

CREATE PROCEDURE borrow_book( IN p_book_id INT, IN p_borrower_id INT, OUT p_result VARCHAR(100) ) BEGIN DECLARE available INT; SELECT COUNT(*) INTO available FROM books WHERE id=p_book_id AND status='available'; IF available > 0 THEN INSERT INTO borrow_records(book_id, borrower_id, borrow_date, due_date) VALUES(p_book_id, p_borrower_id, CURRENT_DATE, DATE_ADD(CURRENT_DATE, INTERVAL 30 DAY)); UPDATE books SET status='borrowed' WHERE id=p_book_id; SET p_result = '借书成功'; ELSE SET p_result = '图书不可借'; END IF; END;

存储过程封装了业务逻辑,可以减少网络往返,提高性能。

使用触发器记录操作日志

CREATE TRIGGER log_book_changes AFTER UPDATE ON books FOR EACH ROW BEGIN IF NEW.status != OLD.status THEN INSERT INTO book_audit_log(book_id, old_status, new_status, change_time) VALUES(NEW.id, OLD.status, NEW.status, NOW()); END IF; END;

触发器可以自动记录数据变更,用于审计追踪。

http://www.cnnetsun.cn/news/3338459.html

相关文章:

  • 3分钟掌握Umi-OCR:免费离线OCR软件的完整使用指南
  • PGSQL(PostgreSQL)数据库入门指南:核心优势、架构解析与一站式安装实践
  • 【Linux】Linux 系统编程——tree 命令的进阶用法与实战场景
  • 【零成本启动AI开发】阿里云DSW免费GPU实例实战指南
  • C语言位操作实战:嵌入式GPIO寄存器4种操作宏封装与避坑指南
  • Linux命令之dpkg:从基础安装到高级系统维护的实战指南
  • ChatGPT写短视频脚本:7天从零到日更10条,实测转化率提升217%的SOP流程
  • Horos:免费开源的macOS医学影像查看器终极指南
  • Day21-Spring事件机制:观察者模式在企业级解耦中的应用
  • ROS C++消息生成:从.msg定义到可发布订阅的完整流程
  • 模板驱动型文档自动化:从Word手工排版到工程化流水线
  • 3分钟解决GitHub下载慢:Fast-GitHub终极加速指南
  • CISP-PTE 靶场环境复现:基于Docker快速搭建5类漏洞实战平台
  • 5种高效方法:使用NoSleep实用工具实现Windows防休眠完整解决方案
  • 从数据切分到精排优化:构建高精度RAG智能导购系统的全链路实践
  • 图像缩放实战:从原理到OpenCV,剖析最近邻、双线性与双三次插值
  • 企业AI转型实战指南:从准备到落地的关键策略
  • 终极指南:如何使用Awoo Installer高效安装Switch游戏
  • 【实战指南】五大主流视频打架行为数据集评测与应用场景解析
  • Simulink 2024b 三相坐标变换仿真:从 Clarke/Park 到对称分量法的 5 种模型对比
  • 模板驱动型文档自动化:从Word填空到业务逻辑编码
  • STM32F103C8T6 + HC-SR04 超声波测距:从 2cm 到 4m 的 3 种滤波算法实测对比
  • 人脸识别系统 42(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • Claude Code 换成了 Kimi K2.5 后,我再也回不去了
  • Horos医学影像软件:macOS上免费开源的DICOM工作站完整指南
  • 光学设计误区解析:5 种 ZEMAX 像差操作数(LONA/AXCL/FCGT等)的适用场景与数值解读
  • Mythos Preview:AI自动攻防如何重塑企业安全工作流
  • PilotGo-web权限控制方案:构建安全的企业级运维管理系统
  • 深度学习代码|从零实现Multi-Head Attention:张量变换与并行计算详解
  • Windows热键侦探:3分钟快速定位谁“偷走“了你的快捷键