3.2 查:查询数据
查询是 CRUD 里内容最多、最重要的部分。本节讲透 2.0 风格查询的基本骨架:
select()构建语句 +session执行,以及各种取结果的方式。
前置:使用 3.1 节 建好的
models.py和已插入的数据。
一、查询的两段式结构
SQLAlchemy 2.0 的查询永远分两步:
from sqlalchemy import select
# 第 1 步:构建语句(纯内存操作,没碰数据库)
stmt = select(User).where(User.age >= 18)
# 第 2 步:交给 session 执行(此刻才真正发 SQL)
users = session.scalars(stmt).all()
stmt 只是一个"SQL 语句的描述对象",你甚至可以打印它看到生成的 SQL:
print(stmt)
# SELECT users.id, users.name, users.email, users.age, users.city, users.created_at
# FROM users
# WHERE users.age >= :age_1
📌 网上老教程的
session.query(User).filter(...)是 1.x 语法。对照关系:session.query(X)→select(X),.filter()→.where(),.all()位置不变。
二、执行查询:scalars vs execute
这是新手最容易混乱的地方,一次讲清。
session.scalars(stmt) —— 查整个对象时用(最常用)
stmt = select(User)
users = session.scalars(stmt).all() # [User(...), User(...), ...]
查模型对象就用 scalars,拿到的直接是 User 对象列表。
session.execute(stmt) —— 查多个列/混合内容时用
stmt = select(User.name, User.age) # 只查两列
rows = session.execute(stmt).all() # [('张三', 25), ('李四', 30), ...]
for row in rows:
print(row.name, row.age) # Row 对象支持属性访问
# 也可以解包: name, age = row
为什么会有这个区别?
execute 返回的每一行都是 Row 对象(类似命名元组)。当你 select(User) 时,每行其实是 (User对象,) ——一个只有一个元素的 Row:
rows = session.execute(select(User)).all()
print(rows[0]) # (User(id=1, name='张三', ...),) ← 套了一层!
print(rows[0][0]) # User 对象本尊
scalars 的作用就是自动帮你剥掉这层壳,取每行的第一个元素。
记忆口诀:查整个模型 → scalars;查指定的列 → execute。
三、取结果的各种姿势
stmt = select(User).where(User.city == "北京")
# ① .all() —— 全部结果,返回 list(空结果 = 空列表)
users = session.scalars(stmt).all()
# ② .first() —— 第一条,没有则 None(自动加 LIMIT 语义,安全常用)
user = session.scalars(stmt).first()
# ③ .one() —— 有且只有一条,否则抛异常(0条抛 NoResultFound,多条抛 MultipleResultsFound)
user = session.scalars(stmt).one()
# ④ .one_or_none() —— 至多一条:1条返回对象,0条返回 None,多条抛异常
user = session.scalars(stmt).one_or_none()
# ⑤ 直接迭代 —— 大结果集时省内存
for user in session.scalars(stmt):
print(user.name)
选择建议:
| 场景 | 用法 |
|---|---|
| 列表页、批量处理 | .all() |
| "找一个符合条件的" | .first() |
| 按唯一字段查(如 email),坚信只有一条 | .one_or_none() |
| 数据不存在就是 bug 的场合 | .one() |
快捷方式:session.scalar()(单数!)
查"一个值/一个对象"的简写,等价于 session.scalars(stmt).first():
user = session.scalar(select(User).where(User.email == "zhangsan@example.com"))
# 配合聚合函数查单个数字特别好用
from sqlalchemy import func
count = session.scalar(select(func.count()).select_from(User))
print(f"共 {count} 个用户")
四、按主键查:session.get()
user = session.get(User, 1) # 查主键 id=1,没有则返回 None
比 select(User).where(User.id == 1) 更简洁,且有 Identity Map 缓存加成(同一 session 内重复 get 不会重复查库)。按 id 查就用它。
五、只查部分列
表字段很多但只需要两列时,避免捞整个对象:
stmt = select(User.name, User.email)
for name, email in session.execute(stmt):
print(name, email)
生成的 SQL 只 SELECT 这两列,网络传输和内存都省了。
六、去重:distinct
stmt = select(User.city).distinct()
cities = session.scalars(stmt).all() # ['北京', '上海', '深圳']
七、完整可运行示例
# query_demo.py
from sqlalchemy import select, func
from models import User, SessionLocal
with SessionLocal() as session:
# 1. 查所有用户
all_users = session.scalars(select(User)).all()
print(f"全部用户:{all_users}")
# 2. 按主键查
first = session.get(User, 1)
print(f"id=1 的用户:{first}")
# 3. 按唯一字段查一个
lisi = session.scalar(select(User).where(User.email == "lisi@example.com"))
print(f"按邮箱查:{lisi}")
# 4. 只查两列
print("姓名和城市:")
for name, city in session.execute(select(User.name, User.city)):
print(f" {name} - {city}")
# 5. 统计总数
total = session.scalar(select(func.count()).select_from(User))
print(f"用户总数:{total}")
# 6. 城市去重
cities = session.scalars(select(User.city).distinct()).all()
print(f"覆盖城市:{cities}")
八、新手避坑
坑1:忘了执行,直接遍历 stmt
stmt = select(User)
for user in stmt: # ❌ stmt 是语句对象,不是结果!
...
for user in session.scalars(stmt): # ✅
...
坑2:对 scalars 的结果再取下标当 Row 用
users = session.scalars(select(User)).all()
print(users[0][0]) # ❌ users[0] 已经是 User 对象,不是元组
print(users[0].name) # ✅
坑3:.one() 的异常没处理
数据可能为 0 条的场景用 .one_or_none() 或 .first(),别用 .one() 裸奔。
📝 本节小结
- 查询两段式:
select()构建 →session.scalars/execute执行 - 查整个对象用
scalars,查指定列用execute - 取结果:
.all()/.first()/.one()/.one_or_none()/ 直接迭代 - 单值快捷方式
session.scalar();按主键用session.get() select(User.name, User.email)可只查部分列
下一节给查询加上真正的火力 → 3.3 过滤、排序与分页