# **【MongoDB 是什麼?Mongodb 優勢、安裝/指令】** :::info - 什麼是MongoDB? - Windows安裝MongoDB (Powershell、Compass、mongoimport) - JSON、BSON差異 - MongoDB 基礎操作 - insert、find、delete - .count() - .skip() - .sort({field:1}) - 查詢 - $eq - $match - $in - $exists、 - lt (less than)、lte (less than or equal to) - gt (greater than)、gte (greater than or equal to) - nor (=not+or) - nin (=not+in) - ne (=not) - $all - $size - $elemMatch - 查詢練習 - 查詢正則表達式 - $regex:、$options: - 查詢正則表達式練習 - update 更新 - 運算子 - $mul - $set - $currentDate - $rename - $push 更新陣列運算子(一筆資料) - $each 更新陣列運算子(多筆資料) - $slice 保留陣列的固定數量 - $addToSet 保留不重複資料 - $pull 把相符的值都拉走(刪除) - $unset - $max - $pop - $inc - $regex $sort - $project - $match - $group 分組 - $group 分組,自定義a、b欄位 - $group 分組,sum = 將count的值相加 - $unwind 展開陣列字串 - $limit - $skip - 運算子練習 - 數據結構(嵌套文檔、數組) - Map-Reduce 操作 - 設置索引(加速對集合中數據的查詢,但較耗容量) - 設置索引練習 ::: ### 什麼是MongoDB? ![MongoDB-database-colection](https://hackmd.io/_uploads/BkPrp7xBa.png) MongoDB 是一種文件導向型數據庫,使用 BSON(Binary JSON)格式來儲存資料 每個資料庫包含多個集合(collection,=mysql table),每個集合包含了多個文檔(document),文檔是一個鍵值對(key-value pair)的 JSON 對象 - 自由靈活 : 不需要像其他關聯式資料庫(Mysql, PostgreSQL, MSSQL)設SCHEMA,自由添加欄位,也不需要先創建表格,use database_name 後,數據可以直接 db.database_name.insertOne({})寫入 - 支援多種複雜的數據結構 : 嵌套的文檔、數組和其他數據類型...適合處理具有多層次結構的數據 - 良好的擴展性 : 可以水平擴展到多個節點,實現更好的性能和容量。這通常通過在集群中添加更多的機器來實現 - 高性能 : 使用索引來實現快速查詢,支援各種類型的索引,地理、圖形、文本搜索... - 自動故障轉移 : 複製工具(稱為副本集)有自動故障轉移功能,提供資料的高可用性(High Availability)。 >PS 在 NoSQL 數據庫中,特別是一些分佈式和高度擴展的 NoSQL 數據庫,有時會放寬對 ACID 特性的要求,以實現更高的性能和擴展性 <br/> ### Windows安裝MongoDB - 到官網上方選取 community server ![螢幕擷取畫面 2023-11-26 102131](https://hackmd.io/_uploads/r15MLVlHp.png) - 選擇要安裝的版本,下面選msi ![螢幕擷取畫面 2023-11-26 102159](https://hackmd.io/_uploads/ByNE8NeBa.png) - Powershell 安裝 ![螢幕擷取畫面 2023-11-26 110457](https://hackmd.io/_uploads/Sk26INer6.png) 記住安裝的位置,它不像PostresSQL會自動出現Shell(psql)應用程式,之後要進資料夾找 例如我的是C:\Users\user\AppData\Local\Programs\mongosh\ 開啟 mongosh.exe (powershell 介面) ![螢幕擷取畫面 2023-11-26 112503](https://hackmd.io/_uploads/B1Z2cExSp.png) 輸入預設mongodb://localhost/,進去系統後就能開始執行了 >PS 要用小寫 ![螢幕擷取畫面 2023-11-26 113904](https://hackmd.io/_uploads/HyGeANgST.png) >PS ```= # 返回統計信息 db.stats ``` ![螢幕擷取畫面 2023-11-27 155717](https://hackmd.io/_uploads/HyUe3pZBT.png) ```= # 查看schema typeof db.collection_name.findOne().field_name ``` ![螢幕擷取畫面 2023-11-27 155641](https://hackmd.io/_uploads/BJpaoaZH6.png) ``` # runCommand 為 任意命令 # 使用 serverStatus 命令來檢索伺服器的狀態信息 var result = db.runCommand({ serverStatus: 1 }); printjson(result); ``` ![螢幕擷取畫面 2023-11-27 184251](https://hackmd.io/_uploads/S16nMgMBT.png) >PS MAC 安裝方法比較特別,載好後要把 /bin 裡面的兩個檔案,貼到 /mongodb/bin <br/> - Compass (ui介面) 如果沒自動安裝,可以手動安裝 ![螢幕擷取畫面 2023-11-26 104437](https://hackmd.io/_uploads/S1L5UNgrT.png) <br/> - mongoimport (匯入資料的工具) 官網下載 [Installing the Database Tools](https://www.mongodb.com/docs/database-tools/installation/installation/) 進去點選 on windows [(連結)](https://www.mongodb.com/docs/database-tools/installation/installation-windows/) 下滑,看到MongoDB Download Center 點進去下載 ![螢幕擷取畫面 2023-11-26 114805](https://hackmd.io/_uploads/BJemgreHa.png) >PS MongoDB Database Tools 包含一系列命令行工具,但沒有特定的圖形化使用者介面(GUI)應用程式。可以通過命令行或終端來使用這些工具 > >mongodump: 備份 >mongorestore: 用於還原從mongodump 創建的備份 >mongoexport: 將 MongoDB 數據庫中的數據導出到 JSON 或 CSV 文件 >mongoimport: 將 JSON、CSV 或 TSV 文件中的數據導入到 MongoDB 數據庫 >mongostat: 顯示實例的統計信息 >mongooplog: 回放 oplog 文件以同步數據 <br/> - (可選) 另一種ui介面,studio 3T,滿好用的,但有限期 ![studio3T_img](https://hackmd.io/_uploads/SkUVDNgHp.png) <br/> - 連接雲 [MongoDB Atlas] (https://www.mongodb.com/atlas/database) ![螢幕擷取畫面 2023-11-27 103904](https://hackmd.io/_uploads/SJQhWFZBT.png) - Python [How to Use Python with MongoDB](https://www.mongodb.com/languages/python) - [更多Drivers](https://www.mongodb.com/docs/drivers/) <br/> ### JSON、BSON差異 MongoDB 表面上看起來採用JSON格式,其實背後存儲和處理數據是BSON格式 - JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,包含字符串、數字、對象、數組、布爾值和 null ...基本數據類型 - BSON(Binary JSON)是二進制 JSON 的簡稱,在 JSON 的基礎上添加了一些額外的數據類型,日期、二進制數據(例如我們會看到 "_id":OnjectID("...")、正則表達式等...能更好地支持 MongoDB 數據庫 <br/> ### MongoDB 基礎操作 資料庫 database 資料表 collection ☑️ 資料庫 (CREATE、READ、DELETE) ```= # 讀取資料庫 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() ``` <br/> ### 查詢 等於 = ```= # $eq equal to db.getCollection('test_data').find({class: {$eq:'A'}}) # 直接相等比較 = db.getCollection('test_data').find({class:'A'}) ``` match 篩選符合條件的文檔(等同sql where) ```= db.agg.aggregate([("$match":fage:($gt:10,$lte:30)))]) ``` or ```= 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}}]}) ``` < >= lt (less than) lte (less than or equal to) ```= 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}} ]} ) ``` nor 不是其中之一 (不滿足以下條件之一) ```= db.getCollection('test_data').find( {$nor:[ {class:'B'}, {weight:{$lt:50}} ]} ) = db.getCollection('test_data').find( {$not:{ $or:[ {class:'B'}, {weight:{$lt:50}} ] }} ) ``` nin 都不是 ```= db.test_data.find({class: {$nin: ['A', 'B', 'C']}}) = db.test_data.find({class: {$not: {$in: ['A', 'B', 'C']}}}) ``` ne 不是 ```= db.test_data.find({age: {$ne: 25}}) = db.test_data.find({age: {$not: {$eq: 25}}}) ``` $all 用 $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"}]}) ``` $size 查詢 字段 field 大小(元素的數量)為 2 的所有文檔。 ```= db.test_data.find( { field: { $size: 2 } } ) ``` $elemMatch,運算符用於指定一個或多個條件 results 是一個陣列,查詢 results 陣列中值大於等於 80 且小於 85 ```= db.test_data.find( { results: { $elemMatch: { $gte: 80, $lt: 85 } } } ) ``` <br/> ### 查詢練習 - 是否有class字段的文檔 ```= db.test_data.find( {class:{$exists:true}} ) ``` - 具有 class 字段且班級是 'B' 或 'C' 的文檔 ```= db.test_data.find( {$and:[{class:{$exists:true}},{class:{$in:['B','C']}}]} ) ``` - 班級是 'A' 或 'C' 且分數大於等於 60 且小於 90 的文檔 ```= db.test_data.find( {$and:[{class:{$in:['A','C']}},{score:{$gte:60,$lt:90}}]} ) ``` - 查詢 name 等於 "mark",只返回符合條件的文檔中的 id 字段,同時排除 _id 字段 ```= db.getCollection("user_likes").find({name:"mark"},{id:1,_id:0}) ``` - 查詢年紀30歲(含)~60歲(不含)且fans有200人以上的人 ```= 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}}) ``` - 查詢fans在100以下(含)或likes在100以下的人(不含) ```= db.getCollection("user_likes").find({$or:[{likes:{$lte:100}},{fans:{$lt:100}}]}) ``` - 查詢 age不為25或60的人 ```= # 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]}}}) ``` - 查詢age不為25、60的人,並且只給我id就好 ```= db.user_likes.find({$and:[{age:{$nin:[25,60]}}]},{_id:0,id:1}) ``` - 查詢likes小於等於100的人(使用 $not) ```= db.getCollection("user_likes").find({likes:{$not:{$gt:100}}}) ``` - 查詢$size 0 沒有任何聊天紀錄的聊天室 ```= db.linechat.find({messages:{$size:0}}) ``` <br/> ### 查詢正則表達式 $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/}) ``` <br/> ### 查詢正則表達式練習 查詢 messages.content 包含 "義大利麵" 的,同時排除 _id 字段 ```= db.linechat.find({"messages.content": /義大利麵/}, {"messages.$": 1, _id: 0}) ``` 使用 $ 顯示第一個符合條件的 ```= db.linechat.find({messages: {$elemMatch: {content: /義大利麵/}}}, {"messages.$": 1, _id: 0}) ``` <br/> ### update 更新 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'}) ``` ![螢幕擷取畫面 2023-11-27 122057](https://hackmd.io/_uploads/HJfrYq-r6.png) <br/> ### Schema 雖然mongodb 強調的是彈性和靈活性,不需要嚴格的結構化模式,但為了防止與其他軟體交互錯誤、結構更清楚、查詢效率,很多時候還是會選擇定義一個類似模式的結構 範例 ```= { "_id": ObjectId(), "username": "string", "email": "string", "age": "number", "address": { "street": "string", "city": "string", "zipcode": "string" }, "createdAt": ISODate(), "updatedAt": ISODate() } ``` 設置時間 new Date() new Timestamp() ![螢幕擷取畫面 2023-11-27 152746](https://hackmd.io/_uploads/r1sxL6-Sp.png) <br/> ### 運算子 $mul 乘以 30 $set currency重新設置為 "TWD" $currentDate lastModified 字段設置為當前日期 >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}) ``` $rename 改名 所有的studentNumber都改成studentId ```= db.school.updateMany({},{$rename:{"studentNumber":"studentId"}}) ``` 指定小明,studentNumber改成studentId ```= db.school.updateOne({studentName: "小明"},{$rename:{"studentNumber":"studentId"}}) ``` $push 更新陣列運算子 增加一筆資料在最後面 ```= # 原始 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()}}) ``` $each 更新陣列運算子 增加多筆資料在最後面 ```= # 原始 db.user.insertOne({ "name" : "mark", "fans" : ["steven","crisis","stanly"] }) # 增加資料 db.user.updateOne({"name":"mark"}, {$push:{"fans":{$each:["jacky","Inadry","max"]}}}) ``` $slice 保留陣列的固定數量 ```= # 原始 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}}}) ``` $addToSet 保留不重複資料 ```= # 原始 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"]}}}) ``` $pull 把相符的值都拉走(刪除) ```= db.fruit.updateOne({"name": "John"}, {$pull: {likes: "apple"}}) = db.fruit.updateOne({ "name": "John" },{ "$pull": { "likes": { "$in": ["apple"] } } }) ``` $unset 假設想要將 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 } } } ) ``` $max 分數不得低於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}}) ``` $inc 增加或減少數字型欄位的值 ```= # 向 log 陣列中推送一個包含 date 和 size 的新物件 # { upsert: false } 如果找不到匹配的文檔,不創建新文檔 db.drink.updateOne( { "_id": "001" }, { $inc: { "sold": 20 }, $push: { "log": { date: Date.now(), size: "M" } } }, { upsert: false } ) ``` $regex $sort ```= # 找到陳 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) ``` -----aggregate $sort 由小到大 1 由大到小 -1 ```= db.agg.aggregate([{ $sort: { age: 1 }}]) = db.agg.find({}).sort({ age: 1 }) ``` $project 對結果進行投影 1 True, 0 False ``` # 原始 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 }}) ``` $group 分組 ```= # 原始 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") ``` ![螢幕擷取畫面 2023-11-26 182206](https://hackmd.io/_uploads/H17v25xB6.png) $group 分組,自定義a、b欄位 ```= db.ox.aggregate([("$group":(_id: (a:"$status", b:"$count")))]) ``` ![螢幕擷取畫面 2023-11-26 180253](https://hackmd.io/_uploads/HkQkdcera.png) $group 分組,sum = 將count的值相加 ```= db.ox.aggregate([("$group":(_id:"$status", total: ("$sum": "$count")))]) ``` ![螢幕擷取畫面 2023-11-26 180218](https://hackmd.io/_uploads/SJ9yd5erp.png) $unwind 展開陣列字串 ![1700993034149](https://hackmd.io/_uploads/S1KSd5eS6.jpg) ```= db.agg2.aggregate([("$unwind" : "$fans")]) = db.agg2.find({}, {fans: 1, _id: 0 }) ``` ![螢幕擷取畫面 2023-11-26 180436](https://hackmd.io/_uploads/SkZL_ceHT.png) $limit ```= db.agg.aggregate([{ $limit: 10 }]) = db.agg3.find({}).limit(10) ``` $skip ```= # 跳過結果集中的前 5 個文檔 db.agg.aggregate([{ $skip: 5 }]) = db.agg.find({}).skip(5) ``` $lookup ```= 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" } ]); ``` 使用 $lookup 進行關聯 ```= db.orders.aggregate([ { $lookup: { from: "customers", // 要關聯的集合名稱 localField: "customerId", // 本地集合的字段 foreignField: "_id", // 外來集合的字段 as: "customerInfo" // 聯合結果的新字段名稱 } } ]); ``` ![螢幕擷取畫面 2023-11-27 182324](https://hackmd.io/_uploads/B1q7A1GHp.png) <br/> ### 運算子練習 - 增加 80 增加 [80,50,60] 刪除最後一筆 ```= 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() ``` ![螢幕擷取畫面 2023-11-26 173840](https://hackmd.io/_uploads/B1z4z9eSp.png) - $group,然後按 _id升序排序,再跳過前 5 個結果 ```= 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}]) ``` <br/> ### 數據結構 (嵌套文檔、數組) 使用嵌套文檔,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' }] } ``` <br/> ### Relationship 關係 在 MongoDB 中,數據之間的關係通常是透過嵌套文檔(Nested Documents, Embedded)或引用(References)來實現的 - ONE TO ONE **嵌套文檔(Nested Documents, Embedded)** ```= # 兩個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 ``` ![螢幕擷取畫面 2023-11-27 164741](https://hackmd.io/_uploads/BJ_yO0ZHT.png) 查詢 ```= db.diseaseSummaries.findOne({id:dsid}) ``` ![螢幕擷取畫面 2023-11-27 164818](https://hackmd.io/_uploads/BkleO0WrT.png) <br/> **引用(References),使用ObjectId("...")** ```= # 一個collection 插入購買者+車子資訊、購買者+個人資訊 db.person.insertOne({name:"catalina",car:{model:"bmw",price:40000}}) db.person.insertOne({name:"catalina",age:26, salary:50000) ``` ![螢幕擷取畫面 2023-11-27 170159](https://hackmd.io/_uploads/H10MiAbrp.png) 插入引用 ```= db.person.insertOne({model:"bmw", price:40000, owner: ObjectId('65645a027ed280d551a4fb2b')}) ``` ![螢幕擷取畫面 2023-11-27 170249](https://hackmd.io/_uploads/HyCSs0-H6.png) <br/> - ONE TO MANY **嵌套文檔(Nested Documents, Embedded)** ```= 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) } }) ``` <br/> **嵌套文檔(Nested Documents, Embedded)** ```= 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!'}]}) ``` ![螢幕擷取畫面 2023-11-27 175959](https://hackmd.io/_uploads/Bk0j_1GBp.png) <br/> **引用(References),使用ObjectId("...")** 插入引用 ```= 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")} ]) ``` ![螢幕擷取畫面 2023-11-27 181247](https://hackmd.io/_uploads/BkE6jkfra.png) ![螢幕擷取畫面 2023-11-27 181334](https://hackmd.io/_uploads/SJCAo1GBa.png) 查詢 ```= db.citizens.find() ``` ![螢幕擷取畫面 2023-11-27 181107](https://hackmd.io/_uploads/H1RLjkfB6.png) <br/> - MANY TO MANY **嵌套文檔(Nested Documents, Embedded)** ```= 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 }] } } ); ``` <br/> **引用(References),使用ObjectId("...")** 少用~ 兩邊數據都要改,很容易出錯 <br/> ### Map-Reduce 操作 >map: map函數.主要功能為產生key給reduce > >reduce:reduce函數 > >out: 輸出結果集合的名稱 > >query: 在map前,可用query先進行篩選 > >sort: 在map前.可用sort進行排序 > >limit: 在map前,可限制數量 > >finalize: 可以將reduce的結果丟給某個key > >scope: 可以在is中使用變數 > <br/> 求id 1 2 ![螢幕擷取畫面 2023-11-26 184203](https://hackmd.io/_uploads/SJmGZslra.png) ```= # 計算每個 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"} ) ``` ![螢幕擷取畫面 2023-11-26 184232](https://hackmd.io/_uploads/rkdmWixSa.png) <br/> id 2 3 + dollar ![螢幕擷取畫面 2023-11-26 184308](https://hackmd.io/_uploads/rJTrbjgHp.png) ```= 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; } } ) ``` <br/> ### 設置索引(加速對集合中數據的查詢,但較耗容量) 通常會使用在 - 查詢頻繁的字段 - 聚合、排序和分組操作 - 唯一性要求 - 模糊查詢 一般用法,沒設索引 ```= 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: '必須是一個數字' } } } } }); ``` <br/> ### 設置索引練習 - 練習一 設索引 {age:1,name:1} 會比較快 ```= db.user_1.ensureIndex({name:1,age:1}) db.user_2.ensureIndex({age:1,name:1}) 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"); ``` <br/> 設索引 在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"); ```