2015-09-30 71 views
2

我有一個3集架構如下圖所示:MongoDB的協助與建議

  1. 用戶收藏有關於每個藝術家

    { 
        user_id : 1, 
        Friends : [3,5,6], 
        Artists : [ 
         {artist_id: 10 , weight : 345}, 
         {artist_id: 17 , weight : 378} 
        ] 
    } 
    
  2. 藝術家他們的朋友和收聽數(重)信息集合模式具有關於藝術家姓名的信息,各個用戶給他們的標籤。

    { 
        artistID : 56, 
        name : "Ed Sheeran", 
        user_tag : [ 
         {user_id : 2, tag_id : 6}, 
         {user_id : 2, tag_id : 5}, 
         {user_id : 3, tag_id : 7} 
        ] 
    } 
    
  3. 具有關於各種標籤的信息的標籤集合。

    {tag_id : 3, tag_value : "HipHop"} 
    

我想通過使用下面的規則來提供與藝術家建議用戶:

規則1:查找用戶的朋友收聽的藝術家,但不是用戶,通過總和責令其朋友的傾聽計數。規則2:選擇用戶使用的任何標籤,找到所有不在用戶的收聽列表中的具有此標籤的藝術家,並按唯一監聽者的數量排序。

任何人都可以幫我寫一個查詢來執行上述操作。

+0

歡迎來到StackOverflow。您似乎在這裏提出了多個問題,其中格式是「一個問題」,希望「有一個確定的答案」。無論如何,這肯定是多個查詢。你的第一個意圖很明顯,你想從用戶的朋友那裏得到所有的結果並訂購它們。你的第二個意圖不是很清楚,因爲你不想說如果你想要做的就是從朋友聽力結果中排除任何已經在用戶自己的聽力結果中的項目。如果你能清楚這一點,那麼你實際上可能會問一個問題。 –

+0

看起來你來自關係背景查看這篇文章:[從關係數據庫過渡到MongoDB - 數據模型](http://blog.mongodb.org/post/72874267152/transitioning-from-relational-databases-to MongoDB不支持連接,而是使用引用。 –

+0

我基本上想找到不在用戶列表中但出現在用戶朋友列表中的藝術家,因此這些藝術家可以被推薦給用戶。第二條規則將要求完成建議,使得這些藝術家具有用戶已經給予其他藝術家的標籤。 @BlakesSeven – Dossoh

回答

2

爲了達到最終結果,您需要在這裏做一些事情,但第一階段相對比較簡單。以用戶對象你提供:

var user = { 
    user_id : 1, 
    Friends : [3,5,6], 
    Artists : [ 
     {artist_id: 10 , weight : 345}, 
     {artist_id: 17 , weight : 378} 
    ] 
}; 

現在,假設你已經有一個數據檢索,那麼這個歸結爲找到相同的結構爲每個「朋友」,並過濾掉「藝術家」的數組內容合併到一個清晰的清單。大概每個「重量」在這裏也將被考慮。

這是一個simlple聚集的操作,將首先在篩選出的藝術家已經在列表中的給定用戶:

var artists = user.Artists.map(function(artist) { return artist.artist_id }); 

User.aggregate(
    [ 
     // Find possible friends without all the same artists 
     { "$match": { 
      "user_id": { "$in": user.Friends }, 
      "Artists.artist_id": { "$nin": artists } 
     }}, 
     // Pre-filter the artists already in the user list 
     { "$project": 
      "Artists": { 
       "$setDifference": [ 
        { "$map": { 
         "input": "$Artists", 
         "as": "$el", 
         "in": { 
          "$cond": [ 
           "$anyElementTrue": { 
            "$map": { 
             "input": artists, 
             "as": "artist", 
             "in": { "$eq": [ "$$artist", "$el.artist_id" ] } 
            } 
           }, 
           false, 
           "$$el" 
          ] 
         } 
        }} 
        [false] 
       ] 
      } 
     }}, 
     // Unwind the reduced array 
     { "$unwind": "$Artists" }, 
     // Group back by each artist and sum weights 
     { "$group": { 
      "_id": "$Artists.artist_id", 
      "weight": { "$sum": "$Artists.weight" } 
     }}, 
     // Sort the results by weight 
     { "$sort": { "weight": -1 } } 
    ], 
    function(err,results) { 
     // more to come here 
    } 
); 

「預過濾器」是這裏唯一真正棘手的部分。您可以再次使用$unwind陣列和$match篩選出您不想要的條目。即使我們想在後面結合$unwind以便將它們結合起來,但它會更有效地將它們從「數組」中移出數組,因此擴展的次數更少。

因此,這裏$map運算符允許檢查用戶「藝術家」數組中的每個元素,並且還用於與篩選的「用戶」藝術家列表進行比較以僅返回想要的細節。 $setDifference用於實際「過濾」任何未作爲數組內容返回的結果,而是返回爲false

之後,只有$unwind去標準化數組中的內容和$group,使每個藝術家的總數。爲了好玩,我們使用$sort來表明列表按照期望的順序返回,但在稍後的階段不需要。

這是一起在這裏爲結果列表中只應其他藝術家不是已經在用戶自己的列表的方式至少部分,由那些可能出現在多個朋友的任何藝術家的總結「權重」來分類的。

下一部分將需要來自「artists」集合的數據,以便將聽衆的數量考慮在內。雖然貓鼬有一個.populate()方法,你真的不希望在這裏,因爲你正在尋找「不同的用戶」計數。這意味着另一個聚合實現爲了獲得每個藝術家的不同計數。

從以前的聚合操作的結果列表之後,你會使用這樣的$_id值:

// First get just an array of artist id's 
var artists = results.map(function(artist) { 
    return artist._id; 
}); 

Artist.aggregate(
    [ 
     // Match artists 
     { "$match": { 
      "artistID": { "$in": artists } 
     }}, 
     // Project with weight for distinct users 
     { "$project": { 
      "_id": "$artistID", 
      "weight": { 
       "$multiply": [ 
        { "$size": { 
         "$setUnion": [ 
          { "$map": { 
           "input": "$user_tag", 
           "as": "tag", 
           "in": "$$tag.user_id" 
          }}, 
          [] 
         ] 
        }}, 
        10 
       ] 
      } 
     }} 
    ], 
    function(err,results) { 
     // more later 
    } 
); 

這裏的竅門是在集合的方式進行與$map做類似的變換值的是餵給$setUnion,使他們成爲一個獨特的名單。然後應用$size運算符來確定該列表有多大。額外的數學運算是將這個數字應用於以前結果中已記錄的權重時的一些含義。

當然,您需要將所有這些結合在一起,因爲現在只有兩組不同的結果。基本過程是一個「哈希表」,其中唯一的「藝術家」id值用作關鍵字,並將「權重」值組合在一起。

你可以用很多方法做到這一點,但是由於希望對合並結果進行「排序」,所以我的偏好會是「MongoDBish」,因爲它遵循了你已經習慣的基本方法。

一個實用的方法是使用nedb,它提供了一個「內存中」存儲,它使用了大部分與讀取和寫入MongoDB集合相同類型的方法。

如果您需要將大量結果用於實際採集,則所有原則保持不變,這也可以很好地擴展。

  1. 首先聚合操作插入新的數據到店

  2. 二聚合「更新」的數據的,將「重量」領域

作爲一個完整的功能列表,並與async庫的一些其他幫助它看起來像這樣:

function GetUserRecommendations(userId,callback) { 

    var async = require('async') 
     DataStore = require('nedb'); 

    User.findOne({ "user_id": user_id},function(err,user) { 
     if (err) callback(err); 

     var artists = user.Artists.map(function(artist) { 
      return artist.artist_id; 
     }); 

     async.waterfall(
      [ 
       function(callback) { 
        var pipeline = [ 
         // Find possible friends without all the same artists 
         { "$match": { 
          "user_id": { "$in": user.Friends }, 
          "Artists.artist_id": { "$nin": artists } 
         }}, 
         // Pre-filter the artists already in the user list 
         { "$project": 
          "Artists": { 
           "$setDifference": [ 
            { "$map": { 
             "input": "$Artists", 
             "as": "$el", 
             "in": { 
              "$cond": [ 
               "$anyElementTrue": { 
                "$map": { 
                 "input": artists, 
                 "as": "artist", 
                 "in": { "$eq": [ "$$artist", "$el.artist_id" ] } 
                } 
               }, 
               false, 
               "$$el" 
              ] 
             } 
            }} 
            [false] 
           ] 
          } 
         }}, 
         // Unwind the reduced array 
         { "$unwind": "$Artists" }, 
         // Group back by each artist and sum weights 
         { "$group": { 
          "_id": "$Artists.artist_id", 
          "weight": { "$sum": "$Artists.weight" } 
         }}, 
         // Sort the results by weight 
         { "$sort": { "weight": -1 } } 
        ]; 

        User.aggregate(pipeline, function(err,results) { 
         if (err) callback(err); 

         async.each(
          results, 
          function(result,callback) { 
           result.artist_id = result._id; 
           delete result._id; 
           DataStore.insert(result,callback); 
          }, 
          function(err) 
           callback(err,results); 
          } 
         ); 

        }); 
       }, 
       function(results,callback) { 

        var artists = results.map(function(artist) { 
         return artist.artist_id; // note that we renamed this 
        }); 

        var pipeline = [ 
         // Match artists 
         { "$match": { 
          "artistID": { "$in": artists } 
         }}, 
         // Project with weight for distinct users 
         { "$project": { 
          "_id": "$artistID", 
          "weight": { 
           "$multiply": [ 
            { "$size": { 
             "$setUnion": [ 
              { "$map": { 
               "input": "$user_tag", 
               "as": "tag", 
               "in": "$$tag.user_id" 
              }}, 
              [] 
             ] 
            }}, 
            10 
           ] 
          } 
         }} 
        ]; 

        Artist.aggregate(pipeline,function(err,results) { 
         if (err) callback(err); 
         async.each(
          results, 
          function(result,callback) { 
           result.artist_id = result._id; 
           delete result._id; 
           DataStore.update(
            { "artist_id": result.artist_id }, 
            { "$inc": { "weight": result.weight } }, 
            callback 
           ); 
          }, 
          function(err) { 
           callback(err); 
          } 
         ); 
        }); 
       } 
      ], 
      function(err) { 
       if (err) callback(err);  // callback with any errors 
       // else fetch the combined results and sort to callback 
       DataStore.find({}).sort({ "weight": -1 }).exec(callback); 
      } 
     ); 

    }); 

} 

因此,在匹配初始源用戶對象之後,將值傳遞到第一個聚合函數中,該函數正在串行執行,並使用async.waterfall來傳遞它的結果。

在此之前發生的,雖然聚集結果被添加到DataStore定期.insert()語句,注意重命名_id領域nedb不喜歡任何比它本身的自我生成的其它_id值。從聚合結果中插入每個結果artist_idweight屬性。

該列表隨後被傳遞到將要基於所述不同的用戶大小計算出「重量」返回每個指定的「藝術家」第二聚合操作。有關於每位藝術家的DataStore上的相同.update()聲明的「更新」並遞增「權重」字段。

一切順利,最後的操作是.find()那些結果和.sort()它們由組合的「權重」,並簡單地將結果返回給傳入的函數中。

,因此會使用這樣的:

GetUserRecommendations(1,function(err,results) { 
    // results is the sorted list 
}); 

而且它會通過朋友收聽的組合權在用戶的列表中,但在他們的朋友列表返回所有藝術家不是目前並下令計數加上該藝術家的不同用戶的數量的得分。

這是你如何應對來自兩個不同的集合,你需要組合成各種聚合細節一個結果數據。它是多重查詢和工作空間,但也是MongoDB哲學的一部分,這種操作比用數據庫將它們「加入」結果更好地執行。