ทดลองเขียน Aggregation ใน MongoDB

พอดีว่าได้ลองนั่งอ่านเรื่อง Aggregation ของ MongoDB แล้วรู้สึกว่ามันน่าสนใจดี ก็เลยทำเป็นบทความซะเลย
หากใครยังไม่รู้จัก MongoDB หรือว่ายังไ่ม่ได้ติดตั้งแนะนำบทความ MongoDB คืออะไร? + สอนวิธีใช้งานเบื้องต้น
สำหรับเวอร์ชันของผมที่ใช้ในการเขียนบทความคือ MongoDB version 3.2.6
Aggregation คืออะไร ?
Aggregation คือคำสั่งที่ใช้ในการประมวลผล data records เช่นการ group, การ sum, การค้นหาข้อมูลหรือแม้แต่การเปลี่ยนแปลงค่าของ output ใน MongoDB เราสามารถเรียกใช้ aggregate()
ได้เลย เช่น
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
Aggregation ทำอะไรได้บ้าง ?
- การ Group ข้อมูล
- เลือกแสดง fields ที่ต้องการได้ (เหมือนกับ projection)
- คำนวณข้อมูลก่อนแสดงผล เช่น sum, average
- นับจำนวนคอมเม้น ในแต่ละโพส (เก็บ comment เป็น array ใน post document)
ตัวอย่าง Operators
$match
: สำหรับ filter documents$project
: เอาไว้ปรับแต่ง documents เช่น จะเอา fields ไหนให้แสดง หรือเปลี่ยนชื่อ fields ได้$group
: group document$sort
: เอาไว้เรียงลำดับ document$limit, $skip
: สำหรับทำ pagination documents
ตัวอย่าง Data
ตัวอย่างจะใช้ Data Set ของทางเว็บ mongo สามารถดาวน์โหลดได้จาก zipcodes collection
ทำการ import ลง database ของเรา ด้วย mongoimport
mongoimport --db ahoy_aggregate_example --collection zipcodes --drop --file /path/to/file/zips.json
--db
: กำหนดชื่อของ database--collection
: ทำการกำหนดชือ collection--drop
: เช็คว่า dbahoy_aggregate_example
มี collection zipcodes หรือเปล่า ถ้ามีก็ drop ทิ้ง--file
: กำหนดไฟล์ที่เราต้องการ import
เมือรันคำสั่งจะได้ผลลัพธ์ลักษณะแบบนี้
2016-06-25T20:45:47.637+0700 connected to: localhost2016-06-25T20:45:47.637+0700 dropping: ahoy_aggregate_example.zipcodes2016-06-25T20:45:48.194+0700 imported 29353 documents
ทดลองดูว่ามีข้อมูลจริงมั้ย
mongo> show dbsahoy_aggregate_example 0.078GB
> use ahoy_aggregate_exampleswitched to db ahoy_aggregate_example
> db.zipcodes.find().count()29353
> db.zipcodes.findOne(){ "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA"}
ซึ่งถ้าดูจากข้อมูลแล้ว เราสามารถสร้าง Schema ด้วย Mongoose จะได้ดังนี้
'use strict'
const mongoose = require('mongoose')const Schema = mongoose.Schema
let Zipcodes = new Schema({ _id: { type: String, unique: true, index: true }, city: String, loc: { type: [Number], index: '2d' }, pop: Number, state: String})
module.exports = mongoose.model('Zipcodes', Zipcodes)
$match
- สำหรับ Filter Document
เช่น หา state
ชื่อ NY
db.zipcodes.aggregate([ { $match: { state: 'NY' } }])
จะได้ข้อมูลเฉพาะ state เท่ากับ NY เท่านั้น
หรือตัวอย่าง เช่น หาเมืองที่มีค่า pop มากกว่า 100000 จะได้
db.zipcodes.aggregate([ { $match: { pop: {$gte: 100000} } }])
ผลลัพธ์
[ { "_id": "10025", "city": "NEW YORK", "loc": [-73.9683119999999974, 40.797466], "pop": 100027, "state": "NY" }, { "_id": "10021", "city": "NEW YORK", "loc": [-73.9588049999999981, 40.7684759999999997], "pop": 106564, "state": "NY" }, { "_id": "11226", "city": "BROOKLYN", "loc": [-73.9569850000000031, 40.6466939999999965], "pop": 111396, "state": "NY" }, { "_id": "60623", "city": "CHICAGO", "loc": [-87.7156999999999982, 41.8490150000000014], "pop": 112047, "state": "IL" }]
$project
ไว้สำหรับปรับแต่ง output คล้ายๆกับ query แบบกำหนด projection
ตัวอย่าง เข่น ต้องการแสดงแค่ชื่อ city และ pop เท่านั้น ไม่แสดงอย่างอื่น จะได้
db.zipcodes.aggregate([ { $project: { _id: 0, city: 1, pop: 1 } }])
ผลลัพธ์
[ { "city" : "AGAWAM", "pop" : 15338 }, { "city" : "BLANDFORD", "pop" : 1240 }, { "city" : "BELCHERTOWN", "pop" : 10579 }, { "city" : "CUSHMAN", "pop" : 36963 }, ...]
Note:
0
คือไม่แสดง field นั้น และ1
คือให้แสดง field นั้น
อีกตัวอย่าง เช่น sub-document fields เอา state และ city ไปไว้ใน info เช่น
db.zipcodes.aggregate([ { $project: { info: { state: "$state", city: "$city" } } }])
ผลลัพธ์
[ { "_id": "01001", "info": { "state": "MA", "city": "AGAWAM" } }, { "_id": "01008", "info": { "state": "MA", "city": "BLANDFORD" } }, { "_id": "01007", "info": { "state": "MA", "city": "BELCHERTOWN" } }]
$group
เหมือนกับ GROUP BY
ใน SQL เช่น การรวม city, state และ sum ค่า pop จะได้ดังนี้
db.zipcodes.aggregate([ { $group: { _id: "$state", totalPop: { $sum: "$pop"} } }])
ผลลัพธ์
[ { "_id" : "NV", "totalPop" : 1.20183e+06 }, { "_id" : "ID", "totalPop" : 1.00675e+06 }, { "_id" : "CO", "totalPop" : 3.29376e+06 }, ...]
อีกตัวอย่าง เช่น นับจำนวน city ในแต่ละ states ว่ามีเท่าไหร่
Note! : บาง city สามารถมีได้หลาย zipcode ฉะนั้นข้อมูลอาจจะคลาดเคลื่อนไปบ้าง และในข้อมูล data sheet บาง fields มี city และ state ซ้ำกัน
db.zipcodes.aggregate([ { $group: { _id: "$state", totalCities: { $sum: 1} } }])
ผลลัพธ์
[ { "_id" : "NV", "totalCities" : 104.0000000000000000 }, { "_id" : "ID", "totalCities" : 244.0000000000000000 }, { "_id" : "CO", "totalCities" : 414.0000000000000000 }, ...]
- อีกตัวอย่าง เช่น การ distinct value รวมกลุ่ม city ทั้งหมดใน state ไว้ใน field
cities
db.zipcodes.aggregate([ { $group: { _id: "$state", cities: { $addToSet: "$city"} } }])
ผลลัพธ์
[ { "_id" : "NV", "cities" : [ "BEOWAWE", "OASIS", "MOUNTAIN CITY", ... ] }, {...}, {...}]
$sort
การ Sorting document เรียงลำดับก่อนหลัง เช่น หาจำนวน city ในแต่ละ state แล้วก็เรียงข้อมูลจากน้อยไปมาก จะเขียนได้
db.zipcodes.aggregate([ { $group: { _id: "$state", totalCities: { $sum: 1} } }, { $sort: { totalCities: 1 } }])
ผลลัพธ์
[ { "_id" : "DC", "totalCities" : 24.0000000000000000 }, { "_id" : "DE", "totalCities" : 53.0000000000000000 }, { "_id" : "RI", "totalCities" : 69.0000000000000000 }, { "_id" : "HI", "totalCities" : 80.0000000000000000 }, { "_id" : "NV", "totalCities" : 104.0000000000000000 }, ...]
ทั้งหมดนี้ก็เป็นแค่ตัวอย่างคร่าวๆในการใช้งาน Aggregation ใน MongoDB ที่ผมได้ลองศึกษา ยังไม่รวมหลายๆอย่าง เช่น $geoNear
หรือ $unwind
อีกทั้งตัว Aggregation นั้นยังทำอะไรได้อีกเยอะแยะ ที่ผมยังไม่ได้ลองโดยเฉพาะตัว $geoNear
ได้ลองแค่เบื้องต้น แต่จริงๆมันทำอะไรได้เยอะแยะมาก เช่น ค้นหา nearby หรือหาระยะทางระหว่าง location เป็นต้น
References
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust