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

数据库查询艺术:从单表操作到多表联查的全面指南

引言:数据查询的核心地位

在当今数据驱动的世界中,数据库查询能力已成为技术人员不可或缺的核心技能。无论是后端开发、数据分析还是业务决策,高效准确地从数据库中提取信息都是关键环节。SQL(结构化查询语言)作为与数据库交互的标准语言,其查询功能尤其重要。

本文将系统性地介绍数据库查询的两大核心领域:单表查询和多表查询。我们将从最基础的SELECT语句开始,逐步深入到复杂的多表连接操作,通过丰富的示例和实践建议,帮助读者掌握数据查询的精髓。无论您是数据库新手还是希望提升查询技能的开发者,这篇文章都将为您提供有价值的参考。

第一部分:单表查询基础

1.1 SELECT语句基础结构

SELECT语句是SQL中最基本也是最重要的命令,用于从数据库表中检索数据。其基本语法结构如下:

sql

SELECT [DISTINCT] 列名1, 列名2, ...
FROM 表名
[WHERE 条件]
[GROUP BY 分组列]
[HAVING 分组条件]
[ORDER BY 排序列 [ASC|DESC]]
[LIMIT 数量];

关键组成部分解析:

  • SELECT:指定要检索的列,可以使用*表示所有列

  • DISTINCT:可选关键字,用于消除重复行

  • FROM:指定要查询的表

  • WHERE:过滤条件,限制返回的行

  • GROUP BY:按指定列分组

  • HAVING:对分组结果进行过滤

  • ORDER BY:对结果排序

  • LIMIT:限制返回的行数

基础示例:

sql

-- 查询员工表中所有列的所有行
SELECT * FROM employees;-- 查询特定列
SELECT employee_id, first_name, last_name FROM employees;-- 使用DISTINCT去重
SELECT DISTINCT department_id FROM employees;

1.2 列的选择与处理

在实际查询中,我们通常不需要选择所有列,而是有选择地获取所需数据。SQL提供了多种处理列的方式:

选择特定列:

sql

SELECT product_name, unit_price, units_in_stock 
FROM products;

使用表达式和计算列:

sql

SELECT product_name, unit_price, units_in_stock, unit_price * units_in_stock AS inventory_value
FROM products;

使用函数处理列:

sql

SELECT first_name, last_name, CONCAT(first_name, ' ', last_name) AS full_name,YEAR(hire_date) AS hire_year
FROM employees;

列别名:
使用AS关键字可以为列指定别名,提高可读性(AS可省略):

sql

SELECT product_id AS "产品ID",product_name AS "产品名称",unit_price AS "单价"
FROM products;

1.3 WHERE条件筛选

WHERE子句是过滤数据的关键,它允许我们指定条件来限制返回的行。WHERE子句支持多种运算符和条件组合。

比较运算符:

  • = 等于

  • <> 或 != 不等于

  • > 大于

  • < 小于

  • >= 大于等于

  • <= 小于等于

sql

-- 简单条件查询
SELECT * FROM products 
WHERE unit_price > 50;-- 多条件组合
SELECT * FROM employees
WHERE department_id = 10 AND salary > 5000;

逻辑运算符:

  • AND 逻辑与

  • OR 逻辑或

  • NOT 逻辑非

sql

-- 使用OR和AND
SELECT * FROM orders
WHERE (customer_id = 'VINET' OR customer_id = 'TOMSP')AND order_date >= '2020-01-01';

特殊条件:

  • BETWEEN 范围查询

  • IN 多值匹配

  • LIKE 模糊匹配

  • IS NULL 空值检查

sql

-- BETWEEN示例
SELECT * FROM products
WHERE unit_price BETWEEN 10 AND 20;-- IN示例
SELECT * FROM customers
WHERE country IN ('USA', 'UK', 'Canada');-- LIKE示例
SELECT * FROM employees
WHERE last_name LIKE 'S%'; -- 以S开头-- IS NULL示例
SELECT * FROM customers
WHERE region IS NULL;

条件优先级:
当多个条件组合时,可以使用括号明确优先级:

sql

SELECT * FROM products
WHERE (category_id = 1 OR category_id = 2)AND discontinued = 0;

1.4 排序(ORDER BY)

ORDER BY子句允许我们对结果集进行排序,可以指定一个或多个排序列,并控制升序(ASC)或降序(DESC)。

基本排序:

sql

-- 单列排序
SELECT product_name, unit_price
FROM products
ORDER BY product_name ASC;-- 多列排序
SELECT first_name, last_name, hire_date
FROM employees
ORDER BY hire_date DESC, last_name ASC;

按表达式排序:

sql

-- 按计算值排序
SELECT product_name, unit_price, units_in_stock,unit_price * units_in_stock AS inventory_value
FROM products
ORDER BY inventory_value DESC;

按列位置排序:
可以按SELECT子句中列的位置排序(不推荐,可读性差):

sql

SELECT product_name, unit_price, units_in_stock
FROM products
ORDER BY 2 DESC; -- 按第二列(unit_price)降序

NULL值的排序:
NULL值在排序时通常被视为最小值(升序时排在最前,降序时排在最后),不同数据库可能有差异。

1.5 分组统计(GROUP BY)

GROUP BY子句将结果集按一个或多个列分组,通常与聚合函数一起使用,生成汇总数据。

基本分组:

sql

-- 按部门统计员工数
SELECT department_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;

多列分组:

sql

-- 按国家和城市统计客户数
SELECT country, city, COUNT(*) AS customer_count
FROM customers
GROUP BY country, city;

GROUP BY与WHERE:
WHERE在分组前过滤行,HAVING在分组后过滤组:

sql

-- 先过滤再分组
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE salary > 3000
GROUP BY department_id;

1.6 聚合函数

聚合函数对一组值执行计算并返回单个值,常与GROUP BY一起使用。

常用聚合函数:

  • COUNT() 计数

  • SUM() 求和

  • AVG() 平均值

  • MAX() 最大值

  • MIN() 最小值

  • STDDEV() 标准差

  • VARIANCE() 方差

聚合函数示例:

sql

-- 基本聚合
SELECT COUNT(*) AS total_products,AVG(unit_price) AS avg_price,MAX(unit_price) AS max_price,MIN(unit_price) AS min_price
FROM products;-- 分组聚合
SELECT category_id,COUNT(*) AS product_count,AVG(unit_price) AS avg_price
FROM products
GROUP BY category_id;

COUNT的变体:

sql

-- COUNT(*) vs COUNT(列名)
SELECT COUNT(*) AS total_rows, -- 计算所有行数COUNT(region) AS non_null_regions -- 计算region非空的行数
FROM customers;

DISTINCT与聚合:

sql

-- 计算不重复的国家数量
SELECT COUNT(DISTINCT country) AS unique_countries
FROM customers;

1.7 HAVING子句

HAVING子句用于过滤分组后的结果,类似于WHERE但作用于组而非单个行。

HAVING示例:

sql

-- 筛选平均工资超过5000的部门
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 5000;-- 筛选产品数量超过5的分类
SELECT category_id, COUNT(*) AS product_count
FROM products
GROUP BY category_id
HAVING COUNT(*) > 5;

WHERE与HAVING对比:

sql

-- 先过滤单价>20的产品,再筛选平均库存值>500的分类
SELECT category_id, AVG(unit_price * units_in_stock) AS avg_inventory_value
FROM products
WHERE unit_price > 20
GROUP BY category_id
HAVING AVG(unit_price * units_in_stock) > 500;

1.8 执行顺序理解

SQL语句的执行顺序与书写顺序不同,理解这一点对编写正确查询至关重要:

  1. FROM:确定数据来源

  2. WHERE:过滤行

  3. GROUP BY:分组

  4. HAVING:过滤组

  5. SELECT:选择列

  6. ORDER BY:排序

  7. LIMIT:限制结果

理解执行顺序有助于避免常见错误,例如在WHERE中使用SELECT中定义的别名。

第二部分:多表查询进阶

2.1 连接查询基础

现实世界的数据通常分散在多个表中,多表查询允许我们组合这些数据。连接查询是最常用的多表查询方式。

2.1.1 连接的类型

SQL支持多种连接类型,每种类型决定了如何处理不匹配的行:

  • INNER JOIN(内连接):只返回匹配的行

  • LEFT JOIN(左外连接):返回左表所有行,右表不匹配则为NULL

  • RIGHT JOIN(右外连接):返回右表所有行,左表不匹配则为NULL

  • FULL JOIN(全外连接):返回两表所有行,不匹配则为NULL

  • CROSS JOIN(交叉连接):两表的笛卡尔积

2.1.2 连接语法

基本连接语法:

sql

SELECT 列名...
FROM 表1
[连接类型] JOIN 表2 ON 连接条件
[连接类型] JOIN 表3 ON 连接条件...
[WHERE 条件]
[GROUP BY 分组]
[HAVING 分组条件]
[ORDER BY 排序];

2.2 INNER JOIN详解

INNER JOIN是最常用的连接类型,只返回两表中匹配的行。

基本INNER JOIN:

sql

-- 查询订单及客户信息
SELECT o.order_id, o.order_date, c.company_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;

多表INNER JOIN:

sql

-- 查询订单详情,包含产品信息
SELECT o.order_id, p.product_name, od.quantity, od.unit_price
FROM orders o
INNER JOIN order_details od ON o.order_id = od.order_id
INNER JOIN products p ON od.product_id = p.product_id;

INNER JOIN与WHERE:

sql

-- 带过滤条件的INNER JOIN
SELECT e.first_name, e.last_name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1700;

自连接(Self Join):
表与自身连接,常用于层级数据:

sql

-- 查询员工及其经理信息
SELECT e.first_name AS employee, m.first_name AS manager
FROM employees e
INNER JOIN employees m ON e.manager_id = m.employee_id;

2.3 LEFT JOIN与RIGHT JOIN

外连接(OUTER JOIN)保留至少一个表中的所有行,即使另一表中没有匹配。

2.3.1 LEFT JOIN

LEFT JOIN返回左表所有行,右表不匹配则为NULL。

sql

-- 查询所有客户及其订单(包括没有订单的客户)
SELECT c.company_name, o.order_id, o.order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

查找不匹配的行:

sql

-- 查找没有订单的客户
SELECT c.company_name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
2.3.2 RIGHT JOIN

RIGHT JOIN返回右表所有行,左表不匹配则为NULL(较少使用,通常可用LEFT JOIN替代)。

sql

-- 查询所有产品及其订单详情(包括未被订购的产品)
SELECT p.product_name, od.order_id
FROM order_details od
RIGHT JOIN products p ON od.product_id = p.product_id;

2.4 FULL JOIN

FULL JOIN返回两表所有行,不匹配则为NULL(MySQL不支持FULL JOIN)。

sql

-- 查询所有员工和部门(包括无部门的员工和无员工的部门)
SELECT e.first_name, d.department_name
FROM employees e
FULL JOIN departments d ON e.department_id = d.department_id;

MySQL中的FULL JOIN模拟:

sql

-- 使用UNION模拟FULL JOIN
SELECT e.first_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
UNION
SELECT e.first_name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id
WHERE e.department_id IS NULL;

2.5 CROSS JOIN

CROSS JOIN生成两表的笛卡尔积(所有可能的组合),慎用。

sql

-- 生成颜色和尺寸的所有组合
SELECT c.color_name, s.size_name
FROM colors c
CROSS JOIN sizes s;

2.6 自然连接与USING

自然连接(NATURAL JOIN):
自动连接同名列(不推荐,易出错):

sql

-- 自动连接customer_id列
SELECT c.company_name, o.order_id
FROM customers c
NATURAL JOIN orders o;

USING子句:
当连接列名相同时,可使用USING简化:

sql

-- 使用USING简化连接条件
SELECT c.company_name, o.order_id
FROM customers c
JOIN orders o USING (customer_id);

2.7 子查询

子查询是嵌套在其他查询中的查询,可用于多种场景。

2.7.1 标量子查询

返回单个值的子查询,可用于SELECT、WHERE等。

sql

-- 查询高于平均单价的产品
SELECT product_name, unit_price
FROM products
WHERE unit_price > (SELECT AVG(unit_price) FROM products);
2.7.2 列子查询

返回单列多行的子查询,常与IN、ANY、ALL等使用。

sql

-- 查询没有订单的客户
SELECT company_name
FROM customers
WHERE customer_id NOT IN (SELECT DISTINCT customer_id FROM orders);
2.7.3 行子查询

返回单行多列的子查询。

sql

-- 查询与特定员工工资和部门相同的其他员工
SELECT first_name, last_name
FROM employees
WHERE (salary, department_id) = (SELECT salary, department_id FROM employees WHERE employee_id = 105);
2.7.4 表子查询

返回多行多列的子查询,可作为临时表使用。

sql

-- 查询每个分类中单价最高的产品
SELECT p.category_id, p.product_name, p.unit_price
FROM products p
JOIN (SELECT category_id, MAX(unit_price) AS max_priceFROM productsGROUP BY category_id) tmp
ON p.category_id = tmp.category_id AND p.unit_price = tmp.max_price;
2.7.5 EXISTS子查询

检查子查询是否返回行,常用于相关子查询。

sql

-- 查询至少有一个订单价值超过1000的客户
SELECT company_name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o JOIN order_details od ON o.order_id = od.order_idWHERE o.customer_id = c.customer_idAND od.unit_price * od.quantity > 1000);

2.8 派生表与CTE

2.8.1 派生表

在FROM子句中的子查询,作为临时表使用。

sql

-- 计算每个分类的产品数量和平均价格
SELECT c.category_name, p_stats.product_count, p_stats.avg_price
FROM categories c
JOIN (SELECT category_id, COUNT(*) AS product_count,AVG(unit_price) AS avg_priceFROM productsGROUP BY category_id) p_stats
ON c.category_id = p_stats.category_id;
2.8.2 公共表表达式(CTE)

使用WITH子句定义临时结果集,提高可读性。

sql

-- 使用CTE重写上例
WITH product_stats AS (SELECT category_id, COUNT(*) AS product_count,AVG(unit_price) AS avg_priceFROM productsGROUP BY category_id
)
SELECT c.category_name, ps.product_count, ps.avg_price
FROM categories c
JOIN product_stats ps ON c.category_id = ps.category_id;

递归CTE:
处理层级数据,如组织结构。

sql

-- 查找员工的所有下属(递归)
WITH RECURSIVE emp_hierarchy AS (-- 基础查询(起点)SELECT employee_id, first_name, last_name, manager_id, 1 AS levelFROM employeesWHERE employee_id = 100UNION ALL-- 递归查询SELECT e.employee_id, e.first_name, e.last_name, e.manager_id, eh.level + 1FROM employees eJOIN emp_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM emp_hierarchy ORDER BY level;

2.9 联合查询(UNION)

UNION操作符合并多个SELECT的结果集。

基本UNION:

sql

-- 合并供应商和客户所在城市
SELECT city, country, 'Supplier' AS type
FROM suppliers
UNION
SELECT city, country, 'Customer' AS type
FROM customers
ORDER BY country, city;

UNION规则:

  • 列数必须相同

  • 对应列数据类型必须兼容

  • UNION自动去重,UNION ALL保留所有行

sql

-- UNION ALL示例(不去重)
SELECT product_id FROM order_details WHERE order_id = 10250
UNION ALL
SELECT product_id FROM order_details WHERE order_id = 10251;

2.10 连接查询优化建议

  1. 明确连接类型:根据需求选择正确的连接类型

  2. 限制结果集:只选择必要的列和行

  3. 使用索引:确保连接列有适当索引

  4. 注意NULL值:外连接会产生NULL值,处理要小心

  5. 避免笛卡尔积:确保连接条件正确

  6. 考虑性能:多表连接时,大表放在JOIN顺序后面

第三部分:查询方式比较与应用场景

3.1 连接 vs 子查询

连接查询特点:

  • 通常性能更好

  • 可同时访问多个表的列

  • 语法更直观

  • 适合表间关系明确的情况

子查询特点:

  • 逻辑表达更清晰

  • 适合分步处理复杂逻辑

  • 相关子查询可能性能较差

  • 适合存在性检查(EXISTS)

何时使用子查询:

  1. 需要比较聚合值时

    sql

    SELECT product_name, unit_price
    FROM products
    WHERE unit_price > (SELECT AVG(unit_price) FROM products);

  2. 需要存在性检查时

    sql

    SELECT company_name
    FROM customers c
    WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);

  3. 在FROM子句中创建临时结果集时

何时使用连接:

  1. 需要组合多个表的列时

    sql

    SELECT o.order_id, c.company_name, e.last_name
    FROM orders o
    JOIN customers c ON o.customer_id = c.customer_id
    JOIN employees e ON o.employee_id = e.employee_id;

  2. 需要保留不匹配的行时(外连接)

  3. 性能是关键因素时

3.2 IN vs EXISTS

IN操作符:

  • 适合静态列表或小型结果集

  • 子查询返回单列

  • NULL值处理需注意

sql

-- 查询有订单的客户
SELECT company_name
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders);

EXISTS操作符:

  • 适合相关子查询

  • 子查询返回存在性

  • 通常对NULL更安全

  • 大表查询性能可能更好

sql

-- 同上,使用EXISTS
SELECT company_name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);

性能考虑:

  • 对于大型数据集,EXISTS通常性能更好

  • IN适合小型静态列表

  • 现代数据库优化器可能对简单情况优化相同

3.3 JOIN vs 子查询性能

一般原则:

  1. 对于简单关系,JOIN通常更快

  2. 相关子查询可能性能较差

  3. 派生表可能物化中间结果,影响性能

  4. 实际性能取决于数据库优化器、索引和数据分布

优化建议:

  1. 对关键查询检查执行计划

  2. 为连接列建立索引

  3. 避免在子查询中使用SELECT *

  4. 考虑重写复杂子查询为JOIN

3.4 典型应用场景

适合使用连接的情况:

  1. 报表需要多表数据

  2. 需要保留不匹配记录(外连接)

  3. 表间关系清晰的一对多关系

适合使用子查询的情况:

  1. 存在性检查

  2. 与聚合值比较

  3. 分步处理复杂逻辑

  4. 需要临时结果集

适合使用联合查询的情况:  

          关联数据查询
          数据分散在多个表
          一对多或多对多关系
          需要聚合多表数据
          数据完整性验证
          替代子查询提高性能
          复杂业务逻辑实现

相关文章:

  • C语言(3)—分支和循环
  • Java基础高频面试
  • Neowise Labs Contest 1 (Codeforces Round 1018, Div. 1 + Div. 2)
  • 前端权限管理
  • C语言学习之结构体
  • 《代码整洁之道》第9章 单元测试 - 笔记
  • 《代码整洁之道》第5章 格式 - 笔记
  • MRI学习笔记-conjunction analysis
  • docker(3) -- 图形界面
  • 驱动开发硬核特训 · Day 22(下篇): # 深入理解 Power-domain 框架:概念、功能与完整代码剖析
  • 《操作系统真象还原》第十章(1)——输入输出系统
  • 加密算法 AES、RSA、MD5、SM2 的对比分析与案例(AI)
  • 「Docker已死?」:基于Wasm容器的新型交付体系如何颠覆十二因素应用宣言
  • 2025.4.21-2025.4.26学习周报
  • 泰迪杯实战案例超深度解析:基于YOLOv5的农田害虫图像识别系统设计
  • 「Mac畅玩AIGC与多模态04」开发篇01 - 创建第一个 LLM 对话应用
  • 迷你世界UGC3.0脚本Wiki组件事件管理
  • 显存在哪里看 分享查看及优化方法
  • 分布式一致性算法起源思考与应用
  • 从“世界工厂”到“智造之都”:双运放如何改写东莞产业基因?
  • 劳动最光荣!2426人受到表彰
  • 5145篇报道中的上海车展:40年,什么变了?
  • 白酒瓶“神似”北京第一高楼被判侵权,法院一审判赔45万并停售
  • 摩根士丹利基金雷志勇:AI带来的产业演进仍在继续,看好三大景气领域
  • 居民被脱落的外墙瓦砖砸中致十级伤残,小区物业赔付16万元
  • 金隅集团:今年拿地将选择核心热门地块,稳健审慎投资