ما به stage های مختلفی در فریم ورک aggregation پرداخته ایم اما حالا باید به سمت اپراتور ها و stage هایی برویم که با آرایه ها سر و کار دارند. به همین خاطر من داده هایی را از قبل برایتان آماده کرده ام:
[ { "name": "Max", "hobbies": ["Sports", "Cooking"], "age": 29, "examScores": [ { "difficulty": 4, "score": 57.9 }, { "difficulty": 6, "score": 62.1 }, { "difficulty": 3, "score": 88.5 } ] }, { "name": "Manu", "hobbies": ["Eating", "Data Analytics"], "age": 30, "examScores": [ { "difficulty": 7, "score": 52.1 }, { "difficulty": 2, "score": 74.3 }, { "difficulty": 5, "score": 53.1 } ] }, { "name": "Maria", "hobbies": ["Cooking", "Skiing"], "age": 29, "examScores": [ { "difficulty": 3, "score": 75.1 }, { "difficulty": 8, "score": 44.2 }, { "difficulty": 6, "score": 61.5 } ] } ]
شما می توانید این داده ها را در یک فایل ذخیره کرده و با دستور mongoimport آن ها را وارد پایگاه داده خود کنید یا اینکه آن ها را کپی کرده و یک کالکشن جدید به نام friends بسازید. حتما می دانید که با insertMany می توانیم این کار را انجام بدهیم. البته باید برای کار با دستوراتی که به شکل بالا در چند خط نوشته شده اند، توضیحاتی بدهم. شما برای اجرای این دستورات چند راه دارید:
db.friends.insertMany([ { "name": "Max", "hobbies": ["Sports", "Cooking"], "age": 29, "examScores": [ { "difficulty": 4, "score": 57.9 }, { "difficulty": 6, "score": 62.1 }, { "difficulty": 3, "score": 88.5 } ] }, { "name": "Manu", "hobbies": ["Eating", "Data Analytics"], "age": 30, "examScores": [ { "difficulty": 7, "score": 52.1 }, { "difficulty": 2, "score": 74.3 }, { "difficulty": 5, "score": 53.1 } ] }, { "name": "Maria", "hobbies": ["Cooking", "Skiing"], "age": 29, "examScores": [ { "difficulty": 3, "score": 75.1 }, { "difficulty": 8, "score": 44.2 }, { "difficulty": 6, "score": 61.5 } ] } ])
در نهایت مسئله به خود شما مربوط است. پس از اجرای این دستور مثل همیشه از mongodb یک تاییدیه می گیرید. در فریم ورک aggregation می توان کار های مختلفی با آرایه ها انجام داد بنابراین بهتر است بدون مقدمه شروع کنیم. در قدم اول می خواهم داده هایمان را بر اساس سن group$ کرده و سپس hobbies را در هم ادغام کنیم تا ببینیم هر سرگرمی متعلق به چند نفر است:
db.friends.aggregate([ { $group: { _id: { age: "$age" }, allHobbies: { $push: "$hobbies" } } } ]).pretty()
اپراتور push$ به ما اجازه می دهد که مقادیر مورد نظر خودمان را در یک آرایه وارد کنیم؛ این مقادیر ممکن است از فیلد های موجود باشد و ممکن است مقادیری باشد که خودمان به صورت دستی وارد کرده ایم. من در کد بالا گفته ام که کل سند ها را بر اساس age گروه بندی کن و سپس فیلد جدیدی به نام allHobbies را تعریف کرده ام. این فیلد، push$ را دارد که به ازای هر کدام از سند ها، فیلد hobbies را درون allHobbies قرار می دهد. با اجرای کوئری بالا نتیجه به شکل زیر خواهد بود:
"_id" : { "age" : 30 }, "allHobbies" : [ [ "Eating", "Data Analytics" ] ] } "_id" : { "age" : 29 }, "allHobbies" : [ [ "Sports", "Cooking" ], [ "Cooking", "Skiing" ] ] }
همانطور که مشخص است ما دو گروه داریم (گروه سنی 30 و 29 سال) که هر کدام فیلد allHobbies را دارد. نکته جالب اینجاست که allHobbies یک آرایه است که درون خود آرایه های مختلفی دارد. به نظر شما چرا چنین ساختاری به وجود آمده است؟ همانطور که گفتم push$ مخصوص کار با آرایه است بنابراین هر زمان که از آن استفاده کنید یک آرایه ساخته می شود. به همین دلیل است که allHobbies یک آرایه است. همچنین ما در کوئری خود گفته بودیم که فیلد hobbies را به allHobbies اضافه کن و می دانیم که hobbies خودش یک آرایه است بنابراین hobbies درون allHobbies می شود آرایه در آرایه!
حالا اگر بخواهیم به جای اینکه آرایه hobbies را درون allHobbies قرار بدهیم، مقادیر درونش را داخل allHobbies قرار بدهیم چطور؟ برای این کار نیاز به یک stage جدید داریم اما قبل از آن باید با اپراتور unwind$ (به معنی از هم باز کردن) آشنا شوید. به کوئری زیر نگاه کنید:
db.friends.aggregate([ { $unwind: "$hobbies" } ]).pretty()
در این کوئری ساده گفته ایم که فیلد hobbies را unwind کن، یعنی از هم باز کن. با اجرای آن نیز نتیجه زیر را می گیریم:
"_id" : ObjectId("5ec48cfe35361420f36550a7"), "name" : "Max", "hobbies" : "Sports", "age" : 29, "examScores" : [ { "difficulty" : 4, "score" : 57.9 }, { "difficulty" : 6, "score" : 62.1 }, { "difficulty" : 3, "score" : 88.5 } ] } "_id" : ObjectId("5ec48cfe35361420f36550a7"), "name" : "Max", "hobbies" : "Cooking", "age" : 29, "examScores" : [ { "difficulty" : 4, "score" : 57.9 }, { "difficulty" : 6, "score" : 62.1 }, { "difficulty" : 3, "score" : 88.5 } ] } "_id" : ObjectId("5ec48cfe35361420f36550a8"), "name" : "Manu", "hobbies" : "Eating", "age" : 30, "examScores" : [ { "difficulty" : 7, "score" : 52.1 }, { "difficulty" : 2, "score" : 74.3 }, { "difficulty" : 5, "score" : 53.1 } ] } "_id" : ObjectId("5ec48cfe35361420f36550a8"), "name" : "Manu", "hobbies" : "Data Analytics", "age" : 30, "examScores" : [ { "difficulty" : 7, "score" : 52.1 }, { "difficulty" : 2, "score" : 74.3 }, { "difficulty" : 5, "score" : 53.1 } ] } "_id" : ObjectId("5ec48cfe35361420f36550a9"), "name" : "Maria", "hobbies" : "Cooking", "age" : 29, "examScores" : [ { "difficulty" : 3, "score" : 75.1 }, { "difficulty" : 8, "score" : 44.2 }, { "difficulty" : 6, "score" : 61.5 } ] } "_id" : ObjectId("5ec48cfe35361420f36550a9"), "name" : "Maria", "hobbies" : "Skiing", "age" : 29, "examScores" : [ { "difficulty" : 3, "score" : 75.1 }, { "difficulty" : 8, "score" : 44.2 }, { "difficulty" : 6, "score" : 61.5 } ] }
آیا متوجه کار unwind شدید؟ unwind تک تک اعضای آرایه hobbies را از درون این آرایه خارج کرده است و از آنجایی که ما دستور حذف چیزی را نداده ایم، مجبور به تکرار سند ها شده است تا هر فرد یا سند با تک تک hobbies خود ذکر شود. با این تفاسیر حالا می توانیم کوئری قبلی خود را تکمیل کنیم تا allHobbies مقادیر hobbies را بگیرد بدون اینکه آرایه های تو در تو داشته باشیم:
db.friends.aggregate([ { $unwind: "$hobbies" }, { $group: { _id: { age: "$age" }, allHobbies: { $push: "$hobbies" } } } ]).pretty()
یعنی در ابتدا تک تک مقادیر درون آرایه hobbies را از آن خارج کرده و سپس تمام سند ها را group می کنیم تا ببینیم در هر رده سنی، چه سرگرمی (hobby) هایی وجود دارد. نتیجه اجرای کوئری بالا به شکل زیر است:
"_id" : { "age" : 29 }, "allHobbies" : [ "Sports", "Cooking", "Cooking", "Skiing" ] } "_id" : { "age" : 30 }, "allHobbies" : [ "Eating", "Data Analytics" ] }
دقیقا همان چیزی که می خواستیم! تنها مشکل اینجاست که آرایه allHobbies دارای مقادیر تکراری است. هدف ما این بود که ببینیم در هر رده سنی چه سرگرمی هایی وجود دارد بنابراین وجود مقادیر تکراری هیچ کمکی به ما نمی کند. برای حل این مشکل نیز اپراتور دیگری به نام addToSet$ وجود دارد که جایگزین push$ است:
db.friends.aggregate([ { $unwind: "$hobbies" }, { $group: { _id: { age: "$age" }, allHobbies: { $addToSet: "$hobbies" } } } ]).pretty()
تفاوت addToSet و push در این است که push با مقادیر تکراری مشکلی ندارد و آن ها را نیز اضافه می کند اما addToSet مقادیر تکراری را نادیده می گیرد. نتیجه اجرای کوئری بالا به شکل زیر است:
"_id" : { "age" : 29 }, "allHobbies" : [ "Cooking", "Skiing", "Sports" ] "_id" : { "age" : 30 }, "allHobbies" : [ "Eating", "Data Analytics" ]
حالا مقادیر تکراری حذف شده اند و به مقصودمان رسیده ایم!
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.