如何在SQLAlchemy中为继承列定义约束?

时间:2015-06-10 19:04:33

标签: python sqlalchemy

我有一个类继承方案,如http://docs.sqlalchemy.org/en/latest/orm/inheritance.html#joined-table-inheritance所示,我想定义一个使用父类和子类列的约束。

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']

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

2 个答案:

答案 0 :(得分:2)

简单回答:你不能使用CHECK Constraint执行此操作。

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

class Child(Parent):
    # ...

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

了解更多Simple Validators

答案 1 :(得分:1)

经过一些修补后,我提出了一个解决方案:在parent_name中创建一个引用Childname的“复制”Parent列。它浪费了一些存储空间,但为了拥有真正的CHECK CONSTRAINT,这可能是不可避免的。

以下是代码:

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)