SQLAlchemy的两种查询query()查询、stmt=select()查询

SQLAlchemy

SQLAlchemy 是一个强大的 Python ORM (对象关系映射)工具,它提供了多种方法来执行数据库查询操作。以下是 SQLAlchemy 中常用的查询方法的总结。

1.x

session.query():使用 session.query(Model) 来创建一个查询对象,其中 Model 是你要查询的数据库模型类。

filter():在查询对象上使用 filter() 方法可以添加过滤条件,例如 filter(Model.column == value)。

all():使用 all() 方法可以获取查询的所有结果,并以列表的形式返回。

first():使用 first() 方法可以获取查询结果的第一条记录。

get():使用 get(primary_key_value) 方法可以根据主键值直接获取相应的记录。

filter_by():使用 filter_by(column=value) 方法可以根据指定列的数值进行过滤。

join():使用 join() 方法可以进行表的内连接查询。

outerjoin():使用 outerjoin() 方法可以进行表的外连接查询。

group_by():使用 group_by() 方法可以按指定列分组查询。

order_by():使用 order_by() 方法可以对查询结果进行排序。

count():使用 count() 方法可以统计查询结果的数量。

delete():使用 delete() 方法可以删除满足条件的记录。

update():使用 update() 方法可以更新满足条件的记录。

python 复制代码
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import func

# 创建数据库连接
engine = create_engine('sqlite:///users_example.db')

Base = declarative_base()

# 定义模型类
class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)

# 创建数据表
Base.metadata.create_all(engine)

# 创建 Session 类
Session = sessionmaker(bind=engine)
session = Session()

# 事先清空数据
session.query(User).delete()
session.commit()

# 插入更多数据
users_data = [
    {'name': 'Charlie', 'age': 28},
    {'name': 'David', 'age': 32},
    {'name': 'Eve', 'age': 27},
    {'name': 'gemm', 'age': 43},
    {'name': 'riyu', 'age': 43}
]

for user_data in users_data:
    user = User(**user_data)
    session.add(user)
session.commit()

# 查询数据
query = session.query(User)

# 使用 filter() 方法添加过滤条件
result = query.filter(User.age > 25).all()
print("年龄大于25的用户:", [(user.name, user.age) for user in result])

# 使用 func.group_concat() 函数将同一年龄下的用户名合并成一个字符串
result = session.query(User.age, func.group_concat(User.name)).group_by(User.age).all()
print("按年龄分组查询:")
for age, names in result:
    name_list = names.split(',')
    print(f"年龄 {age} 的用户有:{', '.join(name_list)}")

# 使用 group_by() 方法按年龄分组查询(与上面等价的另一种 1.x 写法:从已有 query 改写查询列)
result = query.with_entities(User.age, func.group_concat(User.name)).group_by(User.age).all()
print("按年龄分组查询:")
for age, names in result:
    name_list = names.split(',')
    print(f"年龄 {age} 的用户有:{', '.join(name_list)}")

# 使用 order_by() 方法对结果进行排序
result = query.order_by(User.age).all()
print("按年龄排序:", [(user.name, user.age) for user in result])

# 统计查询结果的数量
count = query.count()
print("查询结果的数量:", count)

# 删除满足条件的记录
session.query(User).filter(User.name == 'Charlie').delete()
session.commit()

# 更新满足条件的记录
session.query(User).filter(User.name == 'David').update({User.age: 35})
session.commit()

# 执行 SELECT 查询(1.x 风格:一律走 session.query,不再混用 engine.connect + select)
# session.query(User) 查询的对象是 User 这个模型类,检索 users 表的所有列,返回 User 实例列表
result = session.query(User).filter(User.age > 25).all()
print("年龄大于25的用户(删除/更新后重查):", [(user.name, user.age) for user in result])

# 收尾:关闭会话释放连接
session.close()

query() 返回的是 Query 对象,不是数据 。它是惰性的(lazy)------你可以继续往上叠 .filter().order_by().offset(),全程只是在拼 SQL 字符串,一个网络包都没发。直到遇到终止操作才真正执行:

  • .all() → 执行,返回列表
  • .first() / .one() → 执行,返回单行
  • .count() → 执行 SELECT COUNT(*)

链式不可变 :每次 .filter(...) 都返回一个新的 Query 对象,所以代码里要写 result = query.filter(...) 把新对象接回来,光写 query.filter(...) 条件就丢了。

2.x

SQLAlchemy 2.0 是这个库十几年最大的一次重构,核心是统一了两套 API、全面拥抱类型标注和 select() 语法

传统查询 API --- SQLAlchemy 2.0 文档 - SQLAlchemy 文档

用于 Core 的相同 select() 构造也同样适用于 ORM,为构建查询提供了统一的接口。

对于任何在 2.0 API 之前基于 SQLAlchemy ORM 构建的应用程序,Query API 通常代表应用程序中绝大多数的数据库访问代码,因此,绝大多数 Query API 不会从 SQLAlchemy 中删除Query 对象在幕后现在将其自身转换为 2.0 风格的 select() 对象,当 Query 对象执行时,所以它现在只是一个非常薄的适配器 API。

变化如下:

1. 查询风格统一:select() 成为唯一主角

1.x 时代有两套查询 API 并存,新人很懵:

python 复制代码
# 1.x 老风格(咱们项目现在用的)
db.query(Competitor).filter(Competitor.name.ilike(like)).order_by(...).all()

# 2.x 原生风格
db.execute(
    select(Competitor).where(Competitor.name.ilike(like)).order_by(...)
).scalars().all()

区别不只是写法:

维度 query() (1.x) select() (2.x)
来源 只挂在 ORM Session 上 Core 和 ORM 共用同一个 select()
条件关键字 filter() / filter_by() where()(和 SQL 对齐)
执行方式 .all() 直接返回 execute()Result.scalars() 取 ORM 对象
语义 隐式"查整行 ORM 对象" 显式:查列、查对象、混合查都一个语法

优化点 :学会一个 select() 就通吃 ORM 查询、Core 查询、子查询、CTE,心智负担减半。.scalars() 这一步也让"我要的是 ORM 对象还是裸值"变得显式------1.x 里 query(Competitor.name)query(Competitor) 返回结构完全不同,经常踩坑。

2. where() 链式更直观

python 复制代码
# 1.x:filter 支持逗号隐式 AND,也支持 kwargs,规则杂
query.filter(a == 1, b == 2).filter_by(name="x")

# 2.x:只有 where(),多次调用即 AND,一种规则
select(Competitor).where(a == 1).where(b == 2)

动态拼条件(就是竞争对手接口那种 if q: ... if province: ...)在 2.x 里写法不变,照样 stmt = stmt.where(...) 叠上去。

3. 类型标注与 IDE 支持(2.0 的重头戏)

python 复制代码
# 2.x 推荐的声明式模型写法
class Competitor(Base):
    __tablename__ = "competitor"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(200), comment="单位名称")
    former_names: Mapped[list[str] | None] = mapped_column(JSON, comment="曾用名")
  • Mapped[...]mypy / IDE 能推断出 c.namestrc.former_nameslist | None,写错类型保存就报红。
  • select() 的返回类型也是泛型的,scalars().all() 推出来就是 list[Competitor],不再需要盲猜。
  • 咱们项目模型用的还是经典 Column 写法(兼容可用),换 Mapped 是纯增量收益。

4. 执行结果 Result 对象明确分层

python 复制代码
result = db.execute(select(Competitor))
result.all()            # list[Row],元组式
result.scalars().all()  # list[Competitor],只要第一列/实体
result.mappings().all() # list[dict 风格]
result.scalar_one()     # 恰好一个标量,多了少了都抛错

1.x 里 .one() / .first() / .scalar() 的行为差异藏在文档里,2.x 用方法名把"期望什么形状"写明白了。

5. 性能与内部优化

  • ORM 批量 INSERT 大幅提速 :2.0 重写了 unit of work 的 flush 路径,批量插入会按表分组、复用语句、用 executemany,大批量导入场景(比如咱们的 xlsx 导入脚本)比 1.4 快几倍。
  • 连接池/预编译缓存更聪明:语句编译缓存命中率提升,重复结构的查询(分页接口每秒打很多次那种)编译开销更低。
  • Session 自动提交语义收紧:2.x 默认 "autobegin",事务边界更清晰,少了 1.x 里一些隐式提交的诡异行为。

6. 配套变化

  • declarative_base()class Base(DeclarativeBase)(旧写法仍兼容)。
  • relationship() 也支持 Mapped[list["X"]] 标注,双向关系类型安全。
  • 异步支持一等公民async_sessionmaker + AsyncSession,如果哪天项目要把 FastAPI 改成全异步栈(现在是同步 Session 跑在线程池里),2.x 的 select() 语法在异步下一字不改

2.x 的优化不在"功能更多",而在一套语法走天下、类型可推断、批量写入更快、异步统一。咱们项目现在跑在兼容层上完全没问题,属于"能享受到 2.x 运行时优化,但还没吃到类型和语法红利"的状态。

python 复制代码
# 1.x                          # 2.x
db.query(Model)                select(Model)
.filter(...)                   .where(...)
query.count()                  db.scalar(select(func.count()).select_from(stmt.subquery()))
query.all()                    db.execute(stmt).scalars().all()   # 单实体
                               db.execute(stmt).all()             # 多实体/带 label,Row 照常解包
.one()                         .scalar_one()

2.x 常用方法速查

操作 2.x 写法 说明
创建查询 select(Model) 独立构造函数,不再挂在 session 上
过滤 .where(Model.column == value) 唯一入口,替代 filter()/filter_by()
全部结果 session.execute(stmt).scalars().all() 单实体查询用 .scalars() 拆出 ORM 对象;多列查询用 .all() 拿 Row 元组
第一条 session.execute(stmt).scalars().first() 无结果返回 None
主键获取 session.get(Model, pk) Query.get() 搬家到 Session 上,2.x 正式户口
连接 select(A, B).join(B, ...) / .outerjoin(B, ...) 方法名不变,只是挂在 select()
分组/排序 .group_by() / .order_by() 原名保留
统计 session.scalar(select(func.count()).select_from(Model)) 没有 count() 捷径了,写完整 select
删除 session.execute(delete(Model).where(...)) DML 语句与 select 平级,从 sqlalchemy 导入
更新 session.execute(update(Model).where(...).values(...)) 同上
恰好一条 session.execute(stmt).scalar_one() 多了少了都抛异常,替代 .one()

记忆口诀:1.x 是"问 session 要一个 query 再往上叠",2.x 是"先拼好 select 语句,再让 session 执行"------构造与执行分离了。

python 复制代码
from sqlalchemy import create_engine, select, delete, update, func
from sqlalchemy.orm import sessionmaker, DeclarativeBase, Mapped, mapped_column

# 创建数据库连接
engine = create_engine('sqlite:///users_example.db')


# 2.x 推荐的声明式基类写法,替代旧的 declarative_base()
class Base(DeclarativeBase):
    pass


# 定义模型类(Mapped 标注让类型可推断,列类型由标注自动推导)
class User(Base):
    __tablename__ = 'users'

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    age: Mapped[int]

    # 2.x 实体打印默认是内存地址,定义 __repr__ 方便教学观察
    def __repr__(self) -> str:
        return f"User(id={self.id}, name={self.name!r}, age={self.age})"


# 创建数据表
Base.metadata.create_all(engine)

# 创建 Session 类
Session = sessionmaker(bind=engine)

# with 语法保证会话结束自动关闭,替代手动 session.close()
with Session() as session:
    # 事先清空数据(2.x 批量删除走 delete() DML 语句)
    session.execute(delete(User))
    session.commit()

    # 插入更多数据(add_all 一次接收整个列表,比循环 add 更简洁)
    users_data = [
        {'name': 'Charlie', 'age': 28},
        {'name': 'David', 'age': 32},
        {'name': 'Eve', 'age': 27},
        {'name': 'gemm', 'age': 43},
        {'name': 'riyu', 'age': 43}
    ]
    session.add_all([User(**user_data) for user_data in users_data])
    session.commit()

    # 使用 where() 添加过滤条件;单实体查询用 scalars() 拆出 User 对象
    stmt = select(User).where(User.age > 25)
    result = session.execute(stmt).scalars().all()
    print("年龄大于25的用户:", [(user.name, user.age) for user in result])

    # 使用 func.group_concat() 将同一年龄下的用户名合并成一个字符串
    # 多列查询不用 scalars(),每行是 Row 元组,直接解包
    stmt = select(User.age, func.group_concat(User.name)).group_by(User.age)
    result = session.execute(stmt).all()
    print("按年龄分组查询:")
    for age, names in result:
        name_list = names.split(',')
        print(f"年龄 {age} 的用户有:{', '.join(name_list)}")

    # 使用 order_by() 对结果进行排序
    stmt = select(User).order_by(User.age)
    result = session.execute(stmt).scalars().all()
    print("按年龄排序:", [(user.name, user.age) for user in result])

    # 统计数量:2.x 没有 query.count() 捷径,用 func.count() 写完整 select
    count = session.scalar(select(func.count()).select_from(User))
    print("查询结果的数量:", count)

    # 删除满足条件的记录
    session.execute(delete(User).where(User.name == 'Charlie'))
    session.commit()

    # 更新满足条件的记录
    session.execute(update(User).where(User.name == 'David').values(age=35))
    session.commit()

    # 主键直查:2.x 用 session.get(),这是 Query.get() 的新家
    david = session.execute(select(User).where(User.name == 'David')).scalar_one()
    print("主键直查示例:", session.get(User, david.id))

    # 删除/更新后重查验证
    result = session.execute(select(User).where(User.age > 25)).scalars().all()
    print("年龄大于25的用户(删除/更新后重查):", [(user.name, user.age) for user in result])

与 1.x 版的关键差异(要点)

  1. 构造与执行分离 :先 stmt = select(...) 拼语句,再 session.execute(stmt) 执行。1.x 的 query.filter().all() 一条链到底,2.x 拆成两步,好处是语句可以复用、可以传给任何 session/connection 执行。
  2. scalars() 是新手第一坑select(User) 查出整个实体时,不加 scalars() 拿到的每行是 (User,) 元组。规则很简单------查实体用 scalars(),查多列(如分组那段)直接用 Row 元组解包。
  3. 模型声明升级declarative_base()class Base(DeclarativeBase)Column(Integer)Mapped[int] + mapped_column(),IDE 能推断 user.namestr
  4. 1.x 注释里的 with_entities 备用写法没有保留 :2.x 没有这个方法,想换查询列直接重写 select(列1, 列2) 即可,一条路反而更清爽。
  5. with Session() as session::自动关闭会话,教学习惯从一开始就养成。
相关推荐
添砖java‘’2 小时前
Redis中常用数据结构
数据库·redis·缓存
Code额2 小时前
Python 连接 DeepSeek API,OpenAI
开发语言·python·ai·ai编程
用户3169353811832 小时前
MyBatis Mapper XML 文件解读
数据库
这个DBA有点耶3 小时前
数据库迁移灰度验证:从读流量到双写再到全量切换的完整路径
数据库·mysql·架构
Wang's Blog3 小时前
PostgreSQL笔记16: 索引基础——概念、类型、创建与最佳实践
数据库·笔记·postgresql
用户3169353811833 小时前
"批量"删除-sql
数据库
AAA@峥3 小时前
Python 基础开篇:认识 Python、版本选择、环境部署与首个 HelloWorld
开发语言·python
Metaphor6923 小时前
使用 Python 快速在 Word 文档中插入图片
python·word
瀚高PG实验室3 小时前
SQL优化案例:使用分区表优化SQL查询性能
linux·服务器·数据库·sql·microsoft·postgresql