如何使用SQLAlchemy在多对多关系上指定连接条件

时间:2017-11-09 15:24:33

标签: python join sqlalchemy

这是我的模特:

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()

我想使用连接条件“Map.id == user_map.map_id”查询user_map表,但SQLAlchemy正尝试使用“Map.id == user_map.map_id和Map.owner_id == user_map”进行连接。用户身份”。如何指定我的连接条件?

我尝试在关系中使用primaryjoin属性并在.join()中指定条件但没有成功。提前谢谢!

1 个答案:

答案 0 :(得分:0)

根据您的代码,我重建了设置;我想你的relationship混淆了。此外,我在sqlalchemy的多对多关联表中几乎没见过主键(或PrimaryKeyConstraints)。从非自然角度来看,它可能是有意义的,但据我所知,这是不寻常的,甚至根本不需要。

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)

目前为止的设置;打印字符串时,我已经将它扩展了一点以获得正确的输出。此外,您的Map.shared_maps关系实际上是指User s,而非Map s,因此我也将其重命名为。{/ p>

将关联表绑定到两个类时,可以从两端引用它(即使back_populates似乎覆盖/替换原始定义) - 这简化了任何一方的连接。

按预期执行以下工作:

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))

输出结果为:

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)