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

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

พอดีว่าได้ลองนั่งอ่านเรื่อง 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 : เช็คว่า db ahoy_aggregate_example มี collection zipcodes หรือเปล่า ถ้ามีก็ drop ทิ้ง
  • --file : กำหนดไฟล์ที่เราต้องการ import

เมือรันคำสั่งจะได้ผลลัพธ์ลักษณะแบบนี้

2016-06-25T20:45:47.637+0700  connected to: localhost
2016-06-25T20:45:47.637+0700  dropping: ahoy_aggregate_example.zipcodes
2016-06-25T20:45:48.194+0700  imported 29353 documents

ทดลองดูว่ามีข้อมูลจริงมั้ย

mongo
> show dbs
ahoy_aggregate_example     0.078GB

> use ahoy_aggregate_example
switched 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.7974660000000000
        ],
        "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

Chai Chai Phonbopit : MEAN Stack @Nextzy • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Android, JavaScript (Node.js) และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด