2016-07-26 58 views
3

我有一些文件有一個數組屬性項。 我想獲得n篇文章之間的截距。幾個數組的交集

db.things.insert({name:"A", items:[1,2,3,4,5]}) 
db.things.insert({name:"B", items:[2,4,6,8]}) 
db.things.insert({name:"C", items:[1,2]}) 
db.things.insert({name:"D", items:[5,6]}) 
db.things.insert({name:"E", items:[9,10]}) 
db.things.insert({name:"F", items:[1,5]}) 

數據:

{ "_id" : ObjectId("57974a0d356baff265710a1c"), "name" : "A", "items" : [ 1, 2, 3, 4, 5 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1d"), "name" : "B", "items" : [ 2, 4, 6, 8 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1e"), "name" : "C", "items" : [ 1, 2 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1f"), "name" : "D", "items" : [ 5, 6 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a20"), "name" : "E", "items" : [ 9, 10 ] }, 
{ "_id" : ObjectId("57974a1a356baff265710a21"), "name" : "F", "items" : [ 1, 5 ] } 

例如: things.mane.A截距截距things.mane.C things.mane.F:

[1,2,3,4 ,5]截距[1,2]截距[1,5]

必須是:[1]

我認爲這是使用$ setIntersection可行,但我找不到方法。

我可以做到兩個文件,但如何做到更多?

db.things.aggregate({$match:{"name":{$in:["A", "F"]}}}, 
    {$group:{_id:null, "setA":{$first:"$items"}, "setF":{$last:"$items"} } }, 
    { 
      "$project": { 
       "set1": 1, 
       "set2": 1, 
       "commonToBoth": { "$setIntersection": [ "$setA", "$setF" ] }, 
       "_id": 0 
      } 
     } 
    ) 

{ "commonToBoth" : [ 5, 1 ] } 

回答

1

如果您使用的是蒙戈3.2,你可以使用arrayElemAt精確的$setIntersection所有參數:

db.things.aggregate([{ 
    $match: { 
     "name": { 
      $in: ["A", "B", "C"] 
     } 
    } 
}, { 
    $group: { 
     _id: 0, 
     elements: { 
      $push: "$items" 
     } 
    } 
}, { 
    $project: { 
     intersect: { 
      $setIntersection: [{ 
       "$arrayElemAt": ["$elements", 0] 
      }, { 
       "$arrayElemAt": ["$elements", 1] 
      }, { 
       "$arrayElemAt": ["$elements", 2] 
      }] 
     }, 
    } 
}]); 

你必須動態地添加需要的JSONObject的數量與指數如:

{ 
    "$arrayElemAt": ["$elements", <index>] 
} 

它應該與您的輸入項的元素數相匹配["A", "B", "C"]

如果你要處理重複(一些name存在多個時間),通過name重組所有項目,$unwind兩次$addToSet執行前一個聚合之前合併所有陣列特定$name

db.things.aggregate([{ 
    $match: { 
     "name": { 
      $in: ["A", "B", "C"] 
     } 
    } 
}, { 
    $group: { 
     _id: "$name", 
     "items": { 
      "$push": "$items" 
     } 
    } 
}, { 
    "$unwind": "$items" 
}, { 
    "$unwind": "$items" 
}, { 
    $group: { 
     _id: "$_id", 
     items: { 
      $addToSet: "$items" 
     } 
    } 
}, { 
    $group: { 
     _id: 0, 
     elements: { 
      $push: "$items" 
     } 
    } 
}, { 
    $project: { 
     intersect: { 
      $setIntersection: [{ 
       "$arrayElemAt": ["$elements", 0] 
      }, { 
       "$arrayElemAt": ["$elements", 1] 
      }, { 
       "$arrayElemAt": ["$elements", 2] 
      }] 
     }, 
    } 
}]); 

它不是一個乾淨的解決方案,但它的工作原理

1

一個解決方案,它是不特定的輸入項的數目可能看起來像這樣:

db.things.aggregate(
    { 
     $match: { 
      "name": { 
       $in: ["A", "F"] 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: "$items", 
      count: { 
       $sum: 1 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: null, 
      totalCount: { 
       $sum: "$count" 
      }, 
      items: { 
       $push: "$_id" 
      } 
     } 
    }, 
    { 
     $unwind: { 
      path: "$items" 
     } 
    }, 
    { 
     $unwind: { 
      path: "$items" 
     } 
    }, 
    { 
     $group: { 
      _id: "$items", 
      totalCount: { 
       $first: "$totalCount" 
      },    
      count: { 
       $sum: 1 
      } 
     } 
    }, 
    { 
     $project: { 
      _id: 1, 
      presentInAllDocs: { 
       $eq: ["$totalCount", "$count"] 
      } 
     } 
    }, 
    { 
     $match: { 
      presentInAllDocs: true 
     } 
    }, 
    { 
     $group: { 
      _id: null, 
      items: { 
       $push: "$_id" 
      } 
     } 
    } 
) 

將輸出這個

{ 
    "_id" : null, 
    "items" : [ 
     5, 
     1 
    ] 
} 

當然,你可以添加一個最後$project階段帶來的結果成所需的形狀。


說明

這背後的基本理念是,當我們計算的文件的數量和我們計算每個項目的出現,然後用計數的項目等於總文檔數量計數出現在每個文件中,因此處於相交結果中。
這個想法有一個重要的假設:你的items數組沒有重複(即它們是集合)。如果這個假設是錯誤的,那麼你將不得不在流水線的開始處插入一個額外的階段來將數組轉換爲集合。
也可以用不同的或可能更短的方式構建此管道,但我儘量保持資源使用率儘可能低,因此可能不必要(從功能角度來看)階段。例如,items陣列作爲我的假設的第二階段組是,與文檔相比,有很多不同的值/數組,因此其餘的管道必須使用初始文檔計數的一小部分。然而,從功能的角度來看,我們只需要文檔的總數,因此我們可以跳過這個階段,只需要一個$group階段對所有文檔進行計數,並將它們推入數組中以供以後使用 - 這當然是一個很大的打擊用於內存消耗,因爲我們現在有一系列所有可能的文檔。