You need to enable JavaScript to run this app. 如何在单个Prisma查询中实现LEFT JOIN与聚合操作
优惠活动
大模型
产品
解决方案
定价
更多

如何在单个Prisma查询中实现LEFT JOIN与聚合操作

先补全Prisma Schema的关联关系

首先需要明确两个模型的外键关联,让Prisma可以识别两表的JOIN逻辑,调整后的Schema如下:

model posts {
  id                Int      @id @unique @default(autoincrement())
  user_id           Int
  movie_id          Int      @unique
  title             String   @db.Text
  description       String?  @db.Text
  tags              Json?
  created_at        DateTime @default(now()) @db.DateTime(0)
  image             String?  @default("https://picsum.photos/400/600/?blur=10") @db.VarChar(256)
  year              Int
  submitted_by      String   @db.Text
  tmdb_rating       Decimal? @default(0.0) @db.Decimal(3, 1)
  tmdb_rating_count Int?     @default(0)
  // 新增和ratings表的关联
  ratings           ratings[]
}

model ratings {
  id         Int       @unique @default(autoincrement()) @db.UnsignedInt
  entry_id   Int       @db.UnsignedInt
  user_id    Int       @db.UnsignedInt
  rating     Int       @default(0) @db.UnsignedTinyInt
  created_at DateTime  @default(now()) @db.DateTime(0)
  updated_at DateTime? @db.DateTime(0)
  // 新增和posts表的关联
  post       posts     @relation(fields: [entry_id], references: [id])

  @@id([entry_id, user_id])
}

方案1:使用Prisma原生嵌套聚合查询(无需手写SQL)

Prisma支持在查询时直接嵌套聚合计算,会自动生成LEFT JOIN + GROUP BY的SQL,不需要拆分两次查询,写法如下:

const postsWithRating = await prisma.posts.findMany({
  select: {
    // 按需选择posts表的所有字段
    id: true,
    user_id: true,
    movie_id: true,
    title: true,
    description: true,
    tags: true,
    created_at: true,
    image: true,
    year: true,
    submitted_by: true,
    tmdb_rating: true,
    tmdb_rating_count: true,
    // 嵌套聚合计算平均评分
    user_rating: {
      _avg: {
        rating: true,
      }
    }
  }
})

// 对结果做四舍五入处理,匹配原生SQL的ROUND逻辑
const processedPosts = postsWithRating.map(post => ({
  ...post,
  user_rating: post.user_rating?._avg.rating ? 
    Math.round(post.user_rating._avg.rating * 10) / 10 : 
    0
}))

该方案完全适配Prisma的类型系统,不需要手写SQL,适合1-3个关联表的简单聚合场景。


方案2:使用Raw SQL查询(性能与原生SQL完全一致)

如果你的聚合逻辑更复杂、需要多表JOIN,或者需要更精细的SQL控制,可以直接用Prisma的$queryRaw方法执行原生SQL:

const postsWithRating = await prisma.$queryRaw`
  SELECT 
      p.*, ROUND(AVG(rt.rating), 1) AS user_rating
  FROM
      posts AS p
          LEFT JOIN
      ratings AS rt ON rt.entry_id = p.id
  GROUP BY p.id;
`

该方案性能和手写原生SQL完全一致,支持任意复杂的JOIN、聚合逻辑,你可以手动给返回结果定义TS类型来适配类型校验。


方案3:创建数据库视图映射为Prisma模型

如果这个带平均评分的posts查询是高频使用场景,你可以直接在数据库中创建一个视图:

CREATE VIEW posts_with_rating AS
SELECT 
    p.*, ROUND(AVG(rt.rating), 1) AS user_rating
FROM
    posts AS p
        LEFT JOIN
    ratings AS rt ON rt.entry_id = p.id
GROUP BY p.id;

之后在Prisma Schema中新增对应的视图模型:

model posts_with_rating {
  id                Int      @id
  user_id           Int
  movie_id          Int      @unique
  title             String
  description       String?
  tags              Json?
  created_at        DateTime
  image             String?
  year              Int
  submitted_by      String
  tmdb_rating       Decimal?
  tmdb_rating_count Int?
  user_rating       Decimal?

  @@ignore // 如果你使用prisma migrate工具,加该参数避免迁移时自动生成表
}

之后你就可以像查询普通表一样直接查这个视图,代码最简洁,查询性能也最优,适合固定聚合逻辑的高频查询场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.08 18:06:03