NestJS+TypeORM:PostgreSQL下如何定义实体计算型globalRating列?
需求说明
我希望在实体中直接计算以下两个值:
- 用户的
globalRating:基于该用户作为租户的关联合同(Contract)对应的评分(Rating)计算得出 - 房产(Property)的
globalRating:基于该房产关联的合同(Contract)对应的评分(Rating)计算得出
现有QueryBuilder实现示例
const userGlobalRating = await this.userRepo.createQueryBuilder(User, 'user') .leftJoin('user.tenantships', 'contract') .leftJoin('contract.ratings', 'rating') .select('user.id', 'id') .addSelect('COALESCE(AVG(rating.stars), 0)', 'rating') .groupBy('user.id'); const propertyGlobalRAting = await this.propertyRepo.createQueryBuilder(Property, 'property') .leftJoin('property.contracts', 'contract') .leftJoin('contract.ratings', 'rating') .select('property.id', 'id') .addSelect('COALESCE(AVG(rating.stars), 0)', 'rating') .groupBy('property.id');
相关实体定义
Rating实体
@Entity() export class Rating { @ManyToOne(() => Contract, c => c.ratings) @JoinColumn() @Index() contract!: Contract; }
Contract实体
@Entity() export class Contract { @OneToMany(() => Rating, rating => rating.contract) ratings!: Rating[]; @ManyToOne(() => Property, property => property.contracts) property!: Property | undefined; @ManyToOne(() => User, user => user.tenantships) tenant?: User | undefined; }
Property实体
@Entity() export class Property{ @OneToMany(() => Contract, contract => contract.property) contracts!: Contract[]; @ManyToOne(() => User, user => user.properties) @JoinColumn() @Index() owner!: User | undefined; globalRating!:number }
User实体
@Entity() export class User { @OneToMany(() => Property, property => property.owner) properties: Property[] | undefined; @OneToMany(() => Contract, contract => contract.tenant) tenantships: Contract[]; globalRating!: number; }
咨询问题
- 该需求是否可通过PostgreSQL实现?
- 是否可不注入Repository,仅通过原生SQL在实体中实现?
问题解答
1. 该需求完全可以通过PostgreSQL实现
PostgreSQL原生支持AVG()聚合函数、COALESCE()空值处理,以及多表关联查询,完全覆盖你需要的评分计算逻辑。你当前的QueryBuilder代码本质就是生成对应的PostgreSQL SQL语句,直接用原生SQL编写也能达成同样效果。
2. 可以不注入Repository,通过原生SQL结合实体注解实现
在TypeORM中,你可以通过生成列或数据库视图两种方式,让实体直接包含计算后的globalRating,无需依赖Repository查询:
方案一:使用生成列(PostgreSQL 12+支持)
直接在实体中定义生成列,让数据库自动计算并维护评分值:
@Entity() export class User { // 其他原有字段... @Column({ type: 'numeric', asExpression: `( SELECT COALESCE(AVG(r.stars), 0) FROM contract c JOIN rating r ON c.id = r."contractId" WHERE c."tenantId" = id )`, generated: 'STORED' // STORED会持久化存储值,VIRTUAL则每次查询实时计算 }) globalRating!: number; }
Property实体的生成列写法类似:
@Entity() export class Property{ // 其他原有字段... @Column({ type: 'numeric', asExpression: `( SELECT COALESCE(AVG(r.stars), 0) FROM contract c JOIN rating r ON c.id = r."contractId" WHERE c."propertyId" = id )`, generated: 'STORED' }) globalRating!: number; }
注意:代码中的字段名(如contractId、tenantId)要和TypeORM自动生成的数据库实际字段名保持一致。
方案二:使用数据库视图
创建数据库视图映射为实体,直接返回包含评分的数据:
- 先创建PostgreSQL视图:
CREATE VIEW user_with_rating AS SELECT u.id, u.name, -- 替换为用户表的实际字段 COALESCE(AVG(r.stars), 0) AS globalRating FROM "user" u LEFT JOIN contract c ON u.id = c."tenantId" LEFT JOIN rating r ON c.id = r."contractId" GROUP BY u.id;
- 在TypeORM中定义视图实体:
@ViewEntity({ expression: `SELECT u.id, u.name, -- 替换为用户表的实际字段 COALESCE(AVG(r.stars), 0) AS globalRating FROM "user" u LEFT JOIN contract c ON u.id = c."tenantId" LEFT JOIN rating r ON c.id = r."contractId" GROUP BY u.id` }) export class UserWithRating { @ViewColumn() id!: number; @ViewColumn() name!: string; @ViewColumn() globalRating!: number; }
这种方式无需修改原实体,通过视图实体直接获取带评分的数据,查询时也不需要注入原Repository。
内容的提问来源于stack exchange,提问作者MaxM
相关产品推荐
相关产品推荐

