SqlAlchemy 2.0:ORM定义中mapped_column()与Computed()列的使用问题
根据Stack Overflow鼓励用户自行解答问题以丰富社区内容的倡议,尤其是在投入大量精力排查问题后,我将在下方提供答案。我热切期待更多评论、补充见解或替代方案!同时感谢SQLAlchemy论坛上F. Caselli提供的宝贵帮助,这助力我缩小了解决方案的搜索范围。
问题描述
我尝试在SqlAlchemy 2.0中直接通过ORM定义,使用mapped_column()创建COMPUTED列,但未能成功(已查阅谷歌和官方文档)。
我知道在Core模型中,只需按如下方式声明表即可(示例中area为COMPUTED列):
square = Table( "square", metadata_obj, Column("id", Integer, primary_key=True), Column("side", Integer), Column("area", Integer, Computed("side * side")), )
这等价于PostgreSQL 12+的以下SQL DDL语句:
CREATE TABLE square ( id SERIAL NOT NULL, side INTEGER, area INTEGER GENERATED ALWAYS AS (side * side) STORED, PRIMARY KEY (id) )
然而,当尝试使用Mapped[]和mapped_column()在ORM中实现等价功能时,始终找不到解决方案。原本以为如下写法可行,但并未奏效:
class Square(Base): ... area: Mapped[int] = mapped_column(Computed("side*side"))
在此之前,还尝试过其他配置,例如将Computed()作为server_default=参数传入,以及使用FetchedValue,但均出现错误。
也了解到@hybrid_property,但明白这并非同一概念(因为需要该计算列成为表DDL定义的一部分,而@hybrid_property似乎仅在查询时计算,不会生成DDL)。
解决方案
正确的ORM写法需要为Computed指定persisted=True参数,同时将列标记为FetchedValue(),这样SQLAlchemy就知道该列的值由数据库维护,不会尝试在插入/更新时发送该字段的值。
完整的ORM模型定义如下:
from sqlalchemy import Computed, FetchedValue from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase class Base(DeclarativeBase): pass class Square(Base): __tablename__ = "square" id: Mapped[int] = mapped_column(primary_key=True) side: Mapped[int] area: Mapped[int] = mapped_column( Computed("side * side", persisted=True), FetchedValue() )
关键说明
persisted=True:告诉SQLAlchemy这个计算列是存储在数据库中的(对应PostgreSQL的STORED关键字),会在生成DDL时包含该列的定义。FetchedValue():标记该列的值由数据库生成,SQLAlchemy在执行INSERT或UPDATE操作时不会尝试设置这个字段的值,而是在操作后从数据库获取最新值。
当你使用Base.metadata.create_all(engine)生成表结构时,会自动创建带有GENERATED ALWAYS AS (side * side) STORED的area列,和Core方式生成的DDL完全一致。
内容的提问来源于stack exchange,提问作者A.Sommerh

