thanks for the help. it probably makes sense to alias the on-clause to the query then, i just wasn't thinking in that way for some reason.
> So here you want to first call reset_joinpoint() before calling join() again. tried this and got the same result (doesn't join/outerjoin reset the join point by default anyway): sess = Session() AliasedA = orm.aliased(A) q = sess.query(A) q = q.join((AliasedA, A.some_a)).options(orm.contains_eager(A.some_a,alias=AliasedA)) q = q.reset_joinpoint() # added q = q.join((B, A.some_b)).options(orm.contains_eager(A.some_b)) print q > use the separate orm.join() construct in conjunction with select_from() started converting over to this but on a whim instead aliased all joined targets *and* used that alias when specifying relations (as well as in the relation chain passed to contains_eager). it looks like that provides query.join/outerjoin enough information to choose the desired lhs in the join clause. updated example: ================= from sqlalchemy import create_engine, Table, MetaData, orm, Column, Integer, ForeignKey, String from sqlalchemy.interfaces import PoolListener class Listener(PoolListener): def connect(self, dbapi_con, con_record): dbapi_con.execute("PRAGMA foreign_keys=ON") engine = create_engine( "sqlite:///:memory:", listeners=[Listener()], echo=True) Session = orm.sessionmaker(bind=engine) metadata = MetaData() c_table = Table( "c", metadata, Column("id", Integer, primary_key=True), Column("c_data", String) ) a_table = Table( "a", metadata, Column("id", Integer, primary_key=True), Column("a_data", String), Column("c_id", ForeignKey(c_table.c.id)) ) assoc_table = Table( "assoc", metadata, Column("left_id", ForeignKey(a_table.c.id)), Column("right_id", ForeignKey(a_table.c.id)) ) b_table = Table( "b", metadata, Column("id", Integer, primary_key=True), Column("a_id", ForeignKey(a_table.c.id)), Column("b_data", String) ) class C(object): @staticmethod def apply_relations(q, spec, parent, chain): for name, sub_spec in spec.iteritems(): raise ValueError("Invalid relation '%s'" % name) return q orm.mapper(C, c_table) class B(object): @staticmethod def apply_relations(q, spec, parent, chain): for name, sub_spec in spec.iteritems(): raise ValueError("Invalid relation '%s'" % name) return q orm.mapper(B, b_table) class A(object): @staticmethod def apply_relations(q, spec, parent, chain): for name, sub_spec in spec.iteritems(): if name == "some_a": AliasA = orm.aliased(A) chain.append(parent.some_a) q = q.outerjoin((AliasA, parent.some_a)).options(orm.contains_eager(*chain, alias=AliasA)) q = A.apply_relations(q, sub_spec, AliasA, chain) del chain[-1] elif name == "some_b": AliasB = orm.aliased(B) chain.append(parent.some_b) q = q.outerjoin((AliasB, parent.some_b)).options(orm.contains_eager(*chain, alias=AliasB)) q = B.apply_relations(q, sub_spec, AliasB, chain) del chain[-1] elif name == "c": AliasC = orm.aliased(C) chain.append(parent.c) q = q.outerjoin((AliasC, parent.c)).options(orm.contains_eager(*chain, alias=AliasC)) q = C.apply_relations(q, sub_spec, AliasC, chain) del chain[-1] else: raise ValueError("Invalid relation '%s'" % name) return q orm.mapper( A, a_table, properties={ "some_a": orm.relation( A, primaryjoin=a_table.c.id == assoc_table.c.left_id, secondary=assoc_table, secondaryjoin=assoc_table.c.right_id == a_table.c.id), "some_b": orm.relation( B, primaryjoin=b_table.c.a_id == a_table.c.id), "c": orm.relation(C) } ) relation_spec = { "some_a": { "c": { }, "some_b": { }, "some_a": { "some_a": { "c": { } }, "some_b": { }, }, }, "some_b": { }, "c": { } } sess = Session() q = sess.query(A) q = A.apply_relations(q, relation_spec, A, []) #q = sess.query(A) #AliasedA1 = orm.aliased(A) #q = q.join((AliasedA1, A.some_a)).options(orm.contains_eager(A.some_a, alias=AliasedA1)) #AliasedA1C1 = orm.aliased(C) #q = q.join((AliasedA1C1, AliasedA1.c)).options(orm.contains_eager(A.some_a, AliasedA1.c, alias=AliasedA1C1)) #AliasedA1B1 = orm.aliased(B) #q = q.join((AliasedA1B1, AliasedA1.some_b)).options(orm.contains_eager(A.some_a, AliasedA1.some_b, alias=AliasedA1B1)) #AliasedA1A1 = orm.aliased(A) #q = q.join((AliasedA1A1, AliasedA1.some_a)).options(orm.contains_eager(A.some_a, AliasedA1.some_a, alias=AliasedA1A1)) #AliasedB1 = orm.aliased(B) #q = q.join((AliasedB1, A.some_b)).options(orm.contains_eager(A.some_b, alias=AliasedB1)) print q ================= On Sep 20, 7:56 am, Michael Bayer <mike...@zzzcomputing.com> wrote: > On Sep 19, 2010, at 11:59 PM, me wrote: > > > > > when i apply multiple joins to orm query, one of them self-referring, > > using (target, property) form sqla generates the "wrong" join criteria > > by selecting aliased table for the left side of the join. > > > sess = Session() > > AliasedA = orm.aliased(A) > > q = sess.query(A) > > q = q.join((AliasedA, A.some_a)).options(orm.contains_eager(A.some_a, > > alias=AliasedA)) > > q = q.join((B, A.some_b)).options(orm.contains_eager(A.some_b)) > > print q > > > --> > > > SELECT > > b.id AS b_id, b.a_id AS b_a_id, b.b_data AS b_b_data, > > a_1.id AS a_1_id, a_1.a_data AS a_1_a_data, > > a.id AS a_id, a.a_data AS a_a_data > > FROM a > > JOIN assoc AS assoc_1 ON a.id = assoc_1.left_id > > JOIN a AS a_1 ON assoc_1.right_id = a_1.id > > JOIN b ON b.a_id = a_1.id > > ======================== > > > here i expected "JOIN b ON b.a_id = a.id" not "JOIN b ON b.a_id = > > a_1.id". > > This one is fun. join() always joins from the most recent FROM that it can. > So here you want to first call reset_joinpoint() before calling join() > again. The other "traditional" way is to use the separate orm.join() > construct in conjunction with select_from(), though we are continuing to > refine join() so that wont be needed (I eventually would like it to allow > (left, right, onclause)...but we need to get everyone on a modernized calling > form first). > > > perhaps weight relations so that those that introduce alias are > > applied in right order? > > interesting....but this is really a 50/50 guess. Its just as likely someone > really meant to join from AliasedA to B. -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@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.