具有以下数据结构:
"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的主题的所有功能。使用此查询:
db.features.find( { $or: [ { site.subjects.subject_id: 1 }, { mobile.subjects.subject_id:1 } ] } )
如何通过(移动或网站).subjects.time对此类查询进行排序?
最佳答案
关于“排序”问题的一般情况是,需要在其上进行排序的“特定”字段值。通过在创建或更新文档时包含该字段,实际上可以获得最佳性能。您不能单独使用查找来“有条件地排序”。
如果您需要“动态”地进行操作,那么在这种情况下,您希望“投影”符合您条件的内容,为此,您需要聚合框架。
这里有一些陷阱,因为对文档进行操作以达到此目的时,操作过程不像一般的查询逻辑那样宽容。通常,在处理数组时,需要确保在使用它们时没有空内容。根据您的样本数据,一些额外的样本为解决问题提供了指南:
{
"_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")
}
]
}
}
第一个文档是您的基本样本,而另一个文档则出于特定目的而在某些方面有所不同,以展示一些可能的问题,当然不一定表示您自己的数据。
第二个文档故意省略了“ site”键,而第三个文档虽然存在“ site”,但“ subject_id”将不符合要考虑的条件。是的,这是选择文档的
$or
条件,但是我们在这里进一步考虑仅考虑那些也符合条件的“子文档”元素。此处的意思是,要对内容进行排序甚至是“过滤”的内容的“日期”将不考虑其中任何不具有必需的"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
}}
])
这应该从具有最新值的“站点”开始,优先选择“时间”值上的文档。样本中的最后一个文档应首先显示。
现在,如果您实际上是在标题中要求“限制”,我认为这意味着对实际的“匹配”结果进行“过滤”,那么您所做的工作就略有不同:
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] }] }
}},
第一个示例保留原始字段,因为在确定文档的排序方式之后,它们将“按原样”返回。但是,与副本一样,或者仅对匹配结果进行“过滤”,将以某种方式操纵这些结果以“过滤”并确定用于排序的日期。
在不更改现有阵列的情况下,第一个示例相对简单。在这里,您要做的基本上是对文档中的数组进行“排序”,展开后每次一次获取最新的日期。
{ "$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" }
}},
在“过滤”方法中,不仅进行比较以查看所考虑的“日期”是否符合条件,而且实际上会考虑并删除整个“子文档”元素(如果不匹配)。
这里要注意不要完全“销毁”文档,也不要留下空的数组,或者如果该数组中的任何内容都不匹配,则不要删除该文档。这说明了使用
$unwind
然后使用$project
进行比较以及接下来匹配结果的“大小”的过程。这些可以使用
$group
运算符放入$addToSet
中,因为您可以合理地假定结果是不合理的,并且在这种情况下还可以使用$max
来找到最大的“日期”值。这还将任何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
以删除我们的计划字段以进行日期比较。在任何一种情况下,通过比较示例文档得出的结果顺序为“第四”,“第一”,“第二”和“第三”。 “第四”文档在首选“站点”字段上具有最新日期,因此它是最佳结果。 “第一个”样本具有将要选择的下一个最大日期。
“第二”和“第三”实际上选择了相同的日期值,即使两者都没有“站点”字段可能的匹配条目。此处排序的唯一原因实际上只是文档
_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")
}
]
}
}
这里的主要情况是,虽然可以像这样“投影”一个字段进行比较,但是通常最好将其保留在文档中,因为这样您就可以快速进行排序,而无需为每个文档先构建一个开销。
如果确实需要将数组结果“过滤”为与条件匹配的结果,那么您确实会这样做,因为位置
$
运算符可用的投影将不支持与“两个”数组的匹配。无论如何,至少这是使用聚合框架更高级地使用文档“重塑”的示例,并在那里显示了可能性。但是,就像所有复杂的操作一样,这确实需要付出一定的代价,因此,在性能方面,您应该围绕此设计数据。
关于mongodb - MongoDB:对嵌入式文档使用“或”运算符对查询进行排序和限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24531840/