摘要:本文详细介绍了SQL查询中常用的IN与EXISTS运算符的区别、各种JOIN连接查询的使用场景、嵌套子查询的编写方法以及聚合函数的基本使用和嵌套应用。通过对比分析和实例演示,帮助读者深入理解这些SQL核心概念,提升查询编写和优化能力。
前言:阅读指南
本文旨在为不同层次的SQL学习者提供全面的查询优化知识体系。无论您是SQL初学者,还是希望提升查询性能的开发人员,本文都将帮助您深入理解SQL核心概念及其应用场景。
目标读者
- SQL初学者:希望系统学习IN/EXISTS、JOIN、子查询和聚合函数等基础概念
- 中级开发者:需要优化现有查询,理解不同查询方式的性能差异
- 数据库管理员:希望掌握查询优化技巧,提升数据库性能
- 数据分析师:需要编写高效的数据提取和聚合查询
先验知识要求
为了更好地理解本文内容,建议读者具备以下基础知识:
- 基本的SQL语法(SELECT、FROM、WHERE等)
- 了解关系型数据库的基本概念(表、行、列)
- 熟悉简单的数据查询操作
- 了解主键、外键等基本数据库约束概念
章节概览与学习路径
本文按照从基础到应用的逻辑顺序组织内容,建议按以下顺序阅读:
- 一、IN 与 EXISTS 的区别:深入对比两种运算符的语义、性能差异和使用场景,帮助您根据数据规模选择合适的查询方式。
- 二、连接查询(JOIN):系统讲解INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN四种连接类型,通过Mermaid流程图和实战示例直观展示不同JOIN的结果集差异。
- 三、嵌套子查询:涵盖WHERE、FROM、UPDATE、DELETE等子句中的子查询应用,提供多个实战示例和性能优化建议。
- 四、聚合函数的使用:介绍COUNT、SUM、AVG、MAX、MIN等聚合函数的基本用法,以及WHERE与HAVING子句的区别。
- 五、聚合函数的嵌套使用:深入探讨聚合函数在复杂查询中的应用,包括分组过滤和性能优化。
- 六、总结与参考资料:汇总各章节核心要点,提供官方文档和权威技术资源链接,方便进一步学习。
学习建议
- 动手实践:建议在本地数据库或在线SQL练习平台(如SQL Fiddle、DB Fiddle)中运行文中的示例代码。
- 对比分析:对于IN vs EXISTS、不同JOIN类型等对比性内容,建议创建测试数据并亲自验证结果差异。
- 性能测试:对于涉及性能优化的部分(如子查询优化),可以在自己的数据环境中进行性能测试。
- 循序渐进:如果对某个概念感到困惑,可以先跳过,继续阅读后续内容,待整体理解后再回头深入。
通过本文的学习,您将能够:
- 准确区分IN和EXISTS的使用场景,并做出性能最优的选择
- 根据业务需求选择最合适的JOIN类型
- 编写高效、可维护的嵌套子查询
- 正确使用聚合函数进行数据统计和分析
- 识别和优化常见的SQL性能瓶颈
现在,让我们开始深入学习SQL查询优化的核心知识。
一、IN 与 EXISTS 的区别
IN和EXISTS是SQL查询中常用的两种运算符,它们在语义和使用方式上有明显区别。
1.1 IN运算符
IN是一个集合运算符,语法形式为:a IN {a,c,d,s,d....}。在这个运算中,前面是一个元素,后面是一个集合,集合中的元素类型必须和前面的元素类型一致。
IN运算用在语句中时,它后面带的SELECT语句一定是选一个字段,而不是SELECT *。例如,要判断某班是否存在一个名为"小明"的学生,可以使用IN运算:
"小明" IN (SELECT sname FROM student)这样(SELECT sname FROM student)返回的是一个全班姓名的集合,IN用于判断"小明"是否为此集合中的一个数据。
1.2 EXISTS运算符
EXISTS是一个存在判断,如果后面的查询中有结果,则EXISTS为真,否则为假。例如:
EXISTS (SELECT * FROM student WHERE sname="小明")1.3 性能对比与优化
这两个函数在功能上相似,但由于优化方案的不同,通常NOT EXISTS要比NOT IN快,因为NOT EXISTS可以使用结合算法而NOT IN就不行了。而EXISTS则不如IN快,因为这时候IN可能更多的使用结合算法。
示例对比:
SELECT * FROM 表A WHERE EXISTS(SELECT * FROM 表B WHERE 表B.id=表A.id) -- 这句相当于 SELECT * FROM 表A WHERE id IN (SELECT id FROM 表B)对于表A的每一条数据,都执行SELECT * FROM 表B WHERE 表B.id=表A.id的存在性判断,如果表B中存在表A当前行相同的id,则EXISTS为真,该行显示,否则不显示。
使用建议:EXISTS适合内大外小的查询,IN适合内小外大的查询。
1.4 官方定义对比
IN:确定给定的值是否与子查询或列表中的值相匹配。
EXISTS:指定一个子查询,检测行的存在。
1.5 实例对比
比较使用EXISTS和IN的查询。以下两个查询语义类似,返回相同的信息:
-- 使用EXISTS USE pubs GO SELECT DISTINCT pub_name FROM publishers WHERE EXISTS (SELECT * FROM titles WHERE pub_id = publishers.pub_id AND type = 'business') GO-- 使用IN USE pubs GO SELECT DISTINCT pub_name FROM publishers WHERE pub_id IN (SELECT pub_id FROM titles WHERE type = 'business') GO下面是任一查询的结果集:
pub_name ---------------------------------------- Algodata Infosystems New Moon Books (2 row(s) affected)1.6 逻辑含义总结
EXISTS相当于存在量词:表示集合存在,也就是集合不为空只作用一个集合。例如EXIST P表示P不空时为真;NOT EXIST P表示P为空时为真。
IN表示一个标量和一元关系的关系。例如:s IN P表示当s与P中的某个值相等时为真;s NOT IN P表示s与P中的每一个值都不相等时为真。
二、连接查询(JOIN)
连接查询是SQL中用于从多个表中检索数据的重要技术。不同的SQL JOIN返回的结果集不一样。
SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons INNER JOIN Orders ON Persons.Id_P = Orders.Id_P ORDER BY Persons.LastName除了INNER JOIN(内连接),还可以使用其他几种连接类型:
- JOIN(INNER JOIN):如果表中有至少一个匹配,则返回行
- LEFT JOIN:即使右表中没有匹配,也从左表返回所有的行
- RIGHT JOIN:即使左表中没有匹配,也从右表返回所有的行
- FULL JOIN:只要其中一个表中存在匹配,就返回行
- 注释:INNER JOIN与JOIN是相同的
为了更直观地理解这四种JOIN类型如何连接两个表并选取结果集,下面通过Mermaid流程图展示Persons表和Orders表的连接关系:
flowchart TD subgraph Persons[Persons表] P1[Person A] P2[Person B] P3[Person C] end subgraph Orders[Orders表] O1[Order 1 Person A] O2[Order 2 Person A] O3[Order 3 Person B] end %% INNER JOIN - 只返回匹配的行 subgraph InnerJoin[INNER JOIN结果集] IJ1[Person A - Order 1] IJ2[Person A - Order 2] IJ3[Person B - Order 3] end %% LEFT JOIN - 返回左表所有行,右表匹配的显示,不匹配的显示NULL subgraph LeftJoin[LEFT JOIN结果集] LJ1[Person A - Order 1] LJ2[Person A - Order 2] LJ3[Person B - Order 3] LJ4[Person C - NULL] end %% RIGHT JOIN - 返回右表所有行,左表匹配的显示,不匹配的显示NULL subgraph RightJoin[RIGHT JOIN结果集] RJ1[Person A - Order 1] RJ2[Person A - Order 2] RJ3[Person B - Order 3] RJ4[NULL - Order 4] end %% FULL JOIN - 返回所有行,匹配的显示,不匹配的显示NULL subgraph FullJoin[FULL JOIN结果集] FJ1[Person A - Order 1] FJ2[Person A - Order 2] FJ3[Person B - Order 3] FJ4[Person C - NULL] FJ5[NULL - Order 4] end %% 连接关系展示 P1 -->|匹配| O1 P1 -->|匹配| O2 P2 -->|匹配| O3 P3 -->|无匹配| LJ4 O4[Order 4 无对应Person] -->|无匹配| RJ4 %% 结果集说明 Persons -->|INNER JOIN| InnerJoin Persons -->|LEFT JOIN| LeftJoin Orders -->|RIGHT JOIN| RightJoin Persons & Orders -->|FULL JOIN| FullJoin图例说明:
- Persons表:包含Person A、Person B、Person C三条记录
- Orders表:包含Order 1(属于Person A)、Order 2(属于Person A)、Order 3(属于Person B)、Order 4(无对应Person)四条记录
- INNER JOIN:只返回两个表都匹配的记录(Person A-Order 1、Person A-Order 2、Person B-Order 3)
- LEFT JOIN:返回左表(Persons)所有记录,右表匹配的显示订单信息,不匹配的显示NULL(Person C显示为NULL)
- RIGHT JOIN:返回右表(Orders)所有记录,左表匹配的显示人员信息,不匹配的显示NULL(Order 4显示为NULL)
- FULL JOIN:返回两个表的所有记录,匹配的显示对应信息,不匹配的显示NULL(Person C和Order 4都显示为NULL)
简而言之,就是你希望看到的结果集是怎样的。如两个表person和order连接:
- 如果你只想看到person中和order有联系的记录(即满足order.id_p = person.id的记录),其他不匹配的全不显示,就用INNER JOIN
- LEFT JOIN就是在返回INNER JOIN的结果集的基础上,返回左表person表中其余的记录(这些显示的记录行所有与order表相关的栏为空,因为它们不满足匹配关系)
- 同理,RIGHT JOIN和FULL JOIN也有类似的逻辑
2.1 实战示例:员工与部门表 JOIN 查询
为了更好地理解不同 JOIN 类型的实际效果,我们使用一个具体的员工表(employees)和部门表(departments)进行演示。
表结构与示例数据
部门表 departments:
CREATE TABLE departments ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50) ); INSERT INTO departments (dept_id, dept_name) VALUES (1, '技术部'), (2, '销售部'), (3, '人事部'), (4, '财务部');员工表 employees:
CREATE TABLE employees ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT, salary DECIMAL(10, 2) ); INSERT INTO employees (emp_id, emp_name, dept_id, salary) VALUES (101, '张三', 1, 8000.00), (102, '李四', 1, 7500.00), (103, '王五', 2, 6000.00), (104, '赵六', NULL, 5500.00), (105, '钱七', 5, 7000.00); -- 注意:部门ID 5在departments表中不存在数据说明:
- 技术部(dept_id=1)有2名员工:张三、李四
- 销售部(dept_id=2)有1名员工:王五
- 人事部(dept_id=3)和财务部(dept_id=4)没有员工
- 赵六(emp_id=104)没有分配部门(dept_id为NULL)
- 钱七(emp_id=105)分配到了不存在的部门(dept_id=5)
1. INNER JOIN(内连接)
SQL 语句:
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id = d.dept_id ORDER BY e.emp_id;查询结果集:
emp_id | emp_name | salary | dept_name -------+----------+---------+----------- 101 | 张三 | 8000.00 | 技术部 102 | 李四 | 7500.00 | 技术部 103 | 王五 | 6000.00 | 销售部 (3 rows)解释:INNER JOIN 只返回两个表中都有匹配的记录。赵六(dept_id为NULL)和钱七(dept_id=5在departments中不存在)因为没有匹配的部门记录,所以不显示。人事部和财务部因为没有员工,也不显示。
2. LEFT JOIN(左连接)
SQL 语句:
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name FROM employees e LEFT JOIN departments d ON e.dept_id = d.dept_id ORDER BY e.emp_id;查询结果集:
emp_id | emp_name | salary | dept_name -------+----------+---------+----------- 101 | 张三 | 8000.00 | 技术部 102 | 李四 | 7500.00 | 技术部 103 | 王五 | 6000.00 | 销售部 104 | 赵六 | 5500.00 | NULL 105 | 钱七 | 7000.00 | NULL (5 rows)解释:LEFT JOIN 返回左表(employees)的所有记录,右表(departments)匹配的显示部门名称,不匹配的显示NULL。赵六和钱七虽然部门不匹配,但仍然显示在结果中,对应的dept_name为NULL。
3. RIGHT JOIN(右连接)
SQL 语句:
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name FROM employees e RIGHT JOIN departments d ON e.dept_id = d.dept_id ORDER BY d.dept_id, e.emp_id;查询结果集:
emp_id | emp_name | salary | dept_name -------+----------+---------+----------- 101 | 张三 | 8000.00 | 技术部 102 | 李四 | 7500.00 | 技术部 103 | 王五 | 6000.00 | 销售部 NULL | NULL | NULL | 人事部 NULL | NULL | NULL | 财务部 (5 rows)解释:RIGHT JOIN 返回右表(departments)的所有记录,左表(employees)匹配的显示员工信息,不匹配的显示NULL。人事部和财务部虽然没有员工,但仍然显示在结果中,对应的员工信息为NULL。
4. FULL JOIN(全连接)
SQL 语句:
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name FROM employees e FULL JOIN departments d ON e.dept_id = d.dept_id ORDER BY CASE WHEN e.emp_id IS NULL THEN 1 ELSE 0 END, e.emp_id, d.dept_id;查询结果集:
emp_id | emp_name | salary | dept_name -------+----------+---------+----------- 101 | 张三 | 8000.00 | 技术部 102 | 李四 | 7500.00 | 技术部 103 | 王五 | 6000.00 | 销售部 104 | 赵六 | 5500.00 | NULL 105 | 钱七 | 7000.00 | NULL NULL | NULL | NULL | 人事部 NULL | NULL | NULL | 财务部 (7 rows)解释:FULL JOIN 返回两个表的所有记录,匹配的记录显示对应信息,不匹配的部分显示NULL。结果集包含了:
- INNER JOIN 的结果(3条匹配记录)
- LEFT JOIN 独有的结果(赵六、钱七,共2条)
- RIGHT JOIN 独有的结果(人事部、财务部,共2条)
四种 JOIN 类型对比总结
| JOIN 类型 | 返回结果 | 适用场景 | 示例结果行数 |
|---|---|---|---|
| INNER JOIN | 只返回两个表都匹配的记录 | 需要获取有关联的数据,如"有订单的客户" | 3行 |
| LEFT JOIN | 返回左表所有记录 + 右表匹配记录(不匹配为NULL) | 需要获取左表所有数据,无论右表是否有匹配,如"所有员工及其部门" | 5行 |
| RIGHT JOIN | 返回右表所有记录 + 左表匹配记录(不匹配为NULL) | 需要获取右表所有数据,无论左表是否有匹配,如"所有部门及其员工" | 5行 |
| FULL JOIN | 返回两个表所有记录(匹配的显示对应信息,不匹配为NULL) | 需要获取两个表的完整数据视图,如"员工与部门完整对应关系" | 7行 |
关键差异:
- INNER JOIN:只关心"有对应关系"的数据
- LEFT JOIN:以左表为主,右表为补充
- RIGHT JOIN:以右表为主,左表为补充
- FULL JOIN:两个表的数据都要,无论是否有对应关系
在实际应用中,LEFT JOIN 使用频率最高,因为通常我们有一个主表(如员工表),需要关联其他表(如部门表)获取补充信息,同时不希望丢失主表的任何记录。
三、嵌套子查询
嵌套子查询是指在一个查询(父查询)中嵌套另一个查询(子查询)。子查询可以出现在 SELECT、FROM、WHERE、HAVING 等子句中,用于提供中间结果集或过滤条件。根据子查询与父查询的关联性,可分为相关子查询(引用了父查询中的列)和非相关子查询(独立执行)。父查询可以是 SELECT、UPDATE、DELETE 语句。
WHERE 子句中常用的运算符包括:=, <, >, >=, <=, <> 以及 IN, NOT IN, EXISTS, NOT EXISTS, ALL, ANY 等。
3.1 实战示例一:使用子查询进行数据过滤
场景:查询工资高于本部门平均工资的员工信息。
SQL 语句:
-- 使用非相关子查询(在 FROM 子句中) SELECT e.emp_id, e.emp_name, e.salary, e.dept_id, d.dept_name, dept_avg.avg_salary FROM employees e JOIN departments d ON e.dept_id = d.dept_id JOIN ( -- 子查询:计算每个部门的平均工资 SELECT dept_id, AVG(salary) AS avg_salary FROM employees WHERE dept_id IS NOT NULL GROUP BY dept_id ) dept_avg ON e.dept_id = dept_avg.dept_id WHERE e.salary > dept_avg.avg_salary ORDER BY e.dept_id, e.salary DESC;执行逻辑:
- 子查询
(SELECT dept_id, AVG(salary) ... GROUP BY dept_id)首先独立执行,生成一个临时结果集dept_avg,包含每个部门的平均工资。 - 主查询将
employees表与departments表连接,再与子查询结果dept_avg连接。 WHERE e.salary > dept_avg.avg_salary过滤出工资高于本部门平均工资的员工。
注意事项:
- 性能:当子查询结果集较大时,连接操作可能较慢。可以考虑使用窗口函数(如
AVG() OVER (PARTITION BY dept_id))进行优化。 - NULL 值处理:子查询中通过
WHERE dept_id IS NOT NULL排除了未分配部门的员工,避免因 NULL 值导致分组错误。 - 可读性:将子查询放在 FROM 子句中并为它起别名(
dept_avg),可以提高 SQL 的可读性。
3.2 实战示例二:在 UPDATE 语句中使用子查询
场景:为销售部(dept_id=2)所有员工的工资增加 10%,但上限不超过公司最高工资的 80%。
SQL 语句:
-- 使用子查询确定工资上限 UPDATE employees SET salary = LEAST( salary * 1.1, -- 增加10% ( -- 子查询:获取公司最高工资的80% SELECT MAX(salary) * 0.8 FROM employees WHERE dept_id IS NOT NULL ) ) WHERE dept_id = 2; -- 仅针对销售部执行逻辑:
- 子查询
(SELECT MAX(salary) * 0.8 FROM employees ...)首先执行一次,计算出公司最高工资的 80% 作为上限值。 - 主 UPDATE 语句对销售部(
dept_id = 2)的每一行,计算salary * 1.1(涨薪后)与子查询返回的上限值,取两者中较小者(LEAST函数)。 - 最终更新符合条件的员工工资。
注意事项:
- 单值要求:在 SET 子句中使用子查询时,子查询必须返回单个标量值(一行一列)。本例中,
MAX(salary) * 0.8返回一个数值。 - 相关子查询风险:如果子查询引用了正在更新的表(且未妥善处理),可能导致不可预测的结果或性能问题。本例中子查询独立于更新行,是安全的。
- 事务与锁:UPDATE 语句通常会在事务中执行,并可能锁定相关行。对于大数据量,建议分批更新或评估性能影响。
3.3 实战示例三:在 DELETE 语句中使用子查询
场景:删除那些部门已被撤销(在 departments 表中不存在)的员工记录。
SQL 语句:
-- 使用子查询识别无效部门ID DELETE FROM employees WHERE dept_id IS NOT NULL AND dept_id NOT IN ( -- 子查询:获取所有有效的部门ID SELECT dept_id FROM departments );执行逻辑:
- 子查询
(SELECT dept_id FROM departments)返回所有有效的部门 ID 列表。 - 主 DELETE 语句删除
employees表中那些dept_id不为 NULL 且不在有效部门 ID 列表中的记录。
注意事项:
- NOT IN 与 NULL:如果子查询可能返回 NULL 值,
NOT IN的行为会变得复杂(任何与 NULL 的比较结果都是 UNKNOWN)。确保子查询结果集不包含 NULL,或使用NOT EXISTS替代。 - 性能:对于大型表,
NOT IN子查询可能效率较低。如果 departments 表很大,可考虑在 dept_id 上建立索引,或使用NOT EXISTS配合相关子查询。 - 数据安全:执行 DELETE 前,建议先用 SELECT 语句验证将要删除的记录,或使用事务以便回滚。
3.4 嵌套子查询的通用注意事项
- 执行顺序:通常,子查询先于父查询执行(非相关子查询),但相关子查询需要为父查询的每一行执行一次,可能导致性能问题。
- 替代方案:许多嵌套子查询可以用 JOIN 或窗口函数重写,后者往往性能更优、可读性更好。
- 可读性与维护:过度嵌套会使 SQL 难以理解和调试。建议将复杂逻辑拆分为多个 CTE(Common Table Expressions,使用 WITH 子句)或临时视图。
- 测试:在应用到生产环境前,务必在测试环境中验证子查询的结果和性能。
通过以上示例可以看出,嵌套子查询是 SQL 中强大的工具,合理使用可以解决复杂的数据操作需求,但需注意其执行逻辑和潜在的性能影响。
四、聚合函数的使用
聚集函数和大多数其它关系数据库产品一样,PostgreSQL支持聚集函数。一个聚集函数从多个输入行中计算出一个结果。比如,我们有在一个行集合上计算COUNT(数目)、SUM(总和)、AVG(均值)、MAX(最大值)、MIN(最小值)的函数。
例如,我们可以用下面的语句找出所有低温中的最高温度:
SELECT MAX(temp_lo) FROM weather;结果:
max ----- 46 (1 row)如果我们想知道该读数发生在哪个城市,可能会用:
SELECT city FROM weather WHERE temp_lo = MAX(temp_lo); -- 错!不过这个方法不能运转,因为聚集函数MAX不能用于WHERE子句中。存在这个限制是因为WHERE子句决定哪些行可以进入聚集阶段;因此它必需在聚集函数之前计算。不过,我们可以用其它方法实现这个目的;这里我们使用子查询:
SELECT city FROM weather WHERE temp_lo = (SELECT MAX(temp_lo) FROM weather);结果:
city --------------- San Francisco (1 row)这样做是可以的,因为子查询是一次独立的计算,它独立于外层查询计算自己的聚集。
聚集同样也常用于GROUP BY子句。比如,我们可以获取每个城市低温的最高值:
SELECT city, MAX(temp_lo) FROM weather GROUP BY city;结果:
city | max ---------------+----- Hayward | 37 San Francisco | 46 (2 rows)这样每个城市一个输出。每个聚集结果都是在匹配该城市的行上面计算的。我们可以用HAVING过滤这些分组:GROUP BY可以没有HAVING过滤,但HAVING必须跟在GROUP BY的后面。
SELECT city, MAX(temp_lo) FROM weather GROUP BY city HAVING MAX(temp_lo) < 40;结果:
city | max ---------+----- Hayward | 37 (1 row)这样就只给出那些temp_lo值曾经有低于40度的城市。最后,如果我们只关心那些名字以"S"开头的城市,我们可以用:
SELECT city, MAX(temp_lo) FROM weather WHERE city LIKE 'S%' GROUP BY city HAVING MAX(temp_lo) < 40;五、聚合函数的嵌套使用
语句中的LIKE执行模式匹配。理解聚集和SQL的WHERE和HAVING子句之间的关系非常重要。WHERE和HAVING的基本区别如下:
- WHERE:在分组和聚集计算之前选取输入行(它控制哪些行进入聚集计算)
- HAVING:在分组和聚集之后选取输出行
因此,WHERE子句不能包含聚集函数;因为试图用聚集函数判断那些行将要输入给聚集运算是没有意义的。相反,HAVING子句总是包含聚集函数。当然,你可以写不使用聚集的HAVING子句,但这样做没什么好处,因为同样的条件可以更有效地用于WHERE阶段。
在前面的例子里,我们可以在WHERE里应用城市名称限制,因为它不需要聚集。这样比在HAVING里增加限制更加高效,因为我们避免了为那些未通过WHERE检查的行进行分组和聚集计算。
六、总结与参考资料
6.1 核心要点总结
本文详细介绍了SQL查询优化中的四个核心概念:IN与EXISTS运算符、连接查询(JOIN)、嵌套子查询以及聚合函数的使用。以下是各主题的核心要点与选用原则:
1. IN 与 EXISTS 运算符
- IN运算符:用于判断某个值是否在集合中,适合内小外大的查询场景。
- EXISTS运算符:用于检测子查询是否存在结果,适合内大外小的查询场景。
- 性能对比:NOT EXISTS通常比NOT IN快,因为可以使用结合算法;而EXISTS在多数情况下不如IN快。
- 选用原则:当子查询结果集较小时使用IN,结果集较大时使用EXISTS;处理NULL值时需特别注意NOT IN的行为。
2. 连接查询(JOIN)
- INNER JOIN:只返回两个表都匹配的记录,适用于需要获取有关联数据的场景。
- LEFT JOIN:返回左表所有记录+右表匹配记录(不匹配为NULL),适用于以左表为主表的查询。
- RIGHT JOIN:返回右表所有记录+左表匹配记录(不匹配为NULL),适用于以右表为主表的查询。
- FULL JOIN:返回两个表所有记录(匹配的显示对应信息,不匹配为NULL),适用于需要完整数据视图的场景。
- 选用原则:LEFT JOIN使用频率最高,通常用于主表关联补充表且不希望丢失主表记录的场景。
3. 嵌套子查询
- 数据过滤:在WHERE、FROM子句中使用子查询进行数据筛选和过滤。
- UPDATE/DELETE操作:在数据修改语句中使用子查询确定操作范围。
- 执行逻辑:非相关子查询先于父查询执行,相关子查询为父查询的每一行执行一次。
- 选用原则:优先考虑使用JOIN或窗口函数替代复杂嵌套子查询以提高性能;对于简单过滤可使用子查询。
4. 聚合函数
- 基本函数:COUNT、SUM、AVG、MAX、MIN等用于数据统计和分析。
- WHERE与HAVING区别:WHERE在分组前过滤行,不能使用聚合函数;HAVING在分组后过滤组,必须使用聚合函数。
- 嵌套使用:聚合函数可以嵌套在子查询中,但不能直接在WHERE子句中使用。
- 选用原则:在WHERE子句中使用子查询替代聚合函数过滤;在HAVING子句中使用聚合函数对分组结果进行筛选。
6.2 参考资料
- MySQL官方文档 - 子查询:https://dev.mysql.com/doc/refman/8.0/en/subqueries.html - 详细介绍了MySQL中子查询的语法、类型和使用场景。
- PostgreSQL官方文档 - 聚合函数:PostgreSQL: Documentation: 18: 9.21. Aggregate Functions - 包含了PostgreSQL中所有聚合函数的详细说明和示例。
- SQL JOIN可视化指南:Qlik Talend Cloud | Trusted, AI-Ready Data Integration & Quality - 通过交互式图表直观展示各种JOIN类型的工作原理和结果集差异。
- SQL性能优化最佳实践:SQL Indexing and Tuning e-Book for developers: Use The Index, Luke covers Oracle, MySQL, PostgreSQL, SQL Server, ... - 涵盖了SQL查询优化、索引使用、执行计划分析等高级主题。
通过掌握这些SQL核心概念和优化技巧,开发者可以编写出更高效、更易维护的数据库查询语句,提升应用程序的整体性能。