前两篇介绍了模型定义,这一篇介绍一下数据查询,这应该是日常开发中用得最多的部分。
一、基础查询
1. 查询所有
# 方式一:query(传统写法)
users = session.query(User).all()
# 方式二:select(2.0 推荐写法)
from sqlalchemy import select
result = session.execute(select(User))
users = result.scalars().all()
两种写法对比:
| 写法 | 风格 | 推荐度 |
|---|---|---|
session.query(User) |
1.x 传统 | 仍可用 |
select(User) |
2.0 新风格 | 推荐 |
2. 查询单条
# 按主键查
user = session.get(User, 1) # 按主键 ID 查询
# 查第一条
user = session.query(User).first()
# 查唯一一条(不存在返回 None,多条报错)
user = session.query(User).filter_by(name="Alice").one_or_none()
3. 条件查询
# filter_by:简单等值条件
users = session.query(User).filter_by(name="Alice").all()
# filter:支持复杂表达式
users = session.query(User).filter(User.age > 18).all()
users = session.query(User).filter(User.age >= 18, User.is_active == True).all()
filter_by vs filter:
| 方法 | 写法 | 说明 |
|---|---|---|
filter_by |
filter_by(name="Alice") |
只支持等值,写法简单 |
filter |
filter(User.name == "Alice") |
支持所有表达式 |
4. 常用查询条件
from sqlalchemy import and_, or_, not_
# 等值
session.query(User).filter(User.name == "Alice")
# 不等
session.query(User).filter(User.age != 18)
# 大于/小于
session.query(User).filter(User.age > 18)
session.query(User).filter(User.age < 60)
# 范围
session.query(User).filter(User.age.between(18, 60))
# IN
session.query(User).filter(User.id.in_([1, 2, 3]))
# NOT IN
session.query(User).filter(~User.id.in_([1, 2, 3]))
# LIKE
session.query(User).filter(User.name.like("A%")) # 以 A 开头
session.query(User).filter(User.name.like("%a%")) # 包含 a
# IS NULL / IS NOT NULL
session.query(User).filter(User.email.is_(None))
session.query(User).filter(User.email.isnot(None))
# 逻辑组合
session.query(User).filter(and_(User.age > 18, User.is_active == True))
session.query(User).filter(or_(User.age < 18, User.age > 60))
session.query(User).filter(not_(User.is_active == True))
二、排序、分页、去重
1. 排序
# 升序
session.query(User).order_by(User.age).all()
# 降序
session.query(User).order_by(User.age.desc()).all()
# 多字段排序
session.query(User).order_by(User.age.desc(), User.name.asc()).all()
2. 分页
# 方式一:offset + limit
users = session.query(User).offset(20).limit(10).all() # 第 21-30 条
# 方式二:切片
users = session.query(User).all()[20:30]
3. 去重
# 去重
session.query(User.age).distinct().all()
# 统计不同年龄的数量
from sqlalchemy import distinct, func
count = session.query(func.count(distinct(User.age))).scalar()
三、聚合与分组
1. 常用聚合函数
from sqlalchemy import func
# 总数
total = session.query(func.count(User.id)).scalar()
# 平均值
avg_age = session.query(func.avg(User.age)).scalar()
# 最大值/最小值
max_age = session.query(func.max(User.age)).scalar()
min_age = session.query(func.min(User.age)).scalar()
# 求和
total_amount = session.query(func.sum(Order.amount)).scalar()
2. 分组查询
# 按年龄分组,统计每个年龄的人数
results = session.query(
User.age,
func.count(User.id).label("count")
).group_by(User.age).all()
for age, count in results:
print(f"年龄 {age}: {count} 人")
3. 分组后过滤(HAVING)
results = session.query(
User.age,
func.count(User.id).label("count")
).group_by(User.age).having(func.count(User.id) > 5).all()
四、关联查询
1. 通过关系属性访问(懒加载)
# 查用户
user = session.query(User).first()
# 通过关系属性访问订单(此时才发 SQL)
for order in user.orders:
print(order.amount)
问题:N+1 查询问题------查 1 个用户发 1 条 SQL,访问 orders 又发 N 条。
2. 预加载(解决 N+1)
from sqlalchemy.orm import joinedload, selectinload
# joinedload:用 JOIN 一次性加载
users = session.query(User).options(joinedload(User.orders)).all()
# selectinload:用 IN 查询批量加载(推荐)
users = session.query(User).options(selectinload(User.orders)).all()
# 现在访问 user.orders 不会再发 SQL
for user in users:
for order in user.orders:
print(order.amount)
joinedload vs selectinload:
| 方法 | 原理 | 适用场景 |
|---|---|---|
joinedload |
LEFT JOIN | 一对一、多对一 |
selectinload |
IN 查询 | 一对多、多对多(推荐) |
3. 显式 JOIN
# 查询用户及其订单金额
results = session.query(User.name, Order.amount)\
.join(Order, User.id == Order.user_id)\
.all()
for name, amount in results:
print(f"{name}: {amount}")
4. 左连接
# 查询所有用户,包括没有订单的
results = session.query(User.name, Order.amount)\
.outerjoin(Order, User.id == Order.user_id)\
.all()
5. JOIN 时带条件
# 只查金额大于 100 的订单
results = session.query(User.name, Order.amount)\
.join(Order, and_(User.id == Order.user_id, Order.amount > 100))\
.all()
五、增删改操作
1. 插入
# 单条插入
user = User(name="Alice", email="alice@example.com")
session.add(user)
session.commit()
# 批量插入
users = [User(name=f"user{i}") for i in range(100)]
session.add_all(users)
session.commit()
# 批量插入(性能更好)
session.bulk_save_objects(users)
session.commit()
2. 更新
# 方式一:先查后改
user = session.query(User).filter_by(name="Alice").first()
user.age = 26
session.commit()
# 方式二:批量更新(不加载对象)
session.query(User).filter(User.age < 18).update({"is_active": False})
session.commit()
# 方式三:自增更新
session.query(User).filter(User.id == 1).update({"age": User.age + 1})
session.commit()
3. 删除
# 方式一:删除对象
user = session.query(User).filter_by(name="Alice").first()
session.delete(user)
session.commit()
# 方式二:批量删除
session.query(User).filter(User.age < 18).delete()
session.commit()
4. 插入或更新(Upsert)
from sqlalchemy.dialects.mysql import insert
stmt = insert(User).values(name="Alice", email="alice@example.com")
stmt = stmt.on_duplicate_key_update(age=stmt.inserted.age)
session.execute(stmt)
session.commit()
六、原生 SQL
有时候复杂查询用原生 SQL 更高效。
from sqlalchemy import text
# 执行原生 SQL
result = session.execute(
text("SELECT * FROM users WHERE age > :age"),
{"age": 18}
)
for row in result:
print(row.id, row.name)
# 返回字典
result = session.execute(text("SELECT * FROM users"))
for row in result.mappings():
print(row["name"])
