如何使用SQLAlchemy构建多对多关系:一个很好的例子

dud*_*ein 13 python many-to-many sqlalchemy associations

我已经阅读了关于构建多对多关系的SQLAlchemy文档和教程,但是当关联表包含多于2个外键时,我无法弄清楚如何正确地完成它.

我有一个项目表,每个项目都有很多细节.许多项目的细节可以相同,因此项目和细节之间存在多对多关系

我有以下内容:

class Item(Base):
    __tablename__ = 'Item'
    id = Column(Integer, primary_key=True)
    name = Column(String(255))
    description = Column(Text)

class Detail(Base):
    __tablename__ = 'Detail'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    value = Column(String)
Run Code Online (Sandbox Code Playgroud)

我的关联表是(它在代码中的其他2之前定义):

class ItemDetail(Base):
    __tablename__ = 'ItemDetail'
    id = Column(Integer, primary_key=True)
    itemId = Column(Integer, ForeignKey('Item.id'))
    detailId = Column(Integer, ForeignKey('Detail.id'))
    endDate = Column(Date)
Run Code Online (Sandbox Code Playgroud)

在文档中,据说我需要使用"关联对象".我无法弄清楚如何正确使用它,因为它与mapper表单混合声明,并且示例似乎不完整.我添加了这一行:

details = relation(ItemDetail)
Run Code Online (Sandbox Code Playgroud)

作为Item类和行的成员:

itemDetail = relation('Detail')
Run Code Online (Sandbox Code Playgroud)

作为关联表的成员,如文档中所述.

当我执行item = session.query(Item).first()时,item.details不是Detail对象的列表,而是ItemDetail对象的列表.

如何在Item对象中正确获取详细信息,即item.details应该是Detail对象的列表?

Ben*_*Ben 36

与 Miguel 一样,我也对连接表使用声明式方法。但是,我不断遇到类似的错误

sqlalchemy.exc.ArgumentError:辅助参数<class ' main .ProjectUser'>传递给relationship() User.projects必须是Table对象或其他FROM子句;无法直接发送映射的类,因为“辅助”中的行独立于映射到同一表的类而持久保存。

经过一番摆弄,我得出了以下结论。(请注意,我的课程与 OP 的课程不同,但概念是相同的。)

例子

这是一个完整的工作示例

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session

# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)

# Make the DeclarativeMeta
Base = declarative_base()


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    projects = relationship('Project', secondary='project_users', back_populates='users')


class Project(Base):
    __tablename__ = "projects"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    users = relationship('User', secondary='project_users', back_populates='projects')


class ProjectUser(Base):
    __tablename__ = "project_users"

    id = Column(Integer, primary_key=True)
    notes = Column(String, nullable=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    project_id = Column(Integer, ForeignKey('projects.id'))



# Create the tables in the database
Base.metadata.create_all(engine)

# Test it
with Session(bind=engine) as session:

    # add users
    usr1 = User(name="bob")
    session.add(usr1)

    usr2 = User(name="alice")
    session.add(usr2)

    session.commit()

    # add projects
    prj1 = Project(name="Project 1")
    session.add(prj1)

    prj2 = Project(name="Project 2")
    session.add(prj2)

    session.commit()

    # map users to projects
    prj1.users = [usr1, usr2]
    prj2.users = [usr2]

    session.commit()


with Session(bind=engine) as session:

    print(session.query(User).where(User.id == 1).one().projects)
    print(session.query(Project).where(Project.id == 1).one().users)
Run Code Online (Sandbox Code Playgroud)

笔记

  1. secondary在参数中引用表名,secondary='project_users'而不是secondary=ProjectUser
  2. 使用back_populates而不是backref

我在这里对此做了详细的文章。


ker*_*rma 13

从评论中我看到你找到了答案.但SQLAlchemy文档对于"新用户"而言非常压倒性,而我正在努力解决同样的问题.所以供将来参考:

ItemDetail = Table('ItemDetail',
    Column('id', Integer, primary_key=True),
    Column('itemId', Integer, ForeignKey('Item.id')),
    Column('detailId', Integer, ForeignKey('Detail.id')),
    Column('endDate', Date))

class Item(Base):
    __tablename__ = 'Item'
    id = Column(Integer, primary_key=True)
    name = Column(String(255))
    description = Column(Text)
    details = relationship('Detail', secondary=ItemDetail, backref='Item')

class Detail(Base):
    __tablename__ = 'Detail'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    value = Column(String)
    items = relationship('Item', secondary=ItemDetail, backref='Detail')
Run Code Online (Sandbox Code Playgroud)

  • 不知道为什么,但就我而言,我必须将“Base.metadata”添加到“ItemDetail”表中,如[此处](http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#多对多)。 (3认同)
  • @Kerma我有另一个问题.如何将关系放在同一个类而不是把它放在Item类和Detail Class中? (2认同)

Mig*_*les 11

上一个答案对我有用,但我对表 ItemDetail 使用了类基方法。这是示例代码:

class ItemDetail(Base):
    __tablename__ = 'ItemDetail'
    id = Column(Integer, primary_key=True, index=True)
    itemId = Column(Integer, ForeignKey('Item.id'))
    detailId = Column(Integer, ForeignKey('Detail.id'))
    endDate = Column(Date)

class Item(Base):
    __tablename__ = 'Item'
    id = Column(Integer, primary_key=True)
    name = Column(String(255))
    description = Column(Text)
    details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')

class Detail(Base):
    __tablename__ = 'Detail'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    value = Column(String)
    items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')
Run Code Online (Sandbox Code Playgroud)

  • 它需要是 ` secondary=ItemDetail.__table__` (4认同)
  • 使用这种方法,我收到以下错误:`sqlalchemy.exc.ArgumentError:辅助参数&lt;class&gt;传递给relationship() Activity.participants必须是Table对象或其他FROM子句;无法直接发送映射的类,因为“辅助”中的行独立于映射到同一表的类而持久保存。这使我相信映射的类不能用作辅助属性 (3认同)