You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SQLAlchemy多对多关系中避免重复求和

问题描述

我有三个SQLAlchemy表:TagGroup、Tag和Video。TagGroup与Tag、Tag与Video均通过关联表建立双向多对多关系。我需要构造一个查询,对指定TagGroup.id对应的所有Video的viewCount字段求和。但如果同一个视频关联了同一TagGroup下的多个不同Tag,Video.viewCount会被重复求和,希望找到避免该问题的方法。

示例结构:

Video1
                /
           Tag1
         /      \
 TagGroup         Video2 (会被重复求和)
         \      /
           Tag2
                \
                  Video3

注:为简化内容,已移除所有无关字段。

TagGroup 模型

class TagGroup(Base):
    __tablename__ = "tag_groups"

    id: Mapped[int] = mapped_column(
        Integer,
        primary_key=True,
        autoincrement=True,
    )

    tags = relationship(
        "Tag",
        secondary=tags_and_groups_association_table,
        back_populates="groups",
    )

TagGroup - Tag 关联表

tags_and_groups_association_table = Table(
    "tags_and_groups_association_table",
    Base.metadata,
    Column("tags_id", ForeignKey("tags.id"), primary_key=True),
    Column("tag_groups_id", ForeignKey("tag_groups.id"), primary_key=True),
    PrimaryKeyConstraint('tags_id', 'tag_groups_id')  # 避免重复
)

Tag 模型

class Tag(Base):
    __tablename__ = "tags"

    id: Mapped[int] = mapped_column(
        Integer,
        primary_key=True,
        autoincrement=True,
    )

    in_videos = relationship("Video", secondary=video_tags, back_populates="tags",lazy="select")

    groups = relationship(
        "TagGroup",
        secondary=tags_and_groups_association_table,
        back_populates="tags",
    )

Tag - Video 关联表

video_tags = Table(
    'video_tags', Base.metadata,
    Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True),
    Column('video_id', Integer, ForeignKey('videos.id'), primary_key=True)
)

Video 模型

class Video(Base):
    __tablename__ = "videos"

    id: Mapped[int] = mapped_column(
        Integer,
        primary_key=True,
        autoincrement=True,
    )
    viewCount: Mapped[int] = mapped_column(
        BigInteger,
        nullable=False,
        default=0,
    )

    tags = relationship("Tag", secondary=video_tags, back_populates="in_videos", lazy="select")

尝试过的无效查询

select(
    TagGroup.id,
    func.SUM(Video.viewCount),
    func.COUNT(distinct(Video.id)),  # 按唯一ID去重的计数是正常的
).select_from(
    TagGroup.id
).where(
    TagGroup.id.in_(ids) # 指定TagGroup ID列表
).join(
    tags_and_groups_association_table,
    TagGroup.id == tags_and_groups_association_table.c.tag_groups_id
).join(
    Tag,
    tags_and_groups_association_table.c.tags_id == Tag.id
).join(
    video_tags_model,
    Tag.id == video_tags_model.c.tag_id,
).join(
    Video,
    video_tags_model.c.video_id == Video.id
).group_by(
    TagGroup.id,
)

解决方案

问题根源是多对多关联产生的笛卡尔积,同一视频会因关联多个Tag被多次统计。核心是先确保每个视频在对应TagGroup下仅出现一次,再求和。

方法一:子查询先获取唯一视频集合

先通过子查询筛选出每个TagGroup对应的所有唯一视频,再基于这些视频求和:

# 子查询:获取每个TagGroup关联的唯一视频
unique_videos_subq = select(
    TagGroup.id.label("tag_group_id"),
    Video.id.label("video_id"),
    Video.viewCount
).select_from(TagGroup)
.join(tags_and_groups_association_table)
.join(Tag)
.join(video_tags)
.join(Video)
.where(TagGroup.id.in_(ids))
.distinct(TagGroup.id, Video.id)  # 保证同一分组下每个视频只出现一次

# 主查询:对唯一视频的viewCount求和
final_query = select(
    unique_videos_subq.c.tag_group_id,
    func.SUM(unique_videos_subq.c.viewCount).label("total_views"),
    func.COUNT(unique_videos_subq.c.video_id).label("video_count")
).select_from(unique_videos_subq)
.group_by(unique_videos_subq.c.tag_group_id)

方法二:双层分组聚合

先按TagGroup和Video分组(确保每个视频只算一次),再对TagGroup分组求和:

final_query = select(
    TagGroup.id,
    func.SUM(Video.viewCount).label("total_views"),
    func.COUNT(Video.id).label("video_count")
).select_from(TagGroup)
.join(tags_and_groups_association_table)
.join(Tag)
.join(video_tags)
.join(Video)
.where(TagGroup.id.in_(ids))
.group_by(TagGroup.id, Video.id)  # 先按视频去重
.with_entities(
    TagGroup.id,
    func.sum(Video.viewCount).label("total_views"),
    func.count(Video.id).label("video_count")
)
.group_by(TagGroup.id)  # 再按分组求和

内容的提问来源于stack exchange,提问作者user23086254

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:25:02