获取列表中带有标签的文档,按匹配总数排序

时间:2011-12-23 14:45:31

标签: mongodb aggregation-framework

给出以下MongoDB文档集合:

{
 title : 'shirt one'
 tags : [
  'shirt',
  'cotton',
  't-shirt',
  'black'
 ]
},
{
 title : 'shirt two'
 tags : [
  'shirt',
  'white',
  'button down collar'
 ]
},
{
 title : 'shirt three'
 tags : [
  'shirt',
  'cotton',
  'red'
 ]
},
...

如何检索与标记列表匹配的项目列表,按匹配标记的总数排序?例如,将此标记列表作为输入:

['shirt', 'cotton', 'black']

我想要按照匹配标记的总数检索按desc顺序排列的项目:

item          total matches
--------      --------------
Shirt One     3 (matched shirt + cotton + black)
Shirt Three   2 (matched shirt + cotton)
Shirt Two     1 (matched shirt)

在关系模式中,标签将是一个单独的表,您可以加入该表,计算匹配,并按计数排序。

但是,在Mongo ......?

似乎这种方法可行,

  • 将输入标签分成多个“IN”语句
  • 通过“或”标记输入来查询项目
    • 即。 where('衬衫'IN items.tags)或('cotton'IN items.tags)
    • 这将返回,例如,“Shirt One”的三个实例,“Shirt Three”的2个实例等
  • map / reduce输出
    • map:emit(this._id,{...});
    • reduce:计算_id
    • 的总出现次数
    • 敲定:按总计数排序

但我不清楚如何将其作为Mongo查询实现,或者这是否是最有效的方法。

3 个答案:

答案 0 :(得分:8)

我在In MongoDB search in an array and sort by number of matches

回答

可以使用聚合框架。

<强>假设

  • tags属性是一个集合(没有重复的元素)

查询

这种方法会强制您展开结果并使用未展开的结果重新评估匹配谓词,因此效率非常低。

db.test_col.aggregate(
    {$match: {tags: {$in: ["shirt","cotton","black"]}}}, 
    {$unwind: "$tags"}, 
    {$match: {tags: {$in: ["shirt","cotton","black"]}}},
    {$group: {
        _id:{"_id":1}, 
        matches:{$sum:1}
    }}, 
    {$sort:{matches:-1}}
);

预期结果

{
    "result" : [
        {
            "_id" : {
                "_id" : ObjectId("5051f1786a64bd2c54918b26")
            },
            "matches" : 3
        },
        {
            "_id" : {
                "_id" : ObjectId("5051f1726a64bd2c54918b24")
            },
            "matches" : 2
        },
        {
            "_id" : {
                "_id" : ObjectId("5051f1756a64bd2c54918b25")
            },
            "matches" : 1
        }
    ],
    "ok" : 1
}

答案 1 :(得分:5)

现在,除非您使用MapReduce,否则不可能。 MapReduce的唯一问题是它很慢(与普通查询相比)。

聚合框架定于2.2(因此应该在2.1开发版中提供)并且如果没有MapReduce,应该更容易做到这一点。

就个人而言,我认为使用M / R并不是一种有效的方式。我宁愿查询所有文档并在应用程序端进行这些计算。扩展应用服务器比扩展数据库服务器更容易,也更便宜,所以让应用服务器进行数字运算。其中,考虑到您的数据访问模式和要求,这种方法可能对您不起作用。

更简单的方法可能是在每个标记对象中包含count属性,每当$push新数据添加到数组时,您$inc count {{1}} 1}}属性。这是MongoDB世界中的一种常见模式,至少在聚合框架之前。

答案 2 :(得分:1)

我将第二个@Bryan说MapReduce是目前唯一可行的方式(并且它远非完美)。但是,如果你迫切需要它,你可以去: - )

    var m = function() {
        var searchTerms = ['shirt', 'cotton', 'black'];
        var me = this;
        this.tags.forEach(function(t) {
            searchTerms.forEach(function(st) {
                if(t == st) {
                    emit(me._id, {matches : 1});
                }
            })
        })
    };

    var r = function(k, vals) {
        var result = {matches : 0};
        vals.forEach(function(v) {
            result.matches += v.matches;
        })
        return result;
    };

    db.shirts.mapReduce(m, r, {out: 'found01'});

    db.found01.find();