2014-04-10 156 views
0

使用Mongoid,您将如何查询不包含特定属性的对象?使用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阶段,由于文档被重新放回原来的形式,该“匹配”测试的值将被存储在文档中。这意味着,如果prerequisites数组中的任何元素被视为匹配,则整个文档的值将被视为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之外的“大小”的任何结果。

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

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

+0

感谢您提供快速而详细的答案。学习如何使用脚踏车并理解你的答案会花费我一点时间。但是,一旦我应用了它,我会回来接受它。 –

+0

@MichaelQLarson如您所见,这会从模型类中抓取“collection”对象,并允许Moped驱动程序的较低级别的功能。唯一能够像汇总一样进行汇总的方法,并且可能保持这种状态。这里有很多聚合管道方法的链接,学习使用MongoDB是非常值得的。 –

+0

这对我的问题非常有用!再次感谢您的快速帮助。 –

相关问题