MongoDB裏作表間關聯

MongoDB與關係型數據庫的建模仍是有許多不一樣,由於MongoDB支持內嵌對象和數組類型。MongoDB建模有兩種方式,一種是內嵌(Embed),另外一種是鏈接(Link)。那麼什麼時候Embed什麼時候Link呢?那得看兩個實體之間的關係是什麼類型。java

一對一的關係:Embed,好比用戶信息集合有Address字段,Address字段有省、市、縣三個字段。sql

在關係型數據庫中,經過鏈接運算符能夠實現多個表聯合查詢。而非關係型數據庫的特色是表之間屬於弱關聯,Mongodb做爲Nosql表明,其自己特性不建議對多Collection關聯處理,不過對於有些須要對多表關聯處理的需求,Mongodb也能夠實現。主要分爲幾種方式:簡單手工關聯和DBRef方式關聯、esProcmongodb

 

1.簡單手工關聯shell

下圖表示帖子和用戶兩個Collection的ER圖:數據庫

首先將authors集合中的用戶對象查詢出來,放在一個變量author中,代碼以下:編程

> author=db.authors.findOne({name:"chenzhou"})
{
	"_id" : ObjectId("5030ba7621bdee44765b2147"),
	"name" : "chenzhou",
	"email" : "chenzhou1025@126.com"
}

經過用戶對象author來獲取帖子列表,代碼以下:json

> for(var post=db.posts.find({"author_name":author.name}); post.hasNext();){
... printjson(post.next().title);
... }
"Hello Mongodb"
"Hello World"
"Hello My Friend"

2.DBRef方式關聯數組

{ $ref : <value>, $id : <value>, $db : <value> }
$ref:集合名稱;$id:引用的id;$db:數據庫名稱,可選參數。
能夠看到DBRef的結構比Manual References的複雜,佔用的空間大,可是功能也強大,若是要跨數據庫鏈接,上面講的評論集合的例子,都得須要使用DBRef,MongoDB提供了函數來解析DBRef,不用像Manual References須要本身手動寫兩次查詢。

DBRef就是在兩個Collection之間定義的一個關聯關係,好比,把CollectionB "_id"列的值存在CollectionA的一個列中,而後經過CollectionA這個列中所存的值在CollectionB中找到相應的記錄。app

示例:模擬用戶發帖的過程,看一看如何將帖子表和用戶表創建關聯。框架

步驟1:取得當前用戶信息,代碼以下:

> author=db.authors.find({name:"chenzhou"})[0]
{
	"_id" : ObjectId("5030ba7621bdee44765b2147"),
	"name" : "chenzhou",
	"email" : "chenzhou1025@126.com"
}

步驟2:發帖子並作關聯,代碼以下: 

> db.posts.insert({"title":"Hello Mongodb DBRef1",
... authors:[new DBRef('authors',author._id)]})
> db.posts.insert({"title":"Hello Mongodb DBRef2",
... authors:[new DBRef('authors',author._id)]})
>

步驟3:通知帖子查找用戶信息,代碼以下:

>  db.posts.find({"title":"Hello Mongodb DBRef1"})[0].authors[0].fetch()
{
	"_id" : ObjectId("5030ba7621bdee44765b2147"),
	"name" : "chenzhou",
	"email" : "chenzhou1025@126.com"
}

經過這個例子能夠看出,DBRef就是從文檔的一個屬性指向另外一個文檔的指針。

關於DBRef詳細信息,能夠參見官網說明:http://docs.mongodb.org/manual/applications/database-references/ 

 

$lookup

 咱們來看mongodb另外一個很是有意思的東西,那就是$lookup,咱們知道mongodb是一個文檔型的數據庫,並且它也是最像關係型數據庫的

一種nosql,可是呢,既然mongodb是無模式的,天然就很難在關係型數據庫中很是擅長的多表關聯上發揮做用,在這以前,咱們可使用DbRef,但

是呢,在mongodb 3.2 中給你增長了一個至關牛逼的手段,那就是$lookup,並且放到了aggreation這種重量級的pipeline分析框架上,天然就是一等

公民了,牛逼哈~。

$lookup:

db.product.insert({"_id":1,"productname":"商品1","price":15})
db.product.insert({"_id":2,"productname":"商品2","price":36})


db.orders.insert({"_id":1,"pid":1,"ordername":"訂單1"})
db.orders.insert({"_id":2,"pid":2,"ordername":"訂單2"})
db.orders.insert({"_id":3,"pid":2,"ordername":"訂單3"})
db.orders.insert({"_id":4,"pid":1,"ordername":"訂單4"})

db.product.find()
db.orders.find()


語法:
db.product.aggregate([ { $lookup: { from: "orders", localField: "_id", foreignField: "pid", as: "inventory_docs" } } ])
而後展現的結果以下:
複製代碼
 1 /* 1 */
 2 {
 3     "_id" : 1.0,
 4     "productname" : "商品1",
 5     "price" : 15.0,
 6     "inventory_docs" : [ 
 7         {
 8             "_id" : 1.0,
 9             "pid" : 1.0,
10             "ordername" : "訂單1"
11         }, 
12         {
13             "_id" : 4.0,
14             "pid" : 1.0,
15             "ordername" : "訂單4"
16         }
17     ]
18 }
19 
20 /* 2 */
21 {
22     "_id" : 2.0,
23     "productname" : "商品2",
24     "price" : 36.0,
25     "inventory_docs" : [ 
26         {
27             "_id" : 2.0,
28             "pid" : 2.0,
29             "ordername" : "訂單2"
30         }, 
31         {
32             "_id" : 3.0,
33             "pid" : 2.0,
34             "ordername" : "訂單3"
35         }
36     ]
37 }
複製代碼

下面我簡單介紹一些$lookup中的參數:

from:須要關聯的表【orders】

localField: 【product】表須要關聯的鍵。

foreignField:【orders】的matching key。

as:           對應的外鍵集合的數據,【由於多是一對多的,對吧】

 

MongoDB不支持join,其官網上推薦的unity jdbc能夠把數據取出來進行二次計算實現join運算,但收費版纔有這個功能。其餘免費的jdbc drive只能支持最基本的SQL語句,不支持join。若是用Java等編程語言將數據取出後實現join計算,也比較複雜。
三、esProc
   用免費的esProc配合MongoDB,能夠實現join計算。這裏經過一個例子來講明一下具體做法。

   MongoDB中的文檔orders保存了訂單數據,employee保存了員工數據。以下:
   MongoDB shell version: 2.6.4

   connecting to: test

    > db.orders.find();
    { 「_id」 : ObjectId(「5434f88dd00ab5276493e270″), 「ORDERID」 : 1, 「CLIENT」 : 「UJRNP
    」, 「SELLERID」 : 17, 「AMOUNT」 : 392, 「ORDERDATE」 : 「2008/11/2 15:28″ }
    { 「_id」 : ObjectId(「5434f88dd00ab5276493e271″), 「ORDERID」 : 2, 「CLIENT」 : 「SJCH」
    , 「SELLERID」 : 6, 「AMOUNT」 : 4802, 「ORDERDATE」 : 「2008/11/9 15:28″ }
    { 「_id」 : ObjectId(「5434f88dd00ab5276493e272″), 「ORDERID」 : 3, 「CLIENT」 : 「UJRNP
    」, 「SELLERID」 : 16, 「AMOUNT」 : 13500, 「ORDERDATE」 : 「2008/11/5 15:28″ }
    { 「_id」 : ObjectId(「5434f88dd00ab5276493e273″), 「ORDERID」 : 4, 「CLIENT」 : 「PWQ」,
    」SELLERID」 : 9, 「AMOUNT」 : 26100, 「ORDERDATE」 : 「2008/11/8 15:28″ }
    …
    > db.employee.find();
    { 「_id」 : ObjectId(「5437413513bdf2a4048f3480″), 「EID」 : 1, 「NAME」 : 「Rebecca」, 」
    SURNAME」 : 「Moore」, 「GENDER」 : 「F」, 「STATE」 : 「California」, 「BIRTHDAY」 : 「1974-1
    1-20″, 「HIREDATE」 : 「2005-03-11″, 「DEPT」 : 「R&D」, 「SALARY」 : 7000 }
    { 「_id」 : ObjectId(「5437413513bdf2a4048f3481″), 「EID」 : 2, 「NAME」 : 「Ashley」, 「S
    URNAME」 : 「Wilson」, 「GENDER」 : 「F」, 「STATE」 : 「New York」, 「BIRTHDAY」 : 「1980-07-
    19″, 「HIREDATE」 : 「2008-03-16″, 「DEPT」 : 「Finance」, 「SALARY」 : 11000 }
    { 「_id」 : ObjectId(「5437413513bdf2a4048f3482″), 「EID」 : 3, 「NAME」 : 「Rachel」, 「S
    URNAME」 : 「Johnson」, 「GENDER」 : 「F」, 「STATE」 : 「New Mexico」, 「BIRTHDAY」 : 「1970-
    12-17″, 「HIREDATE」 : 「2010-12-01″, 「DEPT」 : 「Sales」, 「SALARY」 : 9000 }

    …

   Orders中的sellerid對應employee中的eid。須要查詢出employee的state屬性等於California的全部訂單信息。其中orders數據量較大,不能一次裝入內存。Employee數據量較小,Orders過濾以後的結果數據量也比較小。

   查詢條件表達式能夠做爲參數傳遞給esProc,以下圖:

   A1: 鏈接MongoDB數據庫,ip和端口號是localhost:27017,數據庫是test,用戶名和密碼都是test。

   A2: 使用find函數從MongoDB中取數,造成遊標。集合是orders,過濾條件是空,指定鍵_id不取出。esProc在find函數中採用了和mongdb的find語句同樣的參數格式。esProc的遊標支持分批讀取和處理數據,能夠避免數據量過大,內存出現溢出的狀況。

   A3: 取得employee中的數據。由於數據量不大,因此用fetch函數一次取出。

   A4: 使用switch函數,將遊標A2中SELLERID字段的值,轉換爲A3(employee)中的記錄引用。

   A5: 按照條件過濾。這裏使用宏來實現動態解析表達式,其中的where就是傳入參數。集算器將先計算${…}裏的表達式,將計算結果做爲宏字符串值替換${…}以後解釋執行。這個例子中最終執行的是:=A4.select(SELLERID.STATE==」California」)。因爲SELLERID已經轉化爲employee的對應記錄的引用,因此能夠直接寫SELLERID.STATE。過濾以後的結果數據量較小,因此一次取出。若是結果數據量仍然比較大的話,能夠分批取出,好比每次取出10000條:fetch(10000)。

   A6:將過濾結果中的SELLERID從新切換爲普通值。

   A6的計算結果是:
   過濾條件發生變化時不用改變程序,只需改變where參數便可。例如,條件變爲:state等於California的訂單,或者CLIENT等於PWQ的訂單。Where的參數值能夠寫爲:CLIENT==」PWQ」|| SELLERID.STATE==」California」。   esProc並不包含MongoDB的java驅動包。用esProc來訪問MongoDB,必須提早將MongoDB的java驅動包(esProc要求2.12.2或以上版本的驅動,mongo-java-driver-2.12.2.jar)放到[esProc安裝目錄]\common\jdbc中。   esProc協助MongoDB計算的腳本很容易集成到java中,只要增長一行A7,寫成result A6便可向java輸出resultset形式的結果,具體的代碼請參考esProc教程。一樣,用java調用esProc訪問MongoDB也必須將mongdb的java驅動包放到java程序的classpath中。

相關文章
相關標籤/搜索