Facet search using MongoDB

Firoz Ansari picture Firoz Ansari · Sep 14, 2012 · Viewed 18.4k times · Source

I am contemplating to use MongoDB for my next project. One of the core requirements for this application is to provide facet search. Has anyone tried using MongoDB to achieve a facet search?

I have a product model with various attributes like size, color, brand etc. On searching a product, this Rails application should show facet filters on sidebar. Facet filters will look something like this:

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)

Answer

Samuel García picture Samuel García · Sep 15, 2012

I think using Apache Solr or ElasticSearch you get more flexibility and performance, but this is supported using Aggregation Framework.

The main problem using MongoDB is you have to query it N Times: First for get matching results and then once per group; while using a full text search engine you get it all in one query.

Example

//'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}}
)

Once the user filters the search using facets, you have to add this filter to query predicate and match predicate as follows.

//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}}
)