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

TypeOrm+PostgreSQL按postId分组哈希标签为数组的实现求助

问题:将相同postId的哈希标签分组为数组(TypeORM + PostgreSQL)

需求:查询帖子时,将同一帖子(相同postId)的哈希标签聚合为数组形式返回。

当前代码及返回结果

查询代码

async findAll(): Promise<Post[]> {
  const posts = this.postRepository
    .createQueryBuilder('posts')
    .select([
      'posts.id',
      'posts.title',
      'posts.createdAt',
      'posts.hits',
      'users.name',
      'hashtags.hashtag',
    ])
    .addSelect((subQuery) => {
      return subQuery
        .select('SUM(likes.postId)')
        .from(PostLike, 'likes')
        .groupBy('likes.postId');
    }, 'likes')
    .leftJoin('posts.user', 'users')
    .leftJoin('posts.hashtags', 'hashtags')
    .leftJoin('posts.postLikes', 'likes')
    // .groupBy('posts.id')
    .getRawMany();
  return posts;
}

当前返回结果

[
    {
        "posts_createdAt": "2022-11-15T14:56:54.083Z",
        "posts_id": 41,
        "posts_title": "야호",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags_hashtag": "#야호",
        "likes": null
    },
    {
        "posts_createdAt": "2022-11-15T14:56:54.083Z",
        "posts_id": 41,
        "posts_title": "야호",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags_hashtag": "#여어행",
        "likes": null
    },
    {
        "posts_createdAt": "2022-11-15T14:58:17.661Z",
        "posts_id": 42,
        "posts_title": "여행",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags_hashtag": "#나혼자",
        "likes": null
    },
    {
        "posts_createdAt": "2022-11-15T14:58:17.661Z",
        "posts_id": 42,
        "posts_title": "여행",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags_hashtag": "#제주도",
        "likes": null
    }
]

解决方案

方法一:利用PostgreSQL聚合函数array_agg直接查询

通过PostgreSQL的array_agg函数在数据库层面直接聚合哈希标签,同时修正点赞数统计的子查询(原查询中SUM(likes.postId)逻辑错误,应改为统计点赞数量):

async findAll(): Promise<any[]> {
  const posts = this.postRepository
    .createQueryBuilder('posts')
    .select([
      'posts.id',
      'posts.title',
      'posts.createdAt',
      'posts.hits',
      'users.name',
      // 聚合哈希标签为数组,DISTINCT避免重复标签
      'array_agg(DISTINCT hashtags.hashtag) as hashtags',
    ])
    .addSelect((subQuery) => {
      // 子查询关联当前post的id,统计该post的点赞数
      return subQuery
        .select('COUNT(likes.id)')
        .from(PostLike, 'likes')
        .where('likes.postId = posts.id');
    }, 'likesCount')
    .leftJoin('posts.user', 'users')
    .leftJoin('posts.hashtags', 'hashtags')
    // 按非聚合字段分组
    .groupBy('posts.id, posts.title, posts.createdAt, posts.hits, users.name')
    .getRawMany();
  return posts;
}

该方法返回结果示例:

[
    {
        "posts_id": 41,
        "posts_title": "야호",
        "posts_createdAt": "2022-11-15T14:56:54.083Z",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags": ["#야호", "#여어행"],
        "likesCount": 0
    },
    {
        "posts_id": 42,
        "posts_title": "여행",
        "posts_createdAt": "2022-11-15T14:58:17.661Z",
        "posts_hits": null,
        "users_name": "chan",
        "hashtags": ["#나혼자", "#제주도"],
        "likesCount": 0
    }
]

方法二:代码层面合并结果

如果不想依赖数据库聚合函数,可以先查询原始数据,再在代码中合并相同postId的条目,收集哈希标签数组:

async findAll(): Promise<any[]> {
  const rawPosts = this.postRepository
    .createQueryBuilder('posts')
    .select([
      'posts.id',
      'posts.title',
      'posts.createdAt',
      'posts.hits',
      'users.name',
      'hashtags.hashtag',
    ])
    .addSelect((subQuery) => {
      return subQuery
        .select('COUNT(likes.id)')
        .from(PostLike, 'likes')
        .where('likes.postId = posts.id');
    }, 'likesCount')
    .leftJoin('posts.user', 'users')
    .leftJoin('posts.hashtags', 'hashtags')
    .getRawMany();

  // 合并相同postId的记录,聚合hashtags为数组
  const groupedPosts = rawPosts.reduce((acc, current) => {
    const existingPost = acc.find(post => post.posts_id === current.posts_id);
    if (existingPost) {
      // 若当前有哈希标签,添加到已有数组(去重可选)
      if (current.hashtags_hashtag && !existingPost.hashtags.includes(current.hashtags_hashtag)) {
        existingPost.hashtags.push(current.hashtags_hashtag);
      }
    } else {
      acc.push({
        ...current,
        hashtags: current.hashtags_hashtag ? [current.hashtags_hashtag] : []
      });
    }
    return acc;
  }, [] as any[]);

  return groupedPosts;
}

两种方法对比

  • 方法一:性能更优,数据库层面聚合减少数据传输量,适合数据量大的场景;但依赖PostgreSQL特定函数。
  • 方法二:逻辑更灵活,不依赖数据库特性,适合需要复杂数据转换的场景;但数据量大时会增加内存和处理开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:15:32