title: FastAPI中的复杂查询与原子更新指南

date: 2025/05/02 20:33:32

updated: 2025/05/02 20:33:32

author: cmdragon

excerpt:

FastAPI 结合 Tortoise-ORM 实现复杂查询与原子更新。通过 Q 对象构建多条件查询,支持 AND、OR、NOT 逻辑运算符,动态组合查询条件。使用 F 表达式进行原子更新,避免竞态条件,确保数据一致性。示例包括订单状态与金额的复杂查询、库存扣减的原子操作,以及商品促销的价格更新。常见错误包括字段拼写错误、类型不匹配和空结果集,需通过模型检查和异常处理解决。

categories:

  • 后端开发
  • FastAPI

tags:

  • FastAPI
  • Tortoise-ORM
  • 复杂查询
  • 原子更新
  • Q对象
  • F表达式
  • 数据库操作


扫描二维码

关注或者微信搜一搜:编程智域 前端至全栈交流与成长

探索数千个预构建的 AI 应用,开启你的下一个伟大创意https://tools.cmdragon.cn/

第一章:FastAPI复杂查询与原子更新实战

1. 环境准备与模型定义

在开始前确保已安装必要依赖:

pip install fastapi uvicorn tortoise-orm pydantic

创建订单模型示例(models.py):

from tortoise.models import Model
from tortoise import fields class Product(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255)
stock = fields.IntField(default=0)
price = fields.DecimalField(max_digits=10, decimal_places=2) class Order(Model):
id = fields.IntField(pk=True)
status = fields.CharField(max_length=20) # pending/completed/canceled
total_amount = fields.DecimalField(max_digits=10, decimal_places=2)
product = fields.ForeignKeyField('models.Product', related_name='orders')
created_at = fields.DatetimeField(auto_now_add=True)

创建对应的Pydantic模型(schemas.py):

from pydantic import BaseModel
from datetime import datetime class OrderOut(BaseModel):
id: int
status: str
total_amount: float
product_id: int
created_at: datetime class Config:
orm_mode = True

2. 组合Q对象实现复杂查询

2.1 Q对象基础原理

Q对象是Tortoise-ORM的条件表达式构造器,支持逻辑运算符:

  • & 表示AND
  • | 表示OR
  • ~ 表示NOT

示例:查询金额大于100且状态为pending的订单

from tortoise.expressions import Q

async def get_orders():
return await Order.filter(
Q(total_amount__gt=100) & Q(status="pending")
).all()

2.2 多条件动态组合

在路由中实现动态过滤(main.py):

from fastapi import APIRouter, Query
from tortoise.expressions import Q router = APIRouter() @router.get("/orders", response_model=list[OrderOut])
async def search_orders(
min_amount: float = Query(None),
max_amount: float = Query(None),
status: str = Query(None)
):
query = Q()
if min_amount:
query &= Q(total_amount__gte=min_amount)
if max_amount:
query &= Q(total_amount__lte=max_amount)
if status:
query &= Q(status=status) return await Order.filter(query).prefetch_related('product')

2.3 复杂逻辑示例

查询过去7天内金额超过500的已完成订单,或金额低于100的待处理订单:

from datetime import datetime, timedelta

async def complex_query():
seven_days_ago = datetime.now() - timedelta(days=7)
return await Order.filter(
Q(
Q(created_at__gte=seven_days_ago) &
Q(total_amount__gt=500) &
Q(status='completed')
) |
Q(
Q(total_amount__lt=100) &
Q(status='pending')
)
).order_by('-created_at')

3. 使用F表达式进行原子更新

3.1 F表达式的作用原理

F表达式直接在数据库层面执行运算,避免竞态条件。示例:安全扣减库存

from tortoise.expressions import F

async def decrease_stock(product_id: int, quantity: int):
await Product.filter(id=product_id).update(
stock=F('stock') - quantity
)

3.2 复合更新操作

同时更新多个字段:

async def update_product_price(product_id: int, new_price: float):
await Product.filter(id=product_id).update(
price=new_price,
last_updated=datetime.now(),
version=F('version') + 1
)

3.3 条件更新示例

只有当库存充足时才允许扣减:

async def safe_purchase(product_id: int, quantity: int):
updated = await Product.filter(
id=product_id,
stock__gte=quantity
).update(stock=F('stock') - quantity) if not updated:
raise HTTPException(400, "库存不足")

4. 完整案例演示

实现商品促销接口:

@router.post("/products/{product_id}/promotion")
async def create_promotion(
product_id: int,
discount: float = Body(..., gt=0, lt=1)
):
# 原子更新价格并记录操作
updated = await Product.filter(id=product_id).update(
price=F('price') * (1 - discount),
promotion_count=F('promotion_count') + 1
) if not updated:
raise HTTPException(404, "商品不存在") # 获取更新后的对象
product = await Product.get(id=product_id)
return {
"new_price": float(product.price),
"promotion_count": product.promotion_count
}

5. 常见报错解决方案

错误1:字段不存在

FieldError: Unknown field 'total_amout' for model Order

原因:字段名拼写错误(amout → amount)

解决:检查模型定义和查询字段是否一致

错误2:类型不匹配

ValidationError: 1 validation error for OrderOut...

原因:Decimal字段自动转换为float时精度丢失

解决:在Pydantic模型中使用Decimal类型并配置json_encoders

错误3:空结果集

DoesNotExist: Object does not exist

原因:查询条件过于严格导致无结果

解决:添加异常处理或使用first()代替get()

6. 课后Quiz

  1. 当需要同时满足三个条件时,Q对象应该如何组合?

    A) Q(a) | Q(b) | Q(c)

    B) Q(a) & Q(b) & Q(c)

    C) Q(a) & (Q(b) | Q(c))

    答案:B。& 运算符用于AND条件组合

  2. 为什么要使用F表达式而不是先查询再更新?

    A) 减少数据库查询次数

    B) 避免并发导致的数据不一致

    C) 两种方式效果相同

    答案:B。F表达式保证原子操作,防止竞态条件

  3. 如何防止通过Q对象构造的查询出现SQL注入?

    A) 手动转义参数

    B) 使用ORM的内置参数化查询

    C) 限制用户输入字段

    答案:B。Tortoise-ORM会自动处理查询参数化

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:FastAPI中的复杂查询与原子更新指南 | cmdragon's Blog

往期文章归档:

FastAPI中的复杂查询与原子更新指南的更多相关文章

  1. solr/solrj原子更新

    lucene原子更新自己不用多介绍,但solr它的包装,下面是一个简单的介绍是:这个操作是用于索引非常有用. 详细在代码中使用例如以下: /** * 原子更新方式 * */ public static ...

  2. Solr搜索引擎【索引提交、事务日志、原子更新】

    一.索引提交 当一个文档被添加到Solr中,但没有提交给索引之前,这个文档是无法被搜索的.换句话说,从查询的角度看,文档直到提交之后才是可见的.Solr有两种类型的提交:软提交和正常提交[也称硬提交] ...

  3. 利用带关联子查询Update语句更新数据

    Update是T-sql中再简单不过的语句了,update table set column=expression  [where condition],我们都会用到.但update的用法不仅于此,真 ...

  4. Oracle- 提示查询结果不可更新,请使用...更新结果。

    我们在对Oracle数据库进行操作时,有时会在查询完结果后想要对其中的某些数据进行操作,当我们点击编辑(一个锁标志)是,会提示我们上述问题中的错误:这些查询结果不可更新,请使用ROWI或者SELECT ...

  5. 【转】PL/SQL编辑数据"这些查询结果不可更新,请包括ROWID或使用SELECT...FOR UPDATE获得可更新结果"处理

    [转]PL/SQL编辑数据"这些查询结果不可更新,请包括ROWID或使用SELECT...FOR UPDATE获得可更新结果"处理 只要有人用了: select t.* from ...

  6. solr的原子更新/局部更新

    solr支持三种类型的原子更新: set - to set a field. add - to add to a multi-valued field. inc - to increment a fi ...

  7. 【VB】操作ODBC-DAO方式操作只能查询,不能更新插入操作解决。

    最近接手一个改善项目,需要从Access转化到SQL Server 2014,使用原有的ODBC连接方式只能查询,不能更新插入.网上一直找不到解决方案,然后自己测试一下使用ADO方式竟然可以连接了.具 ...

  8. mysql根据查询结果批量更新多条数据(插入或更新)

    mysql根据查询结果批量更新多条数据(插入或更新) 1.1 前言 mysql根据查询结果执行批量更新或插入时经常会遇到1093的错误问题.基本上批量插入或新增都会涉及到子查询,mysql是建议不要对 ...

  9. SQL-在Update中进行子查询和左联查询

    以下总结源自后边的三个参考思索和测试而来: 我们有一张行政区划表,为了查询速度的优化,我们需要在这张表中,将每个乡镇的记录中写入其所属的省.市.县, 表如下: 当然,我们可以使用游标或在存储过程中使用 ...

  10. mysql 在update中实现子查询的方式

    当使用mysql条件更新时--最先让人想到的写法 UPDATE buyer SET is_seller=1 WHERE uid IN (SELECT uid FROM seller) 此语句是错误的, ...

随机推荐

  1. server_patrol.sh服务器巡查脚本

    server_patrol.sh #!/bin/bash #!/usr/bin/expect -f#! auther by wangxp #定义一个变量 LANG="zh_CN.UTF-8& ...

  2. Q:记录一次ssh毫无规律的断线

    查找tailf /var/log/secure 网上一般是这三个原因 1.ssh服务端配置 vim /etc/ssh/sshd_config #客户端每隔多少秒向服务发送一个心跳数据,0代表不发送#C ...

  3. NOIP 游记

    前情提要:color \(100\to 0\),arena \(92/100\to 36\). 最后一场模拟赛喜提 0+0+100+0,挺乐的. Day 0 晚上九点睡,然而还是很早就醒了,但是时间体 ...

  4. 在 GitLab CI/CD 中使用内置的容器镜像库

    配置 Docker-in-Docker Docker-in-Docker (dind) means: 你应该注册一个 Docker executor 或 Kubernetes executor 执行器 ...

  5. Ansible - [03] Ansible ad-hoc模式

    Ansible ad-hoc是一种通过命令行批量管理的方式 格式:ansible 主机集合 -m 模块名 -a "参数" 其他参数: -k 使用密码远程.-i 指定主机列表文件 以 ...

  6. [tldr]通过指令获取github仓库的单个文件的内容

    针对一个公开的github仓库,有些时候不需要clone整个仓库的内容,只需要对应的几个文件.但是直接通过网页点击下载文件很麻烦,在服务器上也不好这样操作. 因此,如何使用curl或者wget指令快速 ...

  7. go string转int strconv包

    前言 strconv 主要用于字符串和基本类型的数据类型的转换 s := "aa"+100 //字符串和整形数据不能放在一起 所以需要将 100 整形转为字符串类型 //+号在字符 ...

  8. jquery submit 解决多次提交

    jquery submit 解决多次提交 web应用中常见的问题就是多次提交,由于表单提交的延迟,有时几秒或者更长,让用户有机会多次点击提交按钮,从而导致服务器端代码的种种麻烦. 为了解决这个问题,我 ...

  9. Python设置递归最大深度

    博客地址:https://www.cnblogs.com/zylyehuo/ import sys sys.setrecursionlimit(100000) # 设置最大递归深度,默认是3000

  10. 【docker】如何将服务器加入集群,成为子节点

    需求:将服务器加入集群,成为集群中的图一.png (18.95 KB, 下载次数: 0) 图一 图二.png (10.92 KB, 下载次数: 0) 图二 图三.png (26.71 KB, 下载次数 ...