Actually there was a type-o in my mapper I had: 'LatestNote':relation(Note,secondary=recent_notes ....
when it should have been: 'LatestNote':relation(Note,secondary=recent_task_notes .... No correlate(None) needed. As a side note adding correlate(None) caused the relation to function when lazy loaded, but to fail when eagerloaded as part of a larger query against the task table. Again thank you. David Gardner wrote: > Thats it! Thank you so much, I turned on the echo, and the SQL looks > exactly as I expected. > > Michael Bayer wrote: >> you've got "note.id AS id" sneaking into your first subquery. this is >> because your recent_task_notes is selecting it. if you mean for "notes" >> to be in the FROM clause of that query, add correlate(None) to that >> subquery. >> >> >> David Gardner wrote: >> >>> So I attempted to implement this, and I think I got perty close but >>> ended up getting stuck on trying to get the mapper working. Using SA >>> 0.5.2 w/ psycopg2 2.0.9. >>> >>> First I worked out the SQL for what I wanted and got this : >>> SELECT * FROM task >>> JOIN >>> ( SELECT note_task.task_name, note_task.task_asset,note.* >>> FROM note_task JOIN >>> (SELECT asset, MAX(updated) AS recent >>> FROM note >>> GROUP BY asset >>> ) AS latest ON (note_task.task_asset=latest.asset) >>> JOIN note ON (latest.asset=note.asset AND latest.recent=note.updated) >>> WHERE note_task.note=note.id >>> ) AS latest_note ON (task.asset=latest_note.asset AND >>> task.name=latest_note.task_name) >>> WHERE >>> task.name='UV' >>> AND task.asset='16307499967233846625'; >>> >>> Keep in mind task has a composite primary key of (name,asset), and asset >>> is a foreign key onto asset, and the note table also has a foreign key >>> onto asset. >>> Not all notes on an asset are related to tasks. >>> >>> Then I wrote two selects: >>> recent_notes = select([func.max(note_table.c.updated).label('updated'), >>> note_table.c.asset]).group_by(note_table.c.asset).alias() >>> recent_task_notes = select([note_task_table.c.task_asset, >>> note_task_table.c.task_name, note_table.c.id]).\ >>> >>> where(and_(recent_notes.c.asset==note_task_table.c.task_asset, >>> >>> note_task_table.c.note==note_table.c.id, >>> note_table.c.updated==recent_notes.c.updated)).alias() >>> >>> >>> When I do: >>> db.echo=True >>> db.execute(recent_task_notes).fetchone() >>> >>> The SQL and the result look good: >>> 2009-03-24 13:31:01,254 INFO sqlalchemy.engine.base.Engine.0x...d410 >>> SELECT note_task.task_asset, note_task.task_name, note.id >>> FROM note_task, note, (SELECT max(note.updated) AS updated, note.asset >>> AS asset >>> FROM note GROUP BY note.asset) AS anon_1 >>> WHERE anon_1.asset = note_task.task_asset AND note_task.note = note.id >>> AND note.updated = anon_1.updated >>> >>> >>> My mapper for Task and Notes now looks like: >>> >>> mapper(Task,task_table, properties={ >>> 'Notes':relation(Note,secondary=note_task_table, >>> order_by=note_table.c.updated.desc(), backref='Tasks'), >>> 'LatestNote':relation(Note,secondary=recent_notes, >>> >>> primaryjoin=and_(task_table.c.name==recent_task_notes.c.task_name,task_table.c.asset==recent_task_notes.c.task_asset), >>> >>> secondaryjoin=note_table.c.id==recent_task_notes.c.id, >>> >>> foreign_keys=[recent_task_notes.c.task_name,recent_task_notes.c.task_asset, >>> recent_task_notes.c.id],viewonly=True), >>> 'State':relation(TaskState), >>> 'Group':relation(Group, lazy=True)}, >>> save_on_init=False) >>> >>> mapper(Note, note_table, polymorphic_on=note_table.c.type, >>> polymorphic_identity='note', properties={ >>> 'Tags':relation(Tag, backref='Notes', >>> secondary=note_tags_table, order_by=note_tags_table.c.tag)}, >>> save_on_init=False) >>> mapper(EditorialNote, editorial_note_table, inherits=Note, >>> polymorphic_identity='editorial', save_on_init=False) >>> mapper(JobNote, job_note_table, inherits=Note, >>> polymorphic_identity='job', save_on_init=False) >>> >>> So When I try it out I get: >>> >>> session=create_session() >>> >>> t=session.query(Task).get(('28510740454639751607','Model')) >>> >>> t.LatestNote >>> Traceback (most recent call last): >>> File "<stdin>", line 1, in <module> >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/attributes.py", >>> line 159, in __get__ >>> return self.impl.get(instance_state(instance)) >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/attributes.py", >>> line 375, in get >>> value = callable_() >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/strategies.py", >>> line 589, in __call__ >>> result = q.all() >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/query.py", line >>> 1186, in all >>> return list(self) >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/query.py", line >>> 1280, in __iter__ >>> return self._execute_and_instances(context) >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/query.py", line >>> 1283, in _execute_and_instances >>> result = self.session.execute(querycontext.statement, >>> params=self._params, mapper=self._mapper_zero_or_none()) >>> File "/var/lib/python-support/python2.5/sqlalchemy/orm/session.py", >>> line 755, in execute >>> clause, params or {}) >>> File "/var/lib/python-support/python2.5/sqlalchemy/engine/base.py", >>> line 824, in execute >>> return Connection.executors[c](self, object, multiparams, params) >>> File "/var/lib/python-support/python2.5/sqlalchemy/engine/base.py", >>> line 874, in _execute_clauseelement >>> return self.__execute_context(context) >>> File "/var/lib/python-support/python2.5/sqlalchemy/engine/base.py", >>> line 896, in __execute_context >>> self._cursor_execute(context.cursor, context.statement, >>> context.parameters[0], context=context) >>> File "/var/lib/python-support/python2.5/sqlalchemy/engine/base.py", >>> line 950, in _cursor_execute >>> self._handle_dbapi_exception(e, statement, parameters, cursor, >>> context) >>> File "/var/lib/python-support/python2.5/sqlalchemy/engine/base.py", >>> line 931, in _handle_dbapi_exception >>> raise exc.DBAPIError.instance(statement, parameters, e, >>> connection_invalidated=is_disconnect) >>> sqlalchemy.exc.ProgrammingError: (ProgrammingError) subquery in FROM >>> cannot refer to other relations of same query level >>> 'SELECT note.id AS note_id, note.updated AS note_updated, note.created >>> AS note_created, note.author AS note_author, note.note AS note_note, >>> note.asset AS note_asset, note.type AS note_type \nFROM note, (SELECT >>> note_task.task_asset AS task_asset, note_task.task_name AS task_name, >>> note.id AS id \nFROM note_task, (SELECT max(note.updated) AS updated, >>> note.asset AS asset \nFROM note GROUP BY note.asset) AS anon_1 \nWHERE >>> anon_1.asset = note_task.task_asset AND note_task.note = note.id AND >>> note.updated = anon_1.updated) AS anon_2 \nWHERE %(param_1)s = >>> anon_2.task_name AND %(param_2)s = anon_2.task_asset AND note.id = >>> anon_2.id' {'param_1': 'Model', 'param_2': '28510740454639751607'} >>> >>> >>> >>> >>> >>> Michael Bayer wrote: >>> >>>> the "relational" way to do this is to select the note with a date >>>> matching the most recent date in the view of notes. you can perhaps >>>> also make a viewonly relation() that selects something similar, like: >>>> >>>> recent_notes = >>>> select([func.max(note_table.c.updated).label('updated'), >>>> note_task_table.c.task_id]).\ >>>> where(note_table.c.id==note_task_table.c.note_id).\ >>>> group_by(note_task_table.c.task_id) >>>> >>>> 'notes': >>>> relation(Note, >>>> secondary=recent_notes, >>>> primaryjoin=task_table.c.id==recent_notes.c.task_id, >>>> secondaryjoin=note_table.c.updated==recent_notes.c.updated, >>>> foreign_keys=list(recent_notes.c), >>>> viewonly=True) >>>> >>>> you can of course issue this kind of SQL more manually using query and >>>> contains_eager(). >>>> >>>> I think there might be some way to work this using a subquery with >>>> LIMIT as well, which probably runs a lot faster in MySQL, but that >>>> doesn't occur to me as naturally. >>>> >>>> On Mar 23, 2009, at 9:02 PM, David Gardner wrote: >>>> >>>> >>>> >>>>> I have a simple many to many relationship between two objects: Task >>>>> and >>>>> Note, where notes are ordered by a timestamp column most recent first. >>>>> About 90% of the time what I really want to do is eagerload only the >>>>> most recent note, is there a way to do this in the mapper? >>>>> >>>>> My mapper for Task looks like this: >>>>> mapper(Task,task_table, properties={ >>>>> 'Notes':relation(Note,secondary=note_task_table, >>>>> order_by=note_table.c.updated.desc(), backref='Tasks'), >>>>> 'State':relation(TaskState), >>>>> 'Group':relation(Group, lazy=True)}, >>>>> save_on_init=False) >>>>> >>>>> Usually my code ends up looking like: >>>>> >>>>> >>>>> tasks >>>>> = >>>>> session >>>>> .query >>>>> (Task >>>>> ).filter >>>>> (Task.assigned_to=='dgardner').options(eagerload(Task.Notes)).all() >>>>> >>>>> >>>>>>>> for task in tasks: >>>>>>>> >>>>>>>> >>>>> ... if len(task.Notes): >>>>> ... latest_note=task.Notes[0] >>>>> >>>>> -- >>>>> David Gardner >>>>> Pipeline Tools Programmer, "Sid the Science Kid" >>>>> Jim Henson Creature Shop >>>>> dgard...@creatureshop.com >>>>> >>>>> >>>>> >>>>> >>>>> >>>> >>>> >>> -- >>> David Gardner >>> Pipeline Tools Programmer, "Sid the Science Kid" >>> Jim Henson Creature Shop >>> dgard...@creatureshop.com >>> >>> >>> >> >> >> >> >> > > > -- > David Gardner > Pipeline Tools Programmer, "Sid the Science Kid" > Jim Henson Creature Shop > dgard...@creatureshop.com > > > > -- David Gardner Pipeline Tools Programmer, "Sid the Science Kid" Jim Henson Creature Shop dgard...@creatureshop.com --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---