MongoDB開發深入之一:文檔數據關系模型詳解(一對多,多對多)


文檔關聯模型通常有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 的中間,查詢必須檢查整個索引。
對於這些查詢,如果索引明顯小於整個集合,則索引可以提供一些性能改進。

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM