2014-04-10 70 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

這對我的問題非常有用!再次感謝您的快速幫助。 –

相關問題