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

avatar
cmdragon 大乘
image image

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

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

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

1. 环境准备与模型定义

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

1
pip install fastapi uvicorn tortoise-orm pydantic

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
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的订单

1
2
3
4
5
6
7
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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的待处理订单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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表达式直接在数据库层面执行运算,避免竞态条件。示例:安全扣减库存

1
2
3
4
5
6
7
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 复合更新操作

同时更新多个字段:

1
2
3
4
5
6
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 条件更新示例

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

1
2
3
4
5
6
7
8
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. 完整案例演示

实现商品促销接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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会自动处理查询参数化

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:

往期文章归档: