查询包含(或不包含)嵌套文档数组(被填充)但匹配特定条件的文档

Posted

技术标签:

【中文标题】查询包含(或不包含)嵌套文档数组(被填充)但匹配特定条件的文档【英文标题】:Query documents containing (or not) array of nested documents (being populated) but then matching a certain criteria 【发布时间】:2022-01-22 07:36:43 【问题描述】:

在将此标记为“已回答”(或类似内容)之前请仔细阅读(不仅仅是标题),因为我确实研究了很长时间,但还没有找到解决方案(我发现了有关查询的问题嵌套数组对象,但不是需要填充的嵌套数组文档)。

我正在使用最新版本的 NodeJS 和 Mongoose。

为了说明,我将使用这两个模型来帮助描绘我的目标:

// Sections
const mongoose = require('mongoose');
var Schema = mongoose.Schema;

const sectionSchema = new Schema(
  _id:          Schema.Types.ObjectId,
  name:          type: String, required: [true, 'Name is required.'],
);

module.exports = mongoose.model('Section', sectionSchema);

还有:

// Departments
const mongoose = require('mongoose');
var Schema = mongoose.Schema;

const departmentSchema = new Schema(
  _id:          Schema.Types.ObjectId,
  name:          type: String, required: [true, 'Name is required.'],
  sections:     [ type: Schema.Types.ObjectId, ref: 'Section' ]
);

module.exports = mongoose.model('Department', departmentSchema);

考虑我将这些存储在数据库中:

// Departments
_id: ObjectId("61b0f1971ad3890000000001")
name: "Department 1",
sections: [ObjectId("61b26a3fb756a1000000000b"), ObjectId("61b26a3fb756a1000000000c")]

_id: ObjectId("61b0f1971ad3890000000002")
name: "Department 2",
sections: [ObjectId("61b26a3fb756a1000000000a"), ObjectId("61b26a3fb756a1000000000c")]

_id: ObjectId("61b0f1971ad3890000000003")
name: "Department 3",
sections: []

_id: ObjectId("61b0f1971ad3890000000004")
name: "Department 4",
sections: [ObjectId("61b26a3fb756a1000000000b")]


// Sections
_id: ObjectId("61b26a3fb756a1000000000a")
name: "Section A"

_id: ObjectId("61b26a3fb756a1000000000b")
name: "Section B"

_id: ObjectId("61b26a3fb756a1000000000c")
name: "Section C"

现在,我要做的是获取不包含任何部分或名称为“Section A”的部分的部门(作为来自前端的过滤器的文本)。

Department.find(criteria).select('name').populate('sections').then(results => 
    console.log('Departments: ', results.map(r => r.name));
);


// Should print:
Departments: [ Department 2, Department 3 ]

对于标准,我尝试了许多不同的方法,但到目前为止似乎都没有奏效(我总是得到零结果)。下面的一个例子:

const criteria =  
  $or: [
     'sections': [] ,
     'sections':  '$elemMatch':  name: 'Section A' ,
  ]
;

我认为这是因为在查询填充数组之前测试了 criteria。如果这是正在发生的事情,那么最好的解决方法是什么?我想避免分别查询这两个文档然后匹配结果。 应该有办法一次性做到这一点。有任何想法吗?提前致谢。

【问题讨论】:

【参考方案1】:

经过详尽的测试和研究,我得出的结论是,仅通过操纵 criteria 中的内容,无法一次性获得所需的结果

为querying documents with nested/embedded array of other documents 提供的最接近的 MongoDB 文档不处理 populate

因此,实现目标的当前/下一个最佳方法(打印不包含节或“节 A”但没有提供节._id 但仅节.name 的部门)是:

Section.find( name: 'Section A' ).then(sections => 
  Department.find( 
    $or: [
       'sections': [] ,
       'sections':  $in: sections ,
    ]
  ).then(departments => console.log('Departments: ', results.map(r => r.name));
  // Will print: Departments: [ Department 2, Department 3 ]
);

【讨论】:

【参考方案2】:

让我们看看您的查询。 您想查找部分 [] 或部分名称为“Section A”的文档。但在您的部门模型中,您只是保存了每个部分的 id。您无法访问在填充之前提交的名称。因此您需要根据部分 id 查找文档。 您可以更改条件,如下所示。

    const criteria = 
    $or: [
         'sections': [] ,
         'sections': mongoose.Types.ObjectId("61b26a3fb756a1000000000a") 
    ]
;

很明显,您无法在填充之前访问该名称。您也可以对填充进行过滤,但我没有找到涵盖这两种情况的解决方案。你也可以阅读populate documentations。

一种方法是获取所有文档并填充它们,然后实施过滤器,但我认为这不是一个好方法。

//Fetch and populate all documents
const departments = await Department.find(
).populate(
    path: 'sections',
    select: 'name',
).exec();

let res = [];
departments.forEach(department => 
    //Select departments which has "Section A" or has not any section
    if (department.sections.some(section => section.name === "Section A") || department.sections.length === 0) 
        res.push(department);
    
);

【讨论】:

问题是它应该能够找到所有不包含部分或仅包含其名称的部分的部门。当然,我可以分两步完成:Section.find( name: 'Section A").then(foundSection => Department.find(criteria including foundSection.id) ); 但我不会一口气完成。 当我们执行 Department.find().populate('sections') 时,我们会在返回数据中获取包含其部分的每个部门,其中填充了 section._id 和 section.name。那么我想通过以某种方式将部分名称放在 Department.find() 的条件中来过滤将要返回的内容。从我目前发现的情况来看,'$elemMatch' 将是最好的方法,因为它是为查询嵌套数组中的对象而构建的,但它不适用于我的情况。也许有人知道另一种方式......也许与其他东西结合(?) 正如我所说,您可以使用节名称的节 id 进行查询(在填充之前您无法访问)。您是否看到并测试了我建议的标准?它将返回正确的结果。 我知道如果我们使用部分 id 查询它会起作用。但这不是我要问的。我试图解释查询将从前端(由客户端)调用,仅使用部分名称(不提供部分 ID)。

以上是关于查询包含(或不包含)嵌套文档数组(被填充)但匹配特定条件的文档的主要内容,如果未能解决你的问题,请参考以下文章

带有 elemMatch 的 MongoDB 查询,用于从对象内部匹配嵌套数组数据 [重复]

无法填充嵌套数组

Mongoose:填充嵌套的 id 数组

MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用

查询匹配连续数组元素的文档

匹配 MongoDB 中嵌套子文档的嵌套数组