MongoDB聚合选择性项目

时间:2014-06-30 18:10:47

标签: mongodb mongodb-query aggregation-framework

在聚合分组期间,我遇到了重塑文档的问题。基本上我想根据类型将条目推送到字段。我的结构如下:

_id: P1
   entities: [{type: A, val: X}, {type: B, val: X}, {type: A, val: Y}]
   ...

我想$ unwind和$投影这些实体,以便获得如下结构:

_id: P1
   A: [X]
   B: []
_id: P1
   A: [Y]
   B: []
_id: P1
   A: []
   B: [X]

所以我可以用A或B或两者进行分组,即

$group: {
   _id: {
       A: $A, 
       B: $B
    }
    count: {$sum : 1}

我以为我可以这样做:

$unwind: $entities
$project: {
   id: §id
   A: {"$cond":[{"$eq":["$type","A"]},"$code"]}
   B: {"$cond":[{"$eq":["$type","B"]},"$code"]}
}
$group: {
  _id: "$id"
  A: {$addToSet : "$A"}
}

或者失败的话

$unwind: $entities
$group: {
  _id: "$id"
  A: {"$cond":[{"$eq":["$type","A"]},$push: "$code", null]}
  ...
}

但两个版本都失败了,因为我在其他方面无法做任何事情,而且我没有设法在条件中使用$ push。我得到的最接近的是项目取决于类型,但由于我找不到在没有匹配时不向场添加任何内容的方法,我最终得到:

_id: P1
   A: [X,null,Y]
   B: [null,X,null]

弄乱了计数。我的第二个想法是过滤数组以删除null。但是我没有找到删除实体的方法,因为再次$ cond不会让我指定一个空的/“什么也不做”的其他情况。

我感觉它可以通过类型和内容进行分组以及所需类型的匹配,但由于我有许多类型和任意分组导致分组树,这可能会变得非常复杂。对错误的想法或提示将非常受欢迎。

谢谢

编辑:基于已接受的anwser的解决方案

我不得不稍微调整它,以过滤一个类型的所有内容都为null的情况,因为否则它会在匹配期间丢失,因为我想保留这些知识。谢谢!

{$project:{
  A: {$cond: [
      {$eq: ["$A", [false]]},
      ["N/A"],
      "$A"
  ]},
  B: {$cond: [
      {$eq: ["$B", [false]]},
      ["N/A"],
      "$B"
  ]},
}},
{ "$unwind": "$A" },
{ "$match": { "A": { "$ne": false } } },
{ "$group": {
    "_id": "$_id",
    "A": { "$push": "$A" },
    "B": { "$first": "$B" }
}},
{ "$unwind": "$B" },
{ "$match": { "B": { "$ne": false } } },
{ "$group": {
    "_id": "$_id",
    "A": { "$first": "$A" },
    "B": { "$push": "$B" }
}}

1 个答案:

答案 0 :(得分:14)

您似乎走在正确的轨道上,只有不同的方法可以从条件中删除false的值。你不能没有任何回报,但你可以摆脱你不想要的价值。

如果你真的想要"套"并且您可以使用MongoDB 2.6或更高版本,然后基本上使用$setDifference过滤掉false值:

db.entities.aggregate([
    { "$unwind": "$entities" },
    { "$group": {
        "_id": "$_id",
        "A": { 
            "$addToSet": {
                "$cond": [
                    { "$eq": [ "$entities.type", "A" ] },
                    "$entities.val",
                    false
                ]
            }
        },
        "B": { 
            "$addToSet": {
                "$cond": [
                    { "$eq": [ "$entities.type", "B" ] },
                    "$entities.val",
                    false
                ]
            }
        }
    }},
    { "$project": {
        "A": {
            "$setDifference": [ "$A", [false] ]
        },
        "B": {
            "$setDifference": [ "$B", [false] ]
        }
    }}
])

或者只是使用$map中的$project运算符的一步:

db.entities.aggregate([
    {"$project": {
        "A": {
             "$setDifference": [
                 {
                     "$map": {
                         "input": "$entities",
                         "as": "el",
                         "in": {
                             "$cond": [
                                 { "$eq": [ "$$el.type", "A" ] },
                                 "$$el.val",
                                 false
                             ]
                         }
                     }
                 },
                 [false]
             ]
         },
        "B": {
             "$setDifference": [
                 {
                     "$map": {
                         "input": "$entities",
                         "as": "el",
                         "in": {
                             "$cond": [
                                 { "$eq": [ "$$el.type", "B" ] },
                                 "$$el.val",
                                 false
                             ]
                         }
                     }
                 },
                 [false]
             ]
         }
    }}
])

或以其他方式与一般$unwind$match运营商联系以过滤这些:

db.entities.aggregate([
    { "$unwind": "$entities" },
    { "$group": {
        "_id": "$_id",
        "A": { 
            "$push": {
                "$cond": [
                    { "$eq": [ "$entities.type", "A" ] },
                    "$entities.val",
                    false
                ]
            }
        },
        "B": { 
            "$push": {
                "$cond": [
                    { "$eq": [ "$entities.type", "B" ] },
                    "$entities.val",
                    false
                ]
            }
        }
    }},
    { "$unwind": "$A" },
    { "$match": { "A": { "$ne": false } } },
    { "$group": {
        "_id": "$_id",
        "A": { "$push": "$A" },
        "B": { "$first": "$B" }
    }},
    { "$unwind": "$B" },
    { "$match": { "B": { "$ne": false } } },
    { "$group": {
        "_id": "$_id",
        "A": { "$first": "$A" },
        "B": { "$push": "$B" }
    }}
])

$push用于普通数组,或$addToSet用于唯一设置。