สรุป TLDR
ในการเผยแพร่ MongoDB สมัยใหม่คุณสามารถบังคับสิ่งนี้ได้โดย$slice
ไม่ต้องใช้ผลการรวมพื้นฐาน สำหรับผลลัพธ์ "ใหญ่" ให้เรียกใช้แบบสอบถามคู่ขนานแทนสำหรับการจัดกลุ่มแต่ละกลุ่ม (รายการสาธิตอยู่ที่ท้ายคำตอบ) หรือรอให้SERVER-9377แก้ไขซึ่งจะทำให้ "จำกัด " จำนวนรายการที่$push
จะ อาร์เรย์
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 ดูตัวอย่าง
ยังไม่สามารถแก้ไขSERVER-9377ได้ แต่ในรุ่นนี้$lookup
อนุญาตให้มีตัวเลือก "ไม่สัมพันธ์" ใหม่ซึ่งใช้"pipeline"
นิพจน์เป็นอาร์กิวเมนต์แทน"localFields"
และ"foreignFields"
ตัวเลือก จากนั้นจะอนุญาตให้ "เข้าร่วมด้วยตนเอง" ด้วยนิพจน์ไปป์ไลน์อื่นซึ่งเราสามารถใช้$limit
เพื่อส่งคืนผลลัพธ์ "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"
}}
])
ส่วนเพิ่มเติมอื่น ๆ ในที่นี้คือความสามารถในการแก้ไขตัวแปรโดย$expr
ใช้$match
เพื่อเลือกรายการที่ตรงกันใน "เข้าร่วม" แต่หลักฐานทั่วไปคือ "ไปป์ไลน์ภายในไปป์ไลน์" ซึ่งเนื้อหาภายในสามารถกรองได้โดยการจับคู่จากพาเรนต์ . เนื่องจากทั้งสองเป็น "ท่อ" ในตัวเราจึงสามารถ$limit
แยกผลลัพธ์
นี่จะเป็นตัวเลือกถัดไปที่ดีที่สุดในการเรียกใช้การสืบค้นแบบขนานและจะดีกว่าถ้าหาก$match
ได้รับอนุญาตและสามารถใช้ดัชนีในการประมวลผล "ท่อย่อย" ได้ ดังนั้นสิ่งที่ไม่ใช้ "ขีด จำกัด$push
" ตามที่ปัญหาที่อ้างถึงถามมันให้สิ่งที่ควรจะทำงานได้ดีกว่า
เนื้อหาต้นฉบับ
ดูเหมือนคุณจะสะดุดกับปัญหา "N" อันดับต้น ๆ วิธีแก้ปัญหาของคุณค่อนข้างง่ายแม้ว่าจะไม่ตรงตามข้อ จำกัด ที่คุณขอ
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 }
])
ตอนนี้จะให้ผลลัพธ์ดังนี้:
{
"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
}
ดังนั้นสิ่งนี้จึงแตกต่างจากสิ่งที่คุณถามในขณะที่เราได้รับผลลัพธ์อันดับต้น ๆ สำหรับค่าที่อยู่การเลือก "หนังสือ" ที่อยู่ภายใต้นั้นไม่ได้ จำกัด อยู่เพียงผลลัพธ์ตามจำนวนที่ต้องการเท่านั้น
สิ่งนี้กลายเป็นเรื่องยากที่จะทำ แต่สามารถทำได้แม้ว่าความซับซ้อนจะเพิ่มขึ้นตามจำนวนรายการที่คุณต้องจับคู่ เพื่อให้ง่ายเราสามารถรักษาสิ่งนี้ไว้ได้สูงสุด 2 รายการ:
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 } }
])
นั่นจะทำให้คุณได้รับ "หนังสือ" 2 อันดับแรกจากรายการ "ที่อยู่" สองอันดับแรก
แต่สำหรับเงินของฉันให้อยู่กับรูปแบบแรกจากนั้นเพียงแค่ "แบ่ง" องค์ประกอบของอาร์เรย์ที่ส่งกลับมาเพื่อรับองค์ประกอบ "N" แรก
รหัสสาธิต
รหัสสาธิตนี้เหมาะสำหรับการใช้งานกับ NodeJS เวอร์ชัน LTS ปัจจุบันตั้งแต่รุ่น v8.x และ v10.x นั่นเป็นส่วนใหญ่สำหรับasync/await
ไวยากรณ์ แต่ไม่มีอะไรในโฟลว์ทั่วไปที่มีข้อ จำกัด ดังกล่าวและปรับเปลี่ยนเพียงเล็กน้อยกับสัญญาธรรมดาหรือแม้กระทั่งกลับไปใช้การเรียกกลับธรรมดา
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]);
await books.deleteMany({});
await books.insertMany(
(await fs.readFile('books.json'))
.toString()
.replace(/\n$/,"")
.split("\n")
.map(JSON.parse)
);
if ( version >= 3.6 ) {
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 });
}
let topaddr = await books.aggregate([
{ "$group": {
"_id": "$addr",
"count": { "$sum": 1 }
}},
{ "$sort": { "count": -1 } },
{ "$limit": 2 }
]).toArray();
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()
)
);
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" }