使用MongoDB进行方面搜索

时间:2012-09-14 17:05:24

标签: mongodb faceted-search

我正在考虑将MongoDB用于我的下一个项目。此应用程序的核心要求之一是提供方面搜索。有没有人尝试使用MongoDB来实现方面搜索?

我有一个产品型号,其中包含大小,颜色,品牌等各种属性。在搜索产品时,此Rails应用程序应在侧栏上显示构面过滤器。 Facet过滤器看起来像这样:

Size:
XXS (34)
XS (22)
S (23)
M (37)
L (19)
XL (29)

Color:
Black (32)
Blue (87)
Green (14)
Red (21)
White (43)

Brand:
Brand 1 (43)
Brand 2 (27)

4 个答案:

答案 0 :(得分:18)

我认为使用Apache Solr或ElasticSearch可以获得更大的灵活性和性能,但使用Aggregation Framework支持此功能。

使用MongoDB的主要问题是你需要查询N次:首先获得匹配的结果,然后每组一次;使用全文搜索引擎时,您可以在一个查询中获得所有内容。

示例

//'tags' filter simulates the search
//this query gets the products
db.products.find({tags: {$all: ["tag1", "tag2"]}})

//this query gets the size facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$size"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

//this query gets the color facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$color"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

//this query gets the brand facet
db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}}, 
    {$group: {_id: "$brand"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

用户使用构面过滤搜索后,您必须添加此过滤器以查询谓词并匹配谓词,如下所示。

//user clicks on "Brand 1" facet
db.products.find({tags: {$all: ["tag1", "tag2"]}, brand: "Brand 1"})

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$size"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$color"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

db.products.aggregate(
    {$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"}, 
    {$group: {_id: "$brand"}, count: {$sum:1}}, 
    {$sort: {count:-1}}
)

答案 1 :(得分:6)

Mongodb 3.4介绍faceted search

  

$ facet阶段允许您创建多面聚合   表征跨越多个维度或方面的数据   单一聚合阶段。多方面聚合提供多种方式   过滤器和分类,以指导数据浏览和分析。

     

输入文档只传递给$ facet阶段一次。

现在,您无需查询N次以检索N个组的聚合。

  

$ facet在同一组输入文档上启用各种聚合,   无需多次检索输入文档。

OP用例的示例查询类似于

db.products.aggregate( [
  {
    $facet: {
      "categorizedByColor": [
        { $match: { color: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$color",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ],
      "categorizedBySize": [
        { $match: { size: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$size",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ],
      "categorizedByBrand": [
        { $match: { brand: { $exists: 1 } } },
        {
          $bucket: {
            groupBy: "$brand",
            default: "Other",
            output: {
              "count": { $sum: 1 }
            }
          }
        }
      ]
    }
  }
])

答案 2 :(得分:2)

你可以进行查询,问题是它是否快速。比如:

find( { size:'S', color:'Blue', Brand:{$in:[...]} } )

问题是表现如何。尚未在产品中进行分面搜索的特殊工具。在路上可能会有一些类似交叉点的查询计划,这些计划很好,但这是tbd / future。

  • 如果您的属性是预定义的集合,并且您知道它们是什么,则可以在每个属性上创建索引。在当前的实现中只使用其中一个索引,所以这将有所帮助,但只能让你到目前为止:如果数据集中等大小,可能没问题。

  • 您可以使用可能复合两个或更多属性的复合索引。如果你有一小部分属性,这可能会很好。索引不需要使用所有变量查询,但在上面的一个中,三个中任意两个上的复合索引可能比单个项目上的索引表现更好。

  • 如果你没有太多skus蛮力会起作用;例如如果你是1MM skues,ram中的表扫描可能足够快。在这种情况下,我会创建一个只包含facet值的表,并使其尽可能小,并将完整的sku文档保存在单独的集合中。 e.g:

    facets_collection: {SZ:1,品牌:123,CLR: 'B',_ ID:} ...

如果小平面尺寸的数量不是太高,你可以改为制作一个高度复合的facit尺寸索引,你可以得到相当于上述尺寸而无需额外的工作。

如果你创建退出一些索引,最好不要创建那么多它们不再适合ram。

鉴于查询运行并且这是一个性能问题,可能只有mongo,如果它不够快,那么就可以使用solr。

答案 3 :(得分:0)

分面解决方案(基于计数)取决于您的应用程序设计。

db.product.insert(
{
 tags :[ 'color:green','size:M']

}
)

但是,如果能够以上述格式提供数据,其中facet及其值连接在一起以形成一致的标记,则使用以下查询

db.productcolon.aggregate(
   [
      { $unwind : "$tags" },
      {
        $group : {
          _id : '$tags',
          count: { $sum: 1 }
        }
      }
   ]
)

请参阅下面的结果输出

{ 
    "_id" : "color:green", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "color:red", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "size:M", 
    "count" : NumberInt(3)
}
{ 
    "_id" : "color:yellow", 
    "count" : NumberInt(1)
}
{ 
    "_id" : "height:5", 
    "count" : NumberInt(1)
}

除此步骤外,您的应用程序服务器可以在发送回客户端之前进行颜色/大小分组。

注意 - 组合facet及其值的方法为你提供了聚合的所有方面值,你可以避免 - "使用MongoDB的主要问题是你必须查询N次:首先获得匹配的结果然后一次每组;使用全文搜索引擎时,您可以在一个查询中获得所有内容。"看加西亚的回答