如何让SQLAlchemy映射数据库托管字段且不参与增改操作?
我来帮你搞定这个问题!这种数据库托管字段的场景在SQL Server里太常见了——比如自增ID、默认值列、计算列或者timestamp/rowversion字段,SQLAlchemy默认的映射方式确实会踩坑,下面分情况给你一步步解决:
本质是要告诉SQLAlchemy:这些字段只做读取用,创建/更新时绝对不要把它们放到SQL语句里。SQLAlchemy提供了几个关键参数来控制这个行为,结合SQL Server的特性调整后就能解决IntegrityError。
1. 自增主键(SQL Server IDENTITY列)
先看SQL Server的DDL示例:
CREATE TABLE dbo.ExampleTable ( Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, CreatedAt DATETIME2 DEFAULT GETUTCDATE() );
这里Id是IDENTITY列,数据库自动生成,用户完全不能插入/更新它;CreatedAt有数据库默认值,也不需要手动传值。
sqlacodegen生成的默认代码(需要修改)
sqlacodegen通常会生成这样的代码,但它没识别出数据库托管特性:
class ExampleTable(Base): __tablename__ = 'ExampleTable' __table_args__ = {'schema': 'dbo'} id = Column(Integer, primary_key=True) name = Column(NVARCHAR(50), nullable=False) created_at = Column(DATETIME2)
修改后的正确代码
from sqlalchemy import text class ExampleTable(Base): __tablename__ = 'ExampleTable' __table_args__ = {'schema': 'dbo'} id = Column(Integer, primary_key=True, autoincrement=True, insert_default=None) # autoincrement=True标记自增列,insert_default=None强制INSERT时不包含该字段 name = Column(NVARCHAR(50), nullable=False) created_at = Column(DATETIME2, server_default=text("GETUTCDATE()"), insert_default=None) # server_default绑定数据库默认值,insert_default=None让SQLAlchemy插入时不传递该字段
2. 计算列(SQL Server COMPUTED列)
比如自动计算订单总价的场景:
CREATE TABLE dbo.OrderTable ( OrderId INT IDENTITY(1,1) PRIMARY KEY, Quantity INT NOT NULL, UnitPrice DECIMAL(18,2) NOT NULL, TotalAmount AS (Quantity * UnitPrice) PERSISTED );
TotalAmount是计算列,数据库自动维护,用户不能修改。
SQLAlchemy映射修改
class OrderTable(Base): __tablename__ = 'OrderTable' __table_args__ = {'schema': 'dbo'} order_id = Column(Integer, primary_key=True, autoincrement=True) quantity = Column(Integer, nullable=False) unit_price = Column(Numeric(18,2), nullable=False) total_amount = Column(Numeric(18,2), computed=True, insert_default=None, update_default=None) # computed=True(SQLAlchemy 1.4+支持)标记计算列,双default=None彻底排除写操作
3. Timestamp/RowVersion字段
SQL Server的ROWVERSION(原TIMESTAMP)是自动生成的版本戳,用于并发控制,用户完全不能操作:
CREATE TABLE dbo.UserTable ( UserId INT IDENTITY(1,1) PRIMARY KEY, Username NVARCHAR(50) NOT NULL, RowVersion ROWVERSION );
SQLAlchemy映射修改
class UserTable(Base): __tablename__ = 'UserTable' __table_args__ = {'schema': 'dbo'} user_id = Column(Integer, primary_key=True, autoincrement=True) username = Column(NVARCHAR(50), nullable=False) row_version = Column(LargeBinary, insert_default=None, update_default=None, nullable=False) # 用LargeBinary映射ROWVERSION,双default=None确保不写入,nullable=False匹配数据库特性
4. 通用配置技巧(适用于所有托管字段)
如果上面的针对性配置还不够,这两个参数是万能钥匙:
insert_default=None:强制SQLAlchemy在INSERT语句中完全忽略该字段update_default=None:强制SQLAlchemy在UPDATE语句中完全忽略该字段
另外注意:不要给数据库会自动赋值的字段设置nullable=False(除非数据库本身不允许NULL),否则SQLAlchemy可能会默认插入NULL触发约束错误。
5. 优化sqlacodegen生成的代码
sqlacodegen默认不会自动识别SQL Server的托管字段特性,生成后需要手动批量调整:
- 给IDENTITY列添加
autoincrement=True, insert_default=None - 给带DEFAULT约束的列添加
server_default=text("你的默认表达式"), insert_default=None - 给计算列添加
computed=True, insert_default=None, update_default=None - 给ROWVERSION字段添加
insert_default=None, update_default=None
6. 验证是否生效
开启SQLAlchemy的SQL日志,查看生成的语句是否符合预期:
import logging logging.basicConfig() logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
比如插入ExampleTable时,生成的SQL应该是:
INSERT INTO dbo.ExampleTable (name) VALUES (?)
完全不包含id和created_at,这样就不会触发IntegrityError了。
内容的提问来源于stack exchange,提问作者Barka

