第 4 章 记录查询
从一个生活场景说起
查询是数据库操作中最高频的动作,它很像在图书馆找书:
- "把所有书都搬给我看看" →
select(user_table),查全表; - "我只要书名和作者这两栏信息" → 查询部分列;
- "只要 2020 年以后出版的书" →
.where()加过滤条件; - "按出版年份从新到旧排" →
.order_by()排序; - "先给我前 10 本" →
.limit(10)限量。
SQL 查询本质上就是把人类的找书需求,翻译成图书馆管理员(数据库)听得懂的指令。SQLAlchemy 的 select() 就是用 Python 代码拼这条指令的工具。
核心概念
查询三步走
select(...):构建查询语句(写下"我要找什么");conn.execute(stmt):把查询发给 MySQL 执行;result对象:MySQL 返回的"一摞书",你从里面取数据。
查询语句是"链式拼装"的
SQLAlchemy 的查询像搭积木,一个方法接在另一个后面,每加一块就多一点限制:
python
select(user_table.c.name) # 要什么
.where(...) # 满足什么条件
.order_by(...) # 怎么排序
.limit(10) # 要多少条table.c.列名 是什么?
user_table.c 是这张表的"列名册"(c 是 columns 的缩写),通过 user_table.c.name 就能引用 name 这一列,用来参与条件、排序或作为查询结果。
示例代码(逐行讲解)
python
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select
engine = create_engine(
"mysql+pymysql://root:123456@localhost:3306/demo_db?charset=utf8mb4",
echo=True,
)
metadata = MetaData()
user_table = Table(
"user", metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("name", String(50), nullable=False),
Column("age", Integer),
)
with engine.connect() as conn:
# ① 查询全部列:相当于 SELECT * FROM user
stmt = select(user_table)
result = conn.execute(stmt)
for row in result:
print(row.id, row.name, row.age) # row 可以像对象一样用属性取列值
# ② 查询部分列 + 条件过滤 + 排序 + 限量
# 相当于:SELECT name, age FROM user WHERE age >= 18
# ORDER BY age DESC LIMIT 10
stmt = (
select(user_table.c.name, user_table.c.age)
.where(user_table.c.age >= 18)
.order_by(user_table.c.age.desc())
.limit(10)
)
result = conn.execute(stmt)
print(result.all()) # 全部结果,list[Row]从 Result 中取数的四种方式
python
result.all() # 所有行,返回列表
result.first() # 第一行,没有则返回 None
result.scalar() # 第一行第一列的单个值(比如查 count 时最方便)
result.scalars() # 第一列的所有值组成的迭代器选择口诀:要几行?要几列?
- 多行多列 →
all();一行多列 →first(); - 只要一个值 →
scalar();只要一列 →scalars().all()。
关键知识点
| 方法 | 作用 |
|---|---|
select() | 构建查询 |
.where() | 过滤条件,可多次调用(AND 语义) |
.order_by() | 排序,.desc() 降序 |
.limit() / .offset() | 分页 |
table.c.列名 | 引用某张表的某一列 |
深入理解:Result 是一次性筷子
conn.execute() 返回的 Result 是一个游标(Cursor)——它像一根一次性筷子,从头到尾扫一遍就报废了:
python
result = conn.execute(select(user_table))
print(result.all()) # 正常输出所有行
print(result.all()) # 输出 []!因为数据已经被"读完了"需要反复使用结果时,先把数据取到 Python 列表里:
python
rows = result.all() # 数据落袋为安进阶内容
1. 条件组合:&、|、~。
python
# 年龄在 18~30 岁之间(AND)
.where((user_table.c.age >= 18) & (user_table.c.age <= 30))
# 姓张 或 姓李(OR)
.where((user_table.c.name == "张三") | (user_table.c.name == "李四"))注意:Python 的 and/or 关键字在这里不能用,必须用 &/|,且每个子条件都要加括号(因为 & 的运算优先级比 >= 高)。
2. 常用筛选器速查。
python
user_table.c.name.like("张%") # 模糊匹配:姓张的
user_table.c.name.in_(["张三", "李四"]) # 在列表中
user_table.c.age.is_(None) # IS NULL(不能用 == None)
user_table.c.age.between(18, 30) # 区间3. 分页的两种写法。
python
# 第 3 页,每页 10 条
.limit(10).offset(20)深分页警告:
LIMIT 10 OFFSET 1000000时 MySQL 要先扫过前 100 万行再丢弃,非常慢。大数据量翻页的优化做法是"主键游标":记住上一页最后一行的 id,下一页用.where(user_table.c.id > 上次的id).limit(10)。
注意事项
.where()中条件用==、>=、&(与)、|(或)组合,注意每个子条件要加括号:(A > 1) & (B < 5)。Result是一次性游标,遍历完就没了;需要多次使用请先result.all()存下来。- 2.0 中不再支持
engine.execute(sql)直接执行,必须先拿到Connection。 - 判断空值用
.is_(None),不要用== None(后者会被翻译成= NULL,在 SQL 里永远不为真)。 - MySQL 的
LIMIT分页在大offset时会变慢(深分页问题),真实项目中可改用"主键游标"方式翻页。
本章小结
- 查询三步走:
select()构建 →execute()执行 → 从Result取数; - 查询语句链式拼装:要什么列、什么条件、怎么排序、要多少条;
- 取数四方法:
all()/first()/scalar()/scalars(); Result是一次性的,多次使用先all()存成列表。