當我們想要找出某些欄位是 NULL 的資料…
做為資料工作者,在處理資料的時候經常會遇到所謂的空值「NULL」,也常看到這樣的值被存在資料庫的欄位中,當我們使用 SQL 想要找出某些欄位中是空值的資料,就會使用像下方這樣的 WHERE 去做篩選,但這樣其實無法得到我們想要的結果。
SELECT
column_1,
column_2
FROM `my_table`
WHERE column_1 = NULL
做為資料工作者,在處理資料的時候經常會遇到所謂的空值「NULL」,也常看到這樣的值被存在資料庫的欄位中,當我們使用 SQL 想要找出某些欄位中是空值的資料,就會使用像下方這樣的 WHERE 去做篩選,但這樣其實無法得到我們想要的結果。
SELECT
column_1,
column_2
FROM `my_table`
WHERE column_1 = NULL
MongoDB 屬於一種非關聯式資料庫,與關聯式資料庫的差異其實網路上已經有非常多的資料,個人覺得主要是 Schema 的彈性不同:MongoDB 可以不用事先定義資料欄位和型態,但像是 MySQL 就得先 Create Table 並定義固定的 schema,若後續有想要更改或是新增欄位就會比較麻煩。
所以如果你的資料隨時間變動的可能性較大、具有較高不確定性的話,MongoDB 就會是很好的選擇!
關於差異也可以參考 AWS 所做的表格
| MongoDB | MySQL | |
| 資料模型 | 將資料儲存在 JSON 文件中 然後將其組織到集合中 | 將資料儲存在資料欄和資料列中 資料儲存採用表格式和關聯式方式 |
| 可擴展性 | 使用複寫和碎片化來水平擴展 | 使用垂直擴展和僅供讀取複本來大規模提升效能 |
| 查詢語言 | 使用 MongoDB 查詢語言 | 使用 SQL |
| 效能 | 擅長 INSERT 或 UPDATE 大量記錄 | SELECT 大量記錄時,MySQL 的速度更快 |
| 靈活性 | 沒有結構描述,這點提供更多靈活性 使其能處理非結構化、半結構化和結構化資料 | 具有剛性結構描述,能有效處理結構化資料 |
| 安全性 | 使用 Kerberos、X.509 和 LDAP 憑證來驗證使用者 | 使用內建身分驗證方法 |
基於以上所述的一些差異,在使用 MongoDB 時的操作就會有很多要注意的地方,這次藉由官方最新發布的 MongoDB Best Practices Guide 來整理一些建議的 Tips。
預計會分成四個主題,包含
數據建模要考慮的,就是最後將會如何使用這些數據、如何訪問這些數據,來決定怎麼設計資料儲存的形式。
把可能會一起使用的數據放在同一個 document 中,可以大幅提升查詢效能,因為等於直接省去了很多類似關聯式資料庫的 Join。
如下方的 document,user 的 alerts 是直接寫在每個 user 的資料底下,而不是像 MySQL 那樣另外寫一張叫做 alerts 的資料表,這樣在查詢的時候只要查詢 users 這個 collection 就可以直接取得各自的 alerts,完全不需要再另外 Join。
像這樣的 embedded data (subdocuments) 在 MongoDB 是比較建議的做法,因為也可以讓 UPDATE 資料能在單一一個操作中完成。
// db.users
{
_id: "abc",
email: "xyz@example.com",
preferences: {
alerts:[
{ name: "morning", frequency: "daily", time: { h: 6, m: 0 } }
],
colors: { bg: "#cccccc" }
}
}
由於 MongoDB 每個 document 的最大儲存上限是 16 MB,所以像是餐廳商店的評論,資料的則數和字數都是未知的,用陣列儲存在同一個餐廳商店資料階層底下,有可能就會占用很大的儲存空間。
因此這類的數據就建議每個評論分成不同的 document,並帶有餐廳商店的 ID 做參照 (如下方範例)
// businesses
{
_id: "100",
name: "Bake and Go",
addresses: [
{ street: "40 Elm", state: "NY" },
{ street: "101 Main St", state: "VT" }
]
}
// businessReviews
{
_id: "61e706e2450fac1271c9b27a",
storeId: "100",
rating: 5,
comment: "Best bagels in town, a must try if you are in the area."
}
{
_id: "61e706e2450fac1271c9c522",
storeId: "100",
rating: 5,
comment: "Yummy desserts and a nice place to sit and eat."
}
其實並非所有的 1:1 或 1:many 關聯都可以直接無腦放在同一個文件中,如果有以下狀況的話,就建議將數據放在不同的文件:
下方以一個汽車製造商的資料庫為例,汽車車款 (models) 是陣列的形式,但由於車款的資料通常都是個別顯示 (比較不會有同時需要取用多個車款的情況),所以這樣儲存陣列的話,查詢數據後就會需要進行額外的資料處理。
這是錯誤示範:
// manufacturers
{
_id: "200",
name: "Swaab Automotive",
type: "auto",
models: [
{ name: "Swaab Model X", year: 2022, sku: "SWA-X-22Z"},
{ name: "Swaab Model Y", year: 2022, sku: "SWA-Y-22Z"},
//...
]
}
因此這種情況下,就比較建議將汽車 models 另外獨立成一個 collection,並且帶有製造商 ID 做參照。
而且通常 models 資料的使用也會較獨立,也可能更新比較頻繁,所以把它們拉出來做為另一個 collection 會比較方便讀取和寫入。
正確示範如下:
// models
{
_id: "1201",
name: "Swaab Model X",
year: 2022,
sku: "SWA-X-22Z",
manufacturer_id: "200"
}
{
_id: "1202",
name: "Swaab Model Y",
year: 2022,
sku: "SWA-Y-22Z",
manufacturer_id: "200"
}
為了提高效能,就要避免額外的資料 JOIN 操作,所以建議可以在不同的文件中存放相同的數據。
如下方範例,在 users, posts 兩個 collections 中都存放相同的使用者 Email 數據:
// users
{
_id: "1",
name: { first: "Jane", last: "Doe" }
email: "jdoe@example.com"
}
// posts
{
_id: "1055",
title: "My First Post",
text: "Hello World",
userId: "1",
userEmail: "jdoe@example.com"
}
雖然資料看起來會比較複雜,但 MongoDB 提供了 change streams 和 triggers,來確保跨文件的重複存放數據都能保持同步更新和一致性。
uniq()在 ClickHouse 中很常用又很相像的兩個 Aggregate Function: uniq() 和 uniqExact()
其實 uniqExact(x) 就等同 COUNT(DISTINCT x),若是使用 COUNT(DISTINCT x) 會看到欄位名稱直接被轉成 uniqExact(x)
官方也有提醒:「除非一定要最精確的數字,否則建議使用 uniq」,因為 uniq 可以最佳化 memory 的使用。
以下是官方文件的描述:
uniq(x [, …])
Calculates the approximate number of different values of the argument.
計算輸入參數的不重複的大約數量。
參數可以是
TupleArrayDateDateTimeStringReturned value
uniqExact(x [, …])
Calculates the exact number of different argument values.
計算輸入參數的不重複的精確數量。
可接受的參數和回傳的值跟 uniq 是一樣的。
歡迎追蹤我的 IG 和 Facebook