SQLAlchemy与单表继承的关系

sss*_*ver 3 python sqlalchemy

我使用的SQLAlchemy的单表继承了Transaction,StudentTransactionCompanyTransaction:

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)'
    )
Run Code Online (Sandbox Code Playgroud)

然后,我有一个学生,它定义了与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])
Run Code Online (Sandbox Code Playgroud)

问题是,调用Student yield:NotImplementedError: <built-in function getitem>对于Student.balance()函数中的返回行.

我究竟做错了什么?

谢谢.

zzz*_*eek 10

一个混合属性是一个构建体,其允许生产一个Python描述符中在实例级别的一种方式其行为,并且在在类级别的另一种方式.在类级别,我们希望它生成一个SQL表达式.使用纯Python函数sum()或列表推导来生成SQL表达式是不合法的.

在这种情况下,如果我是从"学生"表查询,我希望生产的"金额"栏的总和,在"交易"表中,我可能会想使用相关子查询与SQL聚合函数.我们希望看到的SQL类似于:

SELECT * FROM student WHERE (
      SELECT SUM(amount) FROM transaction WHERE student_id=student.id) > 500
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

最后的输出:

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>]
Run Code Online (Sandbox Code Playgroud)