给定您当前的模式,您可以利用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 个文档插入到一个集合中,根据一些模算术使用a
、b
、c
和属性填充每个文档。d
我们定义了一个 map 函数,Mongo 将使用它来遍历集合,1
为每个文档的每个属性键发出一个值。然后,reduce 函数通过发射键处理这些结果并对值求和。我们的结果是:
a: 1000
c: 334
b: 500
d: 250
虽然这一切都很好,但当前的模式及其动态字段名称给索引带来了问题。对于您打算查询的每个字段,您必须在集合上为其定义一个显式索引。如果attributes
是嵌入对象的数组(例如{k: 'age', v: 25}
),那么您可以利用多键索引。我强烈建议阅读 Derick Rethan 关于Indexing Freeform-Tagged Data的帖子,其中深入讨论了这一点。
此外,此模式将允许我们利用聚合框架(在 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();
您应该会发现输出相同。随意增加测试规模,看看您是否可以发现大型集合的性能差异。