如何计算传入链接?

How to count incoming links?

我有这样的文档:

{
    "url" : "http://example.com",
    "links" : [
        "http://example.com/foo",
        "http://example.com/bar"
    ]
},
{
    "url" : "http://example.com/foo",
    "links" : [
        "http://example.com/bar"
    ]
},
{
    "url" : "http://example.com/lost",
    "links" : [
        "http://example.com/bar"
    ]
},
{
    "url" : "http://example.com/bar",
    "links" : []
}

如何按 url 聚合并计算 传入 链接的数量:

{
    "url": http://example.com,
    "count" : 0
},
{
    "url": http://example.com/lost,
    "count" : 0
},
{
    "url": http://example.com/foo,
    "count" : 1
},
{
    "url": http://example.com/bar,
    "count" : 3
}

你知道我该怎么做吗?

使用aggregation framework达到想要的效果。以下聚合管道将为您提供:

db.test.aggregate([
    {
        "$unwind": "$links"
    },
    {
        "$group": {
            "_id": "$url",
            "count": { "$sum": 1 }
        }
    },
    {
        "$project": {
            "_id": 0,
            "url": "$_id",
            "count": 1
        }
    }
])

输出:

/* 0 */
{
    "result" : [ 
        {
            "count" : 1,
            "url" : "http://example.com/lost"
        }, 
        {
            "count" : 1,
            "url" : "http://example.com/foo"
        }, 
        {
            "count" : 2,
            "url" : "http://example.com"
        }
    ],
    "ok" : 1
}

-- 更新 --

因为我没有看到您想计算 传入 链接的数量,所以上面的聚合将不起作用。但是,要根据该条件进行聚合,然后将分组更改为按 links 数组元素分组,并使用 $out 运算符创建输出集合作为最终聚合管道。这对于查询原始集合中的空传入链接数组并相应地更新结果集合是必要的。例如:

db.test.aggregate([
    {
        "$unwind": "$links"
    },
    {
        "$group": {
            "_id": "$links",
            "count": { "$sum": 1 }
        }
    },
    {
        "$project": {
            "_id": 0,
            "url": "$_id",
            "count": 1
        }
    },
    {
        "$out": "results"
    }
])

在结果集合中,您可以使用 map() and forEach() 游标方法的组合来更新文档,以获得具有计数的 urls 的数组并迭代原始集合以查找url 不是上述数组的文档:

var urlsWithCount = db.results.find().map(function(u){ return u.url });
db.test.find({"url": {"$nin": urlsWithCount}}).forEach(function(doc){
    var obj = {};
    obj.url = doc.url;
    obj.count = 0;
    db.results.save(obj);   
});    


db.results.find();
/* 0 */
{
    "_id" : ObjectId("5555c1c49cd8fa39c7971e54"),
    "count" : 3,
    "url" : "http://example.com/bar"
}

/* 1 */
{
    "_id" : ObjectId("5555c1c49cd8fa39c7971e55"),
    "count" : 1,
    "url" : "http://example.com/foo"
}

/* 2 */
{
    "_id" : ObjectId("5555c3829bbec0dd0344e4ac"),
    "url" : "http://example.com",
    "count" : 0
}

/* 3 */
{
    "_id" : ObjectId("5555c3829bbec0dd0344e4ad"),
    "url" : "http://example.com/lost",
    "count" : 0
}

这很棘手,因为您要计算 传入 link 的次数。您可以使用 map-reduce:

来达到预期的效果

地图阶段将为当前检查的 URL 瞄准的每个 link 发射一个“1”。此外,为了确保每个 source URL 都在结果集中,我为源 link:

发出一个“0”
map = function() {
    for (var idx = 0; idx < this.links.length; idx++) {
        emit(this.links[idx], 1)
    }
    emit(this.url, 0) // this ensure that all URL are in the output set
}

在那之后,你的 reduce 步骤只是对多个值求和的问题:

reduce = function(key, values) {
    return values.reduce(function(a, b){return a+b;});
}

给定样本数据集:

> db.test.mapReduce(map, reduce, {out:{inline:1}})
{
    "results" : [
        {
            "_id" : "http://example.com",
            "value" : 0
        },
        {
            "_id" : "http://example.com/bar",
            "value" : 3
        },
        {
            "_id" : "http://example.com/foo",
            "value" : 1
        },
        {
            "_id" : "http://example.com/lost",
            "value" : 0
        }
    ],
    "timeMillis" : 1,
    "counts" : {
        "input" : 4,
        "emit" : 8,
        "reduce" : 2,
        "output" : 4
    },
    "ok" : 1
}