You are on page 1of 51

Chương 4: TỔNG HỢP DỮ LIỆU MONGODB

1/. Aggregation:
https://www.geeksforgeeks.org/aggregation-in-mongodb/
A/. Aggregation: là một framework tổng hợp dữ liệu của MongoDB. Aggregation được xây
dựng dựa trên mô hình xử lý dữ liệu dưới dạng pipeline. Aggregation pipeline bao gồm
nhiều stage. Trong mỗi stage, chúng ta sử dụng một aggregation operator để biến đổi dữ
liệu của các input document. Các output document của stage phía trước sẽ là input
document của stage ngay sau. Các aggregation operator có thể được sử dụng nhiều lần trong
pipeline, ngoại trừ $out, $merge, và $geoNear.
Điểm mạnh của aggregation framework là:
 Xử lý nhanh và mạnh mẽ với lượng ít băng thông.
 Giải quyết được các yêu cầu phức tạp.
 Có thể làm việc với dữ liệu lớn.
MongoDB cung cấp phương thức db.collection.aggregate() để chạy aggregation
pipeline.
Cú pháp:

db.Collection_Name.aggregate( [{<stage 1> }, {<stage 2> }, ..., {<stage N> } ],


{<options> } );

Các stage được đặt trong một array [ ] theo thứ tự thực hiện trước sau.
Các options là tùy chọn, không nhất thiết phải có.
MongoDB cung cấp ba cách để thực hiện tổng hợp:
 Aggregation pipeline
 Map-reduce function
 Single-purpose aggregation

Trong MongoDB, Aggregation pipeline bao gồm các giai đoạn và mỗi stage (giai đoạn) sẽ
biến đổi tài liệu. Hay nói cách khác, Aggregation pipeline là một pipeline nhiều giai đoạn,
vì vậy ở mỗi trạng thái, các tài liệu được lấy làm đầu vào và tạo ra bộ tài liệu kết quả bây
giờ ở stage tiếp theo (có id) các tài liệu kết quả được lấy làm đầu vào và tạo ra đầu ra… ,
quá trình này đang diễn ra cho đến stage cuối cùng. Các stage pipeline cơ bản cung cấp các
bộ lọc sẽ thực hiện giống như các truy vấn và việc chuyển đổi tài liệu sẽ sửa đổi tài liệu kết
quả và pipeline khác cung cấp các công cụ để nhóm và phân loại document.
Sau đây là các pipeline stage có thể có các phép toán tổng hợp:
 $project: chọn một số trường cụ thể từ một tập hợp.
1
 $match: Đây là một hoạt động chọn lọc. do đó, điều này có thể làm giảm số lượng
document được cung cấp làm đầu vào cho giai đoạn tiếp theo.
 $group: thực hiện gom nhóm.
 $sort: Sắp xếp các document.
 $skip: bỏ qua số lượng document nhất định.
 $limit: giới hạn số lượng document cần xem, theo số lượng nhất định bắt đầu từ các
vị trí hiện tại.
 $unwind: được dùng để phân tách giá trị của một array field trong các input
document. Nếu như array field của một input document có N phần tử thì trong output
sẽ có N document (được sử dụng để chia một Document đang sử dụng mảng thành
nhiều Document. Sử dụng hoạt động này sẽ tạo một số lượng Document cho bước
tiếp theo.).
Cú pháp của $group:
{
$group:
{
_id: <expression>, // Group By Expression
<field1>: { <accumulator1> : <expression1> },
...
}
}

Nếu _id được set bằng null, MongoDB sẽ query tất cả các input document.

Bộ tính toán (accumulator): về cơ bản chúng được sử dụng ở stage group:


 $sum: tính tổng các giá trị số cho các document (tài liệu) trong mỗi nhóm
 $count: đếm tổng số document
 $avg: tính toán trung bình của tất cả các giá trị đã cho từ tất cả các document
 $min: nhận giá trị tối thiểu từ tất cả các document
 $max: nhận giá trị lớn nhất từ tất cả các document
 $first: lấy tài liệu đầu tiên từ nhóm
 $last: lấy tài liệu cuối cùng từ nhóm

Vd: sinh viên xem hình sau để hiểu ý nghĩa của Aggregate pipeline.
Trong ví dụ bằng hình ta thấy: trước tiên, giai đoạn $ match lọc tài liệu theo giá trị trong
trường class, là “first-class” và chuyển document sang giai đoạn thứ hai. Trong Giai đoạn
thứ hai, giai đoạn nhóm $ group các document theo trường “id” để tính tổng “fare” (giá vé)
cho mỗi id duy nhất.

2
Vd: tạo collection db3, tạo document train, thực hiện phép aggregate 2 stage
> use db3
switched to db db3
> db.createCollection("train") ‘tạo collection
{ "ok" : 1 }
> db.train.insertMany([ ‘tạo document
... {id: "181", class: "first-class", fare: 1200},
... {id: "181", class: "first-class", fare: 1000},
... {id: "181", class: "second-class", fare: 1000},
... {id: "167", class: "first-class", fare: 1200},
... {id: "167", class: "second-class", fare: 1500}])
{
"acknowledged" : true,

3
"insertedIds" : [
ObjectId("60bcc55cf95ca5f2be008870"),
ObjectId("60bcc55cf95ca5f2be008871"),
ObjectId("60bcc55cf95ca5f2be008872"),
ObjectId("60bcc55cf95ca5f2be008873"),
ObjectId("60bcc55cf95ca5f2be008874")
]
}
> db.train.find().pretty()
{
"_id" : ObjectId("60bcc55cf95ca5f2be008870"),
"id" : "181",
"class" : "first-class",
"fare" : 1200
}
{
"_id" : ObjectId("60bcc55cf95ca5f2be008871"),
"id" : "181",
"class" : "first-class",
"fare" : 1000
}
{
"_id" : ObjectId("60bcc55cf95ca5f2be008872"),
"id" : "181",
"class" : "second-class",
"fare" : 1000
}
{
"_id" : ObjectId("60bcc55cf95ca5f2be008873"),
"id" : "167",
4
"class" : "first-class",
"fare" : 1200
}
{
"_id" : ObjectId("60bcc55cf95ca5f2be008874"),
"id" : "167",
"class" : "second-class",
"fare" : 1500
}
‘nhóm theo id và tính tổng theo fare
> db.train.aggregate([ {$group: { _id: "$id", Tổng: {$sum: "$fare"}}} ])
{ "_id" : "167", "Tổng" : 2700 }
{ "_id" : "181", "Tổng" : 3200 }

‘chọn first-class, sau đó nhóm theo id và tính tổng theo fare


> db.train.aggregate([
... {$match: {class: "first-class"}},
... {$group: {_id: "id", total: {$sum: "$fare"}}}
... ])
Stage 1 Stage 2
{ "_id" : "id", "total" : 3400 }
> db.train.aggregate([ {$match: {class: "first-class"}}, {$group: {_id: "$id", total:
{$sum: "$fare"}}} ])
{ "_id" : "181", "total" : 2200 }
{ "_id" : "167", "total" : 1200 }
Hoặc tính trung bình:
> db.train.aggregate([ {$match: {class: "first-class"}}, {$group: {_id: "$id", total:
{$avg: "$fare"}}} ])
{ "_id" : "167", "total" : 1200 }
{ "_id" : "181", "total" : 1100 }

5
Vd: tạo collection Students, nhập 7 document
> db.createCollection("Students")
{ "ok" : 1 }
> db.Students.insertMany([
... {id: 1, name: "Nguyễn Văn An", age: 22, sec: "1", subjects: ["Math", "IT"]},
... {id: 2, name: "Lê Thị Bé", age: 23, sec: "1", subjects: ["Math", "IT", "English"]},
... {id: 3, name: "Ngô Thị Kim Chi", age: 21, sec: "1", subjects: ["Math", "English"]}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60bcdf90983baba85e0e349c"),
ObjectId("60bcdf90983baba85e0e349d"),
ObjectId("60bcdf90983baba85e0e349e")
]
}
> db.Students.insertMany([
... {id: 501, name: "Phan Kim Hương", age: 22, sec: "2", subjects: ["AI", "Software
Testing"]},
... {id: 502, name: "Nguyễn Văn Tài", age: 28, sec: "2", subjects: ["Big data", "Data
mining"]},
... {id: 503, name: "Ngô Thị Hòa", age: 24, sec: "2", subjects: ["AI", "Big data"]},
... {id: 801, name: "Nguyễn Văn Hùng", age: 25, sec: "3", subjects: ["English", "Big
data"]}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60bce0d9983baba85e0e349f"),
ObjectId("60bce0d9983baba85e0e34a0"),
ObjectId("60bce0d9983baba85e0e34a1"),
ObjectId("60bce0d9983baba85e0e34a2")
]
6
}
> db.Students.find().pretty()
{
"_id" : ObjectId("60bcdf90983baba85e0e349c"),
"id" : 1,
"name" : "Nguyễn Văn An",
"age" : 22,
"sec" : "1",
"subjects" : [
"Math",
"IT"
]
}
{
"_id" : ObjectId("60bcdf90983baba85e0e349d"),
"id" : 2,
"name" : "Lê Thị Bé",
"age" : 23,
"sec" : "1",
"subjects" : [
"Math",
"IT",
"English"
]
}
{
"_id" : ObjectId("60bcdf90983baba85e0e349e"),
"id" : 3,
"name" : "Ngô Thị Kim Chi",
"age" : 21,
7
"sec" : "1",
"subjects" : [
"Math",
"English"
]
}
{
"_id" : ObjectId("60bce0d9983baba85e0e349f"),
"id" : 501,
"name" : "Phan Kim Hương",
"age" : 22,
"sec" : "2",
"subjects" : [
"AI",
"Software Testing"
]
}
{
"_id" : ObjectId("60bce0d9983baba85e0e34a0"),
"id" : 502,
"name" : "Nguyễn Văn Tài",
"age" : 28,
"sec" : "2",
"subjects" : [
"Big data",
"Data mining"
]
}
{
"_id" : ObjectId("60bce0d9983baba85e0e34a1"),
8
"id" : 503,
"name" : "Ngô Thị Hòa",
"age" : 24,
"sec" : "2",
"subjects" : [
"AI",
"Big data"
]
}
{
"_id" : ObjectId("60bce0d9983baba85e0e34a2"),
"id" : 801,
"name" : "Nguyễn Văn Hùng",
"age" : 25,
"sec" : "3",
"subjects" : [
"English",
"Big data"
]
}
‘tính tổng số sinh viên học kỳ 1
> db.Students.aggregate([ {$match: {sec: "1"}}, {$count: "Tổng số sinh viên học kỳ
1"}])
{ "Tổng số sinh viên học kỳ 1" : 3 }
‘nhóm sinh viên theo học kỳ, tính tổng số sinh viên theo nhóm và lấy tuổi lớn nhất
> db.Students.aggregate([ {$group: {_id: "$sec", "Tổng số sinh viên": {$sum: 1},
"Tuổi lớn nhất": {$max: "$age"}}}])
{ "_id" : "3", "Tổng số sinh viên" : 1, "Tuổi lớn nhất" : 25 }
{ "_id" : "1", "Tổng số sinh viên" : 3, "Tuổi lớn nhất" : 23 }
{ "_id" : "2", "Tổng số sinh viên" : 3, "Tuổi lớn nhất" : 28 }

9
‘phép chiếu trên cột id, name
> db.Students.aggregate([
... {$project: {_id: 0, ‘ẩn cột _id
... id: 1, name: 1}} ‘hiện cột id, name
... ])
{ "id" : 1, "name" : "Nguyễn Văn An" }
{ "id" : 2, "name" : "Lê Thị Bé" }
{ "id" : 3, "name" : "Ngô Thị Kim Chi" }
{ "id" : 501, "name" : "Phan Kim Hương" }
{ "id" : 502, "name" : "Nguyễn Văn Tài" }
{ "id" : 503, "name" : "Ngô Thị Hòa" }
{ "id" : 801, "name" : "Nguyễn Văn Hùng" }

‘chọn các sinh viên có tuổi > 23


> db.Students.aggregate([
... {$match: {age: {$gt: 23}}}])
{ "_id" : ObjectId("60bce0d9983baba85e0e34a0"), "id" : 502, "name" : "Nguyễn Văn Tài",
"age" : 28, "sec" : "2", "subjects" : [ "Big data", "Data mining" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a1"), "id" : 503, "name" : "Ngô Thị Hòa",
"age" : 24, "sec" : "2", "subjects" : [ "AI", "Big data" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a2"), "id" : 801, "name" : "Nguyễn Văn
Hùng", "age" : 25, "sec" : "3", "subjects" : [ "English", "Big data" ] }
‘sắp sếp các sinh viên theo tuổi (age)
> db.Students.aggregate([{$sort: {"age": 1}}])
{ "_id" : ObjectId("60bcdf90983baba85e0e349e"), "id" : 3, "name" : "Ngô Thị Kim Chi",
"age" : 21, "sec" : "1", "subjects" : [ "Math", "English" ] }
{ "_id" : ObjectId("60bcdf90983baba85e0e349c"), "id" : 1, "name" : "Nguyễn Văn An",
"age" : 22, "sec" : "1", "subjects" : [ "Math", "IT" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e349f"), "id" : 501, "name" : "Phan Kim Hương",
"age" : 22, "sec" : "2", "subjects" : [ "AI", "Software Testing" ] }

10
{ "_id" : ObjectId("60bcdf90983baba85e0e349d"), "id" : 2, "name" : "Lê Thị Bé", "age" :
23, "sec" : "1", "subjects" : [ "Math", "IT", "English" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a1"), "id" : 503, "name" : "Ngô Thị Hòa",
"age" : 24, "sec" : "2", "subjects" : [ "AI", "Big data" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a2"), "id" : 801, "name" : "Nguyễn Văn
Hùng", "age" : 25, "sec" : "3", "subjects" : [ "English", "Big data" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a0"), "id" : 502, "name" : "Nguyễn Văn Tài",
"age" : 28, "sec" : "2", "subjects" : [ "Big data", "Data mining" ] }
‘chọn các sv học kỳ 1 (sec = 1), sắp xếp theo tuổi và chỉ lấy 1 document
> db.Students.aggregate([ {$match: {"sec": "1"}}, {$sort: {"age": 1}}, {$limit: 2}])
{ "_id" : ObjectId("60bcdf90983baba85e0e349e"), "id" : 3, "name" : "Ngô Thị Kim Chi",
"age" : 21, "sec" : "1", "subjects" : [ "Math", "English" ] }
{ "_id" : ObjectId("60bcdf90983baba85e0e349c"), "id" : 1, "name" : "Nguyễn Văn An",
"age" : 22, "sec" : "1", "subjects" : [ "Math", "IT" ] }
‘ta có mảng subjects (các môn học), thực hiện phân giải dữ liệu theo cấu trúc mảng
> db.Students.aggregate([
... {$unwind: "$subjects"}])
{ "_id" : ObjectId("60bcdf90983baba85e0e349c"), "id" : 1, "name" : "Nguyễn Văn An",
"age" : 22, "sec" : "1", "subjects" : "Math" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349c"), "id" : 1, "name" : "Nguyễn Văn An",
"age" : 22, "sec" : "1", "subjects" : "IT" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349d"), "id" : 2, "name" : "Lê Thị Bé", "age" :
23, "sec" : "1", "subjects" : "Math" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349d"), "id" : 2, "name" : "Lê Thị Bé", "age" :
23, "sec" : "1", "subjects" : "IT" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349d"), "id" : 2, "name" : "Lê Thị Bé", "age" :
23, "sec" : "1", "subjects" : "English" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349e"), "id" : 3, "name" : "Ngô Thị Kim Chi",
"age" : 21, "sec" : "1", "subjects" : "Math" }
{ "_id" : ObjectId("60bcdf90983baba85e0e349e"), "id" : 3, "name" : "Ngô Thị Kim Chi",
"age" : 21, "sec" : "1", "subjects" : "English" }
{ "_id" : ObjectId("60bce0d9983baba85e0e349f"), "id" : 501, "name" : "Phan Kim Hương",
"age" : 22, "sec" : "2", "subjects" : "AI" }
{ "_id" : ObjectId("60bce0d9983baba85e0e349f"), "id" : 501, "name" : "Phan Kim Hương",
"age" : 22, "sec" : "2", "subjects" : "Software Testing" }
11
{ "_id" : ObjectId("60bce0d9983baba85e0e34a0"), "id" : 502, "name" : "Nguyễn Văn Tài",
"age" : 28, "sec" : "2", "subjects" : "Big data" }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a0"), "id" : 502, "name" : "Nguyễn Văn Tài",
"age" : 28, "sec" : "2", "subjects" : "Data mining" }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a1"), "id" : 503, "name" : "Ngô Thị Hòa",
"age" : 24, "sec" : "2", "subjects" : "AI" }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a1"), "id" : 503, "name" : "Ngô Thị Hòa",
"age" : 24, "sec" : "2", "subjects" : "Big data" }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a2"), "id" : 801, "name" : "Nguyễn Văn
Hùng", "age" : 25, "sec" : "3", "subjects" : "English" }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a2"), "id" : 801, "name" : "Nguyễn Văn
Hùng", "age" : 25, "sec" : "3", "subjects" : "Big data" }
‘đếm số sinh viên
> db.Students.count()
7
‘hiển thị các tuổi của sinh viên (không lặp lại
> db.Students.distinct("age")
[ 21, 22, 23, 24, 25, 28 ]
‘bỏ qua 2 document đầu
> db.Students.aggregate([ {$skip: 2}])
{ "_id" : ObjectId("60bcdf90983baba85e0e349e"), "id" : 3, "name" : "Ngô Thị Kim Chi",
"age" : 21, "sec" : "1", "subjects" : [ "Math", "English" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e349f"), "id" : 501, "name" : "Phan Kim Hương",
"age" : 22, "sec" : "2", "subjects" : [ "AI", "Software Testing" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a0"), "id" : 502, "name" : "Nguyễn Văn Tài",
"age" : 28, "sec" : "2", "subjects" : [ "Big data", "Data mining" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a1"), "id" : 503, "name" : "Ngô Thị Hòa",
"age" : 24, "sec" : "2", "subjects" : [ "AI", "Big data" ] }
{ "_id" : ObjectId("60bce0d9983baba85e0e34a2"), "id" : 801, "name" : "Nguyễn Văn
Hùng", "age" : 25, "sec" : "3", "subjects" : [ "English", "Big data" ] }

B/. Lookup: (quan trọng)

12
Thực hiện phép nối bên ngoài bên trái với một collection trong cùng một cơ sở dữ liệu để
lọc trong các document từ collection đã kết hợp để xử lý. Đối với mỗi document đầu vào,
giai đoạn $ lookup sẽ thêm một trường Array (mảng) mới có các phần tử là các document
phù hợp từ collection đã kết hợp. Giai đoạn $ lookup chuyển các document được định hình
lại này sang giai đoạn tiếp theo.
Cú pháp:
db.collection_input.aggregate([
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}

}
])
Tham khảo: https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

Field Description

from Chỉ định collection trong cùng một cơ sở dữ liệu để thực hiện phép nối
(join). Collection này gọi là from collection.

localField Chỉ định field của collection_ input. $lookup thực hiện đối sánh trên
localField với ForeignField của collection từ from. Nếu collection_input
không chứa localField, $lookup sẽ coi filed có giá trị null cho các mục
đích phù hợp.

foreignField Chỉ định field ngoại từ from collection. $lookup thực hiện đối sánh trên
ForeignField với localField với collection_input. Nếu collection trong
from (from collection) không chứa ForeignField, $ lookup sẽ coi giá trị
là null cho các mục đích phù hợp.

as Chỉ định tên của output Array field mới để thêm vào collection_input.

13
Field Description

Output Array field mới chứa các dữ liệu phù hợp từ from collection. Nếu
tên được chỉ định đã tồn tại trong collection_input, field hiện có sẽ bị ghi
đè.

Thao tác $lookup sẽ tương ứng với câu lệnh SQL sau:
SELECT *, <output array field>
FROM collection_input
WHERE <output array field> IN (SELECT *
FROM <from collection>
WHERE <foreignField>= <collection.localField>);

Vd: truy vấn dữ liệu từ 2 collection (sv so khớp dữ liệu để hiểu)


> db.createCollection("Order")
{ "ok" : 1 }
> db.createCollection("Inventory")
{ "ok" : 1 }
> db.Order.insert([
... { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
... { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
... { "_id" : 3 }
... ])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 3,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,

14
"nRemoved" : 0,
"upserted" : [ ]
})
> db.Inventory.insert([
... { "_id" : 1, "sku" : "almonds", "description": "product 1", "instock" : 120 },
... { "_id" : 2, "sku" : "bread", "description": "product 2", "instock" : 80 },
... { "_id" : 3, "sku" : "cashews", "description": "product 3", "instock" : 60 },
... { "_id" : 4, "sku" : "pecans", "description": "product 4", "instock" : 70 },
... { "_id" : 5, "sku": null, "description": "Incomplete" },
... { "_id" : 6 }
... ])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 6,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.Order.aggregate([
... {
... $lookup:
... {
... from: "Inventory",
... localField: "item",
... foreignField: "sku",
... as: "inventory_docs"
... }
15
... }
... ])
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2, "inventory_docs" : [ { "_id" : 1,
"sku" : "almonds", "description" : "product 1", "instock" : 120 } ] }
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1, "inventory_docs" : [ { "_id" : 4,
"sku" : "pecans", "description" : "product 4", "instock" : 70 } ] }
{ "_id" : 3, "inventory_docs" : [ { "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 } ] }
‘Truy vấn trên tương đương với:
SELECT *, inventory_docs
FROM orders
WHERE inventory_docs IN (SELECT *
FROM inventory
WHERE sku= orders.item);

Vd: truy vấn dữ liệu từ 2 collection, có chứa mảng giá trị so khớp (sv so khớp dữ liệu để
hiểu)
> db.createCollection("Genres")
{ "ok" : 1 }
> db.createCollection("Movies")
{ "ok" : 1 }
> db.Genres.insertMany( [
... { _id: 1, title: "Notebook", genrelist: [ "comedy", "romance", "fiction" ]},
... { _id: 2, title: "Anabelle", genrelist: [ "horror", "fiction" ]}
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2 ] }
> db.Movies.insertMany( [
... { _id: 1, name: "HarryPotter", "type": "fiction", rating: "A" },
... { _id: 2, name: "LOTR", "type": "fiction", rating: "D" },
... { _id: 3, name: "Witchcraft", "type": "horror", rating: "A" },
... { _id: 4, name: "panda", "type": "romance", rating: "A" },
16
... { _id: 5, name: "What is new", "type": "comedy", rating: "A" },
... { _id: 6, name: "Date", "type": "romance", rating: "D" }
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3, 4, 5, 6 ] }
> db.Genres.aggregate([
... {
... $lookup:
... {
... from : "Movies",
... localField : "genrelist", //mảng
... foreignField : "type",
... as : "movie"
... } } ] )
{ "_id" : 1, "title" : "Notebook", "genrelist" : [ "comedy", "romance", "fiction" ], "movie" :
[ { "_id" : 1, "name" : "HarryPotter", "type" : "fiction", "rating" : "A" }, { "_id" : 2, "name" :
"LOTR", "type" : "fiction", "rating" : "D" }, { "_id" : 4, "name" : "panda", "type" :
"romance", "rating" : "A" }, { "_id" : 5, "name" : "What is new", "type" : "comedy",
"rating" : "A" }, { "_id" : 6, "name" : "Date", "type" : "romance", "rating" : "D" } ] }

{ "_id" : 2, "title" : "Anabelle", "genrelist" : [ "horror", "fiction" ], "movie" : [ { "_id" : 1,


"name" : "HarryPotter", "type" : "fiction", "rating" : "A" }, { "_id" : 2, "name" : "LOTR",
"type" : "fiction", "rating" : "D" }, { "_id" : 3, "name" : "Witchcraft", "type" : "horror",
"rating" : "A" } ] }

Để thực hiện các truy vấn con không tương quan giữa hai collection cũng như cho phép các
điều kiện join (nối) khác bên cạnh một kết hợp bình đẳng duy nhất, giai đoạn $lookup có
thêm các field như sau:
Tham khảo:
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
https://www.javatpoint.com/mongodb-aggregation-pipeline-operators

Field Description

$let Optional. Specifies variables to use in the pipeline field stages. Use the


17
Field Description

variable expressions to access the fields from the documents input to


the $lookup stage.
The pipeline cannot directly access the input document fields. Instead,
first define the variables for the input document fields, and then
reference the variables in the stages in the pipeline.
Không bắt buộc. Chỉ định các biến để sử dụng trong các giai đoạn
pipeline. Sử dụng các biểu thức biến để truy cập các trường từ đầu vào
tài liệu đến giai đoạn $lookup. pipeline không thể truy cập trực tiếp vào
các trường tài liệu đầu vào. Thay vào đó, trước tiên hãy xác định các
biến cho các trường tài liệu đầu vào, sau đó tham chiếu các biến trong
các giai đoạn trong quy trình.
NOTE
To reference variables in pipeline stages, use the "$$<variable>" syntax.
The let variables can be accessed by the stages in the pipeline, including
additional $lookup stages nested in the pipeline.
A $match stage requires the use of an $expr operator to access the
variables. $expr allows the use of aggregation expressions inside of
the $match syntax.
Without the use of the $expr operator, $match can refer to fields in a
document but cannot access variables defined by a $lookup let clause.
The $expr operator only uses indexes on the from collection for equality
matches. Non-equality match queries, such as range queries, cannot use
indexes on the from collection.
Other (non-$match) stages in the pipeline do not require
an $expr operator to access the variables.

$pipeline Specifies the pipeline to run on the joined collection.


The pipeline determines the resulting documents from the joined
collection. To return all documents, specify an empty pipeline [].
The pipeline cannot include the $out stage or the $merge stage.
The pipeline cannot directly access the input document fields. Instead,
first define the variables for the input document fields, and then
reference the variables in the stages in the pipeline.
NOTE

18
Field Description

To reference variables in pipeline stages, use the "$$<variable>" syntax.


The let variables can be accessed by the stages in the pipeline, including
additional $lookup stages nested in the pipeline.
A $match stage requires the use of an $expr operator to access the
variables. $expr allows the use of aggregation expressions inside of
the $match syntax.
Without the use of the $expr operator, $match can refer to fields in a
document but cannot access variables defined by a $lookup let clause.
The $expr operator only uses indexes on the from collection for equality
matches. Non-equality match queries, such as range queries, cannot use
indexes on the from collection.
Other (non-$match) stages in the pipeline do not require
an $expr operator to access the variables.

$replaceRoot { $replaceRoot: { newRoot: <replacementDocument> } }


Thay thế document đầu vào bằng replace document được chỉ định. Thao tác
này sẽ thay thế tất cả các trường hiện có trong document đầu vào, bao gồm cả
trường _id.

$mergeObjects Kết hợp nhiều document thành một document duy nhất.

Vd: dùng $let


> db.createCollection("Hoadon")
{ "ok" : 1 }
> db.Hoadon.insertMany([
... { _sohd: 1, price: 100, tax: 5, applyDiscount: true },
... { _sohd: 2, price: 1000, tax: 50, applyDiscount: false },
... { _sohd: 3, price: 1000, tax: 50, applyDiscount: false },
... { _sohd: 4, price: 500, tax: 25, applyDiscount: true }
... ])
{
"acknowledged" : true,
19
"insertedIds" : [
ObjectId("60c2324dcc18680da463dbf4"),
ObjectId("60c2324dcc18680da463dbf5"),
ObjectId("60c2324dcc18680da463dbf6"),
ObjectId("60c2324dcc18680da463dbf7")
]
}
> db.Hoadon.aggregate( [
... {
... $project: {
... finalTotal: {
... $let: {
... vars: {
... total: { $add: [ '$price', '$tax' ] },
... discounted: { $cond: { if: '$applyDiscount', then: 0.9, else: 1 } }
... },
... in: { $multiply: [ "$$total", "$$discounted" ] }
... }
... }
... }
... }
... ] )
{ "_id" : ObjectId("60c2324dcc18680da463dbf4"), "finalTotal" : 94.5 }
{ "_id" : ObjectId("60c2324dcc18680da463dbf5"), "finalTotal" : 1050 }
{ "_id" : ObjectId("60c2324dcc18680da463dbf6"), "finalTotal" : 1050 }
{ "_id" : ObjectId("60c2324dcc18680da463dbf7"), "finalTotal" : 472.5 }
Giải thích:
o $add: thực hiện pháp cộng.
o $multiply: thực hiện phép nhân

20
o $cond: { if: '$applyDiscount', then: 0.9, else: 1 } }: điều kiện, nếu
$applyDiscount là true thì trả ra 0.9, còn lại trả ra 1.
o vars: gán biểu thức tính cho 2 biến: total và discounted.
o in: đặt biểu thức tính cho field finalTotal.
o $$total, $$discounted: tham chiếu đến 2 biến total và discounted (để tham
chiếu phải có $$ đứng trước biến)

Vd: dùng $pipeline: https://www.educba.com/lookup-in-mongodb/


> db.createCollection("Sports")
{ "ok" : 1 }
> db.Sports.insertMany([
... { "_id" : 1, "student" : "Ann Aardvark", "sports": [ "basketball","Throwball" ] },
... { "_id" : 2, "student" : "Zoe Zebra", "sports": [ "Tennis","TT"] },
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2 ] }
> db.createCollection("Winners")
{ "ok" : 1 }
> db.Winners.insertMany([
…{ "_id" : 1, "sport": "basketball", "place": 1, date: new Date("2018-01-01") },
…{ "_id" : 2, "sport": "basketball", "place": 4, date: new Date("2018-03-14") },
…{ "_id" : 3, "sport": "basketball", "place":2, date: new Date("2018-07-15") },
…{ "_id" : 4, "sport": "TT", "place": 6, date: new Date("2017-01-01") },
…{ "_id" : 5, "sport": "TT", "place": 8, date: new Date("2017-07-16") }
…])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3, 4, 5 ] }

‘Truy vấn này có dùng $pipeline để so khớp với môn thể thao basketball (bóng rổ) trong
tất cả document của collection Winners và chọn place và date, tạo ra một trường mới là
mảng win để lưu trữ tất cả các trận đấu trong mảng.
> db.Sports.aggregate([
…{ $lookup:{from: "Winners",

21
…pipeline: [{ $match: { sport: "basketball" } }, { $project: { _id: 0, date: { place:
"$place", date: "$date" } } }],
…as: "win"}
…}
…])
{ "_id" : 1, "student" : "Ann Aardvark", "sports" : [ "basketball", "Throwball" ], "win" :
[ { "date" : { "place" : 1, "date" : ISODate("2018-01-01T00:00:00Z") } }, { "date" :
{ "place" : 4, "date" : ISODate("2018-03-14T00:00:00Z") } }, { "date" : { "place" : 2, "date"
: ISODate("2018-07-15T00:00:00Z") } } ] }
{ "_id" : 2, "student" : "Zoe Zebra", "sports" : [ "Tennis", "TT" ], "win" : [ { "date" :
{ "place" : 1, "date" : ISODate("2018-01-01T00:00:00Z") } }, { "date" : { "place" : 4, "date"
: ISODate("2018-03-14T00:00:00Z") } }, { "date" : { "place" : 2, "date" : ISODate("2018-
07-15T00:00:00Z") } } ] }
//sinh viên dò và kiểm dữ liệu

Vd: dùng $replaceRoot:


https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/
> db.createCollection("Mycollection")
{ "ok" : 1 }
> db.Mycollection.insertMany([
{ "_id": 1, "name" : { "first" : "John", "last" : "Backus" } },
{ "_id": 2, "name" : { "first" : "John", "last" : "McCarthy" } },
{ "_id": 3, "name": { "first" : "Grace", "last" : "Hopper" } },
{ "_id": 4, "firstname": "Ole-Johan", "lastname" : "Dahl" }
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3, 4 ] }
‘thao tác $ ReplaceRoot sau không thành công vì một trong các document không có field
name:
> db.Mycollection.aggregate([
... { $replaceRoot: { newRoot: "$name" } }
... ])
uncaught exception: Error: command failed: {

22
"ok" : 0,
"errmsg" : "'newRoot' expression must evaluate to an object, but resulting value was:
MISSING. Type of resulting value: 'missing'. Input document: {}",
"code" : 40228,
"codeName" : "Location40228"
} : aggregate failed :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
doassert@src/mongo/shell/assert.js:18:14
_assertCommandWorked@src/mongo/shell/assert.js:639:17
assert.commandWorked@src/mongo/shell/assert.js:729:16
DB.prototype._runAggregate@src/mongo/shell/db.js:266:5
DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12
@(shell):1:1
‘Để tránh lỗi, ta có thể sử dụng $ mergeObjects để hợp nhất (merge) field name vào một số
field khác.
> db.Mycollection.aggregate([
... { $replaceRoot: { newRoot: { $mergeObjects: [ { _id: "$_id", first: "", last: "" },
"$name" ] } } }
... ])
{ "_id" : 1, "first" : "John", "last" : "Backus" }
{ "_id" : 2, "first" : "John", "last" : "McCarthy" }
{ "_id" : 3, "first" : "Grace", "last" : "Hopper" }
{ "_id" : 4, "first" : "", "last" : "" }
‘Ngoài ra, Ta có thể bỏ qua các tài liệu thiếu field name bằng cách bao gồm giai đoạn $
match để kiểm tra sự tồn tại của field trước khi chuyển document sang giai đoạn $
ReplaceRoot:
> db.Mycollection.aggregate([
... { $match: { name : { $exists: true, $not: { $type: "array" }, $type: "object" } } },
... { $replaceRoot: { newRoot: "$name" } }
... ])
{ "first" : "John", "last" : "Backus" }

23
{ "first" : "John", "last" : "McCarthy" }
{ "first" : "Grace", "last" : "Hopper" }

‘Hoặc có thể sử dụng biểu thức $ ifNull để chỉ định một số tài liệu khác là root; ví dụ:
> db.Mycollection.aggregate([
... { $replaceRoot: { newRoot: { $ifNull: [ "$name", { _id: "$_id", missingName:
true} ] } } }
... ])
{ "first" : "John", "last" : "Backus" }
{ "first" : "John", "last" : "McCarthy" }
{ "first" : "Grace", "last" : "Hopper" }
{ "_id" : 4, "missingName" : true }

Vd: $replaceRoot with an Embedded Document Field


> db.createCollection("People")
{ "ok" : 1 }
> db. People.insertMany([
... { "_id" : 1, "name" : "Arlene", "age" : 34, "pets" : { "dogs" : 2, "cats" : 1 } },
... { "_id" : 2, "name" : "Sam", "age" : 41, "pets" : { "cats" : 1, "fish" : 3 } },
... { "_id" : 3, "name" : "Maria", "age" : 25 },
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3 ] }
‘Thao tác sau sử dụng giai đoạn $ ReplaceRoot để thay thế từng document đầu vào bằng kết
quả của thao tác $ mergeObjects. Biểu thức $ mergeObjects hợp nhất document mặc định
được chỉ định với document pets.
> db.People.aggregate( [
... { $replaceRoot: { newRoot: { $mergeObjects: [ { dogs: 0, cats: 0, birds: 0, fish: 0 },
"$pets" ] }} }
... ] )
{ "dogs" : 2, "cats" : 1, "birds" : 0, "fish" : 0 }
{ "dogs" : 0, "cats" : 1, "birds" : 0, "fish" : 3 }
24
{ "dogs" : 0, "cats" : 0, "birds" : 0, "fish" : 0 }

Vd: $replaceRoot with a Document Nested in an Array


> db.createCollection("MonhocXYZ")
{ "ok" : 1 }
> db.MonhocXYZ.insertMany([
... {
... "_maso" : 1,
... "diem" : [
... { "test": "Kỳ 1", "thuchanh" : 8.0, "lythuyet" : 7.5, "kynang" : 6.5 },
... { "test": "Kỳ 2", "thuchanh" : 8.5, "lythuyet" : 9.0, "kynang" : 4.0 },
... { "test": "Kỳ 3", "thuchanh" : 9.5, "lythuyet" : 8.5, "kynang" : 6.0 }
... ]
... },
... {
... "_maso" : 2,
... "diem" : [
... { "test": "Kỳ 1", "thuchanh" : 9.0, "lythuyet" : 7.5, "kynang" : 6.0 },
... { "test": "Kỳ 2", "thuchanh" : 9.0, "lythuyet" : 9.0, "kynang" : 3.0 },
... { "test": "Kỳ 3", "thuchanh" : 9.0, "lythuyet" : 8.5, "kynang" : 4.0 }
... ]
... }
... ])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60c3215e4b8941d9f23fdc91"),
ObjectId("60c3215e4b8941d9f23fdc92")
]

25
}
‘Thao tác sau sẽ lấy các document nhúng có field diem.lythuyet lớn hơn hoặc bằng 8.5 lên
cấp cao nhất (dùng $unwind để chia một document đang sử dụng field Array thành nhiều
document)
> db.MonhocXYZ.aggregate( [
... { $unwind: "$diem" },
... { $match: { "diem.lythuyet" : { $gte: 8.5 } } },
... { $replaceRoot: { newRoot: "$diem" } }
... ] )
{ "test" : "Kỳ 2", "thuchanh" : 8.5, "lythuyet" : 9, "kynang" : 4 }
{ "test" : "Kỳ 3", "thuchanh" : 9.5, "lythuyet" : 8.5, "kynang" : 6 }
{ "test" : "Kỳ 2", "thuchanh" : 9, "lythuyet" : 9, "kynang" : 3 }
{ "test" : "Kỳ 3", "thuchanh" : 9, "lythuyet" : 8.5, "kynang" : 4 }

‘Thao tác sau sẽ lấy các document nhúng có field diem.thuchanh lớn hơn hoặc bằng 9.5
> db.MonhocXYZ.aggregate( [
... { $unwind: "$diem" },
... { $match: { "diem.thuchanh" : { $gte: 9.5 } } },
... { $replaceRoot: { newRoot: "$diem" } }
... ] )
{ "test" : "Kỳ 3", "thuchanh" : 9.5, "lythuyet" : 8.5, "kynang" : 6 }

Vd: dùng trộn đối tượng $mergeobjects: https://www.educba.com/lookup-in-mongodb/


> db.createCollection("Orders")
{ "ok" : 1 }

> db.Orders.insert([ //so sánh với insertMany()


... { "_id" : 1, "item" : "abc", "price" : 12, "ordered" : 2 },
... { "_id" : 2, "item" : "xyz", "price" : 20, "ordered" : 1 }
... ])
26
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.createCollection("Items")
{ "ok" : 1 }
> db.Items.insertMany([ //so sánh với inser()
... { "_id" : 1, "item" : "abc", description: "product 1", "instock" : 120 },
... { "_id" : 2, "item" : "def", description: "product 2", "instock" : 80 },
... { "_id" : 3, "item" : "xyz", description: "product 3", "instock" : 60 }
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3 ] }
‘Thao tác sau trước tiên sử dụng giai đoạn $ lookup để kết hợp hai collection theo các field
item và sau đó sử dụng $ mergeObjects trong $ ReplaceRoot để hợp nhất các document đã
kết hợp từ Items và Orders
> db.Orders.aggregate([
... {
... $lookup: {
... from: "Items",
... localField: "item",
... foreignField: "item",
... as: "fromItems"
... }
... },

27
... {
... $replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$fromItems",
0 ] }, "$$ROOT" ] } }
... },
... { $project: { fromItems: 0 } }
... ])
{ "_id" : 1, "item" : "abc", "description" : "product 1", "instock" : 120, "price" : 12,
"ordered" : 2 }
{ "_id" : 2, "item" : "xyz", "description" : "product 3", "instock" : 60, "price" : 20, "ordered"
:1}

Sinh viên tham khảo thêm:


https://www.educba.com/lookup-in-mongodb/
https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/

2/. Mapreduce:
Trong MongoDB, map-Reduce là một mô hình lập trình xử lý dữ liệu giúp thực hiện các
thao tác trên các Big Data (tập dữ liệu lớn) và tạo ra các kết quả tổng hợp. MongoDB cung
cấp hàm mapReduce () để thực hiện các thao tác rút gọn dữ liệu. Chức năng này có hai chức
năng chính, tức là chức năng Map và chức năng Reduce.
Hàm map được sử dụng để nhóm tất cả dữ liệu dựa trên cặp key-value (khóa-giá trị) và hàm
Reduce được sử dụng để thực hiện các thao tác trên dữ liệu được ánh xạ. Vì vậy, dữ liệu
được ánh xạ và rút gọn một cách độc lập trong các không gian khác nhau và sau đó được kết
hợp với nhau để có kết quả sẽ lưu vào bộ sưu tập mới được chỉ định.
Cú pháp:
>db.collection.example(
function() {emit(key, value);}, //Define map function
function(key,values) {return reduceFunction}, //Define reduce function
{
out: collection,
query: document,
sort: document,
limit: number
28
}
)

 Map: Đây là một hàm JavaScript, được sử dụng để tạo các cặp khóa-giá trị.
 Reduce: Một chức năng JavaScript sử dụng để nhóm tất cả các tài liệu có cùng một
khóa.
 Out: Nó chỉ định vị trí của đầu ra truy vấn MapReduce.
 Query: Chúng ta có thể sử dụng nó để chỉ định các tiêu chí lựa chọn để chọn tài liệu.
 Sort: Sử dụng để chỉ định tiêu chí sắp xếp và các lệnh tùy chọn.
 Limit: Nó chỉ định số lượng tài liệu được trả lại. Nó là một lệnh tùy chọn được sử
dụng.
Ví dụ:

Hàm mapReduce () này thường chỉ hoạt động trên các Big Data (tập dữ liệu lớn). Sử dụng
Map Reduce, bạn có thể thực hiện các phép toán tổng hợp như max, avg trên dữ liệu bằng
cách sử dụng một số khóa và nó tương tự như groupBy trong SQL. Nó thực hiện trên dữ
liệu một cách độc lập và song song. Hãy cố gắng hiểu mapReduce () bằng ví dụ với 4 bước
sau:
Vd: tạo database db4, collection Orders, chèn 09 thêm document
> use db4
> db.createCollection("Orders")
> db.Orders.insertMany([
…{cust_name: "Trần Thị Kim", or_date: new Date("2021-05-01"), amount: 120, items:
[{name: "Táo xanh", qty: 10, price: 12}], status: "A"},
…{cust_name: "Lê Thị Bé", or_date: new Date("2021-05-01"), amount: 200, items:
[{name: "Quít hồng", qty: 10, price: 10}, {name: "Táo vàng", qty: 10, price: 10}], status:
"A"},
…{cust_name: "Nguyễn Phú Đại", or_date: new Date("2021-05-03"), amount: 250, items:
[{name: "Sầu riêng", qty: 10, price: 20}, {name: "Thanh long trắng", qty: 5, price: 10}],
status: "A"},
…{cust_name: "Phạm Phú", or_date: new Date("2021-05-04"), amount: 500, items:
[{name: "Sầu riêng vàng", qty: 10, price: 50}], status: "B"},
29
…{cust_name: "Ngô Văn Lăng", or_date: new Date("2021-05-05"), amount: 350, items:
[{name: "Táo vàng", qty: 20, price: 10}, {name: "Táo đỏ", qty: 10, price: 10}, {name: "Ổi
tím", qty: 5, price: 10}], status: "A"},
…{cust_name: "Lê Thị Bé", or_date: new Date("2021-05-06"), amount: 150, items:
[{name: "Quít vàng", qty: 10, price: 15}], status: "B"},
…{cust_name: "Trần Thị Kim", or_date: new Date("2021-05-06"), amount: 200, items:
[{name: "Táo xanh", qty: 10, price: 12}, {name: "Ổi vàng", qty: 10, price: 8}], status: "A"},
{cust_name: "Ngô Văn Lăng", or_date: new Date("2021-05-07"), amount: 450, items:
[{name: "Sầu riêng đỏ", qty: 10, price: 15},{name: "Ổi hồng", qty: 10, price: 10}, {name:
"Táo đỏ", qty: 20, price: 10}], status: "P"},
…{cust_name: "Lê Thị Bé", or_date: new Date("2021-05-08"), amount: 200, items:
[{name: "Táo vàng", qty: 10, price: 10}, {name: "Sầu riêng", qty: 5, price: 20}], status: "P"}
…])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60beda1da164ce770305514d"),
ObjectId("60beda1da164ce770305514e"),
ObjectId("60beda1da164ce770305514f"),
ObjectId("60beda1da164ce7703055150"),
ObjectId("60beda1da164ce7703055151"),
ObjectId("60beda1da164ce7703055152"),
ObjectId("60beda1da164ce7703055153"),
ObjectId("60beda1da164ce7703055154"),
ObjectId("60beda1da164ce7703055155")
]
}
> db.Orders.find().pretty()
{
"_id" : ObjectId("60beda1da164ce770305514d"),
"cust_name" : "Trần Thị Kim",
"or_date" : ISODate("2021-05-01T00:00:00Z"),

30
"amount" : 120,
"items" : [
{
"name" : "Táo xanh",
"qty" : 10,
"price" : 12
}
],
"status" : "A"
}
{
"_id" : ObjectId("60beda1da164ce770305514e"),
"cust_name" : "Lê Thị Bé",
"or_date" : ISODate("2021-05-01T00:00:00Z"),
"amount" : 200,
"items" : [
{
"name" : "Quít hồng",
"qty" : 10,
"price" : 10
},
{
"name" : "Táo vàng",
"qty" : 10,
"price" : 10
}
],
"status" : "A"
}
{
31
"_id" : ObjectId("60beda1da164ce770305514f"),
"cust_name" : "Nguyễn Phú Đại",
"or_date" : ISODate("2021-05-03T00:00:00Z"),
"amount" : 250,
"items" : [
{
"name" : "Sầu riêng",
"qty" : 10,
"price" : 20
},
{
"name" : "Thanh long trắng",
"qty" : 5,
"price" : 10
}
],
"status" : "A"
}
{
"_id" : ObjectId("60beda1da164ce7703055150"),
"cust_name" : "Phạm Phú",
"or_date" : ISODate("2021-05-04T00:00:00Z"),
"amount" : 500,
"items" : [
{
"name" : "Sầu riêng vàng",
"qty" : 10,
"price" : 50
}
],
32
"status" : "B"
}
{
"_id" : ObjectId("60beda1da164ce7703055151"),
"cust_name" : "Ngô Văn Lăng",
"or_date" : ISODate("2021-05-05T00:00:00Z"),
"amount" : 350,
"items" : [
{
"name" : "Táo vàng",
"qty" : 20,
"price" : 10
},
{
"name" : "Táo đỏ",
"qty" : 10,
"price" : 10
},
{
"name" : "Ổi tím",
"qty" : 5,
"price" : 10
}
],
"status" : "A"
}
{
"_id" : ObjectId("60beda1da164ce7703055152"),
"cust_name" : "Lê Thị Bé",
"or_date" : ISODate("2021-05-06T00:00:00Z"),
33
"amount" : 150,
"items" : [
{
"name" : "Quít vàng",
"qty" : 10,
"price" : 15
}
],
"status" : "B"
}
{
"_id" : ObjectId("60beda1da164ce7703055153"),
"cust_name" : "Trần Thị Kim",
"or_date" : ISODate("2021-05-06T00:00:00Z"),
"amount" : 200,
"items" : [
{
"name" : "Táo xanh",
"qty" : 10,
"price" : 12
},
{
"name" : "Ổi vàng",
"qty" : 10,
"price" : 8
}
],
"status" : "A"
}
{
34
"_id" : ObjectId("60beda1da164ce7703055154"),
"cust_name" : "Ngô Văn Lăng",
"or_date" : ISODate("2021-05-07T00:00:00Z"),
"amount" : 450,
"items" : [
{
"name" : "Sầu riêng đỏ",
"qty" : 10,
"price" : 15
},
{
"name" : "Ổi hồng",
"qty" : 10,
"price" : 10
},
{
"name" : "Táo đỏ",
"qty" : 20,
"price" : 10
}
],
"status" : "P"
}
{
"_id" : ObjectId("60beda1da164ce7703055155"),
"cust_name" : "Lê Thị Bé",
"or_date" : ISODate("2021-05-08T00:00:00Z"),
"amount" : 200,
"items" : [
{
35
"name" : "Táo vàng",
"qty" : 10,
"price" : 10
},
{
"name" : "Sầu riêng",
"qty" : 5,
"price" : 20
}
],
"status" : "P"
}
‘thực hiện MapReduce theo các bước
‘Bước 1: định nghĩa hàm map_function1, có ánh xạ đến trường cust_name và amount
> var map_function1 = function(){
... emit(this.cust_name, this,amount);
... };

‘Bước 2: định nghĩa hàm reduce_function1, có 2 tham số keyCustName và valueAmount,


hàm trả về 1 mảng chứa giá trị tổng valueAmount.
> var reduce_function1 = function(keyCustName, valueAmount){
…return Array.sum(valueAmount);
…};

‘Bước 3: Thực hiện mapReduce trên tất cả các document trong collection Orders bằng cách
sử dụng hàm map_function1 và hàm reduce_function1, và xuất kết quả thực hiện ra
collection Ketqua1
> db.Orders.mapReduce( map_function1, reduce_function1, {out: "Ketqua1"} )
{ "result" : "Ketqua1", "ok" : 1 }

36
‘Bước 4: collection Ketqua1 đã được tạo ra và dùng phương thức find() để xem kết quả
> db.Ketqua1.find()
{ "_id" : "Nguyễn Phú Đại", "value" : 250 }
{ "_id" : "Phạm Phú", "value" : 500 }
{ "_id" : "Lê Thị Bé", "value" : 550 }
{ "_id" : "Trần Thị Kim", "value" : 320 }
{ "_id" : "Ngô Văn Lăng", "value" : 800 }
Hoặc xem kết quả được xắp xếp theo _id giảm dần
> db.Ketqua1.find().sort({_id: -1})
{ "_id" : "Trần Thị Kim", "value" : 320 }
{ "_id" : "Phạm Phú", "value" : 500 }
{ "_id" : "Ngô Văn Lăng", "value" : 800 }
{ "_id" : "Nguyễn Phú Đại", "value" : 250 }
{ "_id" : "Lê Thị Bé", "value" : 550 }
Hoặc xem kết quả được xắp xếp theo value tăng dần
> db.Ketqua1.find().sort({value: 1})
{ "_id" : "Nguyễn Phú Đại", "value" : 250 }
{ "_id" : "Trần Thị Kim", "value" : 320 }
{ "_id" : "Phạm Phú", "value" : 500 }
{ "_id" : "Lê Thị Bé", "value" : 550 }
{ "_id" : "Ngô Văn Lăng", "value" : 800 }

Vd: thực hiện map reduce 1 bước


> db.createCollection("BanHang")
{ "ok" : 1 }> db.BanHang.insertMany([

…{sohd: "1234", makh: "k01", ngay: new Date("2021-05-01"), ttien: 1200},


…{sohd: "1235", makh: "k02", ngay: new Date("2021-05-01"), ttien: 2000},
…{sohd: "1236", makh: "k03", ngay: new Date("2021-05-02"), ttien: 2300},

37
…{sohd: "1237", makh: "k01", ngay: new Date("2021-05-02"), ttien: 2400},
…{sohd: "1238", makh: "k03", ngay: new Date("2021-05-02"), ttien: 1500},
…{sohd: "1239", makh: "k05", ngay: new Date("2021-05-03"), ttien: 4800},
…{sohd: "1240", makh: "k04", ngay: new Date("2021-05-03"), ttien: 2500},
…{sohd: "1241", makh: "k02", ngay: new Date("2021-05-03"), ttien: 5000},
…{sohd: "1242", makh: "k01", ngay: new Date("2021-05-04"), ttien: 2600},
…{sohd: "1243", makh: "k05", ngay: new Date("2021-05-05"), ttien: 4100},
…{sohd: "1243", makh: "k01", ngay: new Date("2021-05-05"), ttien: 3600},
…{sohd: "1244", makh: "k03", ngay: new Date("2021-05-06"), ttien: 1200},
…{sohd: "1245", makh: "k01", ngay: new Date("2021-05-06"), ttien: 1400}
…])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60bf577730ef6451158dd341"),
ObjectId("60bf577730ef6451158dd342"),
ObjectId("60bf577730ef6451158dd343"),
ObjectId("60bf577730ef6451158dd344"),
ObjectId("60bf577730ef6451158dd345"),
ObjectId("60bf577730ef6451158dd346"),
ObjectId("60bf577730ef6451158dd347"),
ObjectId("60bf577730ef6451158dd348"),
ObjectId("60bf577730ef6451158dd349"),
ObjectId("60bf577730ef6451158dd34a"),
ObjectId("60bf577730ef6451158dd34b"),
ObjectId("60bf577730ef6451158dd34c"),
ObjectId("60bf577730ef6451158dd34d")
]
}
> db.BanHang.find().pretty()
38
{
"_id" : ObjectId("60bf577730ef6451158dd341"),
"sohd" : "1234",
"makh" : "k01",
"ngay" : ISODate("2021-05-01T00:00:00Z"),
"ttien" : 1200
}
{
"_id" : ObjectId("60bf577730ef6451158dd342"),
"sohd" : "1235",
"makh" : "k02",
"ngay" : ISODate("2021-05-01T00:00:00Z"),
"ttien" : 2000
}
{
"_id" : ObjectId("60bf577730ef6451158dd343"),
"sohd" : "1236",
"makh" : "k03",
"ngay" : ISODate("2021-05-02T00:00:00Z"),
"ttien" : 2300
}
{
"_id" : ObjectId("60bf577730ef6451158dd344"),
"sohd" : "1237",
"makh" : "k01",
"ngay" : ISODate("2021-05-02T00:00:00Z"),
"ttien" : 2400
}
{
"_id" : ObjectId("60bf577730ef6451158dd345"),
39
"sohd" : "1238",
"makh" : "k03",
"ngay" : ISODate("2021-05-02T00:00:00Z"),
"ttien" : 1500
}
{
"_id" : ObjectId("60bf577730ef6451158dd346"),
"sohd" : "1239",
"makh" : "k05",
"ngay" : ISODate("2021-05-03T00:00:00Z"),
"ttien" : 4800
}
{
"_id" : ObjectId("60bf577730ef6451158dd347"),
"sohd" : "1240",
"makh" : "k04",
"ngay" : ISODate("2021-05-03T00:00:00Z"),
"ttien" : 2500
}
{
"_id" : ObjectId("60bf577730ef6451158dd348"),
"sohd" : "1241",
"makh" : "k02",
"ngay" : ISODate("2021-05-03T00:00:00Z"),
"ttien" : 5000
}
{
"_id" : ObjectId("60bf577730ef6451158dd349"),
"sohd" : "1242",
"makh" : "k01",
40
"ngay" : ISODate("2021-05-04T00:00:00Z"),
"ttien" : 2600
}
{
"_id" : ObjectId("60bf577730ef6451158dd34a"),
"sohd" : "1243",
"makh" : "k05",
"ngay" : ISODate("2021-05-05T00:00:00Z"),
"ttien" : 4100
}
{
"_id" : ObjectId("60bf577730ef6451158dd34b"),
"sohd" : "1243",
"makh" : "k01",
"ngay" : ISODate("2021-05-05T00:00:00Z"),
"ttien" : 3600
}
{
"_id" : ObjectId("60bf577730ef6451158dd34c"),
"sohd" : "1244",
"makh" : "k03",
"ngay" : ISODate("2021-05-06T00:00:00Z"),
"ttien" : 1200
}
{
"_id" : ObjectId("60bf577730ef6451158dd34d"),
"sohd" : "1245",
"makh" : "k01",
"ngay" : ISODate("2021-05-06T00:00:00Z"),
"ttien" : 1400
41
}
‘Map reduce 1 bước: tổng hợp theo makh: k01, tính tổng ttien
> db.BanHang.mapReduce (
…function() {emit(this.makh, this.ttien);},
…function(key, values) {return Array.sum(values)},
…{
…query: {makh: "k01"},
…out: "ketqua2"
…}
…)
{ "result" : "ketqua2", "ok" : 1 }
> db.ketqua2.find()
{ "_id" : "k01", "value" : 11200 }

‘Map reduce 1 bước: tổng hợp theo makh, tính tổng ttien
> db.BanHang.mapReduce(
…function() {emit(this.makh, this.ttien);},
…function(key, values){return Array.sum(values)},
…{
…out : "ketqua3"
…})
{ "result" : "ketqua3", "ok" : 1 }
> db.ketqua3.find()
{ "_id" : "k05", "value" : 8900 }
{ "_id" : "k04", "value" : 2500 }
{ "_id" : "k03", "value" : 5000 }
{ "_id" : "k01", "value" : 11200 }
{ "_id" : "k02", "value" : 7000 }

42
‘Map reduce 1 bước: tổng hợp theo ngay, tính tổng ttien
> db.BanHang.mapReduce(
…function() {emit(this.ngay, this.ttien);},
…function(key, values){return Array.sum(values)},
…{
…out : "ketqua4"
…})
{ "result" : "ketqua4", "ok" : 1 }
> db.ketqua4.find()
{ "_id" : ISODate("2021-05-02T00:00:00Z"), "value" : 6200 }
{ "_id" : ISODate("2021-05-03T00:00:00Z"), "value" : 12300 }
{ "_id" : ISODate("2021-05-04T00:00:00Z"), "value" : 2600 }
{ "_id" : ISODate("2021-05-05T00:00:00Z"), "value" : 7700 }
{ "_id" : ISODate("2021-05-06T00:00:00Z"), "value" : 2600 }
{ "_id" : ISODate("2021-05-01T00:00:00Z"), "value" : 3200 }

‘Map reduce 1 bước: tổng hợp theo makh, tính tổng ttien, sắp xếp theo makh
> db.BanHang.mapReduce(
…function() {emit(this.makh, this.ttien);},
…function(key, values){return Array.sum(values)},
…{
…query: {ngay: {$gt: new Date("2021-05-03")}},
…sort: {makh: 1},
…out : "ketqua5"
…})
{ "result" : "ketqua5", "ok" : 1 }
> db.ketqua5.find()
{ "_id" : "k01", "value" : 7600 }
{ "_id" : "k03", "value" : 1200 }

43
{ "_id" : "k05", "value" : 4100 }

Tham khảo:
https://www.geeksforgeeks.org/mongodb-map-reduce/
https://docs.mongodb.com/manual/core/map-reduce/
https://docs.mongodb.com/manual/reference/method/db.collection.mapReduce/
https://www.tutorialspoint.com/mongodb/mongodb_map_reduce.htm
https://quantrimang.com/map-reduce-trong-mongodb-157785
https://www.geeksforgeeks.org/mongodb-map-reduce/
https://data-flair.training/blogs/mongodb-mapreduce/

3/. Replication, Sharding, Create Backup, … :


Tham khảo: https://www.tutorialspoint.com/mongodb/mongodb_replication.htm

4/. Quan hệ trong MongoDB:


Các mối quan hệ trong MongoDB thể hiện cách các document khác nhau có liên quan với
nhau về mặt logic. Các mối quan hệ có thể được mô hình hóa thông qua các phương pháp
tiếp cận Nhúng và Tham chiếu. Các mối quan hệ như vậy có thể là 1: 1, 1: N, N: 1.
A/. Quan hệ 1-1:
+ Cách nhúng (embedded): xét 2 document patron và address, có quan hệ 1 – 1 (một
khách có 1 địa chỉ)
// patron document
{
_id: "joe",
name: "Joe Bookreader"
}

// address document
{
patron_id: "joe", // reference to patron document
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}

44
Nếu dữ liệu địa chỉ thường xuyên được truy xuất cùng với thông tin tên, thì mô hình dữ liệu
tốt nhất sẽ là nhúng dữ liệu địa chỉ vào dữ liệu khách hàng, như trong tài liệu sau:
{
_id: "joe",
name: "Joe Bookreader",
address: {
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
}

+ Cách dùng Subset Pattern (tham chiếu):


Một vấn đề tiềm ẩn với mẫu dữ liệu nhúng là nó có thể dẫn đến các tài liệu lớn chứa các
trường mà ứng dụng không cần. Dữ liệu không cần thiết này có thể gây ra tải nặng thêm cho
máy chủ và làm chậm hoạt động đọc. Thay vào đó, ta có thể sử dụng mẫu tập hợp con để
truy xuất tập hợp con dữ liệu được truy cập thường xuyên nhất trong một lệnh gọi cơ sở dữ
liệu. Xem xét một ứng dụng hiển thị thông tin trên phim. Cơ sở dữ liệu chứa một bộ sưu tập
phim với lược đồ sau:
{
"_id": 1,
"title": "The Arrival of a Train",
"year": 1896,
"runtime": 1,
"released": ISODate("01-25-1896"),
"poster": "http://ia.media-
imdb.com/images/M/MV5BMjEyNDk5MDYzOV5BMl5BanBnXkFtZTgwNjIxMTEwMzE@
._V1_SX300.jpg",
"plot": "A group of people are standing in a straight line along
the platform of a railway station, waiting for a train, which is
seen coming at some distance. When the train stops at the
platform, ...",
"fullplot": "A group of people are standing in a straight line
along the platform of a railway station, waiting for a train,
which is seen coming at some distance. When the train stops at the
platform, the line dissolves. The doors of the railway-cars open,
and people on the platform help passengers to get off.",
"lastupdated": ISODate("2015-08-15T10:06:53"),
"type": "movie",
"directors": [ "Auguste Lumière", "Louis Lumière" ],
"imdb": {
"rating": 7.3,
"votes": 5043,
"id": 12
},
"countries": [ "France" ],
"genres": [ "Documentary", "Short" ],
45
"tomatoes": {
"viewer": {
"rating": 3.7,
"numReviews": 59
},
"lastUpdated": ISODate("2020-01-09T00:02:53")
}
}

Hiện tại, collection movie chứa một số trường mà ứng dụng không cần thiết hiển thị, chẳng
hạn như thông tin về toàn cảnh và xếp hạng. Thay vì lưu trữ tất cả dữ liệu movie trong một
collection duy nhất, ta có thể chia collection thành hai collection có quan hệ 1 – 1 như sau:
// movie collection
{
"_id": 1,
"title": "The Arrival of a Train",
"year": 1896,
"runtime": 1,
"released": ISODate("1896-01-25"),
"type": "movie",
"directors": [ "Auguste Lumière", "Louis Lumière" ],
"countries": [ "France" ],
"genres": [ "Documentary", "Short" ],
}


// movie_details collection

{
"_id": 156,
"movie_id": 1, // reference to the movie collection
"poster": "http://ia.media-
imdb.com/images/M/MV5BMjEyNDk5MDYzOV5BMl5BanBnXkFtZTgwNjIxMTEwMzE@
._V1_SX300.jpg",
"plot": "A group of people are standing in a straight line along
the platform of a railway station, waiting for a train, which is
seen coming at some distance. When the train stops at the
platform, ...",
"fullplot": "A group of people are standing in a straight line
along the platform of a railway station, waiting for a train,
which is seen coming at some distance. When the train stops at the
platform, the line dissolves. The doors of the railway-cars open,
and people on the platform help passengers to get off.",
"lastupdated": ISODate("2015-08-15T10:06:53"),
"imdb": {
"rating": 7.3,
"votes": 5043,
"id": 12

46
},
"tomatoes": {
"viewer": {
"rating": 3.7,
"numReviews": 59
},
"lastUpdated": ISODate("2020-01-29T00:02:53")
}
}

B/. Quan hệ 1-N:


+ Cách nhúng: Nhúng dữ liệu được kết nối vào một document duy nhất có thể giảm số
lượng thao tác đọc cần thiết để lấy dữ liệu.
Vd: 1 khách hàng có nhiều địa chỉ (dùng mảng chứa các địa chỉ)
{
"_id": "joe",
"name": "Joe Bookreader",
"addresses": [
{
"street": "123 Fake Street",
"city": "Faketon",
"state": "MA",
"zip": "12345"
},
{
"street": "1 Some Other Street",
"city": "Boston",
"state": "MA",
"zip": "12345"
}
]
}

+ Cách tham chiếu:


Vd: 1 trang web thương mại điện tử có danh sách các bài đánh giá cho một sản phẩm.
Collection products.
{
"_id": 1,
"name": "Super Widget",
"description": "This is the most useful item in your toolbox.",
"price": { "value": NumberDecimal("119.99"), "currency":
"USD" },
"reviews": [
{
"review_id": 786,
"review_author": "Kristina",
47
"review_text": "This is indeed an amazing widget.",
"published_date": ISODate("2019-02-18")
},
{
"review_id": 785,
"review_author": "Trina",
"review_text": "Nice product. Slow shipping.",
"published_date": ISODate("2019-02-17")
},
...
{
"review_id": 1,
"review_author": "Hans",
"review_text": "Meh, it's okay.",
"published_date": ISODate("2017-12-06")
}
]
}

Vd: Thay vì lưu trữ tất cả các bài đánh giá với sản phẩm, ta có thể chia collection (bộ sưu
tập) thành hai collection: Collection products lưu trữ thông tin về từng sản phẩm, bao gồm
mười đánh giá gần đây nhất về sản phẩm, và collection reviews chứa tất cả các bài đánh giá
của các sán phẩm.
{
"_id": 1,
"name": "Super Widget",
"description": "This is the most useful item in your toolbox.",
"price": { "value": NumberDecimal("119.99"), "currency":
"USD" },
"reviews": [
{
"review_id": 786,
"review_author": "Kristina",
"review_text": "This is indeed an amazing widget.",
"published_date": ISODate("2019-02-18")
}
...
{
"review_id": 776,
"review_author": "Pablo",
"review_text": "Amazing!",
"published_date": ISODate("2019-02-16")
}
]
}

Và collection reviews lưu trữ tất cả các đánh giá. Mỗi bài đánh giá có chứa một tham chiếu
đến collection products mà nó đã được viết.

48
{
"review_id": 786,
"product_id": 1,
"review_author": "Kristina",
"review_text": "This is indeed an amazing widget.",
"published_date": ISODate("2019-02-18")
}
{
"review_id": 785,
"product_id": 1,
"review_author": "Trina",
"review_text": "Nice product. Slow shipping.",
"published_date": ISODate("2019-02-17")
}
...
{
"review_id": 1,
"product_id": 1,
"review_author": "Hans",
"review_text": "Meh, it's okay.",
"published_date": ISODate("2017-12-06")
}

5/. Truy vấn trên nhiều collection:


Để truy vấn nhiều collection trong mongodb, Ta có thể xâu chuỗi nhiều giai đoạn $lookup
để có kết quả mong muốn.

Vd: tạo 3 collection, truy vấn trên 3 collection


> db.createCollection("Users")
{ "ok" : 1 }
> db.createCollection("Userinfo")
{ "ok" : 1 }
> db.createCollection("Userrole")
{ "ok" : 1 }
> db.Users.insertMany([
... {userid: "Ad", email: "admin1@gmail.com", username: "admin"},
... {userid: "us1", email: "phanthanhlong@gmail.com", username: "user 1"},
... {userid: "us5", email: "nguyenvanan@gmail.com", username: "user 2"}
... ])

49
> db.Users.find()
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9a"), "userid" : "Ad", "email" :
"admin1@gmail.com", "username" : "admin" }
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9b"), "userid" : "us1", "email" :
"phanthanhlong@gmail.com", "username" : "user 1" }
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9c"), "userid" : "us5", "email" :
"nguyenvanan@gmail.com", "username" : "user 2" }
> db.Userinfo.insertMany([
... {userid: "Ad", phone: "1000000000"},
... {userid: "us1", phone: "1000000001"},
... {userid: "us5", phone: "1000000003"}
... ])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60c34e6d4b8941d9f23fdc9d"),
ObjectId("60c34e6d4b8941d9f23fdc9e"),
ObjectId("60c34e6d4b8941d9f23fdc9f")
]
}
> db.Userrole.insertMany([
... {userid: "Ad", role: "Admin"},
... {userid: "us1", role: "Member 1"},
... {userid: "us5", role: "Member 5"}
... ])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("60c34ef34b8941d9f23fdca0"),
ObjectId("60c34ef34b8941d9f23fdca1"),
ObjectId("60c34ef34b8941d9f23fdca2")
50
]
}

> db.Users.aggregate([
... { $lookup: {
... from: "Userrole",
... localField: "userid",
... foreignField: "userid",
... as: "Users_Role" }
... },
... { $lookup: {
... from: "Users_Info",
... localField: "userid",
... foreignField: "userid",
... as: "Users_Info" }
... },
... ])
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9a"), "userid" : "Ad", "email" :
"admin1@gmail.com", "username" : "admin", "Users_Role" : [ { "_id" :
ObjectId("60c34ef34b8941d9f23fdca0"), "userid" : "Ad", "role" : "Admin" } ],
"Users_Info" : [ ] }
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9b"), "userid" : "us1", "email" :
"phanthanhlong@gmail.com", "username" : "user 1", "Users_Role" : [ { "_id" :
ObjectId("60c34ef34b8941d9f23fdca1"), "userid" : "us1", "role" : "Member 1" } ],
"Users_Info" : [ ] }
{ "_id" : ObjectId("60c34d384b8941d9f23fdc9c"), "userid" : "us5", "email" :
"nguyenvanan@gmail.com", "username" : "user 2", "Users_Role" : [ { "_id" :
ObjectId("60c34ef34b8941d9f23fdca2"), "userid" : "us5", "role" : "Member 5" } ],
"Users_Info" : [ ] }

Bài tập nộp chương 4:

51

You might also like