14

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

我有一个具有各种属性的产品模型,如尺寸、颜色、品牌等。在搜索产品时,这个 Rails 应用程序应该在侧边栏上显示构面过滤器。构面过滤器将如下所示:

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

5 回答 5

21

我认为使用 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}}
)
于 2012-09-15T07:56:16.690 回答
15

Mongodb 3.4 引入分面搜索

$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 }
            }
          }
        }
      ]
    }
  }
])
于 2016-12-24T04:15:10.197 回答
3

使用 MongoDB 进行更高级搜索的一个流行选项是将ElasticSearch与社区支持的MongoDB River Plugin结合使用。MongoDB River 插件将来自 MongoDB 的文档流馈送到 ElasticSearch 中以进行索引。

ElasticSearch 是一个基于Apache Lucene的分布式搜索引擎,具有基于 http 的 RESTful JSON 接口。有一个Facet Search API和许多其他高级功能,例如Percolate“More like this”

于 2012-09-14T21:10:44.430 回答
2

您可以进行查询,问题是它是否快。即类似的东西:

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

问题是性能如何。产品中还没有任何用于分面搜索的特殊功能。未来可能会有一些类似交叉路口的查询计划,这些计划很好,但那是待定/未来。

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

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

  • 如果您没有太多 sku 蛮力将起作用;例如,如果您是 1MM skues,则在 ram 中进行表扫描可能足够快。在这种情况下,我将制作一个仅包含方面值的表格,并使其尽可能小,并将完整的 sku 文档保存在单独的集合中。例如:

    facets_collection: {sz:1,brand:123,clr:'b',_id:} ...

如果刻面维度的数量不是太高,您可以改为制作一个高度复合索引的 facit 维度,并且无需额外工作即可获得与上述相同的索引。

如果您创建退出一些索引,最好不要创建太多以至于它们不再适合 ram。

鉴于查询运行,这是一个性能问题,可能只是使用 mongo,如果它不够快,则使用 solr。

于 2012-09-15T15:41:21.797 回答
1

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

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

}
)

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

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 及其值的方法为您提供了聚合的所有 facet 值,您可以避免 - “使用 MongoDB 的主要问题是您必须查询它 N 次:首先获取匹配结果,然后每组一次;同时使用全文搜索引擎,一次查询即可获得所有信息。” 见加西亚的回答

于 2016-02-16T14:56:20.327 回答