Search code examples
pythonsqlalchemysubquerydeclarative

SQLAlchemy Subquery for Suming values from another table


I am struggling to understand the proper syntax for executing a subquery in SQLAlchemy when the value needs to be returned (i.e. not used in the WHERE).

I am using the declarative approach.

There are two models being used:

class ProjectInvoices(Base):
    InvoiceID = Column(Integer(unsigned=True), default=0, primary_key=True, autoincrement=True)
    MasterProjectID = Column(Integer(unsigned=True), index=True, nullable=False)
    ExpenseAmount = Column(Numeric(10, 2), default=0)
    HoursAmount = Column(Numeric(10, 2), default=0)
    IsVoid = Column(Boolean, default=0, index=True)
    IsSubmit = Column(Boolean, default=0, index=True)

class ProjectMasters(Base):
    MasterProjectID = Column(Integer(unsigned=True), default=0, primary_key=True, autoincrement=True)
    MasterProjectName = Column(Unicode(255))
    MasterProjectMemo = Column(UnicodeText)
    IsActive = Column(Boolean, default=0, index=True)

The point of the query is to determine the current amount invoiced for each project by using a subquery to sum the related invoices. There are other reasons this is being done in a subquery rather than just a join so I really need to figure out the subquery issue.

This is an example of my current SA query:

sqry = session.query(
  func.sum(
    func.ifnull(ProjectInvoices.ExpenseAmount, 0) 
    + func.ifnull(ProjectInvoices.HoursAmount, 0))).label('billed_total')
).filter(and_(ProjectInvoices.IsVoid == 0, ProjectInvoices.IsSubmit == 1)
).subquery()

result = session.query(
  ProjectMasters.MasterProjectID, 
  ProjectMasters.MasterProjectName, 
  sqry.columns.billed_total.label('invoice_total')
).filter(ProjectMasters.IsActive == 1).all()

I have a feeling this is going to be embarrassing simple but I can't seem to crack the code on getting this to work.

I have tried just about every sample I can find with mixed results. If I leave out the .correlate() argument I receive the following error:

'Alias' object has no attribute 'MasterProjectID'

I have also tried adding the following statement to the end of the subquery() without any luck:

.correlate(ProjectMasters.MasterProjectID, ProjectInvoices.MasterProjectID)

If I do include the correlate argument then I receive the following error:

TypeError: Boolean value of this clause is not defined

Thanks for the help in advance...


Solution

  • Usually I would use a column_property to handle such requirement, e.g.

    class ProjectMasters(Base):
        ...
    
        billed_total = column_property(
            select(
                [func.sum(
                    func.coalesce(ProjectInvoices.ExpenseAmount, 0)
                    + func.coalesce(ProjectInvoices.HoursAmount, 0)
                )],
                and_(
                    MasterProjectID == ProjectInvoices.MasterProjectID,
                    ProjectInvoices.IsVoid == False,
                    ProjectInvoices.IsSubmit == True,
                ),
            ).label('billed_total'),
            deferred=True,
        )
    

    After that, you can use it like an ordinary attribute, e.g.

    result = session.query(
        ProjectMasters.MasterProjectID, 
        ProjectMasters.MasterProjectName, 
        ProjectMasters.billed_total.label('invoice_total'),
    ).filter(ProjectMasters.IsActive == 1).all()