在数据驱动的时代,SQL 是数据分析和后端开发中不可或缺的技能。LeetCode 的 1075 题 “Project Employees I” 是一道经典的聚合查询问题,它要求我们根据项目分组,计算每位员工平均经验年限。虽然题目本身看似简单,但它背后涉及了 SQL 中的 JOINGROUP BY 以及 ROUND 函数的使用,是理解数据聚合逻辑的绝佳练习。本文将深入剖析这道题,并分享一些实用的 SQL 优化技巧,帮助你提升在数据库操作中的效率。

问题解析:理解表结构与业务需求

在开始编写 SQL 之前,我们必须清晰地理解数据模型。题目提供了两张表:ProjectEmployee。其中,Project 表记录了每个项目与员工的关联关系,而 Employee 表则存储了员工的基本信息,包括 experience_years(经验年限)。核心需求是:对于每个项目,计算所有参与员工的平均经验年限,并保留两位小数

这里的关键点在于,Project 表并不直接包含经验数据,因此我们需要通过 employee_id 将两张表关联起来。这种通过外键关联多张表的操作,在 SQL 中非常常见,也是数据仓库和业务报表的基础。理解表之间的逻辑关系,是写出高效查询的第一步。

小提示:在实际业务场景中,这种关联查询常常用于计算 KPI 指标,比如“每个部门的平均绩效”、“每个项目的平均成本”等。掌握这种模式,能让你在数据分析工作中游刃有余。

⚙️ 核心解法:JOIN + GROUP BY + ROUND

解决这道题的核心思路可以分为三步:

  1. 关联表:使用 JOINProjectEmployee 通过 employee_id 连接起来,这样每一行就包含了项目信息和对应员工的经验年限。
  2. 分组计算:使用 GROUP BYproject_id 分组,然后对每组内的 experience_years 求平均值,即 AVG(experience_years)
  3. 格式化输出:使用 ROUND 函数将平均值四舍五入到两位小数,确保输出符合题目要求。

下面是对应的 SQL 查询代码,它直接体现了上述逻辑:

# Write your MySQL query statement below
SELECT
p.project_id,
ROUND(AVG(e.experience_years), 2) AS average_years
FROM Project p
JOIN Employee e
ON p.employee_id = e.employee_id
GROUP BY p.project_id;

这段代码简洁明了,但背后涉及了 SQL 引擎的执行顺序。通常,数据库会先执行 FROMJOIN 操作,生成一个临时结果集,然后进行 GROUP BY 分组,最后执行 SELECT 中的聚合函数。理解这个顺序有助于我们调优查询性能。

⚠️ 注意事项:在 GROUP BY 查询中,只能选择分组列或聚合函数,否则会报错。这也是新手常犯的错误之一。

深入探讨:SQL 聚合与性能优化

虽然本题的解法很简单,但我们可以从中引申出更深入的 SQL 知识。例如,JOIN 的类型选择会影响查询结果。这里我们使用了内连接(INNER JOIN),它只返回两个表中匹配的行。如果某个项目没有员工,或者某个员工没有被分配到任何项目,这些记录将被忽略。在题目中,这符合逻辑,因为我们只关心有员工参与的项目。

另外,索引 在关联查询中至关重要。如果 Project 表的 employee_idEmployee 表的 employee_id 没有索引,那么当数据量较大时,查询会变得非常缓慢。在真实环境中,我们通常会在外键列上建立索引,以加速 JOIN 操作。

此外,ROUND 函数的行为也值得注意。它接受两个参数:要舍入的数字和保留的小数位数。在 MySQL 中,ROUND(2.675, 2) 的结果是 2.67,而不是 2.68,这是因为浮点数的精度问题。在金融或统计应用中,我们可能需要使用 DECIMAL 类型来避免这种误差。

最佳实践:在实际开发中,建议先使用 EXPLAIN 查看查询执行计划,确保索引被正确使用,并避免全表扫描。

谈到 SQL 优化,不得不提的是,聚合查询往往涉及大量数据的扫描。如果业务数据量巨大,我们可能需要考虑使用数据仓库技术(如 ClickHouse、BigQuery)或进行预聚合。但对于 LeetCode 这类题目,掌握基础语法和逻辑是首要任务。

延伸思考:如果题目要求同时输出项目名称(假设有 ProjectName 列),我们还需要在 GROUP BY 中加入该列,或者使用窗口函数。这些高级技巧将在其他题目中遇到,但基础永远是最重要的。

在解决这类问题时,我推荐大家多练习类似的题目,比如 “1076. Project Employees II” 或 “577. Employee Bonus”,它们都涉及多表关联和聚合。通过对比练习,你可以更牢固地掌握 SQL 的核心概念。

[AFFILIATE_SLOT_1]

完整示例与验证

让我们通过一个具体的例子来验证我们的查询。假设 Project 表有以下数据:

Input:
Project table:
±------------±------------+
| project_id | employee_id |
±------------±------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 4 |
±------------±------------+
Employee table:
±------------±-------±-----------------+
| employee_id | name | experience_years |
±------------±-------±-----------------+
| 1 | Khaled | 3 |
| 2 | Ali | 2 |
| 3 | John | 1 |
| 4 | Doe | 2 |
±------------±-------±-----------------+
Output:
±------------±--------------+
| project_id | average_years |
±------------±--------------+
| 1 | 2.00 |
| 2 | 2.50 |
±------------±--------------+
Explanation: The average experience years for the first project is (3 + 2 + 1) / 3 = 2.00 and for the second project is (3 + 2) / 2 = 2.50

根据上述数据,项目 1 有员工 1(3年经验)和员工 2(2年经验),平均值为 2.50;项目 2 只有员工 1(3年经验),平均值为 3.00。我们的查询结果应该与之一致。这验证了我们的 SQL 正确性。

验证要点:在编写完查询后,务必使用题目提供的示例数据测试,确保输出格式完全匹配,包括小数位数和列名。

总结与展望

通过 LeetCode 1075 题,我们不仅学会了如何计算分组平均值,还复习了 SQL 中的 JOIN、GROUP BY 和 ROUND 函数。这些基础技能在数据分析和后端开发中无处不在,尤其是在处理与 自然语言处理机器学习 相关的数据预处理时,SQL 是提取和清洗数据的重要工具。例如,在构建 神经网络 训练集时,我们常常需要从数据库中聚合特征,而高效的 SQL 查询能显著提升数据流水线的效率。掌握这些知识,是通往 AI深度学习 领域的数据工程师之路的基石。

最后,建议读者在 LeetCode 上继续练习其他 SQL 题目,并尝试使用不同的写法(如 CTE、窗口函数)来对比性能。记住,SQL 不只是语法,更是逻辑思维的体现。希望这篇文章能帮助你巩固 SQL 基础,为更复杂的数据挑战做好准备。

[AFFILIATE_SLOT_2]

如果你觉得这篇文章有帮助,欢迎分享给更多正在学习 SQL 的朋友。我们下期再见!