你疑惑的这两段代码(detail 接口的 mappings() 查询、Videos 模型类)本质上是「定义」和「查询实现」的关系,本身没有逻辑冲突,但存在「字段约束不匹配」和「查询方法兼容性」两个关键小坑 ,这也是为什么数据库有数据,查询后却取不到(显示 None)的核心原因。
我们先拆解两者的关联与差异,再给出针对性修正。
一、先明确:两段代码的核心关系
class Videos(SQLModel, table=True):是数据库表的「抽象定义」,规定了表的字段名、字段类型、约束(必填/主键等),相当于给数据库表和 Python 代码之间搭了一座桥。session.execute(sql).mappings().first():是查询数据库的「实现方式」 ,目的是从数据库中取出数据,映射成 Python 可操作的对象,本应和Videos模型类匹配,但有两个坑导致映射失败。
二、关键差异/问题:导致数据查询后显示 None
问题1:Videos 模型类的字段约束过于严格,与 mappings() 兼容性差
你的 Videos 模型中,除了 videoid 是 int|None(可选),其他字段(videotitle、filename、keyword 等)都是必填类型(无 |None、无默认值) ,但 mappings() 返回的是「松散的 RowMapping 映射对象」,不遵守 Videos 模型的必填约束:
- 若数据库中某个字段(比如
keyword、reference)是NULL(上传时未赋值),而模型类中定义为str(必填),mappings()映射时无法满足「必填 str」的约束,会直接将该字段置为None,甚至连带影响其他非 NULL 字段的映射(比如filename、summary) ,导致你打印时显示None。 - 而
scalars()返回的是Videos模型实例,会严格遵守模型类的字段定义,自动处理「数据库 NULL 值」和「模型字段类型」的转换,兼容性更好。
问题2:Videos 模型缺少「自增属性」,与数据库自增字段不匹配
你的 videoid 定义:
python
videoid:int|None=Field(default=None,primary_key=True)
虽然数据库中 videoid 大概率设置了「自增主键」,但模型类中缺少自增配置 (sa_column_kwargs={"autoincrement": True}),导致:
- 模型类和数据库的「自增属性」不匹配,
mappings()映射时,无法正确识别videoid的取值,间接影响其他字段的查询结果映射,出现「数据库有值,查询后为None」的现象。
问题3:mappings() vs scalars():查询结果的本质差异(核心)
这是最关键的一点,两者返回的结果完全不同,直接决定了能否取到数据:
| 查询方法 | 返回结果类型 | 与 Videos 模型的关联 |
取值稳定性 |
|---|---|---|---|
mappings().first() |
SQLAlchemy RowMapping(只读字典类似物) |
无直接关联,仅做「列名-值」简单映射 | 差,容易丢失数据、出现 None |
scalars().first() |
Videos 模型实例(严格遵守模型定义) |
强关联,自动处理字段类型转换 | 高,100% 保留模型字段数据 |
简单说:mappings() 是「无差别映射」,不管你的 Videos 模型怎么定义,只按数据库返回的列名映射;scalars() 是「精准映射」,只返回符合 Videos 模型定义的实例,兼容性和稳定性远高于 mappings()。
三、针对性修正(两步搞定,确保数据能正常取出)
步骤1:修正 Videos 模型类(匹配数据库,放宽合理约束)
python
from sqlmodel import SQLModel, Field
from datetime import datetime
class Videos(SQLModel, table=True):
# 修正1:补全自增属性,与数据库自增主键匹配
videoid: int | None = Field(
default=None,
primary_key=True,
sa_column_kwargs={"autoincrement": True} # 新增:开启自增
)
# 修正2:核心字段(必须有值)保留必填,非核心字段加可选约束(|None),兼容数据库 NULL
videotitle: str
filename: str
duration: int
coursename: str
content: str
summary: str
# 非核心字段:允许为 None(兼容数据库 NULL),避免映射失败
keyword: str | None = Field(default=None)
reference: str | None = Field(default=None)
xmindjson: str | None = Field(default=None)
examjson: str | None = Field(default=None)
createtime: datetime
步骤2:修正 detail 接口查询方法(改用 scalars(),转字典确保模板取值)
这是解决「None 问题」的核心,直接替换你现有 detail 接口:
python
from fastapi import HTTPException # 确保已导入
@app.get("/detail/{videoid}")
def detail(request: Request, videoid: int):
with Session(engine) as session:
sql = select(Videos).where(Videos.videoid == videoid)
# 修正:改用 scalars() 获取 Videos 模型实例(而非 mappings())
video_instance = session.execute(sql).scalars().first()
if not video_instance:
raise HTTPException(status_code=404, detail="该视频记录不存在")
# 模型实例转普通字典,确保模板取值稳定
video = video_instance.dict()
# 调试打印:此时能看到真实数据,不会是 None
print("===== 调试:查询到的视频数据 =====")
print("文件名:", video.get("filename"))
print("总结内容:", video.get("summary"))
print("视频标题:", video.get("videotitle"))
print("================================")
return templates.TemplateResponse(
request=request,
name="detail.html",
context={"video": video}
)
四、验证效果
- 重启 FastAPI 服务(
Ctrl+C后重新运行代码); - 访问
http://127.0.0.1:8000/detail/2; - 查看控制台打印:此时会显示数据库中的真实数据(比如
filename是你的视频文件名,summary是你的总结内容),不会再是None; - 模板页面中,
{``{ video['filename'] }}和{``{ video['summary']|safe }}会正常渲染出内容。
总结
- 两段代码无本质逻辑冲突,但存在「字段约束」和「查询方法兼容性」问题;
- 核心坑:
mappings()兼容性差,无法处理模型必填约束与数据库 NULL 值的矛盾,导致数据显示None; - 关键解决:模型类补全自增+放宽非核心字段约束,查询改用
scalars()获取模型实例并转字典。
按这个方法修正后,你的数据就能正常查询和显示了,不会再出现「数据库有值,查询后为 None」的现象。