使用MongoDB为用户查找推荐产品

时间:2014-04-30 08:51:01

标签: mongodb mapreduce aggregation-framework

我在mongodb中收集了一个名为users的集合。它看起来像这样:

db.users.find() =>
{
    {
        _id: 1,

        products: [1, 2, 3, 4, 5]
    },

    {
        _id: 2,

        products: [4, 5, 6, 7, 8]
    },

    {
        _id: 3,

        products: [10, 11, 12]
    }
}

产品数组包含用户购买的产品ID。我想做一些查询/ mapreduce /东西来为每个用户获得“推荐产品”,如下所示:

// result = some kind of query/mapreduce/... on the users collection

print(result) =>
[
    { key: 1, values: [6, 7, 8]},
    { key: 2, values: [1, 2, 3]}
]

我的逻辑是这样的: 用户1购买了产品4和5.对于用户2也是如此。因此,用户1购买的其他产品(1,2,3)对用户2来说是一个很好的推荐。

产品(6,7,8)对于用户2是一个很好的推荐。没有其他用户购买过用户3购买的产品,因此没有针对用户3的推荐。

我怎么能这样做?有没有人可以给我看一个例子?

1 个答案:

答案 0 :(得分:1)

您将永远无法在单个操作中演示所有用户的完整结果。纯粹的原因是mapReduce或聚合框架实际上并没有那样工作,因为你无法以这种方式比较文档。

但是您可以基于每个用户执行此操作,或者如果您希望这些结果位于另一个集合中,那么您需要对每个用户进行迭代才能进行比较。

我最喜欢的方法是使用聚合框架,并且速度最快。但它需要MongoDB 2.6或更高版本才能工作:

 var compare = [1, 2, 3, 4, 5];

 db.colection.aggregate([

     // Get intersections and differences to the current user purchases
     { "$project": {
         "matched": { 
             "$setIntersection": [
                "$products",
                compare
             ]
         },
         "matchedSize": { "$size": {
             "$setIntersection": [
                "$products",
                compare
             ]
         }},
         "difference": {
             "$setDifference": [
                "$products",
                compare
             ]
         },
         "differenceSize": { "$size": {
             "$setDifference": [
                "$products",
                compare
             ]
         }}
     }},

     // Filter where there are no differences or no intersection on the same
     // products purchased
     { "$match": {
         "matchedSize": {"$gt": 0 },
         "differenceSize": { "$gt": 0 } 
     }},

     // Unwind the differences array
     { "$unwind": "$difference" },

     // Combine all the other results to a single set
     { "$group": {
         "_id": null,
         "recommend": { "$addToSet": "$difference" }
     }}
 ])

所以它很好并且不言自明。这在早期版本中是可能的,但这个过程非常复杂。

或者你可以用mapReduce做到这一点,但是你需要定义一些函数;

首先是一个映射器:

var mapper = function () {

  function intersection(a, b) {
    var result = new Array();
    while( a.length > 0 && b.length > 0 )  {
      if      (a[0] < b[0] ) { a.shift(); }
      else if (a[0] > b[0] ) { b.shift(); }
      else /* they're equal */
      {
        result.push(a.shift());
        b.shift();
      }
    }

    return result;
  }

  function difference(a, b) {
    return a.filter(function(x) { return b.indexOf(x) < 0 });
  }

  var result = {
    intersect: intersection( this.products, compare ),
    diff: difference( this.products, compare )
  };

  if ( result.intersect.length > 0 && result.diff.length > 0 )
    emit( null, result.diff );

};

然后是减速器:

var reducer = function (key,values) {

  var reduced = [];

  values.forEach(function(value) {
    value.forEach(function(el) {
      if ( reduced.indexOf(el) < 0 )
        reduced.push(el);
    });
  });

  return { value: reduced };

};

还有一个终结函数:

var finalize = function (key,value) {

  if ( value.hasOwnProperty('value') )
    value = value.value;

  return value;

};

并调用mapReduce:

db.purchase.mapReduce(
    mapper,
    reduce,
    { 
        "scope": { "compare": [ 1, 2, 3, 4, 5 ] },
        "finalize": finalize,
        "out": { "inline": 1 }
    }
)

因此,有一些方法,一旦您获得给定用户的产品列表,您就可以获得比较推荐的项目。为每个用户单独执行此操作,或者在适合您需要的地方迭代并存储该批次。