我有一组这样的文件:
输入
[
{ color: "red", size: "small" },
{ color: "blue", size: "small" },
{ color: "red", size: "medium" },
{ color: "green", size: "medium" },
{ color: "black", size: "large" }
];
我想创建一个由每个键组成的集合,以及每个键的不同值:
输出
[
{ name: "color", values: ["red", "blue", "green", "black"] },
{ name: "size", values: ["small" "medium", "large"] }
]
我不知道输入文档的键是什么。
我知道如何分别解决这两个问题:
我想一次性完成这件事。我认为可以在步骤1中使用聚合框架,并将其导入第2步,但可能不会....
感谢
答案 0 :(得分:1)
正如我在评论中提到的,如果不事先知道字段名称,除非您愿意考虑不同的架构,否则您无法在一次通过中执行此操作。
以下是一个想法,例如使用不同的架构收集相同的数据,但作为键和值:
{ values : [ { "k" : "color", "v" : "red" },
{ "k" : "size", "v" : "small" } ] }
{ values : [ { "k" : "color", "v" : "blue" },
{ "k" : "size", "v" : "small" } ] }
{ values : [ { "k" : "color", "v" : "red" },
{ "k" : "size", "v" : "medium" } ] }
{ values : [ { "k" : "color", "v" : "green" },
{ "k" : "size", "v" : "medium" } ] }
{ values : [ { "k" : "color", "v" : "black" },
{ "k" : "size", "v" : "large" } ] }
汇总是微不足道的,因为它只会对密钥名称进行分组,并使用$addToSet
来收集值。
> db.test.aggregate({ $unwind : '$values' },
{ $group :
{ _id : "$values.k",
value: { $addToSet: "$values.v" } } })
{
"result" : [
{
"_id" : "size",
"value" : [
"large",
"medium",
"small"
]
},
{
"_id" : "color",
"value" : [
"black",
"green",
"blue",
"red"
]
}
],
"ok" : 1
}
答案 1 :(得分:0)
我认为这样做的一种方法完全在mapReduce中:
首先是一个映射器:
var mapper = function () {
for ( var k in this ) {
if ( k != '_id' )
emit( { name: k }, this[k] );
}
};
然后是减速器:
var reducer = function ( key, values ) {
var unique = [];
Array.prototype.inArray = function(value) {
for( var i=0; i < this.length; i++) {
if ( this[i] == value ) return true;
}
return false;
};
Array.prototype.addToSet = function(value) {
if ( this.length == 0 ) {
this.push(value);
} else if ( !this.inArray(value) ) {
this.push(value);
}
};
values.forEach(function(value) {
unique.addToSet(value);
});
return { values: unique };
};
然后运行输出操作:
db.collection.mapReduce(mapper,reducer,{ out: { inline: 1 } })
这给出了“漂亮”的mapReduce样式输出:
{
"results" : [
{
"_id" : {
"name" : "color"
},
"value" : {
"values" : [
"red",
"blue",
"green",
"black"
]
}
},
{
"_id" : {
"name" : "size"
},
"value" : {
"values" : [
"small",
"medium",
"large"
]
}
}
],
"timeMillis" : 2,
"counts" : {
"input" : 5,
"emit" : 10,
"reduce" : 2,
"output" : 2
},
"ok" : 1,
}
只要您可以生成密钥,那么您可以这样构建:
他们列出事情会让事情变得更加困难,但以下内容会出现问题:
db.collection.aggregate([
{ "$group": {
"_id": false,
"size": { "$addToSet": "$size" },
"color": { "$addToSet": "$color" }
}}
])
结果如下:
{
"result" : [
{
"_id" : false,
"size" : [
"large",
"medium",
"small"
],
"color" : [
"black",
"green",
"blue",
"red"
]
}
],
"ok" : 1
}
所以你在一次传递中确实有两个不同的集合。
这样做你如何呈现 是可能的,但只需做更多的工作:
db.collection.aggregate([
// Project with the "name" as an array of possible
{ "$project": {
"size": 1,
"color": 1,
"name": { "$cond": [ 1, [ "size", "color" ], 0 ] }
}},
// Unwind the "name" values. Create duplicates
{ "$unwind": "$name" },
// Conditionally assign the fields to "value"
{ "$project": {
"name": 1,
"value": {"$cond": [
{ "$eq": [ "$name", "size"] },
"$size",
"$color"
]}
}},
// Group the results by name
{ "$group": {
"_id": "$name",
"values": { "$addToSet": "$value" },
}},
// Project the fields you want
{ "$project": {
"_id": 0,
"name": "$_id",
"values": 1
}}
])
这可以为您提供预期的结果。
其中有$cond的“有趣”用法,在将来分配“name”的版本中,应该可以使用$literal运算符替换它。在分配的数组被解开之后,现在有两个的所有内容,但这与后来的$addToSet操作无关。
然后根据匹配的内容有条件地分配“值”。将结果分组到名称上,您有两个按名称键入的文档以及相应的值。
享受。