0

使用 Mongoid,您将如何查询不包含特定属性的对象?

具体来说,我正在寻找 Course.prerequisite_courses 不包括任何不在 Student.courses_taken 中的课程的所有课程对象

例如:

Class Student
  include Mongoid::Document

  field :courses_taken,    type: Array # an array of course IDs
end

Class Course
  include Mongoid::Document

  field :prerequisites,    type: Array # an array of course IDs
end

student_1.courses_taken = [a, b]

course_1.prerequisites = [a]

course_2.prerequisites = [a, b]

course_3.prerequisites = [a, c]

这样 student_1 将被 course_1 和 course_2 录取,但不会被 course_3 录取

两个对象不相关

请注意,在这种情况下,可能有数百个 course.prerequisites 和 student.courses_taken,并且我打算让这只是我的查询中的几个链接方法之一。

是否有一种优雅(或至少相对便宜)的方法可以通过 mongoid 查询来做到这一点?

4

1 回答 1

1

我确实对 moped 查询表单有一个普遍的偏好,因为它在较低级别上工作,并允许您利用 MongoDB 查询运算符的完整功能集。对某些人来说,这可能看起来不是很“随意”,但也有一些优势。特别是当解决方案涉及使用.aggregate()

因此,为了找到与学生所修课程的先决条件相匹配的课程,您将建立如下声明:

Course.collection.aggregate([
    // Filters the documents, not an exact match but a start
    { "$match" => { 
        "prerequisites" => { "$in" => [ "a", "b" ] },
    }},

    // Unwind the array
    { "$unwind" => "$prerequisites" },

    // Tag only the matching entries
    { "$project" => {
        "prerequisites" => 1,
        "matching" => { "$or" => [
            { "$eq" => [ "$prerequisites", "a" ] },
            { "$eq" => [ "$prerequisites", "b" ] },
        ]}
    }},

    // Group back to the course _id
    { "$group" => {
        "_id" => "$_id",
        "prerequisites" => { "$push" => "$prerequisites" },
        "matching" => { "$min" => "$matching" }
    }},

    // Match only the true values (all prerequisites met )
    { "$match" => { "matching" => true } },

    // Project only the wanted fields
    { "$project" => { "prerequisites" => 1 } }
])

因此,“courses_taken”的每个元素都被添加到$in运算符中,因此只有包含其中内容的课程最初才会匹配。但这当然不能完全过滤学生必须满足所有必备课程的条件,这里的重点是将文档数量减少到可能匹配的文档数量。

数组展开后,可以比较每个值。这就是$project通过从数组元素构建一个语句来测试是否找到该值来执行的操作。所以在这种$or情况下,任何不匹配的东西都会false作为这个值返回。

在稍后$group阶段,当文档恢复到其原始形式$min时,该“匹配”测试的值将针对文档存储。这意味着如果先决条件数组的任何元素被认为是false匹配的,那么将考虑整个文档的值false

下一个$match用于过滤掉任何因此包含与用于输入的学生所学课程不匹配的课程先决条件的课程。所以现在你只剩下可以参加的课程了,期末考试$project只是删除了“匹配”字段(通过省略),所以文件现在是原始形式。

如果您实际上有 MongoDB 2.6 版(刚刚发布)或更高版本,那么有新的聚合运算符使语句更简单:

Course.collection.aggregate([
    { "$match" => { 
        "prerequisites" => { "$in" => [ "a", "b" ] }
    }},
    { "$project" => {
        "prerequisites" => 1,
        "diff" => { "$size" => {"$setDifference" => [ 
            "$prerequisites", 
            [ "a", "b" ] 
        ]}}
    }},
    { "$match" => { "diff" => 0  } },
    { "$project" => { "prerequisites" => 1 } }
])

因此,这利用了新的运算符$setDifference,可以直接比较数组以查找不在集合中的元素,并且使用$size它将返回测试数组的长度。由于任何包含不在学生的课程中的先决条件元素的课程将作为 * 的结果返回这些元素,因此$setDifference任何具有“大小”以外0的结果都可以从整体结果中排除。

除了更简单并具有一些速度优势之外,您还可以通过将课程数组从学生直接传递到管道查询的构造中来避免生成的复杂性,而不必搞乱构造“平等” " 第一个示例中使用的测试语句。

但这为您提供了一种相当强大的方法来进行这种匹配,而无需在代码中使用循环结果。它还指出聚合框架的使用不仅仅是为了对结果进行分组,而是一个非常强大的查询工具。

于 2014-04-10T02:28:02.290 回答