Mongo 数据库中的多面过滤器,用于具有多个选项的产品

Faceted filters in Mongo DB with for products with multiple options

我们正在尝试创建对 MangoDB 的调用以接收所有可能的产品过滤器。

我会尝试创建我们产品的示例

第一个产品是阿迪达斯鞋,它有两种选择 select - 颜色和尺码。但是对于不同的颜色,你有不同的尺寸。

{
    id: 1
    name: "Adidas Shoes",
        filters: [
        [
            {
                code: "brand",
                value: "Adidas"
            },
            {
                code: "colour",
                value: "white"
            },
            {
                code: "size",
                value: 41
            }
        ],
        [
            {
                code: "brand",
                value: "Adidas"
            },
            {
                code: "colour",
                value: "white"
            },
            {
                code: "size",
                value: 42
            }
        ],
        [
            {
                code: "brand",
                value: "Adidas"
            },
            {
                code: "colour",
                value: "white"
            },
            {
                code: "size",
                value: 43
            }
        ],
        [
            {
                code: "brand",
                value: "Adidas"
            },
            {
                code: "colour",
                value: "blue"
            },
            {
                code: "size",
                value: 41
            }
        ],
        [
            {
                code: "brand",
                value: "Adidas"
            },
            {
                code: "colour",
                value: "blue"
            },
            {
                code: "size",
                value: 44
            }
        ]
    ]
}

第二个产品是耐克鞋。

{
    id: 2
    name: "Nike Shoes",
        filters: [
    [
        {
            code: "brand",
            value: "Nike",
        },
        {
            code: "colour",
            value: "white",
        },
        {
            code: "size",
            value: 41,
        }
    ],
    [
        {
            code: "brand",
            value: "Nike",
        },
        {
            code: "colour",
            value: "white",
        },
        {
            code: "size",
            value: 42,
        }
    ],
    [
        {
            code: "brand",
            value: "Nike",
        },
        {
            code: "colour",
            value: "green",
        },
        {
            code: "size",
            value: 41,
        }
    ]
]
}

还有 Reebook 鞋

{
    id: 3
    name: "Reebook Shoes",
    filters: [
    [
        {
            code: "brand",
            value: "Reebook",
        },
        {
            code: "colour",
            value: "black",
        },
        {
            code: "size",
            value: 41,
        }
    ]
    ]
}

如您所见,选项大小取决于颜色,而颜色又取决于大小。

我们如何创建 MongoDb.aggregate 以拥有所有可能的过滤器?

品牌: 阿迪达斯 (1), 耐克 (1), Reebook (1)

尺码: 41 (3), 42 (2), 43 (1), 44 (1)

颜色: 白色 (2)、蓝色 (1)、绿色 (1)、黑色 (1)

调用应该独立于我们有多少个过滤器和哪些过滤器(有一个选项的产品,有更多选项和不同过滤器的产品)。你能解释一下在这种情况下如何使用 $group, $unwind 吗?以及我们以后如何使用 $facet 改进它?

非常感谢!!!

编辑 2017 年 2 月 10 日

响应示例

facets: [
    {
        code: "brand",
        values: [
            {
                name: "Adidas",
                count: 1
            },
            {
                name: "Nike",
                count: 1
            },
            {   
                name: "Reebook",
                count: 1
            }
        ]
    },
    {
        code: "size",
        values: [
            {
                name: 41,
                count: 3
            },
            {
                name: 42,
                count: 2
            },
            {   
                name: 43,
                count: 1
            },
            {   
                name: 44,
                count: 1
            }
        ]
    },
    {
        code: "colour",
        values: [
            {
                name: "White",
                count: 2
            },
            {
                name: "Blue",
                count: 1
            },
            {   
                name: "Green",
                count: 1
            },
            {   
                name: "Black",
                count: 1
            }
        ]
    }
]

facets: {
    "brand": {
        "Adidas": 1,
        "Nike":1,
        "Reebook":1
    },
    "size": {
        "41": 3,
        "42":2,
        "43":1,
        "44":1
    },
    "colour": {
        "White": 2,
        "Blue":1,
        "Green":1,
        "Black":1
    }
}

这是我们的第一阶段。下一步将是如何在我 selected 尺寸:41 和颜色:白色时搜索可能的过滤器。

谢谢

这是一个可能适合您的汇总。

db.getCollection('test').aggregate([
 {$unwind: '$filters'},
 {$unwind: '$filters'},
 {
  $group: {
   _id: {code: '$filters.code', value: '$filters.value'},
   products: {$addToSet: '$_id'}
  }
 },
 {
  $project: {
   'filter.value': '$_id.value',
   'filter.count': {$size: '$products'}
  }
 },
 {
  $group: {
   _id: '$_id.code',
   filters: {$push: '$filter'}
  }
 }
]);

您需要的数据来自 slightly different format,因为没有简单的方法将分组值数组转换为对象属性。

如果一些过滤器已经 selected 你需要在第一个 $unwind 之后的另一个 $match 阶段。 它还支持多个 selects。假设我想要 white/black 由 Reebook/Adidas 制造的鞋子。

db.getCollection('test').aggregate([
 {$unwind: '$filters'},
 {
  $match: {
   $and: [
    //Add objects here fo everything that is selected already
    {'filters': {$elemMatch: {code: 'colour', value: {$in: ['black', 'white']}}}},
    {'filters': {$elemMatch: {code: 'brand', value: {$in: ['Adidas', 'Reebook']}}}}
   ]
  }
 },
 {$unwind: '$filters'},
 {
  $group: {
   _id: {code: '$filters.code', value: '$filters.value'},
   products: {$addToSet: '$_id'}
  }
 },
 {
  $project: {
   'filter.value': '$_id.value',
   'filter.count': {$size: '$products'}
  }
 },
 {
  $group: {
   _id: '$_id.code',
   filters: {$push: '$filter'}
  }
 }
]);

最后一件事是这样的依赖行为: Select Nike => 尺码和颜色按品牌筛选,但您仍然可以 select 所有品牌。 Select Nike + 42 尺码 => 您可以 select 只有尺码为 42 的品牌、颜色和有 42 尺码鞋子的品牌。 等等。

您可以为此利用 $facet。事实上,当这个想法是下一个。 如果我们正在计算品牌 - 我们应该根据 select 在大小和颜色下拉列表中编辑的内容来过滤记录。 如果我们计算尺寸 - 应用颜色和品牌。颜色的逻辑相同。

这是在 mongo shell 中工作的代码:

//This hash is going to by dynamic
//Add and remove properties, change $in arrays
//Depending on what user does
var conditionsForUserSelect = {
 'colour': {'filters': {$elemMatch: {code: 'colour', value: {$in: ['green']}}}},
 'brand': {'filters': {$elemMatch: {code: 'brand', value: {$in: ['Nike']}}}},
 'size': {'filters': {$elemMatch: {code: 'size', value: {$in: [41]}}}}
};

var getFacetStage = function (code) {
 //Empty object, accept all filters if nothing is selected
 var matchStageCondition = {};

 var selectedFilters = Object.keys(conditionsForUserSelect);
 if (selectedFilters && selectedFilters.length) {
  //Take all queries EXCEPT for the passed
  //E.g. if we are counting brand filters then we should apply color and size.
  //Because for example if no size/colour selected we should
  //allow all brands even if Reebok is selected
  var conditionsToApply = selectedFilters
   .filter(function (key) {
    return key !== code
   })
   .map(function (key) {
    return conditionsForUserSelect[key]
   });

  if (conditionsToApply && conditionsToApply.length) {
   matchStageCondition = {
    $and: conditionsToApply
   };
  }
 }

 return [
  {$unwind: '$filters'},
  {
   $match: matchStageCondition
  },
  {$unwind: '$filters'},
  {
   $group: {
    _id: {code: '$filters.code', value: '$filters.value'},
    products: {$addToSet: '$_id'}
   }
  },
  {
   $project: {
    'filter.value': '$_id.value',
    'filter.count': {$size: '$products'}
   }
  },
  {
   $group: {
    _id: '$_id.code',
    filters: {$push: '$filter'}
   }
  },
  {
   $match: {_id: code}
  }
 ];
};


db.getCollection('test').aggregate([
 {
  $facet: {
   colour: getFacetStage('colour'),
   size: getFacetStage('size'),
   brand: getFacetStage('brand')
  }
 }
]);