MongoDB Tutorial(2)MongoDB 的 Query Language by MonsterSupreme | CodeData
top

MongoDB Tutorial(2)MongoDB 的 Query Language

分享:

MongoDB Tutorial(1)雲端時代的 MongoDB 環境建置 裡頭,我們帶大家註冊了 MongoLab 帳號、建立了 cities Database、簡單介紹了 MongoDB 的 Database、Collection、與 Document 觀念,然後透過 Web 介面新增刪除查詢修改了幾筆資料做示範。不過畢竟是 Web 介面,不適合平常工作使用,所以我們必須另外找個工具,方便我們介紹 MongoDB 的 Query Language。

MongoDB 本身其實是有提供自己的 Client 端命令列工具。因為 MongoDB 上層透過 JSON 格式來交換資料、底層透過 BSON (Binary JSON) 格式來儲存資料,所以 Client 端的命令列工具很自然地就採用 JavaScript 作為彼此溝通的語言。不過因為是命令列工具,在 Windows 作業系統底下,就脫離不了 Command Prompt 的原罪,編輯中文的時候游標位置很容易算錯。

Robomongo 提供的圖形介面比起 MongoDB 的 JavaScript Shell 要方便的多,而且支援 Windows、Mac OS X、與 Linux 等平台,所以我們先安裝 Robomongo,再進行 Query Language 的練習。

Robomongo

Robomongo Installation

安裝 Robomongo 的方式很簡單,只要到 Robomongo 網站下載 Robomongo-0.8.4-i386.exe 檔案,執行之後照著畫面提示安裝即可。

MongoLab cities Database Information

登入 MongoLab,從 cities Database 頁面,我們可以知道 cities 所在的 MongoDB Server 相關資訊:

  • Host/Port:dbh55.mongolab.com:27557
  • Database:cities

MongoLab cities

點選 Users 標籤頁,我們可以看到上次加入的 User 帳號:

MongoLab cities

Robomongo Configuration

第一次執行 Robomongo,會看到 MongoDB Connections 對話方塊:

Configuration 01

按下畫面左上方的 Create 連結,會看到 Connection Settings 對話方塊,Connection 標籤頁 Name 欄位請自行取個連線名稱,比方說 MongoLab Cities,Address 欄位請填入剛剛查出來的 cities Database 所在 Server 與 Port:

Configuration 02

Authentication 標籤頁請勾選 Perform authentication 查核方塊,Database 欄位請輸入 cities,User Name 與 Password 欄位請輸入剛剛看到、自己之前在 cities Database 所建立的帳號與密碼 (不是 MongoLab 網站的帳號密碼喔):

Configuration 03

Advanced 標籤頁 Default Database 欄位請輸入 cities

Configuration 04

按下 Test 按鈕測試:

Configuration 05

測試成功的話,再按下 Save 按鈕儲存:

Configuration 06

回到 MongoDB Connections 對話方塊,點選 MongoLab Cities,再按下 Connect 按鈕:

Configuration 07

就可以連上 MongoLab 的 MongoDB Server:

Configuration 08

在 MongoLab 節點上按下滑鼠右鍵,選取 Open Shell,就可以開啟一個 Command-Line Shell:

Configuration 09

方便我們對 cities Database 進行底下的操作練習。

CRUD – Create

MongoDB Document 的 Create 方式:

  • 透過 db.collectionName.insert(document, options) 方法達成
  • 如果 document 沒有 _id 欄位,會自動加入,型別是 ObjectId
  • 如果 document_id 欄位,內容在 Collection 裡頭必須是唯一的,否則會產生 Duplicate Key 例外

比方說,如果要建立 publishers Collection,然後新增底下 2 個 Document:

publisherId publisherName
PH Prentice Hall PTR
OA O'Reilly & Associates

指令如下:

db.publishers.insert(
    {publisherId: "OA", publisherName: "O'Reilly & Associates"});
db.publishers.insert(
    {publisherId: "PH", publisherName: "Prentice Hall PTR"});

畫面如下:

Collection 00

Robomongo 的指令輸入區是個神奇的地方:

  • 看起來似乎只是個單行的 Text Box,實際上是個多行的 Text Area,可以按 ENTER 鍵換行,無聊的話也可以將指令跟參數縮排對齊,方便檢查有無輸入錯誤。
  • 因為是透過 JavaScript 語法進行 MongoDB 操作,每個指令其實都是一個 JavaScript 敘述,所以每個指令之間記得用 ; 結尾的話,就可以同時輸入多個指令。
  • 手不想離開鍵盤去按上面的綠色三角執行圖示,也可以直接按 Ctrl+ENTER 鍵執行指令。

很方便吧!

展開左邊的樹狀架構的 Cities、Collection,在 publishers 上點兩下,就可以看到剛剛輸入的結果:

Collection 01 Text Mode

剛剛看到的是 Text Mode 檢視。畫面右邊有三個小圖示,可以分別切換 Tree Mode、Table Mode、與剛剛的 Text Mode:

Collection 01 Tree Mode

Collection 01 Table Mode

如果要建立 books Collection,然後新增底下 4 個 Document:

_id isbn title releaseDate listPrice pubId
1 0131002872 Thinking in Java 2002-12-01 54.99 PH
2 059600530X Enterprise JavaBeans 2004-06-02 44.95 OA
3 0596005717 Head First EJB 2003-10-03 44.95 OA
4 0596004656 Head First Java 2003-05-04 39.95 OA

指令如下:

db.books.insert({_id: 1, isbn: "0131002872", title: "Thinking in Java", 
    releaseDate: "2002-12-01", listPrice: 54.99, pubId: "PH"});
db.books.insert({_id: 2, isbn: "059600530X", title: "Enterprise JavaBeans", 
    releaseDate: "2004-06-02", listPrice: 44.95, pubId: "OA"});
db.books.insert({_id: 3, isbn: "0596005717", title: "Head First EJB", 
    releaseDate: "2003-10-03", listPrice: 44.95, pubId: "OA"});
db.books.insert({_id: 4, isbn: "0596004656", title: "Head First Java", 
    releaseDate: "2003-05-04", listPrice: 39.95, pubId: "OA"});

CRUD – Retrieve

MongoDB Document 的 Query 方式:

  • 只能從單一一個 Collection 找出符合條件的 Document
  • 透過 db.collectionName.find(criteria, projection) 方法達成,回傳 Cursor
  • 跟 SQL 語言的 WHERE 子句很像
  • findOne 方法跟 find 方法一樣,不過只傳回一個 Document
  • criteria 就是一個 JSON 物件

比方說,如果要查詢所有的出版社與書籍資料,指令如下:

db.publishers.find();
db.books.find();

也就是剛剛在 publishersbooks Collection 上面用滑鼠點兩下,Robomongo 直接帶出的指令。

如果要查詢 OA 出版社出版的所有書籍資料,指令如下:

db.books.find({pubId: "OA"});

/* 0 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "releaseDate" : "2004-06-02",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 1 */
{
    "_id" : 3,
    "isbn" : "0596005717",
    "title" : "Head First EJB",
    "releaseDate" : "2003-10-03",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 2 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 39.95,
    "pubId" : "OA"
}

Query Selector 提供類似 SQL 敘述裡面 where 子句的各種運算,方便撰寫查詢條件:

  • $lt$lte$gt$gte
  • $neq
  • $exists
  • $in$nin$all
  • $or$not
  • $mod

比方說,如果要查詢所有定價超過 50 元美金的書籍資料,指令如下:

db.books.find({listPrice: {$gte: 50}});

/* 0 */
{
    "_id" : 1,
    "isbn" : "0131002872",
    "title" : "Thinking in Java",
    "releaseDate" : "2002-12-01",
    "listPrice" : 54.99,
    "pubId" : "PH"
}

如果要查詢 OA 出版社出版的所有書籍中,書名出現過 Java 的書籍資料,指令如下:

db.books.find({pubId: "OA", title: /.*Java.*/g});

/* 0 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "releaseDate" : "2004-06-02",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 1 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 39.95,
    "pubId" : "OA"
}

如果要查詢 OAPH 出版社出版的所有書籍資料,指令如下:

db.books.find({$or: [{pubId: "OA"}, {pubId: "PH"}]}); 
或
db.books.find({pubId: {$in: ["OA", "PH"]}});

/* 0 */
{
    "_id" : 1,
    "isbn" : "0131002872",
    "title" : "Thinking in Java",
    "releaseDate" : "2002-12-01",
    "listPrice" : 54.99,
    "pubId" : "PH"
}

/* 1 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "releaseDate" : "2004-06-02",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 2 */
{
    "_id" : 3,
    "isbn" : "0596005717",
    "title" : "Head First EJB",
    "releaseDate" : "2003-10-03",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 3 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 39.95,
    "pubId" : "OA"
}

Query 預設會傳回所有欄位,所以如果想要指定回傳的欄位與順序,請加上 projection 參數,field: 1 表示 Query 結果必須包含這個欄位,field: 0 表示不要。除非特別指明,否則 _id 欄位預設都會回傳。

比方說,如果要查詢 OA 出版社出版的所有書籍,只需要書名與定價資料,指令如下:

db.books.find({pubId: "OA"}, {title:1, listPrice: 1, _id: 0});

/* 0 */
{
    "title" : "Enterprise JavaBeans",
    "listPrice" : 44.95
}

/* 1 */
{
    "title" : "Head First EJB",
    "listPrice" : 44.95
}

/* 2 */
{
    "title" : "Head First Java",
    "listPrice" : 39.95
}

如果要查詢所有書籍,不想要出版日期,指令如下:

db.books.find(null, {releaseDate: 0});

/* 0 */
{
    "_id" : 1,
    "isbn" : "0131002872",
    "title" : "Thinking in Java",
    "listPrice" : 54.99,
    "pubId" : "PH"
}

/* 1 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 2 */
{
    "_id" : 3,
    "isbn" : "0596005717",
    "title" : "Head First EJB",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

/* 3 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "listPrice" : 39.95,
    "pubId" : "OA"
}

搭配 sortlimitskip 等方法,可以控制輸出的順序,或是進行分頁。

比方說,如果要查詢所有書籍,只需要書名與定價資料,根據定價由小排到大或由大排到小,指令如下:

db.books.find(null, {title:1, listPrice: 1, _id: 0}).sort({listPrice: 1});

/* 0 */
{
    "title" : "Head First Java",
    "listPrice" : 39.95
}

/* 1 */
{
    "title" : "Enterprise JavaBeans",
    "listPrice" : 44.95
}

/* 2 */
{
    "title" : "Head First EJB",
    "listPrice" : 44.95
}

/* 3 */
{
    "title" : "Thinking in Java",
    "listPrice" : 54.99
}

db.books.find(null, {title:1, listPrice: 1, _id: 0}).sort({listPrice: -1});

/* 0 */
{
    "title" : "Thinking in Java",
    "listPrice" : 54.99
}

/* 1 */
{
    "title" : "Enterprise JavaBeans",
    "listPrice" : 44.95
}

/* 2 */
{
    "title" : "Head First EJB",
    "listPrice" : 44.95
}

/* 3 */
{
    "title" : "Head First Java",
    "listPrice" : 39.95
}

如果要查詢所有書籍,根據定價由小排到大,只要前兩筆,指令如下:

db.books.find().sort({listPrice: 1}).limit(2);

/* 0 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 39.95,
    "pubId" : "OA"
}

/* 1 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "releaseDate" : "2004-06-02",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

透過 count 之類的方法,可以輕易達到彙總的功能。

比方說,如果要查詢 OA 出版社出版的所有書籍總數,指令如下:

db.books.count({pubId: "OA"});

3

CRUD – Update

MongoDB Document 的 Update 方式:

  • 透過 db.collectionName.update(query, update, options) 方法達成
  • 根據 query 找出資料之後,以 update 取代
  • Update 預設是 Full-Document Replacement,也就是會用新內容取代舊內容,跟我們一般對 Update 的想像非常不一樣

比方說,如果要修改 Thinking in Java 的定價,假設輸入指令如下:

db.books.update({title: "Thinking in Java"}, {listPrice: 55.99});

這時受影響的 Document 內容,會從原來底下的內容:

{
    _id: 1, 
    isbn: "0131002872", 
    title: "Thinking in Java", 
    releaseDate: "2002-12-01", 
    listPrice: 54.99, 
    pubId: "PH"
}

變成令人驚訝的結果:

{
    "_id" : 1,
    "listPrice" : 55.99
}

所以如果只想要修改某幾個特定 Field 的內容,也就是想要比較接近一般想像的 Update,請使用 $ 開頭的 Update Operator

  • $set:設定 Field 內容,Field 存在就修改,Field 不存在就新增
  • $unset:從 Document 移除這個 Field
  • $inc:將 Field 加上特定值,Field 存在就修改,Field 不存在就新增,而且只能用在數值型別

比方說,如果只想要修改 Thinking in Java 的定價,指令如下:

db.books.update({title: "Thinking in Java"}, {$set: {listPrice: 55.99}});
db.books.find({title: "Thinking in Java"});

/* 0 */
{
    "_id" : 1,
    "isbn" : "0131002872",
    "title" : "Thinking in Java",
    "releaseDate" : "2002-12-01",
    "listPrice" : 55.99,
    "pubId" : "PH"
}

Update 預設只會處理第一筆符合 query 條件的資料,這也是跟以往熟悉 SQL 的人想像中比較不一樣的地方。如果想要啟用 Multiple Update 功能,也就是所有符合 query 條件的 Document 都要更新,請加上 options 參數,並且將 multi 屬性值設定為 true

比方說,如果想要將 OA 出版的所有書籍定價調高 5 美金,但是使用底下的指令:

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 44.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 44.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 39.95
}

db.books.update({pubId: "OA"}, {$inc: {listPrice: 5.0}});

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 49.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 44.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 39.95
}

就只會修改符合條件的第一筆。如果想要修改所有符合條件的 Document,那就必須補上第 3 個參數,指令如下:

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 49.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 44.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 39.95
}

db.books.update({pubId: "OA"}, {$inc: {listPrice: 5.0}}, {multi: true});

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 54.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 49.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 44.95
}

如果想要啟用 UpSert 功能,也就是找得到符合 query 條件的 Document 就進行修改,找不到符合 query 條件的 Document 就新增 Document 的話,一樣要加上第 3 個參數,但是將 upsert 屬性設定為 true,指令如下:

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 54.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 49.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 44.95
}

db.books.update({pubId: "OA"}, {$inc: {listPrice: 5.0}}, {upsert: true});

db.runCommand({getLastError: 1});

/* 0 */
{
    "updatedExisting" : true,
    "n" : 1,
    "lastOp" : Timestamp(1400464977, 1),
    "connectionId" : 118463,
    "err" : null,
    "ok" : 1
}

db.books.find({pubId: "OA"}, {"listPrice": 1});

/* 0 */
{
    "_id" : 2,
    "listPrice" : 59.95
}

/* 1 */
{
    "_id" : 3,
    "listPrice" : 49.95
}

/* 2 */
{
    "_id" : 4,
    "listPrice" : 44.95
}

db.books.update({pubId: "XX"}, {$inc: {listPrice: 5.0}}, {upsert: true});

db.runCommand({getLastError: 1});

/* 0 */
{
    "updatedExisting" : false,
    "upserted" : ObjectId("537967ca611c8b4dcdc2e815"),
    "n" : 1,
    "lastOp" : Timestamp(1400465354, 1),
    "connectionId" : 118463,
    "err" : null,
    "ok" : 1
}

db.books.find({pubId: "XX"});

/* 0 */
{
    "_id" : ObjectId("537967ca611c8b4dcdc2e815"),
    "listPrice" : 5,
    "pubId" : "XX"
}

執行完 Update 或 Remove 動作之後馬上接著執行 db.runCommand({getLastError: 1}) 指令,傳回資料內的 n 欄位值,就是受到剛剛動作影響的 Document 總數。

CRUD – Delete

MongoDB Document 的 Delete 方式:

比方說,如果想要刪除剛剛沒處理好的 XX 出版社的書籍資料,指令如下:

db.books.remove({pubId: "XX"});

db.runCommand({getLastError: 1});

/* 0 */
{
    "n" : 1,
    "lastOp" : Timestamp(1400465476, 1),
    "connectionId" : 118463,
    "err" : null,
    "ok" : 1
}

Delete 預設會刪除所有符合條件的 Document,但是也可以透過 options 參數裡頭的 justOne 屬性設定成只刪除一個 Document。

比方說,如果想要刪除找到的第一本 OA 出版社的書籍資料,指令如下:

db.books.find({pubId: "OA"});

/* 0 */
{
    "_id" : 2,
    "isbn" : "059600530X",
    "title" : "Enterprise JavaBeans",
    "releaseDate" : "2004-06-02",
    "listPrice" : 59.95,
    "pubId" : "OA"
}

/* 1 */
{
    "_id" : 3,
    "isbn" : "0596005717",
    "title" : "Head First EJB",
    "releaseDate" : "2003-10-03",
    "listPrice" : 49.95,
    "pubId" : "OA"
}

/* 2 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

db.books.remove({pubId: "OA"}, {justOne: true});

db.books.find({pubId: "OA"});

/* 0 */
{
    "_id" : 3,
    "isbn" : "0596005717",
    "title" : "Head First EJB",
    "releaseDate" : "2003-10-03",
    "listPrice" : 49.95,
    "pubId" : "OA"
}

/* 1 */
{
    "_id" : 4,
    "isbn" : "0596004656",
    "title" : "Head First Java",
    "releaseDate" : "2003-05-04",
    "listPrice" : 44.95,
    "pubId" : "OA"
}

這時,就只會刪除 OA 出版社的第一本書。

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

目前從事教育訓練工作。自認為會的技術不多,但是學不會的也不多,最擅長把老闆交代的工作,以及找不到老師教的技術,想辦法變成自己的專長。

熱門論壇文章

熱門技術文章