Appreciate the response, that all makes sense to me. In hindsight, I'm wondering how I ever expected there to be two Album objects with the same identifier and different relationship collections...
On a conceptual level, I wonder how feasible it'd be to have objects expunged immediately as they're loaded. That way the parent Album with album_id==1 could load, have it's tracks load, then when the child album is loaded it wouldn't be the same Album object despite being album_id==1. My use case is purely read only, so I'm not particularly concerned with any changes being tracked/persisted,so such a scenario would accomplish what I'd need. But I realize my use case is pretty niche. On Monday, August 6, 2018 at 9:58:33 AM UTC-5, Mike Bayer wrote: > > On Sat, Aug 4, 2018 at 11:27 PM, Nick Repole <nick....@gmail.com > <javascript:>> wrote: > > Hi, > > > > I'm attempting to load some filtered relationships, and am running into > > issues when trying to independently load the same relationship multiple > > times. > > > > As an example, I'd like to load Album.tracks, and > Album.tracks.album.tracks > > in different ways. In the first tracks relationship, I'm trying to > include > > all tracks with track_id >= 4, and in the second relationship every > trackId > > < 4. > > > > Is this possible? And if so, what load options or technique should I be > > using? The query generated below contains all the information I'm trying > to > > get, it's a matter of loading that information into my models that's > > tripping me up. > > > > Complete/runnable example can be found attached or below: > > great test case. What you're doing is not possible because you are > looking at the "album.tracks" collection in two different contexts, > and expecting them to have different contents, but this is the same > Album object and the same "tracks" collection: > > (Pdb) results[0] > <__main__.Album object at 0x7f2e1eea3898> > (Pdb) results[0].tracks[0].album > <__main__.Album object at 0x7f2e1eea3898> > (Pdb) results[0].tracks is results[0].tracks[5].album.tracks > True > > this is how the ORM identity map works, for any class / primary key, > there's only one of them. > > now since this is SQLAlchemy, there are ways to do this, but it means > you'd have two different Album objects with the same primary key in > play. if you were to change both of them in a session and try to > flush, you will get conflicts and/or errors. > > Here's the quickest way, note thre's now Album.tracks.parent_album for > the real relationship: > > class Album(Base): > > __tablename__ = 'Album' > > album_id = Column("AlbumId", Integer, primary_key=True) > title = Column("Title", Unicode(160), nullable=False) > tracks = relationship("Track", backref="parent_album") > > > class SubAlbum(Album): > pass > > > class Track(Base): > > __tablename__ = 'Track' > > track_id = Column("TrackId", Integer, primary_key=True) > name = Column("Name", Unicode(200), nullable=False) > album_id = Column("AlbumId", ForeignKey('Album.AlbumId'), index=True) > album = relationship('SubAlbum') > > > query = session.query(Album).filter(Album.album_id == 1) > track_1_alias = aliased(Track, name="Track1") > album_2_alias = aliased(SubAlbum, name="Album2") > track_2_alias = aliased(Track, name="Track2") > > then your assertion passes. > > > as you can see it is not possible for album<->track to be a > bidirectional relationship and refer to two different Album objects at > the same time. you have to choose one or the other. > > > > > > > > > > > > from sqlalchemy import create_engine > > from sqlalchemy.orm import aliased, sessionmaker, relationship, > > contains_eager > > from sqlalchemy import Column, ForeignKey, Integer, Unicode > > from sqlalchemy.ext.declarative import declarative_base > > > > Base = declarative_base() > > metadata = Base.metadata > > > > > > class Album(Base): > > > > __tablename__ = 'Album' > > > > album_id = Column("AlbumId", Integer, primary_key=True) > > title = Column("Title", Unicode(160), nullable=False) > > > > > > class Track(Base): > > > > __tablename__ = 'Track' > > > > track_id = Column("TrackId", Integer, primary_key=True) > > name = Column("Name", Unicode(200), nullable=False) > > album_id = Column("AlbumId", ForeignKey('Album.AlbumId'), > index=True) > > > > album = relationship('Album', backref="tracks") > > > > > > engine = create_engine('sqlite://', echo=True) > > Base.metadata.create_all(engine, checkfirst=True) > > session_cls = sessionmaker(bind=engine) > > session = session_cls() > > > > > > # Set up the database with Chinook data > > session.add(Album(album_id=1, title="For Those About To Rock We Salute > > You")) > > session.add(Album(album_id=2, title="Balls to the Wall")) > > session.add(Album(album_id=3, title="Restless and Wild")) > > session.add(Album(album_id=4, title="Let There Be Rock")) > > session.add(Track(track_id=1, name="For Those About To Rock", > album_id=1)) > > session.add(Track(track_id=2, name="Balls to the Wall", album_id=2)) > > session.add(Track(track_id=3, name="Fast As a Shark", album_id=3)) > > session.add(Track(track_id=4, name="Restless and Wild", album_id=3)) > > session.add(Track(track_id=5, name="Princess of the Dawn", album_id=3)) > > session.add(Track(track_id=6, name="Put The Finger On You", album_id=1)) > > session.add(Track(track_id=7, name="Let's Get It Up", album_id=1)) > > session.add(Track(track_id=8, name="Inject The Venom", album_id=1)) > > session.add(Track(track_id=9, name="Snowballed", album_id=1)) > > session.add(Track(track_id=10, name="Evil Walks", album_id=1)) > > session.add(Track(track_id=11, name="C.O.D.", album_id=1)) > > session.add(Track(track_id=12, name="Breaking The Rules", album_id=1)) > > session.add(Track(track_id=13, name="Night Of The Long Knives", > album_id=1)) > > session.add(Track(track_id=14, name="Spellbound", album_id=1)) > > session.add(Track(track_id=15, name="Go Down" , album_id=4)) > > session.add(Track(track_id=16, name="Dog Eat Dog", album_id=4)) > > session.add(Track(track_id=17, name="Let There Be Rock", album_id=4)) > > session.add(Track(track_id=18, name="Bad Boy Boogie", album_id=4)) > > session.add(Track(track_id=19, name="Problem Child", album_id=4)) > > session.add(Track(track_id=20, name="Overdose", album_id=4)) > > session.add(Track(track_id=21, name="Hell Aint A Bad Place To Be", > > album_id=4)) > > session.add(Track(track_id=22, name="Whole Lotta Rosie", album_id=4)) > > session.commit() > > > > > > # Query set up > > query = session.query(Album).filter(Album.album_id == 1) > > track_1_alias = aliased(Track, name="Track1") > > album_2_alias = aliased(Album, name="Album2") > > track_2_alias = aliased(Track, name="Track2") > > > > # Subqueries we want to load our relationships from > > # This will match 9 tracks for AlbumId==1 > > track_1_subquery = session.query(track_1_alias).filter( > > track_1_alias.track_id >= 4).subquery("Track1") > > album_2_subquery = session.query(album_2_alias).subquery("Album2") > > # This should only match 1 track for AlbumId==1 > > track_2_subquery = session.query(track_2_alias).filter( > > track_2_alias.track_id < 4).subquery("Track2") > > > > # Set up our joins > > query = query.outerjoin(track_1_subquery, Album.tracks) > > query = query.outerjoin(album_2_subquery, track_1_alias.album) > > # Using album_2_alias.tracks for the second argument doesn't seem to > work > > here. > > query = query.outerjoin(track_2_subquery, > > album_2_alias.album_id == > track_2_alias.album_id) > > > > > > # Options - These don't work all at once, but seem fine separately > > option1 = contains_eager("tracks", alias=track_1_subquery) > > option2 = contains_eager("tracks.album", alias=album_2_subquery) > > option3 = contains_eager("tracks.album.tracks", alias=track_2_subquery) > > > > > > # Attempt to load just tracks > > option3_query = query.options(option3) > > results = option3_query.all() > > assert(len(results) == 1) > > # len here should be 9, but it's erroneously filtered to 1 > > assert(len(results[0].tracks) == 1) > > assert(len(results[0].tracks[0].album.tracks) == 1) > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google > Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send > an > > email to sqlalchemy+...@googlegroups.com <javascript:>. > > To post to this group, send email to sqlal...@googlegroups.com > <javascript:>. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > For more options, visit https://groups.google.com/d/optout. > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.