sequelize-typescript一对多关联查询仅返回单条数据问题排查
一对多关联查询仅返回单条关联数据的排查与解决
问题场景
使用sequelize-typescript定义Album与Photo一对多模型(一个Album对应多个Photo),通过findByPk查询Album并关联Photo时,数据库中明明存在3条以上关联Photo,但返回结果仅包含1条。
模型定义
Album.ts
import { Column, CreatedAt, DataType, HasMany, Model, PrimaryKey, Table, UpdatedAt } from "sequelize-typescript"; import { Photo } from "./photo"; @Table({ timestamps: true, deletedAt: "albumDeletedAt", paranoid: true, createdAt: true, updatedAt: true }) export class Album extends Model { @PrimaryKey @Column({ type: DataType.INTEGER, primaryKey: true, autoIncrement: true }) declare id: number; @Column({ type: DataType.STRING, allowNull: false }) declare name: string; @Column({ type: DataType.STRING, allowNull: true }) declare description?: string; @Column({ type: DataType.STRING, allowNull: false }) declare thumbnailURL: string; @HasMany(() => Photo) declare photos: Photo[]; @CreatedAt declare createdAt: Date; @UpdatedAt declare updatedAt: Date; }
Photo.ts
import { BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, PrimaryKey, Table, UpdatedAt } from "sequelize-typescript"; import { Album } from "./album"; @Table({ timestamps: true, deletedAt: "photoDeletedAt", paranoid: true, createdAt: true, updatedAt: true }) export class Photo extends Model { @PrimaryKey @Column({ type: DataType.INTEGER, primaryKey: true, autoIncrement: true }) declare id: number; @Column({ type: DataType.STRING, allowNull: false }) declare name: string; @Column({ type: DataType.STRING, allowNull: false }) declare photoURL: string; @ForeignKey(() => Album) @Column({ type: DataType.INTEGER, allowNull: true, }) declare albumId: number; @BelongsTo(() => Album) declare album: Album; @CreatedAt declare createdAt: Date; @UpdatedAt declare updatedAt: Date; }
原查询代码
public async getAlbumById(id: number): Promise<Album | null> { try { const searchedResult = await this.albumRepository.findByPk(id, { include: { model: this.photoRepository, where: { albumId: id } }, raw: true, nest: true }); return searchedResult; } catch (error) { console.error(`Error finding the album via ID for the ID of ${id} due to`, error); return null; } }
异常返回结果
{ "name": "Sample Card Photos Album (Test)", "description": "Pictures for testing placeholder", "thumbnailURL": "", "photos": [ { "name": "beautiful-village-snow-covered-mountain.jpg", "photoURL": "http://localhost:8080/uploads/pictures/Sample_Card_Photos_Album_(Test)/beautiful-village-snow-covered-mountain.jpg" } ], "albumDeletedAt": null }
问题原因排查
raw: true的核心影响:启用raw: true后,Sequelize直接返回数据库查询的原始行数据,而非封装后的Model实例。一对多关联的SQL查询本质是JOIN操作,会生成多行结果(每行对应一个Photo+Album组合),但raw: true结合nest: true无法自动将多行关联数据聚合为数组,只会保留单条关联数据。- 冗余的where条件:
include中添加的where: { albumId: id }属于多余配置,findByPk已指定Album的主键,关联查询会自动通过外键匹配,该条件不会导致数据丢失,但无保留必要。
解决方法
方法1:移除raw: true,使用Model实例转换为纯JSON
优先推荐此方案,sequelize-typescript的Model实例会自动处理关联数据的聚合,确保返回完整的photos数组。若需纯JSON对象,可通过get({ plain: true })转换:
public async getAlbumById(id: number): Promise<Album | null> { try { const album = await this.albumRepository.findByPk(id, { include: { model: this.photoRepository } }); // 转换为纯JSON对象(可选,若需返回Model实例则直接返回) return album ? album.get({ plain: true }) : null; } catch (error) { console.error(`Error finding the album via ID ${id}:`, error); return null; } }
方法2:保留raw: true时添加分组配置
若必须使用raw模式,需通过group指定按Album主键分组,同时确保关联字段被正确聚合(需数据库支持聚合函数):
public async getAlbumById(id: number): Promise<Album | null> { try { const searchedResult = await this.albumRepository.findByPk(id, { include: { model: this.photoRepository, attributes: ['id', 'name', 'photoURL'] }, raw: true, nest: true, group: ['Album.id', 'Photos.id'] // 按Album和Photo的主键分组 }); return searchedResult; } catch (error) { console.error(`Error finding the album via ID ${id}:`, error); return null; } }
额外检查点
- 确认关联的Photo未被软删除:由于模型启用了
paranoid: true,需检查Photo的photoDeletedAt字段是否为null,被软删除的Photo不会出现在查询结果中。 - 验证数据库中外键关联:确认Photo表的
albumId字段确实指向目标Album的id,无数据错误。
内容的提问来源于stack exchange,提问作者Adrian Joseph
相关产品推荐
相关产品推荐

