Python 如何在SQLAlchemy中对继承的列定义约束?

Python 如何在SQLAlchemy中对继承的列定义约束?,python,sqlalchemy,Python,Sqlalchemy,我有一个在中展示的类继承方案,我想定义一个约束,它使用父类和子类中的列 from sqlalchemy import ( create_engine, Column, Integer, String, ForeignKey, CheckConstraint ) from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Parent(Base): __tab

我有一个在中展示的类继承方案,我想定义一个约束,它使用父类和子类中的列

from sqlalchemy import (
    create_engine, Column, Integer, String, ForeignKey, CheckConstraint
)
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()


class Parent(Base):
    __tablename__ = 'parent'

    id = Column(Integer, primary_key=True)
    type = Column(String)
    name = Column(String)

    __mapper_args__ = {'polymorphic_on': type}


class Child(Parent):
    __tablename__ = 'child'

    id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
    child_name = Column(String)

    __mapper_args__ = {'polymorphic_identity': 'child'}
    __table_args__ = (CheckConstraint('name != child_name'),)


engine = create_engine(...)
Base.metadata.create_all(engine)
这不起作用,因为
name
不是
child
中的列;我得到了错误

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "name" does not exist
 [SQL: '\nCREATE TABLE child (\n\tid INTEGER NOT NULL, \n\tPRIMARY KEY (id), \n\tCHECK (name="something"), \n\tFOREIGN KEY(id) REFERENCES parent (id)\n)\n\n']

那么我如何定义这样的约束呢?

简单回答:您不能使用CHECK约束来执行此操作

您不能在普通的RDBMS中执行此操作,因此您不能在使用SQLAlchemy中执行此操作。
但是,如果所有数据修改都通过应用程序(而不是直接访问数据库),则可以向类中添加验证例程:

class Child(Parent):
    # ...

    @validates('child_name')
    def validate_child_name(self, key, child_name):
        assert child_name != name
        return child_name

阅读更多。

经过一些修改后,我想出了一个解决方案:在
中创建一个“副本”
parent\u name
列,引用
parent
中的
name
。它会浪费一些存储空间,但为了获得真正的
检查约束
,这可能是不可避免的

代码如下:

from sqlalchemy import (
    create_engine, Column, Integer, String,
    CheckConstraint, UniqueConstraint, ForeignKeyConstraint
)
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.exc import IntegrityError


Base = declarative_base(metadata=metadata)


class Parent(Base):
    __tablename__ = 'parent'

    id = Column(Integer, primary_key=True)
    type = Column(String, nullable=False)
    name = Column(String, nullable=False)

    __mapper_args__ = {'polymorphic_on': type}
    __table_args__ = (UniqueConstraint('id', 'name'),)


class Child(Parent):
    __tablename__ = 'child'

    id = Column(Integer, primary_key=True)
    parent_name = Column(String, nullable=False)
    child_name = Column(String, nullable=False)

    __mapper_args__ = {'polymorphic_identity': 'child'}
    __table_args__ = (
        ForeignKeyConstraint(
            ['id', 'parent_name'], ['parent.id', 'parent.name'],
            onupdate='CASCADE', ondelete='CASCADE'
        ),
        CheckConstraint('parent_name != child_name'),
    )


engine = create_engine(...)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine, autocommit=True)()

print('Works without error:')
print('--------------------')

with session.begin():
    session.add(Child(name='a', child_name='b'))

print(session.query(Child).one().__dict__)

with session.begin():
    child = session.query(Child).one()
    child.name = 'c'

print(session.query(Child).one().__dict__)

print('\nFails due to IntegerityError:')
print('-------------------------------')
try:
    with session.begin():
        session.add(Child(name='a', child_name='a'))
except IntegrityError as e:
    print(e.orig)
    print(e.statement)
此脚本的输出是

Works without error:
--------------------
{'type': 'child', '_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7f5e9b9b7898>, 'id': 1, 'child_name': 'b', 'parent_name': 'a', 'name': 'a'}
{'type': 'child', '_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7f8fc80f2b38>, 'id': 1, 'child_name': 'b', 'parent_name': 'c', 'name': 'c'}

Fails due to IntegerityError:
-------------------------------
new row for relation "child" violates check constraint "child_check"
DETAIL:  Failing row contains (2, a, a).

INSERT INTO child (id, parent_name, child_name) VALUES (%(id)s, %(parent_name)s, %(child_name)s)
可以正常工作:
--------------------
{'type':'child','sa_instance_state':,'id':1,'child_name':'b','parent_name':'a','name':'a'}
{'type':'child','sa_instance_state':,'id':1,'child_name':'b','parent_name':'c','name':'c'}
由于IntegerityError而失败:
-------------------------------
关系“child”的新行违反了检查约束“child\u check”
详细信息:失败行包含(2,a,a)。
在子(id、父项名称、子项名称)中插入值(%(id)s、%(父项名称)s、%(子项名称)s)

我提出了一个解决方案/解决方法,我将其发布到了这个问题上。您认为该解决方案是否有任何不幸的副作用(性能等)?我不理解
父项上的
UniqueConstraint
的目的,因为它无论如何都不会被违反。您的解决方案基本上是信息的复制。我不确定这是一个积极的发展还是一个消极的发展。如果没有
UniqueConstraint
ForeignKeyConstraint
将无法工作。我同意最好不要重复,但我不认为有任何其他方法可以实现真正的
CheckConstraint
子项名称
,您没有检查父项名称!=parent.name。那么回到第1步,你不认为吗?
ForeignKeyConstraint
确保了
parent\u name==parent.name
。你是对的。我没有意识到SA会考虑设置
parent\u name
值,因为
FK
。看起来你的解决方案很好,谢谢分享。