Tóm tắt TLDR
Trong các bản phát hành MongoDB hiện đại, bạn có thể buộc điều này với $slice
chỉ ngoài kết quả tổng hợp cơ bản. Đối với kết quả "lớn", thay vào đó, hãy chạy các truy vấn song song cho mỗi nhóm [danh sách trình diễn ở cuối câu trả lời] hoặc chờ đợi máy chủ-9377 để giải quyết một mảng.
db.books.aggregate[[
{ "$group": {
"_id": {
"addr": "$addr",
"book": "$book"
},
"bookCount": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.addr",
"books": {
"$push": {
"book": "$_id.book",
"count": "$bookCount"
},
},
"count": { "$sum": "$bookCount" }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$project": {
"books": { "$slice": [ "$books", 2 ] },
"count": 1
}}
]]
MongoDB 3.6 Xem trước
Vẫn chưa giải quyết Server-9377, nhưng trong bản phát hành này, ____99 cho phép tùy chọn "không tương quan" mới lấy biểu thức
db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
0 làm đối số thay vì các tùy chọn db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
1 và db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
2. Điều này sau đó cho phép "tự tham gia" với một biểu thức đường ống khác, trong đó chúng ta có thể áp dụng db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
3 để trả về kết quả "Top-N".db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
Sự bổ sung khác ở đây tất nhiên là khả năng nội suy biến thông qua
db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
4 bằng cách sử dụng db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
5 để chọn các mục phù hợp trong "tham gia", nhưng tiền đề chung là "đường ống trong đường ống" trong đó nội dung bên trong có thể được lọc bởi các trận đấu từ cha mẹ. Vì cả hai đều là "đường ống", chúng tôi có thể db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
3 mỗi kết quả riêng biệt.Đây sẽ là tùy chọn tốt nhất tiếp theo để chạy các truy vấn song song và thực sự sẽ tốt hơn nếu
db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
5 được phép và có thể sử dụng một chỉ mục trong quá trình xử lý "đường dẫn phụ". Vì vậy, đó không sử dụng "giới hạn cho $push
" vì vấn đề được tham chiếu yêu cầu, nó thực sự cung cấp một cái gì đó sẽ hoạt động tốt hơn.Nội dung gốc
Bạn dường như đã vấp phải vấn đề "N" hàng đầu. Theo một cách nào đó, vấn đề của bạn khá dễ giải quyết mặc dù không giới hạn chính xác mà bạn yêu cầu:
db.books.aggregate[[
{ "$group": {
"_id": {
"addr": "$addr",
"book": "$book"
},
"bookCount": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.addr",
"books": {
"$push": {
"book": "$_id.book",
"count": "$bookCount"
},
},
"count": { "$sum": "$bookCount" }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
]]
Bây giờ điều đó sẽ cho bạn một kết quả như thế này:
{
"result" : [
{
"_id" : "address1",
"books" : [
{
"book" : "book4",
"count" : 1
},
{
"book" : "book5",
"count" : 1
},
{
"book" : "book1",
"count" : 3
}
],
"count" : 5
},
{
"_id" : "address2",
"books" : [
{
"book" : "book5",
"count" : 1
},
{
"book" : "book1",
"count" : 2
}
],
"count" : 3
}
],
"ok" : 1
}
Vì vậy, điều này khác với những gì bạn đang hỏi trong đó, trong khi chúng tôi nhận được kết quả hàng đầu cho các giá trị địa chỉ, lựa chọn "sách" cơ bản không chỉ giới hạn ở một lượng kết quả cần thiết.
Điều này hóa ra rất khó thực hiện, nhưng nó có thể được thực hiện mặc dù độ phức tạp chỉ tăng theo số lượng vật phẩm bạn cần để phù hợp. Để giữ cho nó đơn giản, chúng ta có thể giữ điều này ở mức 2 trận đấu nhiều nhất:
db.books.aggregate[[
{ "$group": {
"_id": {
"addr": "$addr",
"book": "$book"
},
"bookCount": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.addr",
"books": {
"$push": {
"book": "$_id.book",
"count": "$bookCount"
},
},
"count": { "$sum": "$bookCount" }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$unwind": "$books" },
{ "$sort": { "count": 1, "books.count": -1 } },
{ "$group": {
"_id": "$_id",
"books": { "$push": "$books" },
"count": { "$first": "$count" }
}},
{ "$project": {
"_id": {
"_id": "$_id",
"books": "$books",
"count": "$count"
},
"newBooks": "$books"
}},
{ "$unwind": "$newBooks" },
{ "$group": {
"_id": "$_id",
"num1": { "$first": "$newBooks" }
}},
{ "$project": {
"_id": "$_id",
"newBooks": "$_id.books",
"num1": 1
}},
{ "$unwind": "$newBooks" },
{ "$project": {
"_id": "$_id",
"num1": 1,
"newBooks": 1,
"seen": { "$eq": [
"$num1",
"$newBooks"
]}
}},
{ "$match": { "seen": false } },
{ "$group":{
"_id": "$_id._id",
"num1": { "$first": "$num1" },
"num2": { "$first": "$newBooks" },
"count": { "$first": "$_id.count" }
}},
{ "$project": {
"num1": 1,
"num2": 1,
"count": 1,
"type": { "$cond": [ 1, [true,false],0 ] }
}},
{ "$unwind": "$type" },
{ "$project": {
"books": { "$cond": [
"$type",
"$num1",
"$num2"
]},
"count": 1
}},
{ "$group": {
"_id": "$_id",
"count": { "$first": "$count" },
"books": { "$push": "$books" }
}},
{ "$sort": { "count": -1 } }
]]
Vì vậy, điều đó thực sự sẽ cung cấp cho bạn 2 "cuốn sách" hàng đầu từ hai mục "địa chỉ" hàng đầu.
Nhưng đối với tiền của tôi, hãy ở lại với hình thức đầu tiên và sau đó chỉ đơn giản là "cắt" các yếu tố của mảng được trả lại để lấy các yếu tố "n" đầu tiên.
Mã trình diễn
Mã trình diễn phù hợp để sử dụng với các phiên bản LTS hiện tại của NodeJS từ các bản phát hành V8.x và V10.x. Điều đó chủ yếu là cho cú pháp
db.books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"let": {
"addr": "$_id"
},
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr"] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
],
"as": "books"
}}
]]
9, nhưng không có gì thực sự trong dòng chảy chung có bất kỳ hạn chế nào như vậy và điều chỉnh với rất ít sự thay đổi đối với những lời hứa đơn giản hoặc thậm chí quay lại thực hiện gọi lại đơn giản.index.js
const { MongoClient } = require['mongodb'];
const fs = require['mz/fs'];
const uri = 'mongodb://localhost:27017';
const log = data => console.log[JSON.stringify[data, undefined, 2]];
[async function[] {
try {
const client = await MongoClient.connect[uri];
const db = client.db['bookDemo'];
const books = db.collection['books'];
let { version } = await db.command[{ buildInfo: 1 }];
version = parseFloat[version.match[new RegExp[/[?:[?!-].]*/]][0]];
// Clear and load books
await books.deleteMany[{}];
await books.insertMany[
[await fs.readFile['books.json']]
.toString[]
.replace[/\n$/,""]
.split["\n"]
.map[JSON.parse]
];
if [ version >= 3.6 ] {
// Non-correlated pipeline with limits
let result = await books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 },
{ "$lookup": {
"from": "books",
"as": "books",
"let": { "addr": "$_id" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$addr", "$$addr" ] }
}},
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 },
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
]
}}
]].toArray[];
log[{ result }];
}
// Serial result procesing with parallel fetch
// First get top addr items
let topaddr = await books.aggregate[[
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
]].toArray[];
// Run parallel top books for each addr
let topbooks = await Promise.all[
topaddr.map[[{ _id: addr }] =>
books.aggregate[[
{ "$match": { addr } },
{ "$group": {
"_id": "$book",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
]].toArray[]
]
];
// Merge output
topaddr = topaddr.map[[d,i] => [{ ...d, books: topbooks[i] }]];
log[{ topaddr }];
client.close[];
} catch[e] {
console.error[e]
} finally {
process.exit[]
}
}][]
books.json
{ "addr": "address1", "book": "book1" }
{ "addr": "address2", "book": "book1" }
{ "addr": "address1", "book": "book5" }
{ "addr": "address3", "book": "book9" }
{ "addr": "address2", "book": "book5" }
{ "addr": "address2", "book": "book1" }
{ "addr": "address1", "book": "book1" }
{ "addr": "address15", "book": "book1" }
{ "addr": "address9", "book": "book99" }
{ "addr": "address90", "book": "book33" }
{ "addr": "address4", "book": "book3" }
{ "addr": "address5", "book": "book1" }
{ "addr": "address77", "book": "book11" }
{ "addr": "address1", "book": "book1" }