文檔關聯模型通常有3種方式:
- 嵌入式(一對一、一對多)
- 后期手動統一ID處理(一對多、多對多)
- References引用(一對一、一對多)
文檔樹模型通常有3種方式:
- 父引用(Parent References)
- 子引用(Child References)
- 祖先數組(Array of Ancestors )
- 物化路徑(Materialized Paths )
- 嵌套_Set,不常用,不詳寫了
關聯模型
1、嵌入式
直接在單獨文檔中嵌入“子文檔”
嵌入一個文檔(1對1):
{ _id: "joe", name: "Joe Bookreader", address: { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } }
嵌入多個文檔(1對n),使用“數組”形式:
{ _id: "joe", name: "Joe Bookreader", addresses: [ { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" }, { street: "1 Some Other Street", city: "Boston", state: "MA", zip: "12345" } ] }
這個很簡單。
2、后期手動統一ID處理
后期統一ID,用於程序中確立關聯,可以處理1對多,也可以處理多對多(通過中間關聯表來實現),比如一個典型的多對多中間表,用於確定用戶的權限:用戶ID-權限ID
使用手動ID, 是在另一個文檔中包含一個文檔 _id字段的做法。然后,應用程序可以發出第二個查詢以根據需要的(ID)解析數據。無論是一對一,還是多對多都可用.
original_id = ObjectId() db.places.insert({ "_id": original_id, "name": "Broadway Center", "url": "bc.example.net" }) db.people.insert({ "name": "Erin", "places_id": original_id, "url": "bc.example.net/Erin" })
3、References引用
出版商和書的一對一、一對多關聯(出版商可以出版很多書)
這個是正確的姿勢,確保關聯鍵(publisher_id)必須只有一個值:
publisher: { _id: "oreilly", name: "O'Reilly Media", founded: 1980, location: "CA" } books: { _id: 123456789, title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly" } { _id: 234567890, title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher_id: "oreilly" }
錯誤的引用方式,books的數組持續增長,造成失控,必須使用原子操作來維護數組。
{ name: "O'Reilly Media", founded: 1980, location: "CA", books: [123456789, 234567890, ...] }
總結正確的姿勢:在父子表中的子表中引用父ID,在這里books為子表,publisher為父表,其實和關系數據庫的設計模型是一致的。
樹結構
1、父引用(Parent References)
以下的樹結構,如圖:
數據模型:
db.categories.insert( { _id: "MongoDB", parent: "Databases" } ) db.categories.insert( { _id: "dbm", parent: "Databases" } ) db.categories.insert( { _id: "Databases", parent: "Programming" } ) db.categories.insert( { _id: "Languages", parent: "Programming" } ) db.categories.insert( { _id: "Programming", parent: "Books" } ) db.categories.insert( { _id: "Books", parent: null } )
父引用的含義,顧名思義就是每個子節點指定自己的父親。
檢索節點的父親的查詢: db.categories.findOne( { _id: "MongoDB" } ).parent
在字段parent上創建索引以啟用 parent 節點的快速搜索: db.categories.createIndex( { parent: 1 } )
通過parent字段查詢以查找其直接的子節點: db.categories.find( { parent: "Databases" } )
要檢索子樹,可使用$graphLookup。
2、子引用(Child References)
顧名思義就是父親要標明自己的兒子(數組),感覺模型有些不大合理。最好別沾。
db.categories.insert( { _id: "MongoDB", children: [] } ) db.categories.insert( { _id: "dbm", children: [] } ) db.categories.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ] } ) db.categories.insert( { _id: "Languages", children: [] } ) db.categories.insert( { _id: "Programming", children: [ "Databases", "Languages" ] } ) db.categories.insert( { _id: "Books", children: [ "Programming" ] } )
檢索節點的直接 children 的查詢: db.categories.findOne( { _id: "Databases" } ).children 您可以在字段children上創建索引以啟用 child 節點的快速搜索: db.categories.createIndex( { children: 1 } ) 您可以在children字段中查詢節點以查找其 parent 節點及其兄弟節點: db.categories.find( { children: "MongoDB" } )
3、完全祖先數組(Array of Ancestors )
這個比較合理,就是存儲父的全路徑數組。每個樹節點除了父節點,還存儲所有的節點的祖先。
db.categories1.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories1.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories1.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories1.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories1.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } ) db.categories1.insert( { _id: "Books", ancestors: [ ], parent: null } )
用於檢索節點的祖先或路徑的查詢快速而直接: db.categories1.findOne( { _id: "MongoDB" } ).ancestors
您可以在字段ancestors上創建索引以啟用祖先節點的快速搜索: db.categories1.createIndex( { ancestors: 1 } )
您可以通過字段ancestors查詢以查找其所有后代: db.categories1.find( { ancestors: "Programming" } )

Array of Ancestors pattern 提供了一種快速有效的解決方案,通過創建祖先字段元素的索引來查找節點的后代和祖先。是開發應用中的好選擇。
4、物化路徑(Materialized Paths )
和完全祖先數組原理一樣,只是在處理路徑時提供了更大的靈活性,例如通過 partial paths 查找節點。
db.categories.insert( { _id: "Books", path: null } ) db.categories.insert( { _id: "Programming", path: ",Books," } ) db.categories.insert( { _id: "Databases", path: ",Books,Programming," } ) db.categories.insert( { _id: "Languages", path: ",Books,Programming," } ) db.categories.insert( { _id: "MongoDB", path: ",Books,Programming,Databases," } ) db.categories.insert( { _id: "dbm", path: ",Books,Programming,Databases," } )
您可以查詢以檢索整個樹,按字段path排序: db.categories.find().sort( { path: 1 } ) 您可以在path字段上使用正則表達式來查找Programming的后代: db.categories.find( { path: /,Programming,/ } ) 您還可以檢索Books的后代,其中Books也位於層次結構的最頂層 level: db.categories.find( { path: /^,Books,/ } ) 要在字段path上創建索引,請使用以下調用: db.categories.createIndex( { path: 1 } ) 此索引可能會根據查詢提高 performance: 對於來自根Books sub-tree(e.g. /^,Books,/或/^,Books,Programming,/)的查詢,path字段上的索引可顯着改進查詢性能。 對於 sub-trees 的查詢,其中查詢(e.g. /,Databases,/)中未提供根的路徑,或者 sub-trees 的類似查詢,其中節點可能位於索引 string 的中間,查詢必須檢查整個索引。 對於這些查詢,如果索引明顯小於整個集合,則索引可以提供一些性能改進。