跳到主要内容

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 过滤、排序与分页