在FastAPI与SQLAlchemy ORM的协同开发中,查询操作是数据库交互的核心环节。基于已创建的书籍表,我们可通过ORM提供的简洁API,无需编写原生SQL,就能高效实现各类查询需求。本文将严格按照规范的文件命名逻辑,分5个核心场景拆解查询操作,每个场景对应独立的实现代码与思路,全程结合可直接运行的示例,仅围绕查询操作核心展开,确保内容简洁、可落地,助力开发者快速掌握各类查询技巧。
一、通用前置配置(所有查询场景共用)
所有查询操作均依赖统一的基础配置,包括数据库引擎创建、模型定义、会话管理及建表操作,确保查询能正常连接数据库并映射到书籍表。以下是完整的通用配置代码,可直接复制运行。
from datetime import datetime
from fastapi import FastAPI, Depends
from sqlalchemy import DateTime, func, String, Float, select
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
# 初始化FastAPI应用
app = FastAPI(debug=True)
# 1. 创建异步数据库引擎(适配MySQL,修改为自身数据库配置)
ASYNC_DATABASE_URL = "mysql+aiomysql://root:654321@localhost:3306/book_manage?charset=utf8"
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 输出SQL日志,便于调试查询语句
pool_size=8, # 连接池活跃连接数,适配查询并发
max_overflow=15 # 额外允许的临时连接数
)
# 2. 定义模型基类(包含公共字段)
class Base(DeclarativeBase):
# 公共字段:创建时间、修改时间,自动填充
create_time: Mapped[datetime] = mapped_column(
DateTime,
insert_default=func.now(),
default=func.now,
comment="创建时间"
)
update_time: Mapped[datetime] = mapped_column(
DateTime,
insert_default=func.now(),
default=func.now,
onupdate=func.now(),
comment="修改时间"
)
# 3. 定义书籍表模型(查询操作的核心载体)
class Book(Base):
__tablename__ = "book" # 数据表名,与数据库中表一致
id: Mapped[int] = mapped_column(primary_key=True, comment="书籍主键ID")
book_title: Mapped[str] = mapped_column(String(255), comment="书籍标题")
book_author: Mapped[str] = mapped_column(String(100), comment="书籍作者")
book_price: Mapped[float] = mapped_column(Float, comment="书籍售价")
book_publisher: Mapped[str] = mapped_column(String(255), comment="出版社")
book_pages: Mapped[int] = mapped_column(comment="书籍页数")
# 4. 建表操作(确保书籍表已存在,为查询提供数据载体)
async def create_book_table():
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# 启动FastAPI时自动建表
@app.on_event("startup")
async def startup():
await create_book_table()
# 5. 创建异步会话工厂与依赖项(查询操作必须通过会话实现)
AsyncSessionLocal = async_sessionmaker(
bind=async_engine,
class_=AsyncSession,
expire_on_commit=False # 提交后会话不过期,提升查询效率
)
# 会话依赖项:自动管理会话的创建、提交、回滚与关闭
async def get_db_session():
async with AsyncSessionLocal() as session:
try:
yield session # 将会话传递给路由查询函数
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
# 基础测试接口,验证配置是否正常
@app.get("/")
async def index():
return {"status": "success", "message": "查询环境配置完成,可进行各类查询操作"}
二、分场景查询实现
场景1:基础查询数据
基础查询主要实现“单条数据查询”和“全部数据查询”,是最常用、最基础的查询场景,通过简单API即可快速获取书籍数据,无需添加复杂条件。
# 1. 基础查询:根据主键ID查询单本书籍(最常用)
@app.get("/book/query_single/{book_id}")
async def query_single_book(book_id: int, db: AsyncSession = Depends(get_db_session)):
# 核心:通过db.get()方法,根据主键快速查询单条记录
book = await db.get(Book, book_id)
# 处理查询不到数据的情况,返回友好提示
return book if book else {"message": "未查询到对应ID的书籍"}
# 2. 基础查询:查询所有书籍数据
@app.get("/book/query_all")
async def query_all_books(db: AsyncSession = Depends(get_db_session)):
# 构建查询语句:查询书籍表所有记录
query_stmt = select(Book)
# 执行查询,获取结果集
result = await db.execute(query_stmt)
# 提取所有书籍记录,返回列表
all_books = result.scalars().all()
return all_books if all_books else {"message": "书籍表暂无数据"}
# 3. 基础查询:查询第一条书籍数据
@app.get("/book/query_first")
async def query_first_book(db: AsyncSession = Depends(get_db_session)):
query_stmt = select(Book)
result = await db.execute(query_stmt)
# 提取查询结果中的第一条数据
first_book = result.scalars().first()
return first_book if first_book else {"message": "书籍表暂无数据"}
关键说明:基础查询核心依赖db.get()和select(Book),前者适用于主键查询,简洁高效;后者适用于批量查询,搭配scalars().all()或scalars().first()提取结果。
场景2:带基础条件查询
实际开发中,常需要根据具体条件筛选数据,如根据价格、出版社、页数等字段筛选书籍,通过where()方法添加查询条件,实现精准筛选。
# 1. 条件查询:根据出版社筛选书籍
@app.get("/book/query_by_publisher")
async def query_book_by_publisher(publisher: str, db: AsyncSession = Depends(get_db_session)):
# 构建带条件的查询语句:筛选指定出版社的书籍
query_stmt = select(Book).where(Book.book_publisher == publisher)
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到该出版社的书籍"}
# 2. 条件查询:根据价格范围筛选书籍(大于等于指定价格)
@app.get("/book/query_by_price")
async def query_book_by_price(min_price: float, db: AsyncSession = Depends(get_db_session)):
query_stmt = select(Book).where(Book.book_price >= min_price)
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到符合价格条件的书籍"}
# 3. 条件查询:根据页数筛选书籍(小于指定页数)
@app.get("/book/query_by_pages")
async def query_book_by_pages(max_pages: int, db: AsyncSession = Depends(get_db_session)):
query_stmt = select(Book).where(Book.book_pages < max_pages)
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到符合页数条件的书籍"}
关键说明:where()方法中可直接使用字段与值的比较关系(==、>=、<等),实现基础条件筛选,多个条件可叠加(后续场景详解)。
场景3:模糊查询与与非逻辑查询
当需要模糊匹配(如根据作者姓名模糊搜索)或多条件组合(与、或、非)筛选时,可使用like()、in_()及逻辑运算符(&、|、~)实现,满足更灵活的查询需求。
# 1. 模糊查询:根据作者姓名模糊搜索(支持前缀、后缀、包含模糊)
@app.get("/book/query_by_author_like")
async def query_book_by_author_like(author_keyword: str, db: AsyncSession = Depends(get_db_session)):
# 模糊匹配:作者姓名包含关键词(%表示任意个字符)
query_stmt = select(Book).where(Book.book_author.like(f"%{author_keyword}%"))
# 若需前缀匹配(以关键词开头),使用 f"{author_keyword}%"
# 若需后缀匹配(以关键词结尾),使用 f"%{author_keyword}"
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到匹配作者的书籍"}
# 2. 包含查询:查询ID在指定列表中的书籍(in_()方法)
@app.get("/book/query_by_id_in")
async def query_book_by_id_in(db: AsyncSession = Depends(get_db_session)):
target_ids = [2, 5, 8, 10] # 目标ID列表,可根据需求修改
query_stmt = select(Book).where(Book.id.in_(target_ids))
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到指定ID的书籍"}
# 3. 与逻辑查询:同时满足两个条件(出版社为某值 且 价格大于指定值)
@app.get("/book/query_and_condition")
async def query_book_and_condition(publisher: str, min_price: float, db: AsyncSession = Depends(get_db_session)):
# 多个条件用 & 连接,注意每个条件需用括号包裹
query_stmt = select(Book).where((Book.book_publisher == publisher) & (Book.book_price > min_price))
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到同时满足条件的书籍"}
# 4. 或逻辑查询:满足任意一个条件(作者包含关键词 或 页数小于指定值)
@app.get("/book/query_or_condition")
async def query_book_or_condition(author_keyword: str, max_pages: int, db: AsyncSession = Depends(get_db_session)):
query_stmt = select(Book).where((Book.book_author.like(f"%{author_keyword}%")) | (Book.book_pages< max_pages))
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "未查询到满足条件的书籍"}
# 5. 非逻辑查询:排除指定条件的书籍(排除某出版社的书籍)
@app.get("/book/query_not_condition")
async def query_book_not_condition(publisher: str, db: AsyncSession = Depends(get_db_session)):
# ~ 表示非逻辑,排除指定出版社的书籍
query_stmt = select(Book).where(~(Book.book_publisher == publisher))
result = await db.execute(query_stmt)
books = result.scalars().all()
return books if books else {"message": "除指定出版社外,无其他书籍"}
关键说明:模糊查询使用like(),搭配%(任意字符)、_(单个字符);包含查询使用in_(),传入列表;逻辑运算需用&(与)、|(或)、~(非),且每个条件需用括号包裹。
场景4:聚合查询
聚合查询用于对书籍表数据进行统计分析,如统计书籍总数、平均价格、最高价格、总页数等,通过func模块提供的聚合函数实现,无需手动计算。
# 1. 聚合查询:统计书籍总数
@app.get("/book/aggregate/count")
async def count_books(db: AsyncSession = Depends(get_db_session)):
# func.count() 统计指定字段的非空记录数,通常用主键ID
query_stmt = select(func.count(Book.id))
result = await db.execute(query_stmt)
# 聚合查询结果用 scalar() 提取单个数值
book_count = result.scalar()
return {"书籍总数": book_count}
# 2. 聚合查询:查询书籍平均价格
@app.get("/book/aggregate/avg_price")
async def avg_book_price(db: AsyncSession = Depends(get_db_session)):
query_stmt = select(func.avg(Book.book_price))
result = await db.execute(query_stmt)
avg_price = result.scalar()
# 保留2位小数,提升显示效果
return {"书籍平均价格": round(avg_price, 2) if avg_price else 0.0}
# 3. 聚合查询:查询书籍最高价格与最低价格
@app.get("/book/aggregate/max_min_price")
async def max_min_book_price(db: AsyncSession = Depends(get_db_session)):
# 同时查询最高价格和最低价格
query_stmt = select(func.max(Book.book_price), func.min(Book.book_price))
result = await db.execute(query_stmt)
max_price, min_price = result.first()
return {
"书籍最高价格": max_price if max_price else 0.0,
"书籍最低价格": min_price if min_price else 0.0
}
# 4. 聚合查询:统计书籍总页数
@app.get("/book/aggregate/sum_pages")
async def sum_book_pages(db: AsyncSession = Depends(get_db_session)):
query_stmt = select(func.sum(Book.book_pages))
result = await db.execute(query_stmt)
total_pages = result.scalar()
return {"书籍总页数": total_pages if total_pages else 0}
关键说明:聚合函数需导入func模块,常用聚合函数包括count(计数)、avg(平均)、max(最大)、min(最小)、sum(求和),结果用scalar()或first()提取。
场景5:分页查询
当书籍表数据量较大时,一次性查询所有数据会影响性能,分页查询可将数据分批次返回,通过offset()(跳过指定条数)和limit()(限制返回条数)实现,适配大量数据场景。
# 分页查询:根据页码和每页条数,返回分页数据
@app.get("/book/paginate")
async def paginate_books(
page: int = 1, # 页码,默认第1页
page_size: int = 5, # 每页条数,默认5条
db: AsyncSession = Depends(get_db_session)
):
# 计算跳过的记录数:(当前页码 - 1) * 每页条数
skip = (page - 1) * page_size
# 构建分页查询语句:跳过skip条,返回page_size条
query_stmt = select(Book).offset(skip).limit(page_size)
result = await db.execute(query_stmt)
paginate_books = result.scalars().all()
# 统计总条数,用于前端分页显示(可选,提升体验)
total = await db.execute(select(func.count(Book.id)))
total_count = total.scalar()
return {
"分页信息": {
"当前页码": page,
"每页条数": page_size,
"总条数": total_count,
"总页数": (total_count + page_size - 1) // page_size # 向上取整计算总页数
},
"分页数据": paginate_books if paginate_books else []
}
关键说明:offset(skip)用于跳过前skip条记录,limit(page_size)用于限制本次返回的记录数;统计总条数可帮助前端计算总页数,提升用户体验,按需添加即可。
三、查询操作注意事项
-
所有查询操作必须通过异步会话实现,不可直接操作引擎,避免出现连接泄露或阻塞事件循环;
-
模糊查询中,%和_的使用需注意:%表示任意个字符,_表示单个字符,根据需求选择合适的匹配方式;
-
多条件逻辑查询时,每个条件需用括号包裹,避免逻辑优先级错误;
-
聚合查询结果需用
scalar()或first()提取,不可用scalars().all(),否则会返回异常; -
分页查询中,skip的计算需准确,避免出现页码错乱或数据重复/遗漏的情况。
总结
通过SQLAlchemy ORM的查询API,我们无需编写复杂的原生SQL,即可实现高效、规范的查询操作,大幅提升开发效率。在实际开发中,可根据具体业务需求,灵活组合各类查询方法,实现精准的数据筛选与统计。