简体   繁体   中英

How to specify the join condition on a many-to-many relationship using SQLAlchemy

Here is my model:

user_map = Table(
    "user_map",
    Column('user_id', Integer, ForeignKey('user.id'), primary_key=True),
    Column('map_id', Integer, ForeignKey('map.id'), primary_key=True),
    PrimaryKeyConstraint('user_id', 'map_id', name='pk_user_map')
)


class Map(Base):

    id = Column(Integer, primary_key=True)
    name = Column(String)
    owner_id = Column(Integer, ForeignKey('user.id'))
    shared_maps = relationship(
        'User',
        secondary=user_map,
        backref=backref('maps', lazy='dynamic')
    )

class User(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String, unique=True)

shared_maps = Map.query.filter(Map.shared_maps.any()).all()

I want to query the user_map table, using the join condition "Map.id == user_map.map_id", but SQLAlchemy is trying to join using "Map.id == user_map.map_id and Map.owner_id == user_map.user_id". How can I specify my join condition?

I tried to use primaryjoin attribute in the relationship and to specify the condition inside the .join() but without success. Thanks in advance!

Based on your code, I've rebuilt the setup; I guess your relationship s were mixed up. Furthermore, I've hardly ever seen primary keys (or PrimaryKeyConstraints ) in sqlalchemy's many-to-many association tables. It may make sense from a non-orm perspective, but as far as I know, it is unusual or even not required at all.

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

Base = declarative_base()

UsersXMaps = sa.Table(
    'users_x_maps',
    Base.metadata,
    sa.Column('user', sa.Integer, sa.ForeignKey('users.id')),
    sa.Column('map', sa.Integer, sa.ForeignKey('maps.id'))
)

class User(Base):
    __tablename__ = 'users'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)
    mail = sa.Column(sa.String, unique=True)
    own_maps = relationship('Map', back_populates='owner')
    maps = relationship(
        'Map',
        secondary=UsersXMaps,
        back_populates='users'
    )

    def __str__(self):
        return '{} ({}) with {} maps'.format(
            self.name, self.mail, len(self.own_maps))

class Map(Base):
    __tablename__ = 'maps'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)
    owner_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
    owner = relationship('User', back_populates='own_maps')
    users = relationship(
        'User',
        secondary=UsersXMaps,
        back_populates='maps'
    )

    def __str__(self):
        return '{} (by {})'.format(self.name, self.owner.name)

So far for the setup; I've extended it a bit for proper output when printing strings. Additionally, your Map.shared_maps relationship actually refers to User s, not Map s, so I also renamed that one.

When binding your association table to the two classes, you can refer to it from both sides (even though back_populates seems to overwrite/replace the original definition) - this simplifies joins from either side.

Executing the following works as expected:

if __name__ == '__main__':
    engine = sa.create_engine('sqlite:///usermaps.db')
    sfactory = sessionmaker(engine)

    session = sfactory()

    Base.metadata.create_all(bind=engine)

    bilbo = User(id=1, name='Bilbo', mail='bilbo@shire.nz')
    frodo = User(id=2, name='Frodo', mail='frodo@shire.nz')

    mordor = Map(id=1, name='Mordor', owner=frodo, users=[bilbo, frodo])
    gondor = Map(id=2, name='Gondor', owner=bilbo, users=[bilbo, frodo])
    rohan = Map(id=3, name='Rohan', owner=bilbo, users=[bilbo, frodo])

    session.add_all([frodo, bilbo, mordor, gondor, rohan])
    session.commit()

    print('Maps by owner:')
    for owner in [bilbo, frodo]:
        print(owner)
        for item in session.query(Map).filter(Map.owner == owner).all():
            print(' - ' + str(item))

    print('Maps by users:')
    for item in session.query(Map).filter(Map.users.any()).all():
        print(' - ' + str(item))

The output is:

Maps by owner:
Bilbo (bilbo@shire.nz) with 2 maps
 - Gondor (by Bilbo)
 - Rohan (by Bilbo)
Frodo (frodo@shire.nz) with 1 maps
 - Mordor (by Frodo)
Maps by users:
 - Mordor (by Frodo)
 - Gondor (by Bilbo)
 - Rohan (by Bilbo)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM