Mặc dù câu hỏi của bạn lẽ ra phải được làm rõ ràng hơn, nhưng mẫu đầu ra của bạn từ nguồn gợi ý rằng bạn đang tìm kiếm:
- Tổng số tin nhắn trên mỗi "uid"
- Số lượng giá trị riêng biệt trong "tới"
- Số lượng giá trị riêng biệt trong "from"
- Tóm tắt số lượng mỗi "giờ" cho mỗi "uid"
Tất cả điều này có thể thực hiện được trong một câu lệnh tổng hợp duy nhất và chỉ cần quản lý cẩn thận các danh sách riêng biệt và sau đó thực hiện một số thao tác để lập bản đồ kết quả cho mỗi giờ trong khoảng thời gian 24 giờ.
Cách tiếp cận tốt nhất ở đây được hỗ trợ bởi các toán tử được giới thiệu trong MongoDB 3.2:
db.collection.aggregate([
// First group by hour within "uid" and keep distinct "to" and "from"
{ "$group": {
"_id": {
"uid": "$uid",
"time": { "$hour": "$timestamp" }
},
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"count": { "$sum": 1 }
}},
// Roll-up to "uid" and keep each hour in an array
{ "$group": {
"_id": "$_id.uid",
"total": { "$sum": "$count" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": {
"$push": {
"index": "$_id.time",
"count": "$count"
}
}
}},
// Getting distinct "to" and "from" requires a double unwind of arrays
{ "$unwind": "$to" },
{ "$unwind": "$to" },
{ "$unwind": "$from" },
{ "$unwind": "$from" },
// And then adding back to sets for distinct
{ "$group": {
"_id": "$_id",
"total": { "$first": "$total" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": { "$first": "$temp_hours" }
}},
// Map out for each hour and count size of distinct lists
{ "$project": {
"count": "$total",
"from_count": { "$size": "$from" },
"to_count": { "$size": "$to" },
"hours": {
"$map": {
"input": [
00,01,02,03,04,05,06,07,08,09,10,11,
12,13,14,15,16,17,18,19,20,21,22,23
],
"as": "el",
"in": {
"$ifNull": [
{ "$arrayElemAt": [
{ "$map": {
"input": { "$filter": {
"input": "$temp_hours",
"as": "tmp",
"cond": {
"$eq": [ "$$el", "$$tmp.index" ]
}
}},
"as": "out",
"in": "$$out.count"
}},
0
]},
0
]
}
}
}
}},
// Optionally sort in "uid" order
{ "$sort": { "_id": 1 } }
])
Trước MongoDB 3.2, bạn cần tham gia nhiều hơn một chút để ánh xạ nội dung mảng cho tất cả các giờ trong ngày:
db.collection.aggregate([
// First group by hour within "uid" and keep distinct "to" and "from"
{ "$group": {
"_id": {
"uid": "$uid",
"time": { "$hour": "$timestamp" }
},
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"count": { "$sum": 1 }
}},
// Roll-up to "uid" and keep each hour in an array
{ "$group": {
"_id": "$_id.uid",
"total": { "$sum": "$count" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": {
"$push": {
"index": "$_id.time",
"count": "$count"
}
}
}},
// Getting distinct "to" and "from" requires a double unwind of arrays
{ "$unwind": "$to" },
{ "$unwind": "$to" },
{ "$unwind": "$from" },
{ "$unwind": "$from" },
// And then adding back to sets for distinct, also adding the indexes array
{ "$group": {
"_id": "$_id",
"total": { "$first": "$total" },
"from": { "$addToSet": "$from" },
"to": { "$addToSet": "$to" },
"temp_hours": { "$first": "$temp_hours" },
"indexes": { "$first": { "$literal": [
00,01,02,03,04,05,06,07,08,09,10,11,
12,13,14,15,16,17,18,19,20,21,22,23
] } }
}},
// Denormalize both arrays
{ "$unwind": "$temp_hours" },
{ "$unwind": "$indexes" },
// Marry up the index entries and keep either the value or 0
// Note you are normalizing the double unwind to distinct index
{ "$group": {
"_id": {
"_id": "$_id",
"index": "$indexes"
},
"total": { "$first": "$total" },
"from": { "$first": "$from" },
"to": { "$first": "$to" },
"count": {
"$max": {
"$cond": [
{ "$eq": [ "$indexes", "$temp_hours.index" ] },
"$temp_hours.count",
0
]
}
}
}},
// Sort to keep index order - !!Important!!
{ "$sort": { "_id": 1 } },
// Put the hours into the array and get sizes for other results
{ "$group": {
"_id": "$_id._id",
"count": { "$first": "$total" },
"from_count": { "$first": { "$size": "$from" } },
"to_count": { "$first": { "$size": "$to" } },
"hours": { "$push": "$count" }
}},
// Optionally sort in "uid" order
{ "$sort": { "_id": 1 } }
])
Để chia nhỏ điều đó, cả hai phương pháp ở đây đều tuân theo các bước cơ bản giống nhau, với sự khác biệt thực sự duy nhất xảy ra trên ánh xạ "giờ" trong khoảng thời gian 24 giờ.
Trong tập hợp đầu tiên $ group
, mục tiêu là nhận được kết quả mỗi giờ có trong dữ liệu và cho mỗi giá trị "uid". Toán tử tổng hợp ngày đơn giản của $ hour
giúp lấy giá trị này như một phần của khóa nhóm.
$ addToSet
hoạt động tự thân là một loại "nhóm nhỏ" và điều này cho phép giữ "tập hợp riêng biệt" cho từng giá trị "đến" và "từ" trong khi về cơ bản vẫn phân nhóm mỗi giờ.
$ group
tiếp theo mang tính "tổ chức" hơn, vì "số lượng" đã ghi cho mỗi giờ được lưu giữ trong một mảng trong khi cuộn tất cả dữ liệu để chỉ được nhóm theo "uid". Về cơ bản, điều này cung cấp cho bạn tất cả "dữ liệu" mà bạn thực sự cần cho kết quả, nhưng tất nhiên là $ addToSet
các phép toán ở đây chỉ là thêm "mảng trong mảng" của các tập hợp riêng biệt được xác định mỗi giờ.
Để nhận được các giá trị này dưới dạng danh sách thực sự riêng biệt cho mỗi "uid" và duy nhất, cần phải giải cấu trúc từng mảng bằng cách sử dụng $ unwind
và sau đó cuối cùng nhóm lại chỉ là các "bộ" riêng biệt. $ addToSet
giống nhau thu gọn điều này và $ first
các phép toán chỉ lấy các giá trị "đầu tiên" của các trường khác, các giá trị này đã giống nhau cho dữ liệu "per uid" mục tiêu. Chúng tôi hài lòng với những điều đó, vì vậy hãy giữ chúng như hiện tại.
(Các) giai đoạn cuối cùng ở đây về bản chất là "thẩm mỹ" và có thể đạt được như nhau trong mã phía máy khách. Vì không có dữ liệu hiển thị cho mỗi khoảng giờ đơn lẻ, nó cần được ánh xạ thành một mảng giá trị đại diện cho mỗi giờ. Hai cách tiếp cận ở đây khác nhau tùy thuộc vào khả năng của các toán tử khả dụng giữa các phiên bản.
Trong bản phát hành MongoDB 3.2, có $ filter
và $ arrayElemAt
toán tử cho phép bạn tạo logic một cách hiệu quả để "chuyển đổi" nguồn đầu vào của tất cả các vị trí chỉ mục có thể có (24 giờ) thành các giá trị đã được xác định cho số giờ đó trong dữ liệu có sẵn. Đây là basicalyl một "tra cứu trực tiếp" các giá trị đã được ghi lại cho mỗi giờ khả dụng để xem liệu nó có tồn tại hay không, nơi nó có số lượng được chuyển thành mảng đầy đủ hay không. Trường hợp nó không có, giá trị mặc định là 0
được sử dụng tại chỗ.
Nếu không có các toán tử đó, thực hiện "đối sánh" này về cơ bản có nghĩa là hủy chuẩn hóa cả hai mảng (dữ liệu được ghi lại và 24 vị trí đầy đủ) để so sánh và chuyển vị. Đây là những gì đang xảy ra trong cách tiếp cận thứ hai với một so sánh đơn giản của các giá trị "chỉ mục" để xem liệu có kết quả cho giờ đó hay không. $ max
ở đây chủ yếu được sử dụng vì hai $ unwind
các câu lệnh, trong đó mỗi giá trị được ghi lại từ dữ liệu nguồn sẽ được sao chép cho mọi vị trí chỉ mục có thể. Điều này "thu gọn" xuống chỉ các giá trị mong muốn cho mỗi "giờ lập chỉ mục".
Trong cách tiếp cận thứ hai đó, điều đó trở nên quan trọng đối với $ sort
trên nhóm _id
giá trị. Điều này là do nó chứa vị trí "chỉ mục" và điều này sẽ cần thiết khi di chuyển nội dung này trở lại một mảng mà bạn mong đợi được sắp xếp theo thứ tự. Tất nhiên, đây là $ group
cuối cùng ở đây, nơi các vị trí đã sắp xếp được đưa vào một mảng với $ push
.
Quay lại "danh sách riêng biệt", $ size
toán tử được sử dụng trong mọi trường hợp để xác định "độ dài" và do đó "đếm" các giá trị riêng biệt trong danh sách cho "đến" và "từ". Đây là ràng buộc thực sự duy nhất trên MongoDB 2.6 ít nhất, nhưng có thể được thay thế bằng cách đơn giản là "unwinding" từng mảng riêng lẻ và sau đó nhóm lại trên _id
đã có mặt để đếm các mục nhập mảng trong mỗi tập hợp. Đây là một quy trình cơ bản, nhưng như bạn sẽ thấy $ size
toán tử là tùy chọn tốt hơn ở đây cho hiệu suất tổng thể.
Lưu ý cuối cùng, dữ liệu kết luận của bạn hơi khác một chút, vì có thể mục nhập với "ddd" trong "from" được dự định cũng giống như "to", nhưng thay vào đó được ghi là "bbb". Điều này thay đổi số lượng riêng biệt của nhóm "uid" thứ ba cho "đến" xuống một mục nhập. Nhưng tất nhiên, kết quả logic cho dữ liệu nguồn là hợp lý:
{ "_id" : 1000000, "count" : 3, "from_count" : 2, "to_count" : 2, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 2000000, "count" : 2, "from_count" : 1, "to_count" : 1, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 3000000, "count" : 5, "from_count" : 5, "to_count" : 4, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0 ] }
N.B Nguồn cũng có lỗi đánh máy với dấu phân cách được xen kẽ với :
thay vì dấu phẩy ngay sau dấu thời gian trên tất cả các dòng.