鑑於您當前的架構,您可以利用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();
在這裏,我插入1000個文檔轉換成一個集合,這取決於一些模運算a
,b
,c
,並d
屬性填充每個。我們定義了一個映射函數,Mongo將使用該映射函數迭代集合,爲每個文檔的每個屬性鍵發出值1
。然後reduce函數通過發射鍵處理這些結果並對這些值求和。我們的結果最終爲:
a: 1000
c: 334
b: 500
d: 250
雖然這是一個好主意,目前的模式,其充滿活力的領域名稱提出了索引的一個問題。對於您打算查詢的每個字段,您必須爲集合定義一個明確的索引。相反,如果attributes
是嵌入對象的數組(例如{k: 'age', v: 25}
),則可以利用multikey indexing。我強烈建議閱讀Derby 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();
你應該找到相同的輸出。隨意挑起測試尺寸,看看你是否能夠發現大型系列的性能差異。