我知道这个问题已经被问过很多次了,但我对 mongo 和 mongoose 还是个新手,我想不通!
我的问题:
我有一个看起来像这样的:
var rankingSchema = new Schema({
userId : { type : Schema.Types.ObjectId, ref:'User' },
pontos : {type: Number, default:0},
placarExato : {type: Number, default:0},
golVencedor : {type: Number, default:0},
golPerdedor : {type: Number, default:0},
diferencaVencPerd : {type: Number, default:0},
empateNaoExato : {type: Number, default:0},
timeVencedor : {type: Number, default:0},
resumo : [{
partida : { type : Schema.Types.ObjectId, ref:'Partida' },
palpite : [Number],
quesito : String
}]
});
这将返回这样的文档:
{
"_id" : ObjectId("539d0756f0ccd69ac5dd61fa"),
"diferencaVencPerd" : 0,
"empateNaoExato" : 0,
"golPerdedor" : 0,
"golVencedor" : 1,
"placarExato" : 2,
"pontos" : 78,
"resumo" : [
{
"partida" : ObjectId("5387d991d69197902ae27586"),
"_id" : ObjectId("539d07eb06b1e60000c19c18"),
"palpite" : [
2,
0
]
},
{
"partida" : ObjectId("5387da7b27f54fb425502918"),
"quesito" : "golsVencedor",
"_id" : ObjectId("539d07eb06b1e60000c19c1a"),
"palpite" : [
3,
0
]
},
{
"partida" : ObjectId("5387dc012752ff402a0a7882"),
"quesito" : "timeVencedor",
"_id" : ObjectId("539d07eb06b1e60000c19c1c"),
"palpite" : [
2,
1
]
},
{
"partida" : ObjectId("5387dc112752ff402a0a7883"),
"_id" : ObjectId("539d07eb06b1e60000c19c1e"),
"palpite" : [
1,
1
]
},
{
"partida" : ObjectId("53880ea52752ff402a0a7886"),
"quesito" : "placarExato",
"_id" : ObjectId("539d07eb06b1e60000c19c20"),
"palpite" : [
1,
2
]
},
{
"partida" : ObjectId("53880eae2752ff402a0a7887"),
"quesito" : "placarExato",
"_id" : ObjectId("539d0aa82fb219000054c84f"),
"palpite" : [
2,
1
]
}
],
"timeVencedor" : 1,
"userId" : ObjectId("539b2f2930de100000d7356c")
}
我的问题是,首先:如何通过 quesito 过滤 resumo 嵌套文档?是否可以对这个结果进行分页,因为这个数组会增加。最后一个问题,这是处理这种情况的好方法吗?
谢谢大家!
最佳答案
如前所述,您的架构意味着您实际上拥有嵌入式数据,即使您正在存储外部引用。因此,不清楚您是同时进行嵌入和引用,还是简单地自行嵌入。
这里最大的警告是匹配“文档”和实际过滤数组内容之间的区别。由于您似乎在谈论“分页”您的数组结果,因此这里的重点是这样做,但仍然提到警告。
数组中的多个“过滤”匹配项需要聚合框架。您通常可以“转换”数组元素的单个匹配项,但在您期望多个元素的情况下需要这样做:
Ranking.aggregate(
[
// This match finds "documents" that "contain" the match
{ "$match": { "resumo.quesito": "value" } },
// Unwind de-normalizes arrays as documents
{ "$unwind": "$resumo" },
// This match actually filters those document matches
{ "$match": { "resumo.quesito": "value" } },
// Skip and limit for paging, which really only makes sense on single
// document matches
{ "$skip": 0 },
{ "$limit": 2 },
// Return as an array in the original document if you really want
{ "$group": {
"_id": "$_id",
"otherField": { "$first": "$otherField" },
"resumo": { "$push": "$resumo" }
}}
],
function(err,results) {
}
)
或者通过在 $project
中“过滤”的 MongoDB 2.6 方式使用 $map
运算符(operator)。但你仍然需要 $unwind
为了“分页”数组位置,但由于数组首先被“过滤”,因此处理可能较少:
Ranking.aggregate(
[
// This match finds "documents" that "contain" the match
{ "$match": { "resumo.quesito": "value" } },
// Filter with $map
{ "$project": {
"otherField": 1,
"resumo": {
"$setDifference": [
{
"$map": {
"input": "$resumo",
"as": "el",
"in": { "$eq": ["$$el.questio", "value" ] }
}
},
[false]
]
}
}},
// Unwind de-normalizes arrays as documents
{ "$unwind": "$resumo" },
// Skip and limit for paging, which really only makes sense on single
// document matches
{ "$skip": 0 },
{ "$limit": 2 },
// Return as an array in the original document if you really want
{ "$group": {
"_id": "$_id",
"otherField": { "$first": "$otherField" },
"resumo": { "$push": "$resumo" }
}}
],
function(err,results) {
}
)
$skip
的内部用法和 $limit
只有当您处理单个文档并且只是“过滤”和“分页”数组时,这里才真正有意义。可以对多个文档执行此操作,但是非常复杂,因为没有办法只对数组进行“切片”。这将我们带到下一点。
对于嵌入式数组,对于不需要任何过滤的分页,您只需使用 $slice
运算符,专为此目的而设计:
Ranking.find({},{ "resumo": { "$slice": [0,2] } },function(err,docs) {
});
您的替代方法是简单地引用外部集合中的文档,然后将参数传递给 mongoose .populate()
过滤和“分页”结果。模式本身的变化只是:
"resumo": [{ "type": "Schema.Types.ObjectId", "ref": "Partida" }]
现在,外部引用集合保存了对象的详细信息,而不是直接嵌入到数组中。 .populate()
的使用过滤和分页是:
Ranking.find().populate({
"path": "resumo",
"match": { "questio": "value" },
"options": { "skip": 0, "limit": 2 }
}).exec(function(err,docs) {
docs = docs.filter(function(doc) {
return docs.comments.length;
});
});
当然,可能存在的问题是您无法再实际查询包含“嵌入”信息的文档,因为它现在位于另一个集合中。这会导致提取所有文档,尽管可能通过一些其他查询条件,但随后手动测试它们以查看它们是否被发送以检索这些项目的过滤查询“填充”。
所以这确实取决于您在做什么以及您的方法是什么。如果您经常打算“搜索”内部数组,那么嵌入通常更适合您。另外,如果您真的只对“分页”感兴趣,那么 $slice
运算符可以很好地处理嵌入式文档。但要注意将嵌入式阵列增长得太大。
将引用模式与 Mongoose 一起使用有助于解决一些大小问题,并且有适当的方法来帮助“分页”结果并过滤它们。缺点是您不能再从父级本身“内部”查询这些元素。因此,通过内部元素选择父级在这里不太适合。另请记住,虽然并非所有数据都已嵌入,但仍然存在对外部文档的 _id
值的引用。因此,您仍然可能会得到大型阵列,这可能并不理想。
对于任何大的项目,考虑到您可能会自己完成这项工作,并从“子”项目向后工作,然后匹配父项目。
关于javascript - 用 Mongoose 查询嵌套文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24227047/