简介
在这个实验中,你将学习如何在 MySQL 中使用公共表表达式(Common Table Expressions,CTEs)来提高查询的可读性和可维护性。本实验涵盖了使用 WITH
子句定义简单的 CTE、为层次结构数据编写递归 CTE、将 CTE 与表进行连接,以及测试 CTE 查询输出。
你将首先创建一个示例 employees
表,然后定义一个简单的 CTE 来从“销售”部门中选择员工。这将展示 CTE 在将复杂查询分解为更小、更易于管理的部分时的基本语法和用法。
在这个实验中,你将学习如何在 MySQL 中使用公共表表达式(Common Table Expressions,CTEs)来提高查询的可读性和可维护性。本实验涵盖了使用 WITH
子句定义简单的 CTE、为层次结构数据编写递归 CTE、将 CTE 与表进行连接,以及测试 CTE 查询输出。
你将首先创建一个示例 employees
表,然后定义一个简单的 CTE 来从“销售”部门中选择员工。这将展示 CTE 在将复杂查询分解为更小、更易于管理的部分时的基本语法和用法。
在这一步中,你将学习如何在 MySQL 中使用 WITH
子句定义一个简单的公共表表达式(Common Table Expression,CTE)。CTE 是临时的、有名称的结果集,你可以在单个 SELECT
、INSERT
、UPDATE
或 DELETE
语句中引用它们。CTE 有助于将复杂的查询分解为更小、更易于管理的部分,从而提高查询的可读性和可维护性。
理解 CTE
CTE 本质上是一个有名称的子查询,仅在查询执行期间存在。它使用 WITH
子句进行定义,后面跟着 CTE 的名称、列列表(可选)和 AS
关键字,然后是用括号括起来的子查询。
创建示例表
首先,让我们创建一个名为 employees
的简单表来进行操作。在你的 MySQL 环境中执行以下 SQL 语句。你可以在终端中直接输入 mysql
来访问 MySQL 命令行客户端。
mysql
然后,创建表:
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
department VARCHAR(50),
salary DECIMAL(10, 2)
);
INSERT INTO employees (employee_id, first_name, last_name, department, salary) VALUES
(1, 'John', 'Doe', 'Sales', 60000.00),
(2, 'Jane', 'Smith', 'Marketing', 75000.00),
(3, 'Robert', 'Jones', 'Sales', 55000.00),
(4, 'Emily', 'Brown', 'IT', 90000.00),
(5, 'Michael', 'Davis', 'Marketing', 70000.00);
现在,退出 MySQL 客户端:
exit
定义简单的 CTE
现在,让我们定义一个简单的 CTE 来选择“销售”部门的员工。
WITH SalesEmployees AS (
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department = 'Sales'
)
SELECT employee_id, first_name, last_name, salary
FROM SalesEmployees;
解释:
WITH SalesEmployees AS (...)
:这定义了一个名为 SalesEmployees
的 CTE。SELECT employee_id, first_name, last_name, salary FROM employees WHERE department = 'Sales'
:这是一个子查询,用于从 employees
表中选择部门为“销售”的员工信息。SELECT employee_id, first_name, last_name, salary FROM SalesEmployees
:这是主查询,用于从 SalesEmployees
CTE 中选择所有列。执行查询
要执行该查询,你可以使用 nano
将其保存到 ~/project
目录下一个名为 sales_employees.sql
的文件中:
nano ~/project/sales_employees.sql
将 SQL 代码粘贴到文件中,保存(Ctrl+O)并退出(Ctrl+X)。
然后,使用 MySQL 命令行客户端执行该 SQL 文件:
mysql < ~/project/sales_employees.sql
预期输出:
输出应该是一个包含“销售”部门所有员工的员工 ID、名字、姓氏和工资的表:
+-------------+------------+-----------+----------+
| employee_id | first_name | last_name | salary |
+-------------+------------+-----------+----------+
| 1 | John | Doe | 60000.00 |
| 3 | Robert | Jones | 55000.00 |
+-------------+------------+-----------+----------+
2 rows in set (0.00 sec)
这展示了如何定义和使用简单的 CTE 来过滤表中的数据。在接下来的步骤中,你将学习更高级的技术,例如递归 CTE 以及将 CTE 与表进行连接。
在这一步中,你将学习如何编写递归公共表表达式(Common Table Expression,CTE)来查询 MySQL 中的层次结构数据。递归 CTE 特别适用于遍历树状结构,例如组织结构图、文件系统或类别层次结构。
理解递归 CTE
递归 CTE 是一种引用自身的 CTE。它由两部分组成:
SELECT
语句,定义了递归的起始点。UNION ALL
与锚成员组合。创建示例表
让我们创建一个名为 employee_hierarchy
的表来表示公司内员工的层次结构。在你的 MySQL 环境中执行以下 SQL 语句。如果你在上一步关闭了 MySQL 客户端,可以在终端中输入 mysql
重新打开它。
CREATE TABLE employee_hierarchy (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(50),
manager_id INT,
FOREIGN KEY (manager_id) REFERENCES employee_hierarchy(employee_id)
);
INSERT INTO employee_hierarchy (employee_id, employee_name, manager_id) VALUES
(1, 'David', NULL),
(2, 'Emily', 1),
(3, 'Frank', 1),
(4, 'Grace', 2),
(5, 'Henry', 2),
(6, 'Ivy', 3),
(7, 'Jack', 3);
在这个表中:
employee_id
是每个员工的唯一标识符。employee_name
是员工的姓名。manager_id
是员工经理的 employee_id
。顶级经理的 manager_id
值为 NULL
。现在,退出 MySQL 客户端:
exit
编写递归 CTE
现在,让我们编写一个递归 CTE 来检索特定经理下的整个层次结构。在这个例子中,我们将检索 David(employee_id = 1
)下的层次结构。
WITH RECURSIVE EmployeeHierarchyCTE AS (
SELECT employee_id, employee_name, manager_id, 0 AS level
FROM employee_hierarchy
WHERE manager_id IS NULL -- 锚成员:选择顶级经理
UNION ALL
SELECT e.employee_id, e.employee_name, e.manager_id, h.level + 1
FROM employee_hierarchy e
INNER JOIN EmployeeHierarchyCTE h ON e.manager_id = h.employee_id -- 递归成员:与 CTE 自身连接
)
SELECT employee_id, employee_name, manager_id, level
FROM EmployeeHierarchyCTE
ORDER BY level, employee_name;
解释:
WITH RECURSIVE EmployeeHierarchyCTE AS (...)
:这定义了一个名为 EmployeeHierarchyCTE
的递归 CTE。SELECT employee_id, employee_name, manager_id, 0 AS level FROM employee_hierarchy WHERE manager_id IS NULL
:这选择了顶级经理(David)并将级别设为 0。SELECT e.employee_id, e.employee_name, e.manager_id, h.level + 1 FROM employee_hierarchy e INNER JOIN EmployeeHierarchyCTE h ON e.manager_id = h.employee_id
:这将 employee_hierarchy
表与 EmployeeHierarchyCTE
基于 manager_id
进行连接,以查找所有向 CTE 中的某人汇报的员工。它为层次结构的每一级增加级别。UNION ALL
:这将锚成员和递归成员的结果组合起来。SELECT employee_id, employee_name, manager_id, level FROM EmployeeHierarchyCTE ORDER BY level, employee_name
:这是主查询,从 EmployeeHierarchyCTE
中选择所有列,并按级别和员工姓名对结果进行排序。执行查询
要执行该查询,你可以使用 nano
将其保存到 ~/project
目录下一个名为 employee_hierarchy.sql
的文件中:
nano ~/project/employee_hierarchy.sql
将 SQL 代码粘贴到文件中,保存(Ctrl+O)并退出(Ctrl+X)。
然后,使用 MySQL 命令行客户端执行该 SQL 文件:
mysql < ~/project/employee_hierarchy.sql
预期输出:
输出应该是一个显示 David 下员工层次结构的表:
+-------------+---------------+------------+-------+
| employee_id | employee_name | manager_id | level |
+-------------+---------------+------------+-------+
| 1 | David | NULL | 0 |
| 2 | Emily | 1 | 1 |
| 3 | Frank | 1 | 1 |
| 4 | Grace | 2 | 2 |
| 5 | Henry | 2 | 2 |
| 6 | Ivy | 3 | 2 |
| 7 | Jack | 3 | 2 |
+-------------+---------------+------------+-------+
7 rows in set (0.00 sec)
这展示了如何使用递归 CTE 来查询层次结构数据并检索特定节点下的整个层次结构。
在这一步中,你将学习如何在 MySQL 中将公共表表达式(Common Table Expression,CTE)与表进行连接。将 CTE 与表连接可以让你把 CTE 的结果与另一个表的数据相结合,从而进行更复杂的查询和分析。
理解 CTE 连接
将 CTE 与表连接类似于连接两个表。你使用 JOIN
关键字和 ON
子句来指定连接条件。在查询中,CTE 被视为一个虚拟表。
使用现有表
我们将继续使用在前面步骤中创建的 employees
和 employee_hierarchy
表。如果你跳过了那些步骤,请使用步骤 1 和步骤 2 中提供的 SQL 脚本创建这些表。
创建按部门计算平均工资的 CTE
首先,让我们创建一个 CTE,用于计算 employees
表中每个部门的平均工资。
WITH AvgSalaryByDepartment AS (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT * FROM AvgSalaryByDepartment;
这个名为 AvgSalaryByDepartment
的 CTE 会计算每个部门的平均工资,并返回部门名称和平均工资。
将 CTE 与 employees
表连接
现在,让我们将这个 CTE 与 employees
表连接起来,以获取员工信息以及他们所在部门的平均工资。
WITH AvgSalaryByDepartment AS (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.department,
e.salary,
a.avg_salary
FROM
employees e
JOIN
AvgSalaryByDepartment a ON e.department = a.department;
解释:
WITH AvgSalaryByDepartment AS (...)
:这和之前一样定义了 CTE。SELECT e.employee_id, e.first_name, e.last_name, e.department, e.salary, a.avg_salary FROM employees e JOIN AvgSalaryByDepartment a ON e.department = a.department
:这从 employees
表中选择员工信息,从 AvgSalaryByDepartment
CTE 中选择平均工资。JOIN
子句基于 department
列将两者连接起来。执行查询
要执行该查询,你可以使用 nano
将其保存到 ~/project
目录下一个名为 employee_avg_salary.sql
的文件中:
nano ~/project/employee_avg_salary.sql
将 SQL 代码粘贴到文件中,保存(Ctrl+O)并退出(Ctrl+X)。
然后,使用 MySQL 命令行客户端执行该 SQL 文件:
mysql < ~/project/employee_avg_salary.sql
预期输出:
输出应该是一个包含员工信息以及他们所在部门平均工资的表:
+-------------+------------+-----------+-----------+----------+------------+
| employee_id | first_name | last_name | department | salary | avg_salary |
+-------------+------------+-----------+-----------+----------+------------+
| 2 | Jane | Smith | Marketing | 75000.00 | 72500.00 |
| 5 | Michael | Davis | Marketing | 70000.00 | 72500.00 |
| 4 | Emily | Brown | IT | 90000.00 | 90000.00 |
| 1 | John | Doe | Sales | 60000.00 | 57500.00 |
| 3 | Robert | Jones | Sales | 55000.00 | 57500.00 |
+-------------+------------+-----------+-----------+----------+------------+
5 rows in set (0.00 sec)
这展示了如何将 CTE 与表连接起来,以组合数据并进行更复杂的分析。
在这一步中,你将学习如何测试 CTE 查询的输出,以确保它们能产生预期的结果。这是开发过程中的关键步骤,用于验证查询的正确性并识别任何潜在问题。我们将使用前面步骤中创建的查询来演示测试过程。
为何要测试 CTE 查询?
测试 CTE 查询至关重要,原因如下:
测试策略
测试 CTE 查询有几种策略:
测试简单 CTE(步骤 1)
在步骤 1 中,你创建了一个简单的 CTE 来选择“销售(Sales)”部门的员工。要测试此查询,你可以手动检查输出,并验证它只包含“销售”部门的员工。
再次执行以下查询:
mysql < ~/project/sales_employees.sql
输出应该是:
+-------------+------------+-----------+----------+
| employee_id | first_name | last_name | salary |
+-------------+------------+-----------+----------+
| 1 | John | Doe | 60000.00 |
| 3 | Robert | Jones | 55000.00 |
+-------------+------------+-----------+----------+
2 rows in set (0.00 sec)
验证 John Doe 和 Robert Jones 确实都在销售部门。
测试递归 CTE(步骤 2)
在步骤 2 中,你创建了一个递归 CTE 来检索员工层级结构。要测试此查询,你可以手动检查输出,并验证它是否正确表示了层级结构。
再次执行以下查询:
mysql < ~/project/employee_hierarchy.sql
输出应该是:
+-------------+---------------+------------+-------+
| employee_id | employee_name | manager_id | level |
+-------------+---------------+------------+-------+
| 1 | David | NULL | 0 |
| 2 | Emily | 1 | 1 |
| 3 | Frank | 1 | 1 |
| 4 | Grace | 2 | 2 |
| 5 | Henry | 2 | 2 |
| 6 | Ivy | 3 | 2 |
| 7 | Jack | 3 | 2 |
+-------------+---------------+------------+-------+
7 rows in set (0.00 sec)
验证 David 处于层级 0,Emily 和 Frank 向 David 汇报(层级 1),Grace、Henry、Ivy 和 Jack 向 Emily 或 Frank 汇报(层级 2)。
测试 CTE 连接(步骤 3)
在步骤 3 中,你将一个 CTE 与 employees
表连接起来,以检索员工信息以及他们所在部门的平均工资。要测试此查询,你可以手动检查输出,并验证每个部门的平均工资计算是否正确。
再次执行以下查询:
mysql < ~/project/employee_avg_salary.sql
输出应该是:
+-------------+------------+-----------+-----------+----------+------------+
| employee_id | first_name | last_name | department | salary | avg_salary |
+-------------+------------+-----------+-----------+----------+------------+
| 2 | Jane | Smith | Marketing | 75000.00 | 72500.00 |
| 5 | Michael | Davis | Marketing | 70000.00 | 72500.00 |
| 4 | Emily | Brown | IT | 90000.00 | 90000.00 |
| 1 | John | Doe | Sales | 60000.00 | 57500.00 |
| 3 | Robert | Jones | Sales | 55000.00 | 57500.00 |
+-------------+------------+-----------+-----------+----------+------------+
5 rows in set (0.00 sec)
验证市场(Marketing)部门的平均工资为 (75000 + 70000) / 2 = 72500,信息技术(IT)部门的平均工资为 90000,销售(Sales)部门的平均工资为 (60000 + 55000) / 2 = 57500。
总结
通过测试 CTE 查询的输出,你可以确保它们能正常工作并产生预期的结果。这有助于提高数据分析和报告的质量与可靠性。请记住结合使用手动检查和自动化测试,以全面验证你的 CTE 查询。
在本次实验中,你学习了如何在 MySQL 中定义和使用公共表表达式(Common Table Expressions,CTEs)。第一步是创建一个示例 employees
表,然后使用 WITH
子句定义一个名为 SalesEmployees
的简单 CTE。这个 CTE 用于选择“销售(Sales)”部门的员工,展示了 CTE 如何将复杂查询分解为更小、更易于管理的部分,从而提高可读性。
本次实验介绍了 CTE 的概念,它是临时的、有名称的结果集,仅在查询执行期间存在。你了解到 CTE 使用 WITH
子句进行定义,后面跟着 CTE 的名称以及用括号括起来的子查询。示例展示了如何在后续的 SELECT
语句中从 CTE 中选择数据。