Here are the models:
class User(Base): __tablename__ = 'users' id = Column(CHAR, primary_key=True) first_name = Column(CHAR) last_name = Column(CHAR) email = Column(CHAR) receive_reports = Column(Boolean) class MailPiece(Base): __tablename__ = 'mail_pieces' id = Column(CHAR, primary_key=True) created_at = Column(DateTime) template_id = Column(CHAR, ForeignKey('templates.id')) class Template(Base): __tablename__ = 'templates' id = Column(CHAR, primary_key=True) name = Column(CHAR) created_by_id = Column(CHAR, ForeignKey('users.id')) user = relationship(User, backref='templates')
I want to send to users reports with: which templates they are sent and how many mail pieces was sent for each template.
I wrote the code:
stmt = self.session.query(MailPiece.template_id, func.count('*') .label('mail_pieces_count')).filter( MailPiece.created_at > day_ago, MailPiece.created_at < now, ).group_by(MailPiece.template_id).subquery() query = self.session.query(Template, stmt.c.mail_pieces_count). filter(Template.user.has(receive_reports=True)). join(stmt, Template.id == stmt.c.template_id)
But this is slightly not what I wanted. I got result grouped by templates but need list grouped by users where each user will have templates. So in that way I can iterate through users list and send report to each user with summary.
Current result:
[<Template(id='123', name='Test', mail_pieces_count='123', user=<User(id=1212, first_name='Some name', last_name='Some lastname')>)>, <Template(id='456', name='Test2', mail_pieces_count='456', user=<User(id=1212, first_name='Some name', last_name='Some lastname')>)>]
Expected result:
[<User(id=1212, first_name='Some name', last_name='Some lastname, templates=[<Template(id='123', name='Test', mail_pieces_count='123')>, <Template(id='456', name='Test2', mail_pieces_count='456')>,])>]
In other words, now it can be represented like: Current:
templates = [ { "id": 123, "name": "Test", "mail_pieces_count": 123, "user": { "id": 1212, "first_name": "Some name", "last_name": "Some lastname" } }, { "id": 456, "name": "Test2", "mail_pieces_count": 456, "user": { "id": 1212, "first_name": "Some name", "last_name": "Some lastname" } } ]
Expected:
users = [ { "id": 1212, "first_name": "Some name", "last_name": "Some lastname", "templates": [ { "id": 123, "name": "Test", "mail_pieces_count": 123 }, { "id": 456, "name": "Test2", "mail_pieces_count": 456 },] }, ]
Advertisement
Answer
The reason of current output:
You build a query using Template class:
self.session.query(Template, stmt.c.mail_pieces_count)
This is the main reason why you got Template
instances instead of User
. Below just an example how to get expected result.
Note! I just try to explain how it works. My answer isn’t related to optimization, performance etc.
# I skipped filters... stmt = (session.query(MailPiece.template_id, func.count('*') .label('mail_pieces_count')) .group_by(MailPiece.template_id).subquery()) # to tie templates with upper subquery stmt2 = (session.query(Template.created_by_id, stmt.c.mail_pieces_count) .join(stmt, Template.id == stmt.c.template_id) .subquery()) # User query - to tie template id with user id query = session.query(User, stmt2.c.mail_pieces_count).join( stmt2, # you can add additional conditions into JOIN ON... and_(User.id == stmt2.c.created_by_id, User.receive_reports.is_(True)) ) for result in query: print("count: %s" % result.mail_pieces_count) print("user: %s" % result.User) print("templates: %s" % result.User.templates)
JFYI. If we look at console you will find that alchemy will do 1 query per iteration:
### one more select when you use result.User.templates 2019-02-11 13:02:24,702 INFO sqlalchemy.engine.base.Engine SELECT templates.id AS templates_id, templates.name AS templates_name, templates.created_by_id AS templates_created_by_id FROM templates WHERE %(param_1)s = templates.created_by_id
You can add Template
into query to avoid this, but in this case you will get the number of records equal to the number of templates (not users):
stmt2 = (session.query(Template, stmt.c.mail_pieces_count) .join(stmt, Template.id == stmt.c.template_id) .subquery()) # select User and Template query = session.query(User, Template, stmt2.c.mail_pieces_count).join( stmt2, and_(User.id == stmt2.c.created_by_id) ) for result in query.all(): print("user: %s" % result.User) print("template: %s" % result.Template)
So summarize, you need to use a specific class if you need to get instances of the class as a result:
session.query(ExpectedClass).other_methods()...
Hope this helps.