报错:events.entity_id需加入GROUP BY或用聚合函数,如何无需此条件查询该字段?
解决PostgreSQL GROUP BY时非聚合字段的查询问题
你遇到的报错是PostgreSQL的标准SQL约束:当使用GROUP BY分组时,SELECT列表中的非聚合字段必须全部包含在GROUP BY子句中,因为数据库无法确定同一分组内多个不同的entity_id应该返回哪一个。以下是几种不需要将entity_id添加到GROUP BY或使用常规聚合逻辑的解决办法:
方案1:使用PostgreSQL专属的DISTINCT ON语法
DISTINCT ON可以让你按指定字段分组,同时保留该分组中第一条记录的其他字段(需配合排序),完美匹配你的需求:
const builder = this.eventRepository .createQueryBuilder(EVENTS_TABLE) // 用DISTINCT ON指定分组字段,同时保留该分组第一条记录的字段 .select([`DISTINCT ON (${EVENTS_TABLE}.type) ${EVENTS_TABLE}.type as "eventType"`]) .addSelect(`"${EVENTS_TABLE}".entity_id`, "entityId") .where(`${EVENTS_TABLE}.type in (:...eventType)`, { eventType }) .innerJoin(Entity, ENTITY_TABLE, "events.entity = entity.id") .leftJoin(Country, "country", "country.code = entity.country_code") .andWhere(`${ENTITY_TABLE}.type in (:...entityType)`, { entityType }) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.VIDEO}'))::int as ${EntityType.VIDEO}` ) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.PROFILE}'))::int as ${EntityType.PROFILE}` ) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.COMMENT}'))::int as ${EntityType.COMMENT}` ) .addSelect(`(COUNT(${ENTITY_TABLE}.id))::int as quantity`) // 必须按DISTINCT ON指定的字段排序,确保每个分组取第一条记录 .orderBy(`${EVENTS_TABLE}.type`);
方案2:用MAX()/MIN()包裹entity_id(取任意值)
如果不关心返回的是分组中哪一个entity_id,只是需要拿到一个合法值,可以用聚合函数包裹它,这样无需修改GROUP BY:
// 把原来的addSelect替换成下面的写法 .addSelect(`MAX("${EVENTS_TABLE}".entity_id)`, "entityId")
数据库会返回该分组中最大的entity_id,满足语法要求。
方案3:先聚合统计,再关联获取entity_id
如果需要保留每个entity_id对应的统计数据,可以先做聚合子查询,再关联原表获取entity_id:
// 先定义统计子查询 const statsSubQuery = this.eventRepository .createQueryBuilder(EVENTS_TABLE) .select(`${EVENTS_TABLE}.type as "eventType"`) .where(`${EVENTS_TABLE}.type in (:...eventType)`, { eventType }) .innerJoin(Entity, ENTITY_TABLE, "events.entity = entity.id") .andWhere(`${ENTITY_TABLE}.type in (:...entityType)`, { entityType }) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.VIDEO}'))::int as ${EntityType.VIDEO}` ) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.PROFILE}'))::int as ${EntityType.PROFILE}` ) .addSelect( `(COUNT(${ENTITY_TABLE}.id) filter (WHERE ${ENTITY_TABLE}.type = '${EntityType.COMMENT}'))::int as ${EntityType.COMMENT}` ) .addSelect(`(COUNT(${ENTITY_TABLE}.id))::int as quantity`) .groupBy(`${EVENTS_TABLE}.type`) .getQuery(); // 关联子查询获取entity_id const builder = this.eventRepository .createQueryBuilder("main") .select([`main.type as "eventType"`, `main.entity_id as "entityId"`]) .addSelect(`stats.${EntityType.VIDEO}`, EntityType.VIDEO) .addSelect(`stats.${EntityType.PROFILE}`, EntityType.PROFILE) .addSelect(`stats.${EntityType.COMMENT}`, EntityType.COMMENT) .addSelect(`stats.quantity`, "quantity") .innerJoin(`(${statsSubQuery}) as stats`, `stats.eventType = main.type`) .where(`main.type in (:...eventType)`, { eventType }) .innerJoin(Entity, ENTITY_TABLE, "main.entity = entity.id") .leftJoin(Country, "country", "country.code = entity.country_code") .andWhere(`${ENTITY_TABLE}.type in (:...entityType)`, { entityType });
这个方案会返回每个entity_id对应的统计数据,同一type下会有多条记录。
内容的提问来源于stack exchange,提问作者Motion_forward
相关产品推荐
相关产品推荐

