MySQL 公共表表达式 (CTEs)

MySQLBeginner
立即练习

介绍

在本实验中,你将学习如何在 MySQL 中使用公共表表达式 (Common Table Expressions, CTEs)。CTEs 通过将复杂查询分解为逻辑清晰、易于阅读的步骤来简化它们。CTE 创建一个临时的、命名的结果集,你可以在单个 SQL 语句中引用它。

你将首先设置一个数据库,并创建一个简单的 CTE 来过滤员工数据。然后,你将探索递归 CTEs 来处理层级数据,例如组织结构图。最后,你将学习如何将 CTE 与表连接起来执行更高级的数据分析。

设置数据库和简单的 CTE

在第一步中,你将设置数据库环境并定义一个简单的公共表表达式 (CTE)。CTEs 使用 WITH 子句定义,并充当一个临时视图,仅在单个查询的持续时间内存在。这使得复杂查询更易于阅读和管理。

首先,从你的桌面打开终端。

root 用户连接到 MySQL 服务器。在此实验环境中,你可以使用 sudo 连接而无需密码。

sudo mysql -u root

连接成功后,你将看到 MySQL 提示符 (mysql>)。现在,创建一个名为 labex_db 的数据库并切换到它。

CREATE DATABASE IF NOT EXISTS labex_db;
USE labex_db;

接下来,创建一个 employees 表并插入一些示例数据。此表将存储员工的基本信息,包括他们的部门和薪资。

CREATE TABLE IF NOT EXISTS 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);

现在表已准备就绪,让我们定义一个简单的 CTE 来仅选择 'Sales' 部门的员工。

WITH SalesEmployees AS (
    SELECT employee_id, first_name, last_name, salary
    FROM employees
    WHERE department = 'Sales'
)
SELECT * FROM SalesEmployees;

让我们分解一下这个查询:

  • WITH SalesEmployees AS (...): 这定义了一个名为 SalesEmployees 的 CTE。
  • 括号内的查询从 employees 表中选择 department 为 'Sales' 的员工。
  • SELECT * FROM SalesEmployees;: 这是从我们临时的 SalesEmployees 结果集中检索所有数据的主查询。

你应该看到以下输出,仅列出了来自 Sales 部门的员工:

+-------------+------------+-----------+----------+
| 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 工作正常。请保持在 MySQL shell 中进行下一步。

为层级数据创建递归 CTE

递归 CTEs 是查询层级数据(如组织结构图或嵌套类别)的强大功能。递归 CTE 通过自身引用来逐级处理树状结构。它包含一个“锚定成员”(起始点)和一个“递归成员”(迭代)。

在这一步中,你将创建一个表示员工层级的表,并使用递归 CTE 来显示它。

首先,在 MySQL shell 中,创建 employee_hierarchy 表。此表包含一个指向另一个员工 employee_idmanager_id,从而创建了层级结构。

CREATE TABLE IF NOT EXISTS employee_hierarchy (
    employee_id INT PRIMARY KEY,
    employee_name VARCHAR(50),
    manager_id INT
);

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);

在此结构中,'David' 是最高级别的经理,因为他的 manager_idNULL

现在,编写一个递归 CTE 来遍历此层级结构。需要使用 RECURSIVE 关键字。

WITH RECURSIVE EmployeeHierarchyCTE AS (
    -- Anchor member: selects the top-level manager
    SELECT employee_id, employee_name, manager_id, 0 AS level
    FROM employee_hierarchy
    WHERE manager_id IS NULL

    UNION ALL

    -- Recursive member: joins with itself to find subordinates
    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
)
SELECT * FROM EmployeeHierarchyCTE ORDER BY level, employee_name;

让我们分析一下这个查询:

  • **锚定成员 (Anchor Member)**:第一个 SELECT 语句查找层级的根(manager_idNULL 的地方),并为其分配 level 0。
  • **递归成员 (Recursive Member)**:第二个 SELECT 语句将 employee_hierarchy 与 CTE 本身 (EmployeeHierarchyCTE) 连接起来。它查找所有 manager_id 与 CTE 中已有的 employee_id 匹配的员工,并递增 level
  • UNION ALL: 此运算符将锚定成员和递归成员的结果合并。

查询将产生以下输出,显示完整的组织结构图及其层级:

+-------------+---------------+------------+-------+
| 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 shell 中进行最后一步。

将 CTE 与表进行 JOIN 操作

CTEs 可以像普通表一样与其他表进行连接。这对于将 CTE 的聚合数据与另一个表的详细数据结合起来非常有用。在这一步中,你将创建一个 CTE 来计算每个部门的平均薪资,然后将其连接回 employees 表。

在 MySQL shell 中,执行以下查询。它定义了一个名为 AvgSalaryByDepartment 的 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.department,
    e.salary,
    a.avg_salary
FROM
    employees e
JOIN
    AvgSalaryByDepartment a ON e.department = a.department;

以下是详细说明:

  • AvgSalaryByDepartment CTE 计算每个部门的平均薪资。
  • 主查询然后将 employees 表(别名为 e)与此 CTE(别名为 a)在 department 列上进行连接。
  • 这使你能够显示每个员工的薪资及其部门的平均薪资。

预期的输出将是:

+-------------+------------+------------+----------+--------------+
| employee_id | first_name | department | salary   | avg_salary   |
+-------------+------------+------------+----------+--------------+
|           1 | John       | Sales      | 60000.00 | 57500.000000 |
|           2 | Jane       | Marketing  | 75000.00 | 72500.000000 |
|           3 | Robert     | Sales      | 55000.00 | 57500.000000 |
|           4 | Emily      | IT         | 90000.00 | 90000.000000 |
|           5 | Michael    | Marketing  | 70000.00 | 72500.000000 |
+-------------+------------+------------+----------+--------------+
5 rows in set (0.00 sec)

你可以手动验证结果。例如,'Sales' 部门的平均薪资是 (60000 + 55000) / 2 = 57500,这与输出相符。这证实了你的查询工作正常。

你已成功将 CTE 与表连接。现在可以退出 MySQL shell。

exit;

总结

在此次实验中,你学习了如何在 MySQL 中有效地使用公共表表达式 (CTEs)。你从设置数据库和表开始,然后逐步创建了不同类型的 CTE。

你已学会:

  • 使用 WITH 子句定义一个简单的 CTE,用于过滤数据并提高查询的可读性。
  • 创建一个递归 CTE 来导航和显示表中的层级数据。
  • 将 CTE 与表连接,以将聚合结果与详细的行级数据结合起来,进行更复杂的分析。

这些技能对于编写清晰、可维护且强大的 SQL 查询至关重要,尤其是在处理复杂逻辑或数据结构时。