TLDR सारांश
आधुनिक मोंगोडीबी रिलीज में आप इसे $slice
. के साथ जबरदस्ती कर सकते हैं मूल एकत्रीकरण परिणाम से कुछ ही दूर। "बड़े" परिणामों के लिए, प्रत्येक समूह के लिए समानांतर क्वेरी चलाएँ (एक प्रदर्शन सूची उत्तर के अंत में है), या SERVER-9377 के हल होने की प्रतीक्षा करें, जो $पुश
एक सरणी के लिए।
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
एक नए "गैर-सहसंबद्ध" विकल्प की अनुमति देता है जो एक "पाइपलाइन"
takes लेता है "localFields"
. के बजाय एक तर्क के रूप में अभिव्यक्ति और "विदेशी क्षेत्र"
विकल्प। यह तब एक अन्य पाइपलाइन अभिव्यक्ति के साथ "सेल्फ-जॉइन" की अनुमति देता है, जिसमें हम $limit
. लागू कर सकते हैं "टॉप-एन" परिणाम वापस करने के लिए।
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
. के माध्यम से चर को प्रक्षेपित करने की क्षमता है $मिलान
. का उपयोग करके "जॉइन" में मेल खाने वाली वस्तुओं का चयन करने के लिए, लेकिन सामान्य आधार "पाइपलाइन के भीतर पाइपलाइन" है जहां आंतरिक सामग्री को माता-पिता से मैचों द्वारा फ़िल्टर किया जा सकता है। चूंकि वे दोनों स्वयं "पाइपलाइन" हैं, इसलिए हम $limit
. कर सकते हैं प्रत्येक परिणाम अलग से।
समानांतर क्वेरी चलाने के लिए यह अगला सबसे अच्छा विकल्प होगा, और वास्तव में बेहतर होगा यदि $match
अनुमति दी गई थी और "उप-पाइपलाइन" प्रसंस्करण में एक सूचकांक का उपयोग करने में सक्षम थे। तो जो है "लिमिट टू $पुश
. का उपयोग नहीं करता है " जैसा कि संदर्भित मुद्दा पूछता है, यह वास्तव में कुछ ऐसा प्रदान करता है जिसे बेहतर काम करना चाहिए।
मूल सामग्री
ऐसा लगता है कि आप शीर्ष "एन" समस्या पर ठोकर खा चुके हैं। एक तरह से आपकी समस्या को हल करना काफी आसान है, हालांकि उस सीमित सीमा के साथ नहीं जिसकी आप मांग करते हैं:
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 "पुस्तकें" देगा।
लेकिन मेरे पैसे के लिए, पहले फॉर्म के साथ रहें और फिर पहले "एन" तत्वों को लेने के लिए लौटाए गए सरणी के तत्वों को "स्लाइस" करें।
प्रदर्शन कोड
प्रदर्शन कोड v8.x और v10.x रिलीज़ से NodeJS के वर्तमान LTS संस्करणों के उपयोग के लिए उपयुक्त है। यह ज्यादातर async/wait
. के लिए है वाक्य रचना, लेकिन सामान्य प्रवाह के भीतर वास्तव में ऐसा कुछ भी नहीं है जिसमें इस तरह का कोई प्रतिबंध हो, और सादे वादों में थोड़े बदलाव के साथ या यहां तक कि सादे कॉलबैक कार्यान्वयन के लिए भी अनुकूल हो।
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" }