Skip to content

第 4 章 记录查询 ​

从一个生活场景说起 ​

查询是数据库操作中最高频的动作,它很像在图书馆找书:

  • "把所有书都搬给我看看" → select(user_table),查全表;
  • "我只要书名和作者这两栏信息" → 查询部分列;
  • "只要 2020 年以后出版的书" → .where() 加过滤条件;
  • "按出版年份从新到旧排" → .order_by() 排序;
  • "先给我前 10 本" → .limit(10) 限量。

SQL 查询本质上就是把人类的找书需求,翻译成图书馆管理员(数据库)听得懂的指令。SQLAlchemy 的 select() 就是用 Python 代码拼这条指令的工具。

核心概念 ​

查询三步走 ​

  1. select(...):构建查询语句(写下"我要找什么");
  2. conn.execute(stmt):把查询发给 MySQL 执行;
  3. 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() 存成列表。