【问题标题】:Mongo Map-Reduce - Top Venues By Users in a RadiusMongo Map-Reduce - 半径内用户的热门场地
【发布时间】:2013-07-31 14:52:08
【问题描述】:

我的 MapReduce 函数有问题 - 目标是获取某个 lat/lng 内的顶级场所列表,按 vid 分组,按不同 user_id 排序。

这是一个示例数据集:

  { "_id" : ObjectId("51f9234feb97ff0700000046"), "checkin_id" : 39286249, "created_at" : ISODate("2013-07-31T14:47:11Z"), "loc" : { "lat" : 42.3672, "lon" : -86.2681 }, "icv" : 1, "ipv" : 1, "vid" : 348442, "user_id" : 151556, "bid" : 9346, "pid" : 549 }
  { "_id" : ObjectId("51f9234b488fff0700000006"), "checkin_id" : 39286247, "created_at" : ISODate("2013-07-31T14:47:07Z"), "loc" : { "lat" : 55.6721, "lon" : 12.5576 }, "icv" : 1, "ipv" : 1, "vid" : 3124, "user_id" : 472486, "bid" : 7983, "pid" : 2813 }
  ...

这是我的地图功能:

map1 = function() {
  var tempDoc = {};
  tempDoc[this.user_id] = 1;

  emit(this.vid, {
     users: tempDoc,
     count: 1
  });
}

并减少:

reduce1 = function(key, values) {

    var summary = {
     users: {},
     total: 0
    };

    values.forEach(function (doc) {

       // increment total for every value
       summary.total += doc.count;

       // Object.extend() will only add keys from the right object that do not exist on the left object
      Object.extend(summary.users, doc.user);

    });


   return summary;
};

我的地理查询:

var d = Date("2013-07-31T14:47:11Z");
var geo_query = {loc: {$near: [40.758318,-73.952985], $maxDistance: 25}, "icv":1, "created_at": {$gte: d}};

最后是 mapReduce 查询:

var res = db.myColelction.mapReduce(map1, reduce1,  { out : { inline : 1 }, query : geo_query });

返回的结果与reduce函数匹配,但没有命中finalize1函数:

...
{
    "_id" : 609096,
    "value" : {
        "users" : {
            "487586" : 1
        },
        "count" : 1
    }
},
{
    "_id" : 622448,
    "value" : {
        "users" : {
            "313755" : 1,
            "443180" : 1
        },
        "total" : 4
    }
},
...

此时,我认为我有一个很好的结果集,但是$near函数只扫描了附近的100个场地,我想扫描所有场地(所有符合这个半径(25m)的文档,并查看所有场所 - 对它们进行分组,并计算该时间段内的唯一用户数。我四处搜索,查看了文档,但不确定是否有解决方案。有接受者吗?

对我来说,最终结果将是排序并通过“total”属性限制结果。理想情况下,我想按总 desc 和限制 15 进行排序。

【问题讨论】:

    标签: mongodb mapreduce geospatial


    【解决方案1】:

    我会做以下事情。首先,你有错误的坐标。 MongoDB 想要longitude, latitude,最好是 GeoJSON 格式:

    loc: { type: 'Point', coordinates: [-73.952985, 40.758318] },
    

    MongoDB 不关心 latlon 字段名称,并将忽略它们。

    但您也应该避免使用 Map/Reduce,因为它既慢又复杂。相反,我们可以使用聚合框架来做类似的事情:

    db.so.aggregate( [
        // search for all the (well, million) venues within **250**km
        { $geoNear: {
            near: { type: 'Point', coordinates: [-73.952985, 40.758318] },
            spherical: true,
            distanceField: 'd',
            maxDistance: 250 * 1000,
            limit: 1000000
        } },
        // find only the items where icv=1
        { $match: { icv: 1 } },
        // group by venue and user
        { $group: { 
            _id: { vid: '$vid', user_id: '$user_id' }, 
            count: { $sum: 1 } } 
        },
        // then regroup by just venue:
        { $group: { 
            _id: '$_id.vid', 
            users: { $addToSet: { user_id: '$_id.user_id', count: '$count' } }, 
            total: { $sum: '$count' } 
        } },
        // now we sort by "total", desc:
        { $sort: { 'total': -1 } },
        // and limit by 15:
        { $limit: 15 }
    ] );
    

    我将$geoNear 用作第一阶段,将$icv 上的匹配用作第二阶段,因为$geoNear 索引可能会比$icv 好很多(因为我猜猜,反正它只会有值 0 或 1)。

    请注意,在此示例中,我使用了 250 公里(250 * 1000 米)而不是 25 公里。

    使用以下输入:

    db.so.insert( { "_id" : ObjectId("51f9234feb97ff0700000046"), "loc" : { type: 'Point', coordinates: [ -73.2681, 40.3672 ] }, "vid" : 348442, "user_id" : 151556 } );
    db.so.insert( { "_id" : ObjectId("51f9234b488fff0700000006"), "loc" : { type: 'Point', coordinates: [ -73.5576, 40.6721 ] }, "vid" : 3124, "user_id" : 472486 } );
    db.so.insert( { "_id" : ObjectId("51f92345488fff0700000006"), "loc" : { type: 'Point', coordinates: [ -73.5576, 40.6721 ] }, "vid" : 3124, "user_id" : 47286 } );
    db.so.insert( { "_id" : ObjectId("52f92345488fff0700000006"), "loc" : { type: 'Point', coordinates: [ -73.5576, 40.6721 ] }, "vid" : 3124, "user_id" : 47286 } );
    

    结果如下:

    {
        "result" : [
            {
                "_id" : 3124,
                "users" : [
                    { "user_id" : 472486, "count" : 1 },
                    { "user_id" : 47286, "count" : 2 }
                ],
                "total" : 3
            },
            {
                "_id" : 348442,
                "users" : [
                    { "user_id" : 151556, "count" : 1 }
                ],
                "total" : 1
            }
        ],
        "ok" : 1
    }
    

    与您想要的输出只有一个区别,那就是 user_id 不是计数的键,而是子文档中的一个额外字段。通常,您不能使用聚合框架将值更改为键或键值。

    【讨论】:

    • 德里克 - 这是一个很好的答案。我只有一个问题:我需要对初始查询添加另一个限制,所以我需要查询(X,Y 中的所有内容,icv = 1) - 这在你的公式中是如何工作的?
    • 您可以添加一个匹配作为第一阶段,它作为一个正常的查询工作。我将更新答案以匹配所有 icv=1。
    • 最后几个问题 - 在初始参数中的 1m 中有这么多记录会发生什么?它会出错吗?对我来说,最终结果将是排序并通过“total”属性限制结果。理想情况下,我想按总 desc 和限制 15 进行排序。我打算在查询结束后执行此操作,但如果可以完成 - 这会有所帮助。可能吗?
    • 不,它不会出错。它只会给你第一百万。如果这还不够,请添加 0 ;-)。排序绝对是可能的。我已经更新了你的问题和我的答案以包含它。
    • Derick - 最后 - 这个查询需要什么类型的索引?如果我想用 vid 做同样的事情 - 我需要两个 2dsphere 索引吗?我不知道这是可能的吗?
    【解决方案2】:

    您说该功能仅扫描 100 个场地。我对near的理解是它会扫描整个集合,只返回最接近的100个。

    $near的文档中复制粘贴:

    注意:您可以使用 cursor.limit() 进一步限制结果的数量。 未定义与使用 $near 的查询一起指定批量大小(即 batchSize())。有关详细信息,请参阅 SERVER-5236。

    【讨论】:

    • 是的——但这不是我想做的。我希望它按场地 ID 分组并允许我计算 unqiue 用户。如果它只返回 100 个条目(按最接近的排序),那么我没有查询中的所有文档来制作 MapReduce - 所以我只有一个小节。我不想限制返回的数量,所以我希望它们都通过 Map Reduce 函数。
    • 在这种情况下,我个人不会在您的第一个查询中通过 $near 进行查询。我会在没有距离限制的情况下处理 mapreduce 中的所有记录,并将 mapreduce 的 out 参数指定为“减少”集合,然后执行 db.reduced.find({loc: {$near: [40.758318,-73.952985]}} )
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-02
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    • 2014-10-29
    • 1970-01-01
    相关资源
    最近更新 更多