【问题标题】:SQLAlchemy association table with more than two foreignkeys具有两个以上外键的 SQLAlchemy 关联表
【发布时间】:2022-01-05 18:34:48
【问题描述】:

我有 4 个表,PersonVideoAlias,以及一个视频和人之间的关联表 video_person_association_table

每个人都有别名。例如“Clark Kent”是一个人。他的一个别名是“超人”,另一个是“Kal-El”

A 人和他们的别名之间存在一对多关系,如下所示:

class Person(Base):
    #... other class stuff

    aliases: ["Alias"] = relationship("Alias", back_populates="person")


class Alias(Base):
    #... other class stuff

    person_id: int = Column(Integer, ForeignKey('person.id'))
    person = relationship("Person", back_populates="aliases")

另外,一个人可以有很多视频,一个视频可以有很多人,所以视频和人的关系是多对多的:

class Person(Base):
    #... other class stuff

   videos: ["Video"] = relationship("Video", secondary=video_person_association_table, back_populates="people")

class Video(Base):
    #... other class stuff
    people: list["Person"] = relationship("Person", secondary=video_person_association_table, back_populates="videos")

video_person_association_table = Table("video_person_association_table",
                                       Base.metadata,
                                       Column('video_id', ForeignKey('video.id'), primary_key=True),
                                       Column('person_id', ForeignKey('person.id'), primary_key=True)
                                       )

这很好用。但是,我想在关联表中添加第三列,如下所示:

video_person_association_table = Table("video_person_association_table",
                                       Base.metadata,
                                       Column('video_id', ForeignKey('video.id'), primary_key=True),
                                       Column('person_id', ForeignKey('person.id'), primary_key=True),
                                       Column('alias_id', ForeignKey('alias.id'), primary_key=True)
                                       )

我想这样做是因为我将人们与视频联系起来的方式是通过他们的别名。

例如,名为“这是一只鸟,这是一架飞机,不!是超人”的视频将与别名“超人”相关联。另一个视频“Kal-El the last son of Krypton”将关联 使用别名“Kal-El”

如果 Aliases 表如下所示:

id person_id name
1 1 Clark Kent
2 1 Superman
3 1 Kal-El

People 表如下所示: |编号 |生日| |:----:|:------:| | 1 | 1938|

视频表如下所示:

id title
1 It's a bird, it's a plane, No! It's Superman
2 Kal-El the last son of Krypton
3 Clark Kent receives an award for excellence in journalism

我希望关联表如下所示:

video_id person_id alias_id
1 1 1
2 1 2
3 1 3

我想要这样的原因是,我想保留某个视频为何与某个人相关联的信息。所以为此我想存储我用来做关联的别名的id。

我还需要那里的人员 ID 以使查询更简单。如果人员 ID 不存在并且我想查看哪些人与某个视频相关联,我需要将视频加入别名,然后将别名加入人员。

这个问题是当我尝试运行 SqlAlchemy 并在这个表中插入一些东西时,我得到:

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: video_person_association_table.alias_id
[SQL: INSERT INTO video_person_association_table (video_id, person_id) VALUES (?, ?)]
[parameters: ((1, 1), (1, 2))]

这里的问题不在于 NOT NULL 约束。它只是尝试将两个值插入关联表。有没有办法配置关系,使其使用 3 列而不是 2 列?

我知道有一个Association Object 但它适用于更复杂的场景,您需要在关联表上存储除外键之外的额外信息。但我只需要外键。

【问题讨论】:

  • 我可能会给出周期性问题和重复关系。您不应该创建第三个表。而是在您要使用它时创建一个视图或进行连接。您在创建冗余表时走错了路。在它总是失败之前我已经尝试过类似的事情,因为这是对数据建模的错误方法。
  • @PeterMølgaardPallesen “你不应该创建第三个表。”你是什么意思?第三个表是在两个表之间建立多对多关系的标准方法。无论如何,您对如何以正确的方式实现类似目标有更好的了解吗?
  • 如果你想要 alias_id 组合 video_id 你应该作为 video_person_association_table 和 alias 表的连接。所以基本上保持只有两个主键的结构并加入
  • @PeterMølgaardPallesen 我发布了一个可能的解决方案,但我不确定它是否比您的建议更好。
  • 感谢您的回访和有趣的建议。你需要一个(ondelete="CASCADE")吗?你可以添加一个没有别名的人吗?我认为最初的建议更简单,我认为从长远来看,只要加入,你就会遇到更少的问题。只是出于好奇,原始设置有什么困难?

标签: python sqlalchemy


【解决方案1】:

我最终做的是使用Association ObjectAssociation Proxy,主要使用this SO 问题。

关联对象:

class VideoPersonAssociation(Base):
    """Represents an association between a Person and a video"""
    __tablename__ = 'video_person'

    id = Column(Integer, primary_key=True)
    video_id = Column(Integer, ForeignKey('video.id'), nullable=False)
    person_id = Column(Integer, ForeignKey('person.id'), nullable=False)
    alias_id = Column(Integer, ForeignKey('alias.id'), nullable=False)

    __table_args__ = (UniqueConstraint(video_id, person_id, alias_id),)

    video = relationship("Video", back_populates="people_association_objects")
    person = relationship("Person", back_populates="videos_association_objects")
    alias = relationship("Alias")

视频类:

class Video(Base):
#... other class variables

    #relationship that ties to VideoTagAssociation *video_id* foreign key. This is filled with <VideoTagAssociation objects>
    people_association_objects: list["Person"] = relationship("VideoPersonAssociation", back_populates="video")

    #proxies the people_association_objects relationship to the *person* field of VideoTagAssociation. (Allows us to do `Video.people` instead of `Video.people_association_objects.person`)
    people = association_proxy('people_association_objects', 'person')

    #relationship that ties to VideoTagAssociation *alias_id* foreign key. This is filled with <VideoTagAssociation objects> too
    #viewonly=True means that we do not intend to set aliases through this field
    people_aliases_association_objects: list["Alias"] = relationship("VideoPersonAssociation", viewonly=True)

    #same as the people proxy. Allows us to do `for alias in video.people_aliases: print(alias)` instead of 'for x in video.people_aliases_association_objects: print(x.alias)'
    people_aliases = association_proxy('people_aliases_association_objects', 'alias')

人物类:

class Person(Base):
#... other class variables

   #works the same way as above... We don't care about aliases id in Person. Only in Video. This why we don't have alias fields here.

    videos_association_objects: list["Video"] = relationship("VideoPersonAssociation", back_populates="person")

    videos = association_proxy('videos_association_objects', 'video')

这很好用,但您需要手动在关联对象中输入信息,如下所示:


clark = Person(birth_year=1937)

alias_clark_kent = Alias(name="Clark Kent")
alias_superman = Alias(name="Superman")
alias_Kal_El = Alias(name="Kal El")

clark.aliases.append(alias_clark_kent)
clark.aliases.append(alias_superman)
clark.aliases.append(alias_Kal_El)

video = Video(title="It's a bird, it's a plane, it's Superman")

session.add(video)
session.add(clark)
session.commit()

association = VideoPersonAssociation(video=video,person=clark,alias=alias_superman)

session.add(association)
session.commit()

#now we can do:

loaded_video = session.query(Video).filter(Video.title="It's a bird, it's a plane, it's Superman").first()


print(loaded_video.people[0].birth_year)

# will print 1937

print(loaded_video.people_aliases[0].name)

# will print "Superman"


#also we can do this
results = session.query(Video).filter(Video.people_aliases.any(Alias.name.like("%Super%"))).all()

#results will be all the videos that have a person alias like "Super"


结论:

我不确定这种方法是否比@Peter Mølgaard Pallesen 建议的方法更好。 (只需制作一个额外的关联表和一个额外的连接)。我已经包含了这个答案,因为它完成了我一开始提出的要求,但我不确定是否会更进一步,我会使用它而不是 Peter 建议的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-24
    • 2019-02-04
    • 2015-02-15
    • 1970-01-01
    • 2020-12-23
    • 2011-03-03
    • 1970-01-01
    • 2017-08-23
    相关资源
    最近更新 更多