SQLAlchemy与单表继承的关系

时间:2014-04-19 05:11:32

标签: python sqlalchemy

我正在使用SQLAlchemy的单表继承TransactionStudentTransactionCompanyTransaction

class Transaction(Base):
    __tablename__ = 'transaction'

    id = Column(Integer, primary_key=True)

    # Who paid? This depends on whether the Transaction is a
    # CompanyTransaction or a StudentTransaction. We use
    # SQLAlchemy's Single Table Inheritance to make this work.
    discriminator = Column('origin', String(50))
    __mapper_args__ = {'polymorphic_on': discriminator}

    # When?
    time = Column(DateTime, default=datetime.utcnow)

    # Who administered it?
    staff_id = Column(Integer, ForeignKey('staff.id'))
    staff = relationship(
        'Staff',
        primaryjoin='and_(Transaction.staff_id==Staff.id)'
    )

    # How much?
    amount = Column(Integer)  # Negative for refunds, includes the decimal part

    # Type of transaction
    type = Column(Enum(
        'cash',
        'card',
        'transfer'
    ))


class CompanyTransaction(Transaction):
    __mapper_args__ = {'polymorphic_identity': 'company'}

    company_id = Column(Integer, ForeignKey('company.id'))
    company = relationship(
        'Company',
        primaryjoin='and_(CompanyTransaction.company_id==Company.id)'
    )


class StudentTransaction(Transaction):
    __mapper_args__ = {'polymorphic_identity': 'student'}

    student_id = Column(Integer, ForeignKey('student.id'))
    student = relationship(
        'Student',
        primaryjoin='and_(StudentTransaction.student_id==Student.id)'
    )

然后,我有一个学生,它定义了与StudentTransactions的一对多关系:

class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer, primary_key=True)

    transactions = relationship(
        'StudentTransaction',
        primaryjoin='and_(Student.id==StudentTransaction.student_id)',
        back_populates='student'
    )


    @hybrid_property
    def balance(self):
        return sum([transaction.amount for transaction in self.transactions])

问题是,为NotImplementedError: <built-in function getitem>函数中的返回行调用学生收益:Student.balance()

我做错了什么?

感谢。

1 个答案:

答案 0 :(得分:7)

hybrid property是一种允许生成Python描述符的构造,该描述符在实例级别以一种方式运行,而在类级别以另一种方式运行。在类级别,我们希望它生成一个SQL表达式。使用像sum()这样的纯Python函数或列表推导来生成SQL表达式是不合法的。

在这种情况下,如果我从“学生”表中查询并且我希望在“事务”表中生成“金额”列的总和,我可能希望将相关子查询与SQL一起使用集合函数。我们希望看到的SQL类似于:

SELECT * FROM student WHERE (
      SELECT SUM(amount) FROM transaction WHERE student_id=student.id) > 500

我们的混合动力必须控制并产生这种表达:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property

Base = declarative_base()

class Transaction(Base):
    __tablename__ = 'transaction'

    id = Column(Integer, primary_key=True)
    discriminator = Column('origin', String(50))
    __mapper_args__ = {'polymorphic_on': discriminator}
    amount = Column(Integer)

class StudentTransaction(Transaction):
    __mapper_args__ = {'polymorphic_identity': 'student'}

    student_id = Column(Integer, ForeignKey('student.id'))
    student = relationship(
        'Student',
        primaryjoin='and_(StudentTransaction.student_id==Student.id)'
    )

class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer, primary_key=True)

    transactions = relationship(
        'StudentTransaction',
        primaryjoin='and_(Student.id==StudentTransaction.student_id)',
        back_populates='student'
    )

    @hybrid_property
    def balance(self):
        return sum([transaction.amount for transaction in self.transactions])

    @balance.expression
    def balance(cls):
        return select([
                    func.sum(StudentTransaction.amount)
                ]).where(StudentTransaction.student_id==cls.id).as_scalar()

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)

s.add_all([
    Student(transactions=[StudentTransaction(amount=50), StudentTransaction(amount=180)]),
    Student(transactions=[StudentTransaction(amount=600), StudentTransaction(amount=180)]),
    Student(transactions=[StudentTransaction(amount=25), StudentTransaction(amount=400)]),
])

print s.query(Student).filter(Student.balance > 400).all()

最后的输出:

SELECT student.id AS student_id 
FROM student 
WHERE (SELECT sum("transaction".amount) AS sum_1 
FROM "transaction" 
WHERE "transaction".student_id = student.id) > ?
2014-04-19 19:38:10,866 INFO sqlalchemy.engine.base.Engine (400,)
[<__main__.Student object at 0x101f2e4d0>, <__main__.Student object at 0x101f2e6d0>]