MongoDB 資料之間真的完全沒有關係嗎? 其實不完全。今天的文章我們要來看看隱藏在MongoDB 中的半關聯式寫法,另外我們聊聊 MongoDB 本身的資料模型。
關聯式資料庫中很重要的概念就是我在一個關聯表中可以和另外一個關聯表存蔡某些關係,例如商品的資訊和商品的評論,然後中間用商品編號或評論編號串聯。我們在 Day 12 有提到,非關聯式資料庫設法用單一文檔解決「大數據」的挑戰。問題是,如果有上萬則評論,這樣的文檔是不是儲存起來就沒有意義了呢? (其實 MongoDB 有一個單一檔案不能超過16MB的規定!)
資料庫資料到底要怎麼存,其實是一門藝術,我們在進階關聯式資料庫會再仔細提到。但是在原始的MongoDB 設計中,他就是希望可以讓每一個會用到的資料盡可能地放在同一個文檔當中,官方的解釋稱這種儲存方法是使用內嵌文檔 (Embedded Documents),例如一個商品資料內嵌多筆的評論:
{
_id: <ObjectId1>,
name: "product 1",
price: 20,
reviews: [
{
user: "John",
rating: 4,
review: "..."
},{
user: "Belle",
rating: 3,
review: "..."
}
]
}
這個資料中的評論 reviews
中存的就是一個一個的文檔。對於讀取,這樣的設計可以非常有效率。而且針對這個文檔所做的任何更變都是 atomic 的 (忘記可以回去複習ACID!) 。
但是如果真的要把這些評論拆開是不是可行的? 答案是可以,這時候我們再儲存資料的時候就可以這樣存放
// Product Collection
{
_id: <ObjectId1>,
name: "product 1",
price: 20
}
// Review Collection
{
_id: <ObjectID2>,
product: <ObjectID1>,
user: "John",
rating: 4,
review: "..."
},
{
_id: <ObjectID3>,
product: <ObjectID1>,
user: "Belle",
rating: 3,
review: "..."
}
我們把上面的這個敘述實際打出來,我們先把資料加入資料庫
接下來我們可以用下面的 MongoSH
var productID = db.product.findOne({name:"product 1"})._id
這個指令會把查找到的商品ID 存入 productID 這個變數中 (沒錯他就是javascript!)
接下來我們就把整個資料庫 (因為我們假設這邊的資料都是這個商品的評論,當然也可以再插入這些評論的時候就把ID放入)
db.reviews.updateMany({},{$set:{"product":productID}})
這樣當我們要查詢 product 1 的評分的時候,就可以直接這樣寫
db.reviews.find({"product":productID},{_id:0, rating:1})
這邊要再次強調,這裡的範例會有一點怪怪的,因為資料庫放入資料的大部分都是另外一個應用程式 (例如一個APP 或是一個 Web),但是希望可以透過這個簡單的示範告訴大家可以透過 ObjectID 當作值去做串聯。但是這邊的串聯和 SQL 看到的Join 就很不一樣了,他不會有什麼 Cross Join, Left/Right Join,因為他就是一個文檔把東西分開儲存而已。另外一件事情是,因為拆開來,所以當我們要做資料處理的時候就不再是 atomic operation 了。舉個簡單來說,如果我現在要把product 1 的評論都刪除,MongoDB 必須要針對每一個符合ID的項目去刪除,所以如果這個行為被中斷,已經刪除的文檔就會被刪除,而沒有刪除的文檔就會保留。因為對MongoDB 而言這些都是獨立的。
明天我們簡單解釋MongoDB 中較為複雜的Map-Reduce.