MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python中MySQL数据库的高级查询操作

2023-10-267.9k 阅读

多表关联查询

在实际的数据库应用中,数据往往分散存储在多个相关的表中。多表关联查询允许我们根据表之间的关系,从多个表中检索出有意义的数据组合。在Python与MySQL的结合使用中,实现多表关联查询是一项重要的技能。

JOIN类型及示例

INNER JOIN

INNER JOIN 是最常用的关联类型,它返回两个表中满足连接条件的所有行。假设有两个表,employees 表存储员工信息,departments 表存储部门信息,员工表中有一个 department_id 字段关联到部门表的 id 字段。

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="your_user",
    password="your_password",
    database="your_database"
)

mycursor = mydb.cursor()

sql = """
SELECT employees.name, departments.department_name
FROM employees
INNER JOIN departments ON employees.department_id = departments.id
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在上述代码中,通过 INNER JOINemployees 表和 departments 表连接起来,根据 employees.department_id = departments.id 这个条件,从两个表中获取员工姓名和对应的部门名称。

LEFT JOIN

LEFT JOIN(也称为 LEFT OUTER JOIN)返回左表(LEFT JOIN 关键字左边的表)中的所有行,以及右表中满足连接条件的行。如果右表中没有匹配的行,则结果集中对应列的值为 NULL

sql = """
SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments ON employees.department_id = departments.id
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在这个示例中,即使某个员工所在的部门在 departments 表中不存在(比如部门被删除但员工记录还保留),通过 LEFT JOIN 也能在结果集中看到该员工,只是其部门名称列为 NULL

RIGHT JOIN

RIGHT JOIN(也称为 RIGHT OUTER JOIN)返回右表(RIGHT JOIN 关键字右边的表)中的所有行,以及左表中满足连接条件的行。如果左表中没有匹配的行,则结果集中对应列的值为 NULL

sql = """
SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments ON employees.department_id = departments.id
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

此示例中,即使某个部门没有员工(可能是新成立的部门),通过 RIGHT JOIN 也能在结果集中看到该部门,只是员工姓名列为 NULL

FULL OUTER JOIN

FULL OUTER JOIN 返回左表和右表中的所有行。当某行在另一表中没有匹配行时,结果集中对应列的值为 NULL。然而,MySQL 本身不直接支持 FULL OUTER JOIN 关键字,但可以通过 LEFT JOINRIGHT JOIN 的联合查询来模拟。

sql = """
SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments ON employees.department_id = departments.id
UNION
SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments ON employees.department_id = departments.id
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

上述代码通过 LEFT JOINRIGHT JOINUNION 操作,模拟了 FULL OUTER JOIN 的效果,将两个结果集合并,包含了两个表中的所有行。

多表关联中的复杂条件

在实际应用中,多表关联查询的条件可能不仅仅是简单的相等关系,还可能包含其他逻辑条件。例如,我们可能需要在关联的同时筛选出满足特定条件的记录。

假设有三个表:orders(订单表)、customers(客户表)和 products(产品表)。订单表通过 customer_id 关联客户表,通过 product_id 关联产品表。我们想要查询购买了特定产品且客户所在城市为某一城市的订单信息。

sql = """
SELECT orders.order_id, customers.customer_name, products.product_name
FROM orders
INNER JOIN customers ON orders.customer_id = customers.customer_id
INNER JOIN products ON orders.product_id = products.product_id
WHERE products.product_name = 'Specific Product' AND customers.city = 'Specific City'
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在这个例子中,我们在 INNER JOIN 的基础上,使用 WHERE 子句添加了额外的筛选条件,使得查询结果更加精确地满足业务需求。

子查询

子查询是指在一个查询语句内部嵌套另一个查询语句。子查询可以在 SELECTFROMWHERE 等子句中使用,为复杂查询提供了更灵活的解决方案。

WHERE子句中的子查询

简单子查询示例

假设我们有一个 employees 表,存储员工信息,包括员工的薪资。我们想要找出薪资高于平均薪资的员工。

# 先获取平均薪资
sql_avg_salary = "SELECT AVG(salary) FROM employees"
mycursor.execute(sql_avg_salary)
avg_salary = mycursor.fetchone()[0]

# 使用子查询找出高于平均薪资的员工
sql = f"SELECT name, salary FROM employees WHERE salary > {avg_salary}"
mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在上述代码中,首先通过一个单独的查询获取平均薪资,然后在主查询的 WHERE 子句中使用这个平均薪资作为条件,找出薪资高于平均薪资的员工。

我们也可以将获取平均薪资的查询作为子查询直接嵌入到主查询中。

sql = """
SELECT name, salary FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees)
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

这样,通过在 WHERE 子句中使用子查询,我们可以在一个查询语句中完成复杂的筛选操作。

关联子查询

关联子查询与主查询相关联,子查询会引用主查询中的表或列。例如,在 orders 表和 customers 表中,订单表记录了每个订单的客户 ID 和订单金额,客户表记录了客户的信息。我们想要找出每个客户的订单金额大于该客户平均订单金额的订单。

sql = """
SELECT o.order_id, o.customer_id, o.order_amount
FROM orders o
WHERE o.order_amount > (
    SELECT AVG(sub_o.order_amount)
    FROM orders sub_o
    WHERE sub_o.customer_id = o.customer_id
)
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在这个关联子查询中,子查询根据主查询中 orders 表的每一行记录(每个订单),计算出对应的客户的平均订单金额,然后主查询根据这个平均金额筛选出订单金额大于平均金额的订单。

FROM子句中的子查询(派生表)

将子查询放在 FROM 子句中可以创建一个派生表。派生表就像一个临时表,可用于进一步的查询。

假设我们有一个 sales 表,记录了不同地区不同月份的销售数据。我们想要找出每个月销售总额排名前三的地区。

sql = """
SELECT sub.month, sub.region, sub.total_sales
FROM (
    SELECT month, region, SUM(sales_amount) AS total_sales,
        ROW_NUMBER() OVER (PARTITION BY month ORDER BY SUM(sales_amount) DESC) AS rank
    FROM sales
    GROUP BY month, region
) sub
WHERE sub.rank <= 3
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在这个例子中,内部子查询计算每个地区每月的销售总额,并使用 ROW_NUMBER() 函数为每个月的销售总额按降序排名。这个子查询作为一个派生表 sub,外部查询从这个派生表中筛选出每个月排名前三的记录。

SELECT子句中的子查询

SELECT 子句中使用子查询可以为结果集添加额外的计算列。例如,在 employees 表中,除了员工的基本薪资外,我们想要显示每个员工薪资占部门总薪资的比例。

sql = """
SELECT e.name, e.salary,
       (SELECT SUM(sub_e.salary) FROM employees sub_e WHERE sub_e.department_id = e.department_id) AS department_total_salary,
       e.salary / (SELECT SUM(sub_e.salary) FROM employees sub_e WHERE sub_e.department_id = e.department_id) AS salary_percentage
FROM employees e
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在上述代码中,SELECT 子句中的两个子查询分别计算了每个员工所在部门的总薪资,以及该员工薪资占部门总薪资的比例,并将这些结果作为额外的列添加到最终的查询结果中。

分组与聚合查询

分组与聚合查询是数据库查询中常用的操作,用于对数据进行汇总和统计。在Python与MySQL的交互中,我们可以利用聚合函数和 GROUP BY 子句来实现这些操作。

聚合函数

MySQL提供了多个聚合函数,如 SUM(求和)、AVG(求平均值)、COUNT(计数)、MIN(求最小值)和 MAX(求最大值)。

SUM函数示例

假设我们有一个 orders 表,记录了每个订单的金额。我们想要计算所有订单的总金额。

sql = "SELECT SUM(order_amount) AS total_amount FROM orders"
mycursor.execute(sql)

result = mycursor.fetchone()[0]
print(f"Total amount of all orders: {result}")

上述代码使用 SUM 函数计算了 orders 表中所有订单金额的总和,并将结果打印出来。

AVG函数示例

计算订单的平均金额。

sql = "SELECT AVG(order_amount) AS average_amount FROM orders"
mycursor.execute(sql)

result = mycursor.fetchone()[0]
print(f"Average amount of orders: {result}")

COUNT函数示例

统计订单的数量。

sql = "SELECT COUNT(order_id) AS order_count FROM orders"
mycursor.execute(sql)

result = mycursor.fetchone()[0]
print(f"Number of orders: {result}")

MIN和MAX函数示例

找出订单中的最小金额和最大金额。

sql_min = "SELECT MIN(order_amount) AS min_amount FROM orders"
sql_max = "SELECT MAX(order_amount) AS max_amount FROM orders"

mycursor.execute(sql_min)
min_result = mycursor.fetchone()[0]

mycursor.execute(sql_max)
max_result = mycursor.fetchone()[0]

print(f"Minimum order amount: {min_result}")
print(f"Maximum order amount: {max_result}")

GROUP BY子句

GROUP BY 子句用于将查询结果按照一个或多个列进行分组,然后对每个组应用聚合函数。

例如,在 orders 表中,我们想要按客户统计每个客户的订单数量和订单总金额。

sql = """
SELECT customer_id, COUNT(order_id) AS order_count, SUM(order_amount) AS total_amount
FROM orders
GROUP BY customer_id
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(f"Customer ID: {row[0]}, Order Count: {row[1]}, Total Amount: {row[2]}")

在这个例子中,通过 GROUP BY customer_id 将订单按客户进行分组,然后对每个客户组使用 COUNT 函数统计订单数量,使用 SUM 函数计算订单总金额。

HAVING子句

HAVING 子句用于对分组后的结果进行筛选,它与 WHERE 子句类似,但 WHERE 子句用于筛选行,而 HAVING 子句用于筛选分组。

假设我们只想显示订单总金额大于某个阈值的客户的统计信息。

threshold = 1000
sql = f"""
SELECT customer_id, COUNT(order_id) AS order_count, SUM(order_amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING SUM(order_amount) > {threshold}
"""

mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(f"Customer ID: {row[0]}, Order Count: {row[1]}, Total Amount: {row[2]}")

在上述代码中,HAVING SUM(order_amount) > {threshold} 筛选出了订单总金额大于指定阈值的客户组,并显示这些客户组的统计信息。

排序与分页查询

在处理大量数据时,排序和分页查询是非常重要的操作,它们可以帮助我们更好地组织和呈现数据。

排序查询

通过 ORDER BY 子句可以对查询结果进行排序。可以按升序(ASC)或降序(DESC)排序。

单字段排序示例

employees 表中,按薪资降序排列员工信息。

sql = "SELECT name, salary FROM employees ORDER BY salary DESC"
mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

上述代码将员工按薪资从高到低进行排序,并显示员工姓名和薪资。

多字段排序示例

假设我们想先按部门 ID 升序排序,在同一部门内再按薪资降序排序。

sql = "SELECT name, department_id, salary FROM employees ORDER BY department_id ASC, salary DESC"
mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在这个例子中,先按 department_id 升序排序,对于 department_id 相同的记录,再按 salary 降序排序。

分页查询

在处理大量数据时,分页可以避免一次性返回过多数据,提高查询效率和用户体验。MySQL 中通过 LIMIT 子句实现分页。

基本分页示例

假设我们每页显示 10 条记录,获取第一页的数据。

page_size = 10
page_number = 1
offset = (page_number - 1) * page_size

sql = f"SELECT * FROM employees LIMIT {offset}, {page_size}"
mycursor.execute(sql)

results = mycursor.fetchall()
for row in results:
    print(row)

在上述代码中,通过计算 offset(偏移量)和 page_size(每页记录数),使用 LIMIT 子句获取指定页的数据。

动态分页示例

如果我们想要实现一个可以动态获取不同页数据的功能,可以通过函数来实现。

def get_paginated_data(page_size, page_number):
    offset = (page_number - 1) * page_size
    sql = f"SELECT * FROM employees LIMIT {offset}, {page_size}"
    mycursor.execute(sql)
    return mycursor.fetchall()

page_size = 10
page_number = 2
results = get_paginated_data(page_size, page_number)
for row in results:
    print(row)

这个函数 get_paginated_data 接受每页记录数和页码作为参数,动态地获取并返回指定页的数据。

高级查询优化

在实际应用中,随着数据量的增长和查询复杂度的提高,查询性能优化变得至关重要。以下是一些高级查询优化的技巧。

索引优化

索引是提高查询性能的重要手段。合理地创建和使用索引可以大大减少查询所需的时间。

创建索引

假设我们经常在 orders 表中根据 customer_id 进行查询,为了提高查询速度,可以在 customer_id 列上创建索引。

sql_create_index = "CREATE INDEX idx_customer_id ON orders(customer_id)"
mycursor.execute(sql_create_index)

上述代码在 orders 表的 customer_id 列上创建了一个名为 idx_customer_id 的索引。

复合索引

当查询条件涉及多个列时,可以创建复合索引。例如,我们经常根据 customer_idorder_date 进行查询。

sql_create_compound_index = "CREATE INDEX idx_customer_date ON orders(customer_id, order_date)"
mycursor.execute(sql_create_compound_index)

复合索引的顺序很重要,MySQL 会优先使用索引的最左前缀。在这个例子中,查询条件中如果包含 customer_id,并且可能包含 order_date,这个复合索引将能有效提高查询性能。

查询语句优化

避免使用SELECT *

在查询时,尽量明确指定需要的列,而不是使用 SELECT *。因为 SELECT * 会返回表中的所有列,包括不必要的列,增加了数据传输和处理的开销。

例如,原本的查询 SELECT * FROM employees,如果我们只需要员工姓名和薪资,可以改为 SELECT name, salary FROM employees

优化子查询

对于复杂的子查询,可以考虑将其改写为连接查询,因为连接查询在某些情况下性能更好。例如,之前找出薪资高于平均薪资的员工的子查询示例:

# 子查询
sql_subquery = """
SELECT name, salary FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees)
"""

# 改写为连接查询
avg_salary_sql = "SELECT AVG(salary) AS avg_salary FROM employees"
mycursor.execute(avg_salary_sql)
avg_salary = mycursor.fetchone()[0]

sql_join = f"""
SELECT e.name, e.salary
FROM employees e
JOIN (SELECT {avg_salary} AS avg_salary) sub ON e.salary > sub.avg_salary
"""

mycursor.execute(sql_join)
results_join = mycursor.fetchall()
for row in results_join:
    print(row)

在这个例子中,将子查询改写为连接查询,在某些数据库环境下可能会有更好的性能表现。

数据库配置优化

适当调整MySQL的配置参数也可以提高查询性能。例如,innodb_buffer_pool_size 参数控制 InnoDB 存储引擎的缓冲池大小,增加这个值可以提高数据缓存的效率,减少磁盘 I/O 操作。

此外,合理设置 max_connections 参数,避免过多的连接占用系统资源,影响查询性能。

通过上述对Python中MySQL数据库高级查询操作的详细介绍,包括多表关联、子查询、分组聚合、排序分页以及查询优化等方面,希望能帮助开发者在实际项目中更高效地使用数据库,处理复杂的数据查询需求。在实际应用中,需要根据具体的业务场景和数据特点,灵活运用这些技术和优化手段。