Sequelize调用findAll查询时如何跳过指定列重复的非首条记录
Sequelize按指定列去重、保留首条匹配记录的实现方式
Sequelize没有提供直接按非主键列去重的开箱即用findAll参数,你可以根据自己使用的数据库类型、数据量大小选择以下两种方案实现需求:
方案1:数据库层去重(性能最优,推荐大数据量场景)
通用写法(支持MySQL 8.0+、PostgreSQL、SQL Server等所有支持窗口函数的数据库)
通过ROW_NUMBER()窗口函数按placeId分区,按你指定的distance排序规则给同分区的记录编号,只取每个分区编号为1的记录即可,改造后的代码如下:
const items = await models.Item.findAll({ attributes: { include: [ [ sequelize.literal(`ROW_NUMBER() OVER (PARTITION BY placeId ORDER BY ${distance})`), 'rowNum' ] ] }, where: { [Op.and]: [ sequelize.where(distance, { [Op.lte]: radius }), sequelize.where(descriptionFilter, { [Op.gte]: 2 }), { name: { [Op.notLike]: `%Example%` } }, { userId: { [Op.not]: me.id } } ] }, having: sequelize.literal('rowNum = 1'), order: distance, limit: 25, subQuery: false })
PostgreSQL专属简化写法
如果你用的是PostgreSQL数据库,可以直接用Sequelize原生支持的distinctOn配置,不需要写原生窗口函数:
const items = await models.Item.findAll({ distinctOn: ['placeId'], where: { [Op.and]: [ sequelize.where(distance, { [Op.lte]: radius }), sequelize.where(descriptionFilter, { [Op.gte]: 2 }), { name: { [Op.notLike]: `%Example%` } }, { userId: { [Op.not]: me.id } } ] }, // 注意:DISTINCT ON要求排序规则的最左侧字段必须包含distinctOn指定的列 order: [distance, ['placeId', 'ASC']], limit: 25 })
方案2:Node内存端去重(小数据量场景改造成本最低)
如果你的查询结果集规模不大,可以先把符合筛选条件的记录查出来,再用JS代码按placeId去重,不需要改动原有查询的SQL逻辑:
// 适当放大初始查询的limit,避免去重后结果不足25条 const rawItems = await models.Item.findAll({ where: { [Op.and]: [ sequelize.where(distance, { [Op.lte]: radius }), sequelize.where(descriptionFilter, { [Op.gte]: 2 }), { name: { [Op.notLike]: `%Example%` } }, { userId: { [Op.not]: me.id } } ] }, order: distance, limit: 100 // 可根据实际placeId重复率调整这个值 }) // 遍历去重,保留同placeId的第一条记录 const visitedPlaceIds = new Set() const items = rawItems.filter(item => { if (visitedPlaceIds.has(item.placeId)) return false visitedPlaceIds.add(item.placeId) return true }).slice(0, 25)
注意:内存去重方案需要根据实际业务中
placeId的重复率调整初始查询的limit值,避免去重后最终返回的结果条数不足25条。
内容的提问来源于stack exchange,提问作者MitchEff
相关产品推荐
相关产品推荐

