如何从Mongoose嵌套数组中仅返回books子数组并实现分页渲染?
问题
我定义了如下Mongoose Schema:
var book_listSchema = new mongoose.Schema({ userId: { type: String, required: true }, first_name: { type: String, required: true }, last_name: { type: String, required: true }, newList: [{ list_name: String, books: [{ list_name: String, book_name: { type: String, required: true }, book_author: { type: String, required: true }, date_added: { type: Date, default: Date.now }, date_finished: { type: Date, default: Date.now }, book_image: { type: String, required: true } }] }], }); var book_list = mongoose.model('book_list', book_listSchema); module.exports = book_list;
使用如下查询语句获取数据:
book_list.findOne({ "userId": req.userContext.userinfo.sub }, { newList: { $elemMatch: { "list_name": listName } } }) .skip((perPage * page) - perPage) .limit(perPage) .exec(function(err, doc) { console.log(doc); response.render('display_list', { userContext, list_books: doc.newList[0].books, listName: listName, pages: Math.ceil(doc.newList[0].books.length / perPage), current: page }) }) });
当前返回的文档内容为:
{ _id: new ObjectId("63b60f5844ffdd86146d6e5b"), newList: [{ list_name: 'read', _id: new ObjectId("63b60f5844ffdd86146d6e5c"), books: [Array] }] }
我需要仅返回其中的books子数组,用于将其内容分页渲染到页面上,请问该如何实现?
解决方案
方法1:用聚合管道完成数据库层面的提取与分页
原查询中的skip和limit是对整个文档集合生效,并非针对books数组内部元素。使用聚合管道可以精准筛选目标数据,并直接在数据库端完成分页:
book_list.aggregate([ // 筛选对应用户的文档 { $match: { userId: req.userContext.userinfo.sub } }, // 展开newList数组,方便筛选指定列表 { $unwind: "$newList" }, // 匹配目标list_name的列表项 { $match: { "newList.list_name": listName } }, // 提取books数组并计算总长度 { $project: { books: "$newList.books", totalBooks: { $size: "$newList.books" } } }, // 对books数组执行分页切片 { $project: { paginatedBooks: { $slice: ["$books", (page - 1) * perPage, perPage] }, totalBooks: 1 } } ]).exec(function(err, result) { if (err) { // 处理错误逻辑 return; } const targetData = result[0] || { paginatedBooks: [], totalBooks: 0 }; response.render('display_list', { userContext, list_books: targetData.paginatedBooks, listName: listName, pages: Math.ceil(targetData.totalBooks / perPage), current: page }); });
这种方式的优势是减少服务端数据传输量,性能更优,同时能直接获取数组总长度用于分页计算。
方法2:查询后手动提取并分页
如果不想使用聚合,也可以在获取文档后手动处理数组分页,但需注意移除原查询中作用于文档的skip和limit:
book_list.findOne({ userId: req.userContext.userinfo.sub }, { newList: { $elemMatch: { list_name: listName } } }) .exec(function(err, doc) { let books = []; let totalBooks = 0; if (doc && doc.newList.length > 0) { books = doc.newList[0].books; totalBooks = books.length; } // 计算分页切片范围 const startIdx = (page - 1) * perPage; const paginatedBooks = books.slice(startIdx, startIdx + perPage); response.render('display_list', { userContext, list_books: paginatedBooks, listName: listName, pages: Math.ceil(totalBooks / perPage), current: page }); });
这种方式代码更简洁,但如果books数组过大,会将完整数组传输到服务端,性能不如聚合管道。
关键注意点
- 必须移除原查询中的
skip()和limit(),它们是对文档集合分页,而非数组元素分页,保留会导致错误逻辑 - 要处理
doc为空或newList无匹配项的情况,避免出现doc.newList[0]的空指针错误
内容的提问来源于stack exchange,提问作者Daniel Semel
相关产品推荐
相关产品推荐

