現在のスキーマがあれば、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
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 以降で利用可能) を利用できます。オーバーマップ/リデュースを使用すると、集計フレームワークの開発が容易になる可能性があります。処理が 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();
出力は同じであるはずです。自由にテスト サイズを上げて、大規模なコレクションのパフォーマンスの違いを見つけられるかどうかを確認してください。