2012-07-10 117 views
1

我有一個用戶及其屬性的數據庫。大約40mil +獲取mongo嵌套文檔中的所有唯一鍵和它們的計數

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

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

我只是不知道屬性子文檔中的字段是什麼。我根本不知道它。對於某些用戶,屬性文檔也可能不存在。

我需要知道在整個數據庫和用戶數量的屬性已經他們喜歡誰什麼存在的所有鍵 -

年齡:45000個用戶等

我能做到這一點從蒙戈查詢?我需要從PHP執行一些這樣的事情,並通過cron作業定期將計數定期存入另一個mysql數據庫。

回答

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(); 

在這裏,我插入1000個文檔轉換成一個集合,這取決於一些模運算abc,並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(); 

你應該找到相同的輸出。隨意挑起測試尺寸,看看你是否能夠發現大型系列的性能差異。

相關問題