MySQL - HAVING 子句
MySQL: HAVING 子句
Section titled “MySQL: HAVING 子句”在 MySQL 中,HAVING 子句用于基于分组数据来过滤查询结果。它在数据使用 GROUP BY 子句分组之后,以及任何聚合函数(如 COUNT()、SUM()、AVG())计算之后应用。
这使得它与 WHERE 子句有着根本性的不同。WHERE 子句在行被分组之前过滤单个行,而 HAVING 子句在行被聚合之后过滤整个行组。
理解区别:WHERE 与 HAVING
Section titled “理解区别:WHERE 与 HAVING”关键区别在于查询的执行顺序。MySQL 按照逻辑顺序处理查询:
- FROM / JOIN: 收集初始的行集合。
- WHERE: 根据条件过滤单个行。
- GROUP BY: 将过滤后的行聚合成分组。
- HAVING: 根据条件过滤新创建的分组。
- SELECT: 选择最终的列。
- ORDER BY: 对最终结果集进行排序。
- LIMIT: 限制返回的行数。
由于 WHERE 在 GROUP BY 之前处理,因此您不能在 WHERE 子句中使用聚合函数。为此,您必须使用 HAVING。
HAVING 子句的基本语法如下:
SELECT column_name(s), aggregate_function(column_name)FROM table_nameWHERE 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;| id | first_name | last_name | department | salary | hire_date |
|---|---|---|---|---|---|
| 1 | Ayla | Chen | Engineering | 95000.00 | 2021-06-15 |
| 2 | Ben | Carter | Engineering | 110000.00 | 2020-02-01 |
| 3 | Chloe | Davis | Sales | 72000.00 | 2022-08-20 |
| 4 | David | Miller | Sales | 68000.00 | 2021-11-10 |
| 5 | Emma | Wilson | HR | 65000.00 | 2023-01-05 |
| 6 | Finn | Rodriguez | Engineering | 125000.00 | 2019-09-01 |
| 7 | Grace | Lee | Sales | 88000.00 | 2020-05-25 |
1. HAVING 与 COUNT()
Section titled “1. HAVING 与 COUNT()”让我们找出员工数量多于一个的部门。
SELECT department, COUNT(id) AS number_of_employeesFROM employeesGROUP BY departmentHAVING COUNT(id) > 1;输出:
| department | number_of_employees |
|---|---|
| Engineering | 3 |
| Sales | 3 |
2. HAVING 与 SUM()
Section titled “2. HAVING 与 SUM()”现在,让我们找出总工资(薪资之和)超过 $250,000 的部门。
SELECT department, SUM(salary) AS total_payrollFROM employeesGROUP BY departmentHAVING SUM(salary) > 250000;输出:
| department | total_payroll |
|---|---|
| Engineering | 330000.00 |
3. HAVING 与 AVG()
Section titled “3. HAVING 与 AVG()”我们还可以过滤出平均工资低于 $100,000 的部门。
SELECT department, AVG(salary) as average_salaryFROM employeesGROUP BY departmentHAVING AVG(salary) < 100000;输出:
| department | average_salary |
|---|---|
| Sales | 76000.00 |
| HR | 65000.00 |
4. 结合 WHERE 和 HAVING
Section titled “4. 结合 WHERE 和 HAVING”您可以在同一个查询中同时使用 WHERE 和 HAVING。例如,让我们找出员工数量多于一个的部门,但只考虑 2021 年以后入职的员工。
SELECT department, COUNT(id) AS new_hiresFROM employeesWHERE hire_date >= '2021-01-01' -- 在分组前过滤行GROUP BY departmentHAVING COUNT(id) > 1; -- 在分组后过滤分组输出:
| department | new_hires |
|---|---|
| Sales | 2 |
- 在
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.
```.envDB_HOST=localhostDB_USER=your_userDB_PASSWORD=your_passwordDB_NAME=your_db_name现在,这是用于查找平均工资高于某个阈值的部门的 Python 脚本。
```pythonimport mysql.connectorimport osfrom 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 子句在生成业务洞察方面的实际应用。