获取mongodb嵌套文档中的所有唯一键及其计数

时间:2012-07-10 13:32:54

标签: php mongodb

我有一个用户及其属性的数据库。大约40mil +

{
  uuid:xxxxx-xxxx-xxx-xxxx
  ... : ....
  ... : ....

  attributes {
    age : xxxx
    gender : xxxx
    incomegroup : i
    ... : ...
    ... : ...
  }
}

我只是不知道属性子文档中的字段是什么。我完全不知道。对于某些用户,属性文档可能也不存在。

我需要知道整个数据库中属性中存在的所有键以及拥有它们的用户数量 -

年龄:45000个用户等。

我可以通过mongo查询执行此操作吗?我需要从PHP执行这种类似的事情并定期获取计数,例如每天一次通过cron job到另一个mysql数据库。

1 个答案:

答案 0 :(得分:1)

根据您当前的架构,您可以使用map/reduce来计算集合中的唯一属性字段。请考虑以下示例:

<?php

$mongo = new Mongo();
$db = $mongo->test;
$c = $db->users;
$c->drop();

$fields = ['a', 'b', 'c', 'd'];

for ($i = 0; $i < 1000; ++$i) {
    $user = ['attributes' => []];

    foreach ($fields as $pos => $field) {
        if (0 == $i % ($pos + 1)) {
            $user['attributes'][$field] = 1;
        }
    }

    $c->save($user);
}

$map = <<<'EOF'
function() {
    for (var key in this.attributes) {
        emit(key, 1);
    }
}
EOF;

$reduce = <<<'EOF'
function(k, vals) {
    var sum = 0;
    for (var i in vals) {
        sum += vals[i];
    }
    return sum;
}
EOF;

$result = $db->command([
    'mapreduce' => 'users',
    'map' => new MongoCode($map),
    'reduce' => new MongoCode($reduce),
    'out' => ['inline' => 1],
]);

foreach ($result['results'] as $fields) {
    printf("%s: %d\n", $fields['_id'], $fields['value']);
}

$c->drop();

在这里,我将1,000个文档插入到一个集合中,每个文档都填充abcd属性,具体取决于某些模运算。我们定义一个map函数,Mongo将使用它迭代集合,为每个文档的每个属性键发出1的值。然后,reduce函数通过发射键处理这些结果并对值求和。我们的结果最终为:

a: 1000
c: 334
b: 500
d: 250

虽然这一切都很好,但是当前架构及其动态字段名称存在索引问题。对于要查询的每个字段,您必须在集合上为其定义显式索引。如果attributes是一个嵌入对象数组(例如{k: 'age', v: 25}),那么您可以利用multikey indexing。我强烈建议阅读Derick Rethan在Indexing Freeform-Tagged Data上的帖子,该文章深入讨论了这一点。

此外,此架构允许我们利用aggregation framework(MongoDB 2.1.0+中提供)。您可能会发现使用over map / reduce更容易开发聚合框架。还有一个性能和并发优势,因为处理不是在JavaScript中完成的。用模式更改和新聚合重写上面的示例,我们得到:

<?php

$mongo = new Mongo();
$db = $mongo->test;
$c = $db->users;
$c->drop();

$fields = ['a', 'b', 'c', 'd'];

for ($i = 0; $i < 1000; ++$i) {
    $user = ['attributes' => []];

    foreach ($fields as $pos => $field) {
        if (0 == $i % ($pos + 1)) {
            $user['attributes'][] = ['k' => $field, 'v' => 1];
        }
    }

    $c->save($user);
}

$result = $db->command([
    'aggregate' => 'users',
    'pipeline' => [
        ['$project' => ['attributes' => 1]],
        ['$unwind' => '$attributes'],
        ['$group' => [
            '_id' => '$attributes.k',
            'count' => ['$sum' => 1],
        ]],
    ],
]);

foreach ($result['result'] as $fields) {
    printf("%s: %d\n", $fields['_id'], $fields['count']);
}

$c->drop();

您应该找到相同的输出。您可以随意调整测试大小,看看是否可以发现大型集合的性能差异。