我正在尝试对样本bios集合http://docs.mongodb.org/manual/reference/bios-example-collection/进行查询:
在获得图灵奖之前,先检索他们收到的所有人员及其奖励。
我想出了这个查询:
db.bios.aggregate([
{$match: {"awards.award" : "Turing Award"}},
{$project: {"award1": "$awards", "award2": "$awards", "first_name": "$name.first", "last_name": "$name.last"}},
{$unwind: "$award1"},
{$match: {"award1.award" : "Turing Award"}},
{$unwind: "$award2"},
{$redact: {
$cond: {
if: { $eq: [ { $gt: [ "$award1.year", "$award2.year"] }, true]},
then: "$$KEEP",
else: "$$PRUNE"
}
}
}
])
这就是答案:
/* 0 */
{
"result" : [
{
"_id" : 1,
"award1" : {
"award" : "Turing Award",
"year" : 1977,
"by" : "ACM"
},
"award2" : {
"award" : "W.W. McDowell Award",
"year" : 1967,
"by" : "IEEE Computer Society"
},
"first_name" : "John",
"last_name" : "Backus"
},
{
"_id" : 1,
"award1" : {
"award" : "Turing Award",
"year" : 1977,
"by" : "ACM"
},
"award2" : {
"award" : "National Medal of Science",
"year" : 1975,
"by" : "National Science Foundation"
},
"first_name" : "John",
"last_name" : "Backus"
},
{
"_id" : 4,
"award1" : {
"award" : "Turing Award",
"year" : 2001,
"by" : "ACM"
},
"award2" : {
"award" : "Rosing Prize",
"year" : 1999,
"by" : "Norwegian Data Association"
},
"first_name" : "Kristen",
"last_name" : "Nygaard"
},
{
"_id" : 5,
"award1" : {
"award" : "Turing Award",
"year" : 2001,
"by" : "ACM"
},
"award2" : {
"award" : "Rosing Prize",
"year" : 1999,
"by" : "Norwegian Data Association"
},
"first_name" : "Ole-Johan",
"last_name" : "Dahl"
}
],
"ok" : 1
}
我对这种解决方案不满意的是我放松一下$award2
。相反,我很乐意将award2保留为一个数组,并且只删除那些在award1之后收到的奖项。因此,例如,John Backus的答案应该是:
{
"_id" : 1,
"first_name" : "John",
"last_name" : "Backus",
"award1" : {
"award" : "Turing Award",
"year" : 1977,
"by" : "ACM"
},
"award2" : [
{
"award" : "W.W. McDowell Award",
"year" : 1967,
"by" : "IEEE Computer Society"
},
{
"award" : "National Medal of Science",
"year" : 1975,
"by" : "National Science Foundation"
}
]
}
$redact
不用做就能实现$unwind: "$award2"
吗?
如果您在问题中包含文档的原始状态作为示例,可能会有所帮助,因为这清楚地显示了“您来自何处”然后到达“您要去哪里”作为目标。除了给定的所需输出。
这只是一个提示,但看来您是从这样的文档开始的:
{
"_id" : 1,
"name": {
"first" : "John",
"last" : "Backus"
},
"awards" : [
{
"award" : "W.W. McDowell Award",
"year" : 1967,
"by" : "IEEE Computer Society"
},
{
"award" : "National Medal of Science",
"year" : 1975,
"by" : "National Science Foundation"
},
{
"award" : "Turing Award",
"year" : 1977,
"by" : "ACM"
},
{
"award" : "Some other award",
"year" : 1979,
"by" : "Someone Else"
}
]
}
因此,这里的重点是,尽管您可能已经达到了$redact
这里(这比$project
用于逻辑条件然后再$match
用于过滤该逻辑匹配要好一些),但这可能不是您想要进行比较的最佳工具在这里做。
在继续之前,我只想指出的主要问题$redact
。无论您在这里做什么,逻辑(不放松)本质上都是比较“直接” $$DESCEND
,以便在任何级别上以“ year”的值处理数组元素。
由于它具有相同的字段名称,因此该递归也将使“ award1”条件无效。即使重命名该字段也会破坏逻辑,因为丢失该字段的预计值不会大于测试值。
简而言之,$redact
将其排除在外是因为您不能使用适用的逻辑说“仅从此处取走”。
另一种方法是使用$map
和$setDifference
过滤数组中的内容,如下所示:
db.bios.aggregate([
{ "$match": { "awards.award": "Turing Award" } },
{ "$project": {
"first_name": "$name.first",
"last_name": "$name.last",
"award1": { "$setDifference": [
{ "$map": {
"input": "$awards",
"as": "a",
"in": { "$cond": [
{ "$eq": [ "$$a.award", "Turing Award" ] },
"$$a",
false
]}
}},
[false]
]},
"award2": { "$setDifference": [
{ "$map": {
"input": "$awards",
"as": "a",
"in": { "$cond": [
{ "$ne": [ "$$a.award", "Turing Award" ] },
"$$a",
false
]}
}},
[false]
]}
}},
{ "$unwind": "$award1" },
{ "$project": {
"first_name": 1,
"last_name": 1,
"award1": 1,
"award2": { "$setDifference": [
{ "$map": {
"input": "$award2",
"as": "a",
"in": { "$cond": [
{ "$gt": [ "$award1.year", "$$a.year" ] },
"$$a",
false
]}
}},
[false]
]}
}}
])
实际上,没有任何“漂亮”的方法可以解决$unwind
在迭代阶段甚至$project
此处的第二个阶段的使用,因为$map
(和$setDifference
filter)返回的是“仍然是数组”。因此,$unwind
有必要使“数组”成为一个单数(如果您的条件仅匹配1个元素)条目,以供比较使用。
试图“压缩”单个逻辑$project
只会在第二个输出中导致“数组数组”,因此仍然需要一些“展开”,但是至少以这种方式展开(希望)1个匹配并不是真的成本高昂,并保持输出清洁。
但是这里要真正注意的另一件事是,您根本没有真正在“聚合”任何东西。这只是文档操作,因此您不妨考虑直接在客户端代码中进行该操作。如以下示例所示:
db.bios.find(
{ "awards.award": "Turing Award" },
{ "name": 1, "awards": 1 }
).forEach(function(doc) {
doc.first_name = doc.name.first;
doc.last_name = doc.name.last;
doc.award1 = doc.awards.filter(function(award) {
return award.award == "Turing Award"
})[0];
doc.award2 = doc.awards.filter(function(award) {
return doc.award1.year > award.year;
});
delete doc.name;
delete doc.awards;
printjson(doc);
})
无论如何,两种方法都将输出相同的结果:
{
"_id" : 1,
"first_name" : "John",
"last_name" : "Backus",
"award1" : {
"award" : "Turing Award",
"year" : 1977,
"by" : "ACM"
},
"award2" : [
{
"award" : "W.W. McDowell Award",
"year" : 1967,
"by" : "IEEE Computer Society"
},
{
"award" : "National Medal of Science",
"year" : 1975,
"by" : "National Science Foundation"
}
]
}
唯一真正的区别是,使用.aggregate()
“ award2”的内容从服务器返回时已经对其进行了过滤,这与客户端处理方法可能没有太大不同,除非要删除的项目包括每个文档的列表相当大。
作为记录,这里真正需要的对现有聚合管道的唯一更改是$group
在末尾添加a ,以将数组条目“重新组合”到单个文档中:
db.bios.aggregate([
{ "$match": { "awards.award": "Turing Award" } },
{ "$project": {
"first_name": "$name.first",
"last_name": "$name.last",
"award1": "$awards",
"award2": "$awards"
}},
{ "$unwind": "$award1" },
{ "$match": {"award1.award" : "Turing Award" }},
{ "$unwind": "$award2" },
{ "$redact": {
"$cond": {
"if": { "$gt": [ "$award1.year", "$award2.year"] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$group": {
"_id": "$_id",
"first_name": { "$first": "$first_name" },
"last_name": { "$first": "$last_name" },
"award1": { "$first": "$award1" },
"award2": { "$push": "$award2" }
}}
])
但是话又说回来,这里所有的操作都与“数组复制”和“展开成本”有关。因此,为了避免这种情况,前两种方法中的任何一种都是您真正想要的。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句