使用Mongoid查找不包含任何属性的对象

时间:2014-04-10 00:05:51

标签: mongodb mongoid aggregation-framework querying

使用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查询是否有优雅(或至少相对便宜)的方式?

1 个答案:

答案 0 :(得分: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将返回的长度。测试阵列。由于任何包含不在学生的课程数据中的prerequsite元素的课程将返回这些元素作为* $setDifference的结果,那么任何具有“{1}}以外的”大小“的结果都可以从总体结果中排除。

除了更简单,速度更快之外,还可以通过将学生的课程数组直接传递给管道查询的构建来避免生成的复杂性,并且不必乱搞构建第一个例子中使用的“相等”测试语句。

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