具有以下数据结构:
"feature": {
"site": {
"subjects": [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
],
},
"mobile": {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
我希望进行查询以获取嵌入了ID为1的主题的所有功能,无论是在移动设备中还是#39;或者'网站'。有了这个查询:
db.features.find( { $or: [ { site.subjects.subject_id: 1 }, { mobile.subjects.subject_id:1 } ] } )
如何按(移动或网站).subjects.time?
对这样的查询进行排序答案 0 :(得分:3)
您的"排序"的一般情况问题是需要有一个特定的"要排序的字段值。通过在文档中包含该字段作为创建或更新,可以获得最佳性能。你不能有条件地排序"单独使用查找。
如果你需要动态地做""然后你正在寻找"项目"在这种情况下,符合您条件的东西,为此您需要聚合框架。
这里存在一些陷阱,因为在对文档进行操作时,操作过程并不像通用查询逻辑那样宽容。大多数情况下,在处理数组时,您需要确保在使用数组时没有空内容。基于样本数据的一些额外样本提供了解决问题的指南:
{
"_id" : ObjectId("53b49853c1a7b867c4541482"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b4cf58c4e3a228da24c225"),
"site" : {
"subjects" : [
{
"subject_id" : 2,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b4d03bc4e3a228da24c227"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T18:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T04:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
第一份文件是您的基本样本,但另一种文件在某些方面有所不同,用于特定目的,以展示一些可能的问题,当然不一定表示您自己的数据。
第二份文件故意省略"网站"关键和第三,虽然"网站"存在," subject_id"不符合考虑的条件。是的,这是文件选择的$or
条件,但我们在这里进一步考虑这些"子文档"符合标准的要素。这意味着" date"排序甚至过滤"内容不会考虑那些没有所需"subject_id": 1
的项目。
首先看一下创建一个可以根据条件排序的值:
db.features.aggregate([
{ "$match": {
"$or": [
{ "site.subjects.subject_id": 1 },
{ "mobile.subjects.subject_id": 1 }
]
}},
{ "$project": {
"site": 1,
"mobile": 1,
"scopy": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
"mcopy": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
}},
{ "$unwind": "$scopy" },
{ "$project": {
"site": 1,
"mobile": 1,
"scopy": {
"$cond": [
{ "$eq": [ "$scopy.subject_id", 1 ] },
"$scopy.time",
false
]
},
"mcopy": 1
}},
{ "$sort": { "_id": 1, "scopy": -1 } },
{ "$group": {
"_id": "$_id",
"site": { "$first": "$site" },
"mobile": { "$first": "$mobile" },
"mcopy": { "$first": "$mcopy" },
"scopy": { "$first": "$scopy" }
}},
{ "$unwind": "$mcopy" },
{ "$project": {
"site": 1,
"mobile": 1,
"scopy": 1,
"mcopy": {
"$cond": [
{ "$eq": [ "$mcopy.subject_id", 1 ] },
"$mcopy.time",
false
]
}
}},
{ "$sort": { "_id": 1, "mcopy": -1 } },
{ "$group": {
"_id": "$_id",
"site": { "$first": "$site" },
"mobile": { "$first": "$mobile" },
"mcopy": { "$first": "$mcopy" },
"scopy": { "$first": "$scopy" }
}},
{ "$project": {
"site": {
"$ifNull": [
"$site",
{ "$const": { "subjects": [] } }
]
},
"mobile": {
"$ifNull": [
"$mobile",
{ "$const": { "subjects": [] } }
]
},
"best": {
"$cond": [
{ "$gt": [ "$mcopy", "$scopy" ] },
"$mcopy",
"$scopy"
]
}
}},
{ "$sort": { "best": -1 } },
{ "$project": {
"site": 1,
"mobile": 1
}}
])
那应该按照" time"的顺序排序文件。价值来自"网站"哪一个具有最新价值。样本中的最后一个文档应首先出现。
现在,如果你真的要求"限制"就像你的标题一样,我认为这意味着"过滤"实际的#34;匹配"结果,然后你做的事情有点不同:
db.features.aggregate([
{ "$match": {
"$or": [
{ "site.subjects.subject_id": 1 },
{ "mobile.subjects.subject_id": 1 }
]
}},
{ "$project": {
"wsite": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
"wmobile": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
}},
{ "$unwind": "$wsite" },
{ "$project": {
"wsite": {
"$cond": [
{ "$eq": [ "$wsite.subject_id", 1 ] },
"$wsite",
false
]
},
"wmobile": 1
}},
{ "$group": {
"_id": "$_id",
"wsite": { "$addToSet": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"msite": { "$max": "$wsite.time" },
"csite": { "$sum": 1 }
}},
{ "$unwind": "$wsite" },
{ "$match": {
"$or": [
{ "wsite": { "$ne": false } },
{ "csite": 1 }
]
}},
{ "$group": {
"_id": "$_id",
"wsite": { "$push": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"msite": { "$first": "$msite" }
}},
{ "$unwind": "$wmobile" },
{ "$project": {
"wsite": 1,
"wmobile": {
"$cond": [
{ "$eq": [ "$wmobile.subject_id", 1 ] },
"$wmobile",
false
]
},
"msite": 1,
}},
{ "$group": {
"_id": "$_id",
"wsite": { "$first": "$wsite" },
"wmobile": { "$addToSet": "$wmobile" },
"msite": { "$first": "$msite" },
"mmobile": { "$max": "$wmobile.time" },
"cmobile": { "$sum": 1 }
}},
{ "$unwind": "$wmobile" },
{ "$match": {
"$or": [
{ "wmobile": { "$ne": false } },
{ "cmobile": 1 }
]
}},
{ "$group": {
"_id": "$_id",
"wsite": { "$first": "$wsite" },
"wmobile": { "$push": "$wmobile" },
"msite": { "$first": "$msite" },
"mmobile": { "$first": "$mmobile" }
}},
{ "$project": {
"site": {
"subjects": {
"$cond": [
{ "$eq": [ "$wsite", { "$const": [false] } ] },
{ "$const": [] },
"$wsite"
]
}
},
"mobile": {
"subjects": {
"$cond": [
{ "$eq": [ "$wmobile", { "$const": [false] } ] },
{ "$const": [] },
"$wmobile"
]
}
},
"best": {
"$cond": [
{ "$gt": [ "$mmobile", "$msite" ] },
"$mmobile",
"$msite"
]
}
}},
{ "$sort": { "best": -1 } },
{ "$project": {
"site": 1,
"mobile": 1
}}
])
使用MongoDB 2.6的功能更加清晰,其中大多数阵列过滤可以在一个阶段内完成:
db.features.aggregate([
{ "$match": {
"$or": [
{ "site.subjects.subject_id": 1 },
{ "mobile.subjects.subject_id": 1 }
]
}},
{ "$project": {
"wsite": {
"$let": {
"vars": {
"list": { "$setDifference": [
{
"$map": {
"input": {
"$ifNull": [
"$site.subjects",
{ "$literal": [false] }
]
},
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.subject_id", 1 ] },
"$$el",
false
]
}
}
},
[false]
]}
},
"in": {
"$cond": [
{ "$eq": [{ "$size": "$$list" }, 0 ] },
{ "$literal": [false] },
"$$list"
]
}
}
},
"wmobile": {
"$let": {
"vars": {
"list": { "$setDifference": [
{
"$map": {
"input": {
"$ifNull": [
"$mobile.subjects",
{ "$literal": [false] }
]
},
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.subject_id", 1 ] },
"$$el",
false
]
}
}
},
[false]
]}
},
"in": {
"$cond": [
{ "$eq": [{ "$size": "$$list" }, 0 ] },
{ "$literal": [false] },
"$$list"
]
}
}
}
}},
{ "$unwind": "$wsite" },
{ "$group": {
"_id": "$_id",
"wsite": { "$push": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"fsite": { "$max": "$wsite.time" }
}},
{ "$unwind": "$wmobile" },
{ "$group": {
"_id": "$_id",
"wsite": { "$first": "$wsite" },
"wmobile": { "$push": "$wmobile" },
"fsite": { "$first": "$fsite" },
"fmobile": { "$max": "$wmobile.time" }
}},
{ "$project": {
"site": {
"subjects": {
"$cond": [
{ "$allElementsTrue": "$wsite" },
"$wsite",
{ "$literal": [] }
]
}
},
"mobile": {
"subjects": {
"$cond": [
{ "$allElementsTrue": "$wmobile" },
"$wmobile",
{ "$literal": [] }
]
}
},
"best": {
"$cond": [
{ "$gt": [ "$fmobile", "$fsite" ] },
"$fmobile",
"$fsite"
]
}
}},
{ "$sort": { "best": -1 } },
{ "$project": {
"site": 1,
"mobile": 1
}}
])
这些陈述中要考虑的主要内容属于数组处理。这里的各种操作需要"如果不存在实际数组,则输入数组将失败。更糟糕的情况是$unwind
,如果出现完全"空"数组,将完全从管道中删除该文档,因为它认为没有任何内容可以"展开"。
主要"柜台"这是$ifNull
。这基本上测试了"存在"一个字段,并返回它或替代结果,这是第二个参数。每个案例都使用它来返回一个包含单个元素[false]
的数组,这意味着任何后续的$unwind
不仅不会爆炸"由于缺少一个数组的字段,但也不认为当前文档是空的,因此将其删除。
{ "$project": {
"site": 1,
"mobile": 1,
"scopy": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
"mcopy": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
}},
第一个样本保留原始字段,因为它只是返回它们"按原样#34;在弄清楚文件将如何分类之后。但与副本一样,或者以其他方式过滤"只有匹配的结果,这些将以某种方式被操纵到"过滤"并确定用于排序的日期。
在不更改现有阵列的情况下,第一个示例相对简单。在这里你想要做的基本上是"排序"文档中的数组,在您展开后一次一个,以获得最新的日期。
{ "$unwind": "$mcopy" },
{ "$project": {
"site": 1,
"mobile": 1,
"scopy": 1,
"mcopy": {
"$cond": [
{ "$eq": [ "$mcopy.subject_id", 1 ] },
"$mcopy.time",
false
]
}
}},
{ "$sort": { "_id": 1, "mcopy": -1 } },
在此版本中进行的额外操作是确保将考虑的日期来自"子文档"符合标准。如果没有,那么日期将替换为false
,它将被排序到列表的底部。
$group
然后使用$first
运算符在排序后选取最新的项目。现在为每个数组执行该过程会提供两个比较日期,以便您可以决定在最后对哪一个进行排序。
{ "$group": {
"_id": "$_id",
"site": { "$first": "$site" },
"mobile": { "$first": "$mobile" },
"mcopy": { "$first": "$mcopy" },
"scopy": { "$first": "$scopy" }
}},
在"过滤"方法,不仅是进行比较,看看" date"被认为是符合标准,但事实上整个"子文件"如果元素不匹配,则会考虑并删除它。
这里要注意不要"销毁"如果该数组中的任何内容都不匹配,则文档完全不会留下空数组或以其他方式删除文档。这解释了使用$unwind
然后使用$project
进行比较和"尺寸"接下来的匹配结果。
这些是$group
使用$addToSet
运算符的放置,因为您可以合理地假设结果是unqiue,并且在这种情况下也使用$max
找到最大的&#34} ;日期"值。这也会将任何false
值压缩为单个条目。
{ "$group": {
"_id": "$_id",
"wsite": { "$addToSet": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"msite": { "$max": "$wsite.time" },
"csite": { "$sum": 1 }
}},
只有这样,您才能$unwind
再次安全地使用$match
过滤掉false
的所有内容。如果在该数组中实际上只有false
的单个值,则此处需要注意不要删除该文档。决赛"分组"现在应该有过滤结果或每个数组下只有false
的单个值。
{ "$unwind": "$wsite" },
{ "$match": {
"$or": [
{ "wsite": { "$ne": false } },
{ "csite": 1 }
]
}},
{ "$group": {
"_id": "$_id",
"wsite": { "$push": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"msite": { "$first": "$msite" }
}},
在最终列表中,我们正在利用可以使用MongoDB 2.6中实现的功能完成的新功能。
上一个列表中的各种管道阶段是"组合"因为新的$map
运算符允许一些数组处理而不使用$unwind
。基本上对匹配条件进行相同的评估,并且返回的false
值被"过滤掉"将$setDifference
与仅包含[false]
的数组进行比较。
任何"空"然后使用$size
运算符测试不包含匹配的数组,其中empty将返回0
的大小。这里的条件只是用以前的单[false]
替换那些空数组。
最后一部分的原因是您仍然需要$unwind
才能获得最大或$max
"日期"每个数组的值。
{ "$unwind": "$wsite" },
{ "$group": {
"_id": "$_id",
"wsite": { "$push": "$wsite" },
"wmobile": { "$first": "$wmobile" },
"fsite": { "$max": "$wsite.time" }
}},
从这里开始编码的不同方法大多相似。现在你要从每个数组中比较日期,你只需要确定哪一个是最新的或其他逻辑比较:
"best": {
"$cond": [
{ "$gt": [ "$fmobile", "$fsite" ] },
"$fmobile",
"$fsite"
]
}
}},
{ "$sort": { "best": -1 } },
{ "$project": {
"site": 1,
"mobile": 1
}}
结果日期值用于 $sort
最终结果,然后传递给 $project
以删除我们的预计字段日期比较。
在任何一种情况下,通过与样本文件进行比较的结果顺序是"第四","第一","第二"和"第三"。 "第四"文档包含首选"网站上的最新日期"这是最好的结果。 "第一"样本具有将被选择的下一个最大日期。
" Second"和"第三"实际上选择相同的日期值,即使两者都没有可能的匹配条目#34; site"领域。这里订单的唯一原因实际上只是文档_id
值,这是文档进入管道的方式。
没有"过滤"输出实际上是数组:
{
"_id" : ObjectId("53b4d03bc4e3a228da24c227"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T18:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T04:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b4cf58c4e3a228da24c225"),
"site" : {
"subjects" : [
{
"subject_id" : 2,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
"site" : {
"subjects" : [ ]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
{
"_id" : ObjectId("53b49853c1a7b867c4541482"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
},
{
"subject_id" : 2,
"time" : ISODate("2014-06-24T23:44:29.759Z")
}
]
}
}
并通过过滤:
{
"_id" : ObjectId("53b4d03bc4e3a228da24c227"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T18:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T04:14:29.758Z")
}
]
}
}
{
"_id" : ObjectId("53b49853c1a7b867c4541482"),
"site" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T06:38:29.751Z")
}
]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
}
]
}
}
{
"_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
"site" : {
"subjects" : [ ]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
}
]
}
}
{
"_id" : ObjectId("53b4cf58c4e3a228da24c225"),
"site" : {
"subjects" : [ ]
},
"mobile" : {
"subjects" : [
{
"subject_id" : 1,
"time" : ISODate("2014-06-28T16:14:29.758Z")
}
]
}
}
这里的主要案例是,虽然可以"项目"像这样的字段用于比较通常最好将它保存在您的文档中,因为您可以快速排序,而无需为每个文档构建第一个文档。
如果确实需要"过滤"数组会产生符合条件的数据,那么你确实会这样做,因为位置$
运算符可用的投影不支持与" 2"阵列。
无论如何,至少这可以作为更高级的文档使用的样本"重塑"使用聚合框架并显示其中的可能性。但是,与所有复杂的操作一样,这确实需要付出代价,因此在性能方面,您应该围绕这一点设计数据。