Skip to content

MySQL - HAVING 子句

在 MySQL 中,HAVING 子句用于基于分组数据来过滤查询结果。它在数据使用 GROUP BY 子句分组之后,以及任何聚合函数(如 COUNT()、SUM()、AVG())计算之后应用。

这使得它与 WHERE 子句有着根本性的不同。WHERE 子句在行被分组之前过滤单个行,而 HAVING 子句在行被聚合之后过滤整个行组。

关键区别在于查询的执行顺序。MySQL 按照逻辑顺序处理查询:

  1. FROM / JOIN: 收集初始的行集合。
  2. WHERE: 根据条件过滤单个行。
  3. GROUP BY: 将过滤后的行聚合成分组。
  4. HAVING: 根据条件过滤新创建的分组。
  5. SELECT: 选择最终的列。
  6. ORDER BY: 对最终结果集进行排序。
  7. LIMIT: 限制返回的行数。

由于 WHERE 在 GROUP BY 之前处理,因此您不能在 WHERE 子句中使用聚合函数。为此,您必须使用 HAVING。

HAVING 子句的基本语法如下:

SELECT
column_name(s),
aggregate_function(column_name)
FROM
table_name
WHERE
condition(s) -- 可选:在分组前过滤行
GROUP BY
column_name(s)
HAVING
aggregate_condition(s); -- 在聚合后过滤分组
ORDER BY
column_name(s); -- 可选:对最终结果进行排序

让我们建立一个真实的表来操作。我们将创建一个 employees 表来存储员工数据。

-- 创建 employees 表
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
department VARCHAR(50) NOT NULL,
salary DECIMAL(10, 2) NOT NULL,
hire_date DATE NOT NULL
);
-- 插入示例数据
INSERT INTO employees (first_name, last_name, department, salary, hire_date) VALUES
('Ayla', 'Chen', 'Engineering', 95000.00, '2021-06-15'),
('Ben', 'Carter', 'Engineering', 110000.00, '2020-02-01'),
('Chloe', 'Davis', 'Sales', 72000.00, '2022-08-20'),
('David', 'Miller', 'Sales', 68000.00, '2021-11-10'),
('Emma', 'Wilson', 'HR', 65000.00, '2023-01-05'),
('Finn', 'Rodriguez', 'Engineering', 125000.00, '2019-09-01'),
('Grace', 'Lee', 'Sales', 88000.00, '2020-05-25');

让我们验证一下表的内容:

SELECT * FROM employees;
idfirst_namelast_namedepartmentsalaryhire_date
1AylaChenEngineering95000.002021-06-15
2BenCarterEngineering110000.002020-02-01
3ChloeDavisSales72000.002022-08-20
4DavidMillerSales68000.002021-11-10
5EmmaWilsonHR65000.002023-01-05
6FinnRodriguezEngineering125000.002019-09-01
7GraceLeeSales88000.002020-05-25

让我们找出员工数量多于一个的部门。

SELECT
department,
COUNT(id) AS number_of_employees
FROM
employees
GROUP BY
department
HAVING
COUNT(id) > 1;

输出:

departmentnumber_of_employees
Engineering3
Sales3

现在,让我们找出总工资(薪资之和)超过 $250,000 的部门。

SELECT
department,
SUM(salary) AS total_payroll
FROM
employees
GROUP BY
department
HAVING
SUM(salary) > 250000;

输出:

departmenttotal_payroll
Engineering330000.00

我们还可以过滤出平均工资低于 $100,000 的部门。

SELECT
department,
AVG(salary) as average_salary
FROM
employees
GROUP BY
department
HAVING
AVG(salary) < 100000;

输出:

departmentaverage_salary
Sales76000.00
HR65000.00

您可以在同一个查询中同时使用 WHERE 和 HAVING。例如,让我们找出员工数量多于一个的部门,但只考虑 2021 年以后入职的员工。

SELECT
department,
COUNT(id) AS new_hires
FROM
employees
WHERE
hire_date >= '2021-01-01' -- 在分组前过滤行
GROUP BY
department
HAVING
COUNT(id) > 1; -- 在分组后过滤分组

输出:

departmentnew_hires
Sales2
  • 在 WHERE 中使用聚合函数: 像 SELECT department, COUNT(*) FROM employees WHERE COUNT(*) > 1; 这样的查询会失败。您必须使用 HAVING 来对聚合结果进行条件筛选。
  • 在 HAVING 中使用非聚合列: HAVING 中的条件必须是聚合函数,或者是 GROUP BY 子句中也包含的列。例如,HAVING salary > 80000 将是无效的,除非 salary 也包含在 GROUP BY 列表中。
  • 混淆别名: 尽管 MySQL 非标准地允许您在 HAVING 子句中使用 SELECT 列表中的别名(例如,HAVING total_payroll > 250000),但这在其他 SQL 数据库(如 PostgreSQL 或 SQL Server)中不具备可移植性。为了更好的兼容性,最佳实践是重复聚合函数:HAVING SUM(salary) > 250000。

在应用程序中使用 HAVING(Python 示例)

Section titled “在应用程序中使用 HAVING(Python 示例)”

在实际应用程序中,您会从后端代码执行这些查询。这是一个完整的、现代的 Python 示例,使用了 mysql-connector-python 库。它演示了使用预处理语句和安全管理凭据的最佳实践。

First, ensure you have the necessary library and a way to manage secrets:
1. **Install libraries:** `pip install mysql-connector-python python-dotenv`
2. **Create a `.env` file** in your project directory to store your database credentials. Never hard-code them in your script.
```.env
DB_HOST=localhost
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=your_db_name
现在,这是用于查找平均工资高于某个阈值的部门的 Python 脚本。
```python
import mysql.connector
import os
from dotenv import load_dotenv
# 从 .env 文件加载环境变量
load_dotenv()
def find_high_paying_departments(min_avg_salary):
"""查找平均工资高于给定阈值的部门。"""
departments = []
try:
# 建立安全连接
with mysql.connector.connect(
host=os.getenv('DB_HOST'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
database=os.getenv('DB_NAME')
) as conn:
with conn.cursor(dictionary=True) as cursor:
# 查询使用 GROUP BY 和 HAVING
query = ("""
SELECT
department,
ROUND(AVG(salary), 2) as average_salary
FROM
employees
GROUP BY
department
HAVING
AVG(salary) > %s
ORDER BY
average_salary DESC
""")
# 使用元组作为参数以防止 SQL 注入
cursor.execute(query, (min_avg_salary,))
departments = cursor.fetchall()
except mysql.connector.Error as e:
print(f"连接 MySQL 或执行查询时出错: {e}")
return None
return departments
if __name__ == "__main__":
# 查找平均工资大于 $80,000 的部门
high_paying_depts = find_high_paying_departments(80000)
if high_paying_depts is not None:
if high_paying_depts:
print("找到高薪部门:")
for dept in high_paying_depts:
print(f"- 部门: {dept['department']}, 平均工资: ${dept['average_salary']}")
else:
print("没有部门符合条件。")

这个脚本是健壮、安全的,并演示了 HAVING 子句在生成业务洞察方面的实际应用。