在NestJS+MongoDB中实现多筛选搜索及筛选项计数方案咨询
商品数据筛选与筛选项计数实现方案
需求描述
对MongoDB中的商品数据按字段筛选,同时返回每个筛选项的计数(例如选择特定品牌时,显示该品牌的可用数量),需要了解两种实现路径:使用Azure Search这类搜索服务,或在NestJS+MongoDB中直接实现。
数据库集合示例
[ { "brand": "Screaming Eagle, The Flight", "producer": "Screaming Eagle", "productionCountry": "America", "region": "Napa Valley", "appellation": "Oakville", "vintage": "2016", "grape": "Cabernet Sauvignon", "maturity": "25", "case": "3 bottles", "origin": "SECONDARYMARKET", "type": "Red" }, { "brand": "Joseph Phelps, Insignia", "producer": "Joseph Phelps", "productionCountry": "America", "region": "Napa Valley", "appellation": "St. Helena", "vintage": "2012", "grape": "Cabernet Sauvignon", "maturity": "25", "case": "6 bottles", "origin": "SECONDARYMARKET", "type": "Red" }, { "brand": "Joseph Phelps, Insignia", "producer": "Joseph Phelps", "productionCountry": "America", "region": "Napa Valley", "appellation": "St. Helena", "vintage": "2012", "grape": "Cabernet Sauvignon", "maturity": "25", "case": "6 bottles", "origin": "SECONDARYMARKET", "type": "Red" }, { "brand": "Continuum", "producer": "Continuum", "productionCountry": "America", "region": "Napa Valley", "appellation": "Oakville", "vintage": "2017", "grape": "Cabernet Sauvignon", "maturity": "26", "case": "6 bottles", "origin": "SECONDARYMARKET", "type": "Red" }, { "brand": "Continuum", "producer": "Continuum", "productionCountry": "America", "region": "Napa Valley", "appellation": "Oakville", "vintage": "2017", "grape": "Cabernet Sauvignon", "maturity": "26", "case": "6 bottles", "origin": "SECONDARYMARKET", "type": "Red" } ]
实现方案
一、使用Azure Search实现
Azure Search的分面导航(Faceted Navigation) 原生支持筛选+筛选项计数的场景,是这类需求的成熟解决方案:
- 实现步骤:
- 数据同步:通过Azure Data Factory或MongoDB数据源连接器,将MongoDB中的商品数据同步到Azure Search索引。
- 索引配置:将需要用于筛选的字段(如
brand、vintage、type等)设置为可分面(Facetable) 属性。 - 查询请求:发起搜索时指定
facet参数(例如facet=brand&facet=vintage),Azure Search会返回每个字段的不同值及其匹配文档数;结合$filter参数(如$filter=type eq 'Red'),还能返回筛选后的关联分面计数。
二、NestJS + MongoDB直接实现
方案1:MongoDB聚合管道(推荐,性能优)
利用MongoDB的聚合管道,在数据库层面完成筛选和分面计数,适合复杂筛选场景:
import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { Product } from './schemas/product.schema'; @Injectable() export class ProductService { constructor(@InjectModel(Product.name) private productModel: Model<Product>) {} async getFilteredProductsWithFacets(filter: Record<string, any>) { // 构建筛选阶段 const matchStage = { $match: filter }; // 分面计数与商品分页的聚合阶段 const facetStage = { $facet: { products: [{ $skip: 0 }, { $limit: 10 }], // 可根据需求调整分页参数 brandFacet: [ { $group: { _id: '$brand', count: { $sum: 1 } } }, { $sort: { count: -1 } } ], vintageFacet: [ { $group: { _id: '$vintage', count: { $sum: 1 } } }, { $sort: { count: -1 } } ], typeFacet: [ { $group: { _id: '$type', count: { $sum: 1 } } } ] } }; // 执行聚合查询 const [result] = await this.productModel.aggregate([matchStage, facetStage]).exec(); return result; } }
调用示例:
// 筛选类型为Red的商品,同时返回品牌、年份的计数 const result = await this.productService.getFilteredProductsWithFacets({ type: 'Red' }); // 返回结构示例:{ products: [...], brandFacet: [{ _id: 'Joseph Phelps, Insignia', count: 2 }, ...], ... }
方案2:分步查询(适合简单场景)
如果筛选逻辑简单,可先获取筛选后的商品列表,再单独查询各字段的计数:
async getProductsWithCounts(filter: Record<string, any>) { // 获取筛选后的商品列表 const products = await this.productModel.find(filter).limit(10).exec(); // 查询品牌计数 const brandCounts = await this.productModel.aggregate([ { $match: filter }, { $group: { _id: '$brand', count: { $sum: 1 } } } ]).exec(); // 查询年份计数 const vintageCounts = await this.productModel.aggregate([ { $match: filter }, { $group: { _id: '$vintage', count: { $sum: 1 } } } ]).exec(); return { products, facets: { brand: brandCounts, vintage: vintageCounts } }; }
注意:该方案会发起多次数据库请求,数据量大时性能不如聚合管道。
内容的提问来源于stack exchange,提问作者Kelvin Nketia-Achiampong
相关产品推荐
相关产品推荐

