Mongodb - 使用聚合框架对几个字段进行分组

时间:2015-08-27 12:05:22

标签: mongodb mongodb-query aggregation-framework

我有一些文件

{name: 'apple',        type: 'fruit',  color: 'red'}
{name: 'banana',       type: 'fruit',  color: 'yellow'}
{name: 'orange',       type: 'fruit',  color: 'orange'}
{name: 'eggplant',     type: 'vege',   color: 'purple'}
{name: 'brocoli',      type: 'vege',   color: 'green'}
{name: 'rose',         type: 'flower', color: 'red'}
{name: 'cauli',        type: 'vege',   color: 'white'}
{name: 'potato',       type: 'vege',   color: 'brown'}
{name: 'onion',        type: 'vege',   color: 'white'}
{name: 'strawberry',   type: 'fruit',  color: 'red'}
{name: 'cashew',       type: 'nut',    color: ''}
{name: 'almond',       type: 'nut',    color: ''}
{name: 'lemon',        type: 'vege',   color: 'yellow'}
{name: 'tomato',       type: 'vege',   color: 'red'}
{name: 'tomato',       type: 'fruit',  color: 'red'}
{name: 'fig',          type: 'fruit',  color: 'pink'}
{name: 'nectarin',     type: 'fruit',  color: 'pink'}

我想将它们分组为下面的字母

{
 _id:'a',
 name:['apple','almond'],
 type:[],
 color:[]
}

{
 _id:'b',
 name:['banana','brocoli'],
 type:[],
 color:['brown']
}

...

{
 _id:'f',
 name:['fig'],
 type:['fruit','flower'],
 color:['']
}

...

{
 _id:'n',
 name:['nectarin'],
 type:['nut'],
 color:['']
}

...

{
 _id:'p',
 name:['potato'],
 type:[''],
 color:['pink','purple']
}

...

结果可以保存到另一个集合中。所以我可以在新创建的集合中发出一个查询:find({_id:'a'})返回名称,类型和颜色以字母“a”开头。

我考虑过使用$group

$group: {
  _id: $substr: ['$name', 0, 1],
  name: {$addToSet: '$name'},
}

然后另一个命令

$group: {
  _id: $substr: ['$type', 0, 1],
  name: {$addToSet: '$type'},
}

并且

$group: {
  _id: $substr: ['$color', 0, 1],
  name: {$addToSet: '$color'},
}

但我仍然坚持如何将所有三个统一起来保存到一个新系列中。或者聚合框架不适合这种数据汇总吗?

在现实世界的例子中,例如在电子商务网站上,首页显示的内容如下:“目前我们拥有来自135636品牌的231类别下的111个产品”。当然,这些数字应该缓存在某个地方(在内存或另一个集合中),因为每次运行$group都是资源密集型的?这些情况的最佳架构/设计是什么?

对不起,我的问题有点令人困惑。

2 个答案:

答案 0 :(得分:2)

由于这里有多个阵列,关键是"合并"它们全部合二为一,用于最简单的处理。

聚合框架的$map运算符在此处运行良好,并且转换元素以便您获得第一个字母"来自数据中的每个单词:

db.alpha.aggregate([
  { "$project": {
    "list": {
      "$map": { 
        "input": [ "A", "B", "C" ],
        "as": "el",
        "in": {
          "$cond": [
            { "$eq": [ "$$el", "A" ] },
            { 
              "type": { "$literal": "name" }, 
              "value": "$name",
              "alpha": { "$substr": [ "$name",0,1 ] } 
            },
            { "$cond": [
              { "$eq": [ "$$el", "B" ] },
              {
                "type": { "$literal": "type" }, 
                "value": "$type",
                "alpha": { "$substr": [ "$type",0,1 ] } 
              },
              {
                "type": { "$literal": "color" }, 
                "value": "$color",
                "alpha": { "$substr": [ "$color",0,1 ] } 
              }
            ]}
          ]
        }
      }
    }
  }},
  { "$unwind": "$list" },
  { "$match": { "list.alpha": { "$ne": "" } } },
  { "$group": {
    "_id": "$list.alpha",
    "list": { 
      "$addToSet": "$list"
    }
  }},
  { "$project": {
    "name": { 
      "$setDifference": [
        { "$map": {
          "input": "$list",
          "as": "el",
          "in": {
            "$cond": [
              { "$eq": [ "$$el.type", "name" ] },
              "$$el.value",
              false
            ]
          }
        }},
        [false]
      ]
    },
    "type": { 
      "$setDifference": [
        { "$map": {
          "input": "$list",
          "as": "el",
          "in": {
            "$cond": [
              { "$eq": [ "$$el.type", "type" ] },
              "$$el.value",
              false
            ]
          }
        }},
        [false]
      ]
    },
    "color": { 
      "$setDifference": [
        { "$map": {
          "input": "$list",
          "as": "el",
          "in": {
            "$cond": [
              { "$eq": [ "$$el.type", "color" ] },
              "$$el.value",
              false
            ]
          }
        }},
        [false]
      ]
    }
  }},
  { "$sort": { "_id": 1 } }
])

如果您查看"阶段"中的数据。它很有道理在转型中发生了什么。

第一阶段"地图"每个文档的所有字段都放在一个数组中,所以现在所有文档都是这样的:

{
    "_id" : ObjectId("55df0652c9064ef625d7f36e"),
    "list" : [
            {
                    "type" : "name",
                    "value" : "nectarin",
                    "alpha" : "n"
            },
            {
                    "type" : "type",
                    "value" : "fruit",
                    "alpha" : "f"
            },
            {
                    "type" : "color",
                    "value" : "pink",
                    "alpha" : "p"
            }
    ]
}

$unwind并不重要,因为它符合标准并从每个成员创建新文档。正是$group完成了大部分工作,每个" alpha"在分组中:

{
    "_id" : "o",
    "list" : [
            {
                    "type" : "name",
                    "value" : "orange",
                    "alpha" : "o"
            },
            {
                    "type" : "color",
                    "value" : "orange",
                    "alpha" : "o"
            },
            {
                    "type" : "name",
                    "value" : "onion",
                    "alpha" : "o"
            }
    ]
}

这有一个很好的分组,可以说是一个不错的输出格式。但是为了得到最终结果,$map运算符再次与$setDifference一起使用,可用于删除false值,其中每个字段"键入"转换与所需的输出字段不匹配。

完整的结果是:

{ "_id" : "a", "name" : [ "almond", "apple" ], "type" : [ ], "color" : [ ] }
{ "_id" : "b", "name" : [ "brocoli", "banana" ], "type" : [ ], "color" : [ "brown" ] }
{ "_id" : "c", "name" : [ "cashew", "cauli" ], "type" : [ ], "color" : [ ] }
{ "_id" : "e", "name" : [ "eggplant" ], "type" : [ ], "color" : [ ] }
{ "_id" : "f", "name" : [ "fig" ], "type" : [ "flower", "fruit" ], "color" : [ ] }
{ "_id" : "g", "name" : [ ], "type" : [ ], "color" : [ "green" ] }
{ "_id" : "l", "name" : [ "lemon" ], "type" : [ ], "color" : [ ] }
{ "_id" : "n", "name" : [ "nectarin" ], "type" : [ "nut" ], "color" : [ ] }
{ "_id" : "o", "name" : [ "onion", "orange" ], "type" : [ ], "color" : [ "orange" ] }
{ "_id" : "p", "name" : [ "potato" ], "type" : [ ], "color" : [ "pink", "purple" ] }
{ "_id" : "r", "name" : [ "rose" ], "type" : [ ], "color" : [ "red" ] }
{ "_id" : "s", "name" : [ "strawberry" ], "type" : [ ], "color" : [ ] }
{ "_id" : "t", "name" : [ "tomato" ], "type" : [ ], "color" : [ ] }
{ "_id" : "v", "name" : [ ], "type" : [ "vege" ], "color" : [ ] }
{ "_id" : "w", "name" : [ ], "type" : [ ], "color" : [ "white" ] }
{ "_id" : "y", "name" : [ ], "type" : [ ], "color" : [ "yellow" ] }

所有内容都按字母顺序分组,每个字段都有自己的数组。

即将发布的MongoDB版本将$filter使$map$setDifference组合更加出色。但是,只要在$addToSet处使用它就不会对这个过程产生重大影响。

考虑到这一点,我想"建议"考虑到你想在这里处理的数据量,得到的"数组"因为每个字母可能可能超过BSON限制,具体取决于多少个不同的单词"实际上有。

在这种情况下,"建议"这里将遵循$match之前的流程,但之后只会$group这样:

  { "$group": {
    "_id": { 
      "alpha": "$list.alpha",
      "type": "$list.type",
      "value": "$list.value",
    }
  }},
  { "$sort": { "_id": 1 } }

当然它的输出时间较长,但在任何阶段都不会超过文件的BSON限制。

答案 1 :(得分:0)

使用聚合,您应该使用一些复杂的聚合查询。首先使用name找出所有substr首字母,然后使用name,type and color使用 $map 创建所有group数组,以检查是否给出名称以 $setDifference 开头,用于删除重复的空参数,最后是用于在新集合中编写文档的 $out

检查此聚合查询:

    db.collection.aggregate({
    "$project": {
        "firstName": {
            "$substr": ["$name", 0, 1]
        },
        "name": 1,
        "type": 1,
        "color": 1
    }
}, {
    "$group": {
        "_id": null,
        "allName": {
            "$push": "$name"
        },
        "allType": {
            "$push": "$type"
        },
        "allColor": {
            "$push": "$color"
        },
        "allfirstName": {
            "$push": "$firstName"
        }
    }
}, {
    "$unwind": "$allfirstName"
}, {
    "$group": {
        "_id": "$allfirstName",
        "allType": {
            "$first": "$allType"
        },
        "allName": {
            "$first": "$allName"
        },
        "allColor": {
            "$first": "$allColor"
        }
    }
}, {
    "$project": {
        "type": {
            "$setDifference": [{
                    "$map": {
                        "input": "$allType",
                        "as": "type",
                        "in": {
                            "$cond": {
                                "if": {
                                    "$eq": [{
                                        "$substr": ["$$type", 0, 1]
                                    }, "$_id"]
                                },
                                "then": "$$type",
                                "else": ""
                            }
                        }
                    }
                },
                [""]
            ]
        },
        "color": {
            "$setDifference": [{
                    "$map": {
                        "input": "$allColor",
                        "as": "color",
                        "in": {
                            "$cond": {
                                "if": {
                                    "$eq": [{
                                        "$substr": ["$$color", 0, 1]
                                    }, "$_id"]
                                },
                                "then": "$$color",
                                "else": ""
                            }
                        }
                    }
                },
                [""]
            ]
        },
        "name": {
            "$setDifference": [{
                    "$map": {
                        "input": "$allName",
                        "as": "name",
                        "in": {
                            "$cond": {
                                "if": {
                                    "$eq": [{
                                        "$substr": ["$$name", 0, 1]
                                    }, "$_id"]
                                },
                                "then": "$$name",
                                "else": ""
                            }
                        }
                    }
                },
                [""]
            ]
        }
    }
}, {
    "$sort": {
        "_id": 1
    }
}, {
    "$out": "newCollection"
})