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

Posted

技术标签:

【中文标题】如何使用 SQLAlchemy 建立多对多关系:一个很好的例子【英文标题】:How to build many-to-many relations using SQLAlchemy: a good example 【发布时间】:2011-08-11 00:34:19 【问题描述】:

我已阅读有关构建多对多关系的 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)

我的关联表是(在代码中其他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)

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

details = relation(ItemDetail)

作为 Item 类和行的成员:

itemDetail = relation('Detail')

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

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

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

【问题讨论】:

sqlalchemy.org/docs/orm/extensions/associationproxy.html 非常冗长并且有很多例子。不知道这里还需要什么。 关联代理不是我想要的。我用表一替换了 ItemDetial 类声明,并在关系函数中使用了辅助参数 【参考方案1】:

我看到你已经从 cmets 中找到了答案。但是对于一个“新用户”来说,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')

【讨论】:

不知道为什么,但就我而言,我必须将Base.metadata 添加到ItemDetail 表中,如here 所述。 @Kerma 我还有一个问题。如何将关系放在同一个类而不是把它放在项目类和细节类中?? 就我而言,我实际上必须删除 Base.metadata 才能删除“与列关联的 FK...” 嗯,那么您如何创建新的详细信息并将它们与正确的项目相关联?【参考方案2】:

上一个答案对我有用,但我对表 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, 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')

【讨论】:

使用这种方法我得到以下错误:sqlalchemy.exc.ArgumentError: secondary argument <class> passed to to relationship() Activity.participants must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table. 这让我相信映射的类不能用作辅助属性 应该是secondary=ItemDetail.__table__【参考方案3】:

与 Miguel 一样,我也在为我的连接表使用声明式方法。但是,我一直遇到诸如

之类的错误

sqlalchemy.exc.ArgumentError: 次要参数 ma​​in.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)

注意事项

    secondary 参数中引用表名,例如secondary='project_users',而不是secondary=ProjectUser 使用back_populates 而不是backref

我对此here进行了详细的撰写。

【讨论】:

你试过了吗:secondary=ItemDetail.__table__ or secondary=ItemDetail.__tablename__

以上是关于如何使用 SQLAlchemy 建立多对多关系:一个很好的例子的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy如何反映多对多关系

如何避免在 SQLAlchemy - python 的多对多关系表中添加重复项?

SqlAlchemy 和 Flask,如何查询多对多关系

Flask sqlalchemy 多对多插入数据

使用 ID 而不是对象填充 SQLAlchemy 多对多关系

sqlalchemy 如何使用 automap_base 生成(多对多)关系