MongoDB 2.2聚合框架按字段名称分组

时间:2012-08-31 13:56:15

标签: mongodb

是否可以分组字段名称?或者我需要一个不同的结构,以便我可以按值分组?

我知道我们可以使用group by on值并且我们可以展开数组,但是有可能在这里的三个房子中获得John拥有的苹果,梨和橘子总数没有指定&#34 ;苹果","梨子"和"橘子"明确地作为查询的一部分? (所以不喜欢这样);

// total all the fruit John has at each house
db.houses.aggregate([
    {
        $group: {
            _id: null,

            "apples":  { $sum: "$people.John.items.apples" },
            "pears":   { $sum: "$people.John.items.pears" }, 
            "oranges": { $sum: "$people.John.items.oranges" }, 
        }
    },
])

换句话说,我可以按照"项目"下的第一个字段名称进行分组。并获得苹果的总和:104,梨:202和橙子:306,还有香蕉,甜瓜和其他可能存在的东西?或者我是否需要将数据重组为一组键/值对,如类别?

db.createCollection("houses");
db.houses.remove();
db.houses.insert(
[
    {
        House: "birmingham",
        categories : [
            {
                k : "location",
                v : { d : "central" }
            }
        ],
        people: {
            John: {
                items: {
                    apples: 2,
                    pears: 1,
                    oranges: 3,
                }
            },
            Dave: {
                items: {
                    apples: 30,
                    pears: 20,
                    oranges: 10,
                },
            },
        },
    },
    {
        House: "London", categories: [{ k: "location", v: { d: "central" } }, { k: "type", v: { d: "rented" } }],
        people: {
            John: { items: { apples: 2, pears: 1, oranges: 3, } },
            Dave: { items: { apples: 30, pears: 20, oranges: 10, }, },
        },
    },
    {
        House: "Cambridge", categories: [{ k: "type", v: { d: "rented" } }],
        people: {
            John: { items: { apples: 100, pears: 200, oranges: 300, } },
            Dave: { items: { apples: 0.3, pears: 0.2, oranges: 0.1, }, },
        },
    },
]
);

其次,更重要的是,我是否可以按照" house.categories.k" ?换句话说,是否有可能找出有多少"苹果" "约翰"在"租用" vs"拥有"或者"朋友"房屋(所以分组为" categories.k.type")?

最后 - 如果这是可能的话,这是明智的吗?起初我认为使用对象的实际字段名创建嵌套对象的字典是非常有用的,因为它似乎是文档数据库的逻辑用法,它似乎使MR查询更容易编写与数组,但现在我和#39;我开始怀疑这是不是一个坏主意,并且具有可变字段名称使得编写聚合查询非常棘手/低效。

3 个答案:

答案 0 :(得分:3)

好的,所以我想我已经部​​分解决了这个问题。至少对于初始问题中的数据形状。

// How many of each type of fruit does John have at each location
db.houses.aggregate([
    {
        $unwind: "$categories"
    },
    {
        $match: { "categories.k": "location" }
    },
    {
        $group: {
            _id: "$categories.v.d",
            "numberOf": { $sum: 1 },
            "Total Apples": { $sum: "$people.John.items.apples" },
            "Total  Pears": { $sum: "$people.John.items.pears" },
        }
    },
])

产生;

{
        "result" : [
                {
                        "_id" : "central",
                        "numberOf" : 2,
                        "Total Apples" : 4,
                        "Total  Pears" : 2
                }
        ],
        "ok" : 1
}

请注意,只有“中央”,但如果我的数据库中有其他“位置”,我会获得每个位置的总计范围。如果我为“类别”命名属性而不是数组,我就不需要$ unwind步骤,但这是我发现结构与自身不一致的地方。 “类别”下可能有几个关键字。样本数据显示“类型”和“位置”,但可能有大约10个这些分类都具有不同的值。所以,如果我使用命名字段;

"categories": {
  location: "london",
  type: "owned",
}

...我接下来的问题是索引。我不能简单地索引“位置”,因为这些是用户定义的类别,如果10,000个用户选择10,000种不同的方法来分类他们的房子,我需要10,000个索引,每个字段一个。但是通过使它成为一个数组我只需要一个数组字段本身。缺点是$ unwind步骤。我之前遇到过MapReduce。你要做的最后一件事是在JavaScript中使用ForEach循环来循环数组,如果你能帮助它的话。你真正想要的是按名称过滤掉字段,因为它更快。

现在这一切都很好,我已经知道我正在寻找什么水果,但如果我不知道,那就更难了。我不能(据我所知)$放松或其他ForEach“people.John.items”在这里。如果可以的话,我会高兴极了。因为水果的名称再次由用户定义,看起来我需要将它们转换为数组,就像这样;

{
    "people" : {
        "John" : {
            "items" : [
                { k:"apples", v:100 },
                { k:"pears", v:200 },
                { k:"oranges", v:300 },
            ]
        },
    }
}

所以现在让我得到了水果(我不知道要找哪种水果)总计,再次按位置;

db.houses.aggregate([
    {
        $unwind: "$categories"
    },
    {
        $match: { "categories.k": "location" }
    },
    {
        $unwind: "$people.John.items" 
    },
    {
        $group: { // compound key - thanks to Jenna
            _id: { fruit:"$people.John.items.k", location:"$categories.v.v" },
            "numberOf": { $sum: 1 },
            "Total Fruit": { $sum: "$people.John.items.v" },
        }
    },
])

所以现在我正在做两个$ unwinds。如果你认为看起来非常低效,你就是对的。如果我只有10,000条房屋记录,每条记录有10个类别,还有10种类型的水果,则此查询需要半分钟才能运行。 好的,所以我可以看到在$ unwind之前移动$ match会显着改善,但是输出错误。我不想要每个类别的条目,我想过滤掉“位置”类别。

答案 1 :(得分:2)

我会发表此评论,但在响应文本框中进行格式化会更容易。

{ _id: 1,
  house: "New York",
  people: {
      John: {
          items: {apples: 1, oranges:2}
      }
      Dave: {
          items: {apples: 2, oranges: 1}
      }
  }
}

{ _id: 2,
      house: "London",
      people: {
          John: {
              items: {apples: 3, oranges:2}
          }
          Dave: {
              items: {apples: 1, oranges:3}
          }
      }
}

为了确保我理解你的问题,这是你想要完成的吗?

{location: "New York", johnFruit:3}
{location: "London", johnFruit: 5}

由于类别不是嵌套在house下,你不能按“house.categories.k”进行分组,但是你可以使用复合键作为$ group的_id来获得这个结果:

{ $group: _id: {house: "$House", category: "$categories.k"} 

虽然“k”不包含您可能尝试分组的信息。至于“categories.k.type”,type是k的值,因此您不能使用此语法。你必须按“categories.v.d”进行分组。

使用$ unwind,$ project,可能是$ match,最后是$ group,使用当前架构可以实现此聚合,但命令不会很好。如果可能的话,我强烈建议重组您的数据,以使这种聚合更加简单。如果您想了解架构方面的帮助,请告诉我们。

答案 2 :(得分:0)

我不确定这是否是一种可能的解决方案,但如果您通过使用distinct()确定不同位置的数量来开始聚合过程,并为每个位置运行单独的聚合命令,该怎么办? distinct()可能效率不高,但每个后续聚合都可以使用$ match,因此也可以使用类别索引。您可以使用相同的逻辑来计算“categories.type”的水果。

{
    "_id" : 1,
    "house" : "New York",
    "people" : {
        "John" : [{"k" : "apples","v" : 1},{"k" : "oranges","v" : 2}],
        "Dave" : [{"k" : "apples","v" : 2},{"k" : "oranges","v" : 1}]
    },
    "categories" : [{"location" : "central"},{"type" : "rented"}]
}
{
    "_id" : 2,
    "house" : "London",
    "people" : {
        "John" : [{"k" : "apples","v" : 3},{"k" : "oranges","v" : 2}],
        "Dave" : [{"k" : "apples","v" : 3},{"k" : "oranges","v" : 1}]
    },
    "categories" : [{"location" : "suburb"},{"type" : "rented"}]
}
{
    "_id" : 3,
    "house" : "London",
    "people" : {
        "John" : [{"k" : "apples","v" : 0},{"k" : "oranges","v" : 1}],
        "Dave" : [{"k" : "apples","v" : 2},{"k" : "oranges","v" : 4}]
    },
    "categories" : [{"location" : "central"},{"type" : "rented"}]
}

运行distinct()并通过对“categories.location”的每个唯一值运行aggregate()命令来遍历结果:

db.agg.distinct("categories.location")
[ "central", "suburb" ]

db.agg.aggregate(
    {$match: {categories: {location:"central"}}}, //the index entry is on the entire 
    {$unwind: "$people.John"},                    //document {location:"central"}, so 
    {$group:{                                     //use this syntax to use the index
         _id:"$people.John.k", 
         "numberOf": { $sum: 1 },
         "Total Fruit": { $sum: "$people.John.v"}
        }
     }
 )


{
    "result" : [
        {
            "_id" : "oranges",
            "numberOf" : 2,
            "Total Fruit" : 3
        },
        {
            "_id" : "apples",
            "numberOf" : 2,
            "Total Fruit" : 1
        }
    ],
    "ok" : 1
}