2014-04-19 159 views
1

我使用的SQLAlchemy的單表繼承了TransactionStudentTransactionCompanyTransaction SQLAlchemy的關係:與單表繼承

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

問題是,調用Student yield:NotImplementedError: <built-in function getitem>Student.balance()函數的返回行。

我在做什麼錯?

謝謝。

回答

6

a hybrid property是一種構造,它允許生成在實例級別以一種方式運行的Python描述符,而在另一種類級別則可以運行。在課堂上,我們希望它能夠生成一個SQL表達式。爲了生成SQL表達式,使用普通Python函數(如sum()或列表推導)是不合法的。在這種情況下,如果我從「學生」表中查詢,並且希望在「交易」表中生成「金額」列的總和,那麼我可能會想要使用相關子查詢和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>] 
+0

謝謝,這有效 - 很好的答案! – sssilver

+0

只有一個問題:什麼時候「平衡」在實例級調用,什麼時候在類級調用?當我在實例級別的'balance'上放置一個斷點時,我仍然可以看到它正在被調用。 – sssilver

+1

課程級別是:Student.balance,實例級別是:s = Student(); s.balance。我可以在實例級別balance()中放置「引發Exception()」,運行腳本,但不會調用它。 – zzzeek