ما در قسمت قبل با اپراتورهای size$ و slice$ آشنا شدیم و حالا نوبت به اپراتور filter رسیده است. در حال حاضر ساختار هر سند در کالکشن friends به شکل زیر است:
"_id" : ObjectId("5ec48cfe35361420f36550a7"), "name" : "Max", "hobbies" : [ "Sports", "Cooking" ], "age" : 29, "examScores" : [ { "difficulty" : 4, "score" : 57.9 }, { "difficulty" : 6, "score" : 62.1 }, { "difficulty" : 3, "score" : 88.5 } ]
در قدم اول هدف ما این است که فقط نمرات بالاتر از 60 را نمایش بدهیم. این کار با استفاده از همان project انجام می شود اما پیچیده تر از کد های قبلی است. برای نمایش نمرات بالاتر از 60 می توانیم از اپراتور filter$ استفاده کنیم که کارش فیلتر کردن اعضای یک آرایه است. برای استفاده از این اپراتور باید سه آرگومان را به آن پاس بدهیم (هنوز اجرا نکنید):
db.friends.aggregate([ { $project: { _id: 0, examScores: { $filter: { input: "$examScores", as: "sc", cond: {} } } } } ]).pretty()
در قدم اول فیلدی به نام examScores ساخته ام (شما می توانید نام آن را هر چیز دیگری بگذارید) و سپس اپراتور filter را به آن داده ام. این اپراتور ابتدا یک آرگومان به نام input می گیرد که فیلد هدف را مشخص می کند. من می خواهم فیلد examScores را فیلتر کنم بنابراین آن را به input پاس داده ام. سپس آرگومان دوم به نام as را داریم که یک نام مستعار برای تک تک اعضای درون آرایه است. البته در اصل این آرگومان یک متغیر محلی است که درون عملیات فیلتر استفاده خواهد شد. شما می توانید هر مقدار دلخواهی را برای این آرگومان در نظر بگیرید (من sc را انتخاب کرده ام). آخرین آرگومان ما نیز cond (مخفف condition - شرط) می باشد که فعلا خالی است. ما می توانیم از انواع اپراتور های مختلف در cond استفاده کنیم که یکی از آن ها gt$ است:
db.friends.aggregate([ { $project: { _id: 0, examScores: { $filter: { input: "$examScores", as: "sc", cond: { $gt: ["sc", 60] } } } } } ]).pretty()
ما قبلا با اپراتور gt$ (مخفف greater than - بزرگتر از) آشنا شده ایم اما استفاده از آن در این کوئری متفاوت است. زمانی که از gt$ درون filter$ استفاده می کنیم باید دو آرگومان را در قالب یک آرایه به آن پاس بدهیم. آرگومان اول نام متغیر محلی ای (همان as) است که ما نامش را sc گذاشته بودیم. این متغیر برابر تک تک اعضای آرایه خواهد بود. متغیر دوم نیز عدد مورد نظر ما است که در اینجا 60 است. (یعنی تمام اعضای آرایه examScores که از 60 بیشتر هستند). آیا کوئری ما قابل اجرا است؟ خیر!
در حال حاضر sc (آرگومان دوم پاس داده شده به gt$) به عنوان رشته در نظر گرفته می شود در حالی که باید عدد باشد تا بتوانیم آن را با عدد 60 مقایسه کنیم. به نظر شما برای حل این مشکل چه کار می توان کرد؟ اگر یک علامت $ در کنار sc بگذاریم، آنگاه به MongoDB گفته ایم که به دنبال فیلد خاصی به نام sc هستیم که وجود ندارد اما اگر دو علامت $ را در کنار sc بگذاریم به MongoDB گفته ایم که به دنبال یک متغیر محلی به نام sc هستیم که قبل تر تعریف کرده ایم:
db.friends.aggregate([ { $project: { _id: 0, examScores: { $filter: { input: "$examScores", as: "sc", cond: { $gt: ["$$sc", 60] } } } } } ]).pretty()
آیا حالا می توانیم کوئری خود را اجرا کنیم؟ باز هم خیر! چرا؟ من در ابتدای این جلسه ساختار سند های friends را برایتان قرار دادم:
"examScores" : [ { "difficulty" : 4, "score" : 57.9 }, { "difficulty" : 6, "score" : 62.1 }, { "difficulty" : 3, "score" : 88.5 } ]
کاملا واضح است که examScores یک آرایه از اشیاء مختلف است و مقادیر مستقیما در آن قرار ندارند که بخواهیم مستقیما به آن کوئری بزنیم. بنابراین sc به هر کدام از سند های درون examScores اشاره خواهد کرد نه به مقادیر درون آن ها. بنابراین قدم بعدی این است که در sc به دنبال score بگردیم. چرا؟ به دلیل اینکه به دنبال difficulty (سختی امتحان) نیستیم بنابراین فقط Score باقی می ماند:
db.friends.aggregate([ { $project: { _id: 0, examScores: { $filter: { input: "$examScores", as: "sc", cond: { $gt: ["$$sc.score", 60] } } } } } ]).pretty()
حالا آیا می توانیم این کوئری را اجرا کنیم؟ بالاخره جواب مثبت است! با اجرای کوئری بالا نتیجه زیر را می گیریم:
"examScores" : [ { "difficulty" : 6, "score" : 62.1 }, { "difficulty" : 3, "score" : 88.5 } ] { "examScores" : [ { "difficulty" : 2, "score" : 74.3 } ] } "examScores" : [ { "difficulty" : 3, "score" : 75.1 }, { "difficulty" : 6, "score" : 61.5 } ]
همانطور که می بینید، تک تک سند های برگردانده شده دارای امتیاز بیشتر از 60 هستند. البته می توانیم تغییرات را ادامه بدهیم. مثلا بگوییم برای هر فرد فقط بالاترین نمره کسب شده را نمایش بده! یعنی دیگر آرایه examScores را نداشته باشیم بلکه بالاترین نمره مستقیما به جایش قرار بگیرد. برای انجام این کار چندین روش مختلف وجود دارد. یکی از این روش ها بدین صورت است که ابتدا برای به دست آوردن بالاترین نمره، باید کل آرایه را unwind$ کنیم تا به ازای هر نفر چندین سند بگیریم (اگر یادتان باشد در جلسات قبل این اتفاق افتاد و به ازای هر فرد چندین کپی دریافت کردیم که مقدار hobbies در آن ها فرق می کرد). سپس اسناد برگردانده شده را بر اساس فرد، sort می کنیم و سپس بالاترین نمره برای هر فرد را پیدا کرده و دریافت می کنیم.
این تکلیف شما برای جلسه بعدی است بنابراین سعی کنید خودتان آن را انجام بدهید. من در جلسه بعد جواب این تکلیف را برایتان قرار می دهم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.