
6

MongoDB 是一種文件導向型數據庫,使用 BSON(Binary JSON)格式來儲存資料
每個資料庫包含多個集合(collection,=mysql table),每個集合包含了多個文檔(document),文檔是一個鍵值對(key-value pair)的 JSON 對象
PS 在 NoSQL 數據庫中,特別是一些分佈式和高度擴展的 NoSQL 數據庫,有時會放寬對 ACID 特性的要求,以實現更高的性能和擴展性



記住安裝的位置,它不像PostresSQL會自動出現Shell(psql)應用程式,之後要進資料夾找
例如我的是C:\Users\user\AppData\Local\Programs\mongosh\
開啟 mongosh.exe (powershell 介面)

輸入預設mongodb://localhost/,進去系統後就能開始執行了
PS 要用小寫
PS
# 返回統計信息 db.stats
# 查看schema
typeof db.collection_name.findOne().field_name

# runCommand 為 任意命令
# 使用 serverStatus 命令來檢索伺服器的狀態信息
var result = db.runCommand({ serverStatus: 1 });
printjson(result);

PS MAC 安裝方法比較特別,載好後要把 /bin 裡面的兩個檔案,貼到 /mongodb/bin


PS MongoDB Database Tools 包含一系列命令行工具,但沒有特定的圖形化使用者介面(GUI)應用程式。可以通過命令行或終端來使用這些工具
mongodump: 備份
mongorestore: 用於還原從mongodump 創建的備份
mongoexport: 將 MongoDB 數據庫中的數據導出到 JSON 或 CSV 文件
mongoimport: 將 JSON、CSV 或 TSV 文件中的數據導入到 MongoDB 數據庫
mongostat: 顯示實例的統計信息
mongooplog: 回放 oplog 文件以同步數據


MongoDB 表面上看起來採用JSON格式,其實背後存儲和處理數據是BSON格式
資料庫 database
資料表 collection
# 讀取資料庫
show databases;
= show dbs
# 讀取資料表
show collections
不用創建資料庫,只需要use資料庫
db.資料表.insert資料 進資料表後,資料庫會自動創建
mongodb的基礎CRUD
PS _id 不能重複,默認會照順序
“`=
C:
insertOne(data,options)
insertMany(data,options)
R:
find(data,options)
findOne(data,options)
U:
updateOne(filter,data,options)
updateMany(filter,data,options)
replaceOne(filter,data,options)
D:
deleteOne(filter,options)
deleteMany(filter,options)
匯入資料
```=
use test
db.test_student.insertOne({
id: 1,
no: '001',
name: 'Catalina',
phone: '0900000000',
email: 'catalinakuowork@gmail.com'
})
讀取資料表內容
show databases
use test
show collections
db.test_student.find()
= show collections('test_student’)
# 也可以
# 更易讀db.test_student.find().pretty()
# 也可以
# 結果包含較大的數據集時,find()只會出現一部分,toArray()會出現全部
db.test_student.find().toArray()
PS .forEach() 方法來遍歷查詢結果的每一個文檔,通常會搭配別的軟體(Node.js,PHP…)使用
db.user.find().forEach(function(doc) { printjson(doc); });所有年齡為 68 的乘客
“`=
db.passengers.find({age:68}).name
只包含 “name” 欄位,而不包括 “_id” 欄位
db.passengers.find({ age: 68 }, { _id: 0, name: 1 })
查詢資料表內數量
```=
db.test_student.count()
前三筆
db.test_student.find().limit(3)
忽略前三筆
db.test_student.find().skip(3)
排序
db.test_student.find().sort({age:1})
刪除資料表
db.test_student.drop()
# 假設要指定條件
# db.test_student.deleteOne({id:2})
# db.test_student.deleteMany({id:2})
刪除資料庫
use test
db.dropDatabase()
# $eq equal to
db.getCollection('test_data').find({class: {$eq:'A'}})
# 直接相等比較
= db.getCollection('test_data').find({class:'A'})
db.agg.aggregate([("$match":fage:($gt:10,$lte:30)))])
db.getCollection('test_data').find({name:{$in:['Chad','Ethan']}})
authors 字段包含 ‘Bob’ 或 ‘hhhhh’ ,或者具有 timestamp 字段
db.getCollection("library").find({$or:[{authors:{$in:['Bob','hhhhh']}},{timestamp:{$exists:true}}]})
db.getCollection('test_data').find({weight:{$lt:60}})
gt (greater than)
gte (greater than or equal to)
db.getCollection('test_data').find(
{$and:[
{score:{$gte:80}},
{weight:{$lt:80}}
]}
)
db.getCollection('test_data').find(
{$nor:[
{class:'B'},
{weight:{$lt:50}}
]}
)
= db.getCollection('test_data').find(
{$not:{
$or:[
{class:'B'},
{weight:{$lt:50}}
]
}}
)
db.test_data.find({class: {$nin: ['A', 'B', 'C']}})
= db.test_data.find({class: {$not: {$in: ['A', 'B', 'C']}}})
db.test_data.find({age: {$ne: 25}})
= db.test_data.find({age: {$not: {$eq: 25}}})
用 $all 來確保 tags 陣列中包含指定的所有元素,同時我們添加了一個額外的條件
$and 來包裹一個包含兩個條件的陣列。第一個條件確保 tags 陣列等於 [“ssl”, “security”],而第二個條件確保 additionalField 的值為 “additionalValue”
db.test_data.find({tags: {$all: ["ssl", "security"]}, additionalField: "additionalValue"})
= db.test_data.find({$and: [{tags: ["ssl", "security"]}, {additionalField: "additionalValue"}]})
查詢 字段 field 大小(元素的數量)為 2 的所有文檔。
db.test_data.find( { field: { $size: 2 } } )
results 是一個陣列,查詢 results 陣列中值大於等於 80 且小於 85
db.test_data.find( { results: { $elemMatch: { $gte: 80, $lt: 85 } } } )
db.test_data.find(
{class:{$exists:true}}
)
db.test_data.find(
{$and:[{class:{$exists:true}},{class:{$in:['B','C']}}]}
)
db.test_data.find(
{$and:[{class:{$in:['A','C']}},{score:{$gte:60,$lt:90}}]}
)
db.getCollection("user_likes").find({name:"mark"},{id:1,_id:0})
db.getCollection("user_likes").find({$and:[{age:{$gte:30,$lt:60}},{fans:{$gte:200}}]})
# and 可以簡化
= db.getCollection("user_likes").find({age:{$gte:30,$lt:60},fans:{$gte:200}})
db.getCollection("user_likes").find({$or:[{likes:{$lte:100}},{fans:{$lt:100}}]})
# nor
db.getCollection("user_likes").find({$nor:[{age:{$in:[25,26]}}]})
# not+in
= db.getCollection("user_likes").find({$not: {age: {$in: [25, 26]}}})
# nin
= db.getCollection("user_likes").find({age: {$nin: [25, 26]}})
# not nin
=
db.getCollection("user_likes").find({$not: {age: {$nin: [25, 26]}}})
db.user_likes.find({$and:[{age:{$nin:[25,60]}}]},{_id:0,id:1})
db.getCollection("user_likes").find({likes:{$not:{$gt:100}}})
db.linechat.find({messages:{$size:0}})
$regex:、$options:
查詢book字段中包含 “nosql” 字符串的文檔
PS $options:’i’ 表示不區分大小寫
db.library.find({"book":{$regex: /nosql/, $options:'i'}})
= db.library.find({"book":{$regex: 'nosql', $options: 'i'}})
= db.library.find({"book":{$regex: /nosql/i}})
= db.library.find({"book": /nosql/i})
db.library.find({"book":{$regex: /nosql/, $options: ''}})
= db.library.find({"book":{$regex: 'nosql'}})
= db.library.find({"book":{$regex: /nosql/}})
= db.library.find({"book": /nosql/})
查詢 messages.content 包含 “義大利麵” 的,同時排除 _id 字段
db.linechat.find({"messages.content": /義大利麵/}, {"messages.$": 1, _id: 0})
使用 $ 顯示第一個符合條件的
db.linechat.find({messages: {$elemMatch: {content: /義大利麵/}}}, {"messages.$": 1, _id: 0})
name 為 “Mark” 的文檔的age字段設置為 18
PS 更新操作具有 “upsert” 的特性,如果原本的文檔中沒有 age 欄位,更新操作都會自動將 age 欄位添加到文檔中
db.user.updateOne({name:"Mark"},{age:18})
= db.user.updateOne({name:"Mark"},{$set:{age:18}})
age會全部被改成20
db.user.updateMany({},{$set:{age:20}})
都會成功被增加 status 狀態欄
db.flight.updateMany({},{$set : {status : {description: "on-time", lastUpdated: "1 hour ago"}}})
如果希望只有在文檔中已經存在age 欄位的情況下,才進行更新
db.user.updateOne(
{ name: "Mark", age: { $exists: true } },
{ $set: { age: 18 } }
)
將 _id 為 “4-1_1” 的文檔中的 book 字段設置為 “英語會話”
db.getCollection("library").update({"_id":"4-1_1"},{$set:{"book":"英語會話"}})
將所有 authors 字段包含 [“Tomas”,”kim”,”Biga”] 的文檔中的 authors 字段更新為 [“kim”,”Biga”]
db.getCollection("library").update({"authors":["Tomas","kim","Biga"]},{$set:{"authors":["kim","Biga"]}})
取代 replace
# 原始資料
db.test_student.insertOne({
id: 1,
no: '001',
name: 'Catalina',
phone: '0900000000',
email: 'catalinakuowork@gmail.com'
})
db.test_student.replaceOne({id: 1},{ id: 1, no: '002', name: 'Maite', phone: '0900000000', email: 'catalinakuowork@gmail.com' })
PS 這樣會錯!
db.test_student.replaceOne({no:"001"},{name:'Maite'})
雖然mongodb 強調的是彈性和靈活性,不需要嚴格的結構化模式,但為了防止與其他軟體交互錯誤、結構更清楚、查詢效率,很多時候還是會選擇定義一個類似模式的結構
範例
{
"_id": ObjectId(),
"username": "string",
"email": "string",
"age": "number",
"address": {
"street": "string",
"city": "string",
"zipcode": "string"
},
"createdAt": ISODate(),
"updatedAt": ISODate()
}
設置時間
new Date()
new Timestamp()

PS multi 選項表示更新多筆記錄,upsert 選項表示如果沒有找到符合的記錄,則不插入新的記錄
db.account.updateMany({name: "小華", "currency.type": "USD"}, { $mul: {"currency.$.cash": 30}, $set: {"currency.$.type": "TWD"}, $currentDate: {"currency.$.lastModified": {$type: "date"}} }, {multi: true, upsert: false})
所有的studentNumber都改成studentId
db.school.updateMany({},{$rename:{"studentNumber":"studentId"}})
指定小明,studentNumber改成studentId
db.school.updateOne({studentName: "小明"},{$rename:{"studentNumber":"studentId"}})
增加一筆資料在最後面
# 原始
db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] })
# 增加資料
db.user.updateOne({"name":"mark"}, {$push:{"fans":"jack"}})
db.flights.updateOne({id:1}, {$push:{today:new Date()}})
增加多筆資料在最後面
# 原始
db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] })
# 增加資料
db.user.updateOne({"name":"mark"}, {$push:{"fans":{$each:["jacky","Inadry","max"]}}})
# 原始
db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] })
# 保留 fans 陣列的前 2 個元素,刪除多餘的元素。因此包含 "jacky" 和 "Inadry",不包含 "max"
db.user.updateOne({"name":"mark"}, {$push:{"fans":{$each:["jacky","Inadry","max"],$slice: 2}}})
# 原始
db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] })
# 增加"jacky","Inadry","max"後,保留 fans 陣列的後 4 個元素,刪除多餘的元素。因此包含 "stanly","jacky","Inadry","max",不包含 "steven","crisis"
db.user.updateOne({"name":"mark"}, {$push:{"fans":{$each:["jacky","Inadry","max"],$slice: -4}}})
# 原始
db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] })
# 會出現 "steven","crisis","stanly","jack","peter"
db.user.updateOne({"name":"mark"},{$addToSet:{fans:{$each:["steven","jack","peter"]}}})
db.fruit.updateOne({"name": "John"}, {$pull: {likes: "apple"}})
= db.fruit.updateOne({ "name": "John" },{ "$pull": { "likes": { "$in": ["apple"] } } })
假設想要將 John 的 likes 屬性中的所有元素都刪除
可以使用 $unset 將 likes 屬性刪除
db.fruit.updateMany(
{ "likes": { "$exists": true } },
{ "$unset": { "likes": "" }}
)
但是如果 likes 屬性不是所有文件中都存在,則需要使用 $pull 搭配 $exists 來實現
首先使用 $exists 找出所有存在 likes 屬性的文件,之後把like清空
db.fruit.updateMany(
{ "likes": { "$exists": true } },
{ "$pull": { "likes": { "$exists": true } } }
)
分數不得低於60,若低於60,變成60
db.school.updateMany({},{$max:{score:NumberInt(60)}})
= db.school.updateMany({score: {$lt: 60}}, {$set: {score: 60}})
$pop 刪除陣列的第一個或最後一個元素。第一個元素(1)或最後一個元素(-1)
db.user.updateOne({"name":"mark"},{"$pop":{"fans":1}})
# 向 log 陣列中推送一個包含 date 和 size 的新物件
# { upsert: false } 如果找不到匹配的文檔,不創建新文檔
db.drink.updateOne(
{ "_id": "001" },
{
$inc: { "sold": 20 },
$push: { "log": { date: Date.now(), size: "M" } }
},
{ upsert: false }
)
# 找到陳 0955 開頭的人,age排序
db.user.find(
{
$and: [
{ name: { $regex: /陳/ } },
{ phone: { $regex: /0955/ } }
]
}
).sort({ age: 1 })
= db.user.find((name:($regex:/陳/),phone:($regex:/0955/)).sort(age:1)
$sort 由小到大 1 由大到小 -1
db.agg.aggregate([{ $sort: { age: 1 }}])
= db.agg.find({}).sort({ age: 1 })
# 原始
db.agg.insertMany([
{ "id": 1, "name": "mark", "age": 20, "assets": 100000000 }])
# 聚合操作
db.agg.aggregate([{ $project: { id: 1, name: 1 } }])
= db.agg.find({}, { _id: 0, id: 1, name: 1 })
$match 篩選符合條件的文檔(等同sql where)
db.agg.aggregate([("$match":age:($gt:10,$lte:30)))])
= db.agg.find({ age: {$gt:10, $lte:30 }})
# 原始
use testing
db.ox.insertMany(("id" :1 , "status" : "o", "count" : 5})
db.ox.insert(f"id" :2 , "status" : "o", "count" : 5})
db.ox.insert(("id" :3 , "status" : "o", "count" : 5})
db.ox.insert(("id" :4 , "status" : "x", "count" : 10})
db.ox.insert(("id" :5 , "status" : "x", "count" : 10})
db.ox.insert(["id" :6 , "status" : "x", "count" : 11})
# 分組
db.ox.aggregate([("$group":(_id: "$status"))])
= db.ox.distinct("status")

db.ox.aggregate([("$group":(_id: (a:"$status", b:"$count")))])

db.ox.aggregate([("$group":(_id:"$status", total: ("$sum": "$count")))])


db.agg2.aggregate([("$unwind" : "$fans")])
= db.agg2.find({}, {fans: 1, _id: 0 })

db.agg.aggregate([{ $limit: 10 }])
= db.agg3.find({}).limit(10)
# 跳過結果集中的前 5 個文檔
db.agg.aggregate([{ $skip: 5 }])
= db.agg.find({}).skip(5)
db.orders.insertMany([
{ _id: 1, customerId: "C1", amount: 50 },
{ _id: 2, customerId: "C2", amount: 75 },
{ _id: 3, customerId: "C1", amount: 30 }
]);
db.customers.insertMany([
{ _id: "C1", name: "Alice" },
{ _id: "C2", name: "Bob" }
]);
db.orders.aggregate([
{
$lookup: {
from: "customers", // 要關聯的集合名稱
localField: "customerId", // 本地集合的字段
foreignField: "_id", // 外來集合的字段
as: "customerInfo" // 聯合結果的新字段名稱
}
}
]);

show dbs
use school
db.school.insertOne([_id:"001", , list: [30, 40, 50]])
db.school.updateOne((_id:"001"), ($push:("list":80)))
db.school.updateMany((_id:"001"),($push:("list":($each:[80,50,60]))))
db.school.updateMany((_id:"001"),("$pop":("list":-1))
db.school.find()

db.agg.aggregate([
{ $group: { _id: { a: "$status", b: "$count" } } },
{ $sort: { "_id": 1 } },
{ $skip: 5 }
])
db.agg.aggregate([{$match:{sex:"M"}},{$sort:{age:1}},{$skip:1},{$limit:1}])
加上{ $project: {name:1, age:1}},只投影name、age
db.agg.aggregate([{$match:{sex:"M"}},{$project:{name:1, age:1}},{$sort:{age:1}},{$skip:1},{$limit:1}])
使用嵌套文檔,address 是一個包含 street 和 city 字段的子文檔。這種方式適用於表示一個完整的地址信息,並且可以方便地查詢整個地址
{ id: "001", username: "catalina", address: { street: 'bade st', city: 'taipei' } }
使用了數組,address 是一個包含兩個元素的數組,每個元素都是包含一個字段的子文檔。這種方式適用於表示部分地址信息,每個元素可以單獨地表示一個地址的一部分。
數組(Array)通常包含多個相似值的集合,而使用物件(Object)來表示鍵值對
{ id: "001", username: "catalina", address: [{ street: 'bade st' }, { city: 'taipei' }] }
在 MongoDB 中,數據之間的關係通常是透過嵌套文檔(Nested Documents, Embedded)或引用(References)來實現的
# 兩個collections
db.patients.insertOne({name:"Catalina", age:"26", diseaseSummary:"summary-max-1"})
db.diseaseSummaries.insertOne({id:"summary-max-1", diseases:["cold","covid"]})
定義變數
var dsid = db.patients.findOne().diseaseSummary

查詢
db.diseaseSummaries.findOne({id:dsid})

# 一個collection 插入購買者+車子資訊、購買者+個人資訊
db.person.insertOne({name:"catalina",car:{model:"bmw",price:40000}})
db.person.insertOne({name:"catalina",age:26, salary:50000)

插入引用
db.person.insertOne({model:"bmw", price:40000, owner: ObjectId('65645a027ed280d551a4fb2b')})

db.questionThreads.insertOne({
creator: "catalina",
question: "How is it going?",
answers: [
{ _id: "q1a1" },
{ _id: "q1a2" }
]
});
db.answers.insertMany([{_id: "q1a1", text: "nice"}, {_id: "q1a2", text: "awesome!"}])
定義變數
var questionThreadId = db.questionThreads.findOne()._id;
查詢
db.answers.find({ _id: { $in: db.questionThreads.findOne().answers.map(a => a._id) } })
db.questionThreads.insertOne({
creator: "catalina",
question: "How is it going?",
answers: [
{ _id: "q1a1" },
{ _id: "q1a2" }
]
});
db.answers.insertMany([{_id: "q1a1", text: "nice"}, {_id: "q1a2", text: "awesome!"}])
插入
db.questionThreads.insertOne({creator:"catalina",question: 'How is it going?',answers: [ {text: 'nice'},{text: 'awesome!'}]})

插入引用
db.cities.insertOne({name: "New York City", coordinates: {lat: 21, llng:55}})
# 出現的 ObjectId,貼到下面
db.citizens.insertMany([{name:"catalina",cityId: ObjectId("65646a337
ed280d551a4fb31")}, {name : "Eva", cityId: ObjectId("65646a337ed280d551a4fb31")}
])


查詢
db.citizens.find()

db.products.insert({title:"book", price:"12.22"})
db.customers.insert({name:"catalina", age:26})
更新 customers表
db.customers.updateMany( { $or: [{ name: "Tomas" }, { name: "catalina" }] }, { $set: { orders: [{ productId: ObjectId('656480076e814db5173266c4'), quantity: 5 }] } } );
少用~ 兩邊數據都要改,很容易出錯
map: map函數.主要功能為產生key給reduce
reduce:reduce函數
out: 輸出結果集合的名稱
query: 在map前,可用query先進行篩選
sort: 在map前.可用sort進行排序
limit: 在map前,可限制數量
finalize: 可以將reduce的結果丟給某個key
scope: 可以在is中使用變數
求id 1 2

# 計算每個 class 的總價(price * count),並將結果存儲在 "mapReduceTest" 這個結果集合中
var result = db.order.mapReduce(
function(){
var total = this.price * this.count
emit(this.class,total)
},
function(key,values )(
var total = 0;
for(var i=0;i<values.length;i++)
(total+=values[i];
}
return total;
},
{out : "mapReduceTest"}
)

id 2 3 + dollar

var result = db.order_2.mapReduce(
function(){
var total = thos.price * this.count
emit(this.class, total)
},
function(key,values){
var total = 0;
for(var i=0;i<values.length;i++){
total += values[i];
}
return total;
},
{out: "test",
query:(class:("$in":["2","3"])),
finalize:function(key, reducedVal){
reducedVal = reducedVal + "dollar";
return reducedVal;
}
}
)
通常會使用在
一般用法,沒設索引
db.tests.insertOne({ "x": "hello" })
有設索引
db.tests.createIndex({ "x": 1 })
= db.tests.ensureIndex({ "x": 1 })
防止錯誤
如果輸入不符合createCollection定義的規則會錯誤
db.createCollection('exampleCollection', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['field1', 'field2'],
properties: {
field1: {
bsonType: 'string',
description: '必須是一個字符串'
},
field2: {
bsonType: 'number',
description: '必須是一個數字'
}
}
}
}
});
db.user_1.find({}).sort({age:1})
db.user_2.find({}).sort({age:1})
<br/>
- 練習二
一般用法,沒設索引
使用 for 迴圈插入 100,000 條數據到 test_2 集合
使用 find 查詢 "x" 大於 50,000 的數據,然後使用 sort 將結果按 "x" 字段降序排列,最後使用 explain 顯示查詢的執行統計信息
```=
for (var i = 0; i < 100000; i++) {
db.test_2.insertOne({ "x": i });
}
db.test_2.find({ "x": { "$gt": 50000 } }).sort({"x": -1}).explain("executionStats");
設索引
在test_2 集合的 “x” 字段上創建一個升序索引
for (var i = 0; i < 100000; i++) {
db.test_2.insertOne({ "x": i });
}
db.test_2.ensureIndex({"x": 1});
db.test_2.find({ "x": { "$gt": 50000 } }).sort({"x": -1}).explain("executionStats");