آشنایی با جزئیات stageهای فریم‌ورک aggregation

Familiarity with the Details of the Aggregation Framework Stages

26 اردیبهشت 1401
درسنامه درس 77 از سری دوره جامع آموزش MongoDB
MongoDB: آشنایی با جزئیات stage های فریم ورک aggregation (قسمت 79)

ما تا این قسمت با انواع stage های فریم ورک Aggregation آشنا شده ایم اما باید درک عمیق تری نسبت به آن ها پیدا کنیم. تصور کنید ما به دنبال 10 نفر هستیم که تاریخ تولدشان از همه قدیمی تر باشد. چرا نگفتم سن بالاتری داشته باشند؟ به دلیل اینکه ما در dob یک فیلد date داریم که به صورت یک رشته زمانی خاص نوشته شده است و می خواهیم آن را مقایسه کنیم (گرچه این رشته را تبدیل خواهیم کرد). البته در عمل 10 فردی را پیدا خواهیم کرد که از همه پیر تر هستند. سپس بعد از پیدا کردن این 10 نفر می خواهیم 10 نفر بعدی را با همین خصوصیات پیدا کنیم (چیزی شبیه به pagination). در قدم اول باید یک stage برای project داشته باشیم تا رشته تاریخ تولد را تبدیل کنیم:

db.persons.aggregate([
    { $project: { _id: 0, name: 1, birthdate: { $toDate: "$dob.date" } } }
]).pretty()

اپراتور toDate$ یک رشته تاریخی را گرفته و آن را به Date تبدیل می کند و بقیه موارد آن را نیز بار ها تمرین کرده ایم بنابراین توضیح بیشتری نمی دهم. با اجرای این کوئری نتیجه زیر را می گیریم (من فقط یکی از اسناد برگردانده شده را می آورم):

"name" : {                                       
    "title" : "mr",                          
    "first" : "بنیامین",                     
    "last" : "سالاری"                        
},                                               
"birthdate" : ISODate("1984-03-10T22:12:43Z")    

همانطور که می بینید رشته تاریخ ما تبدیل به داده ISODate شده است. در مرحله بعدی می توانیم داده های برگردانده شده را sort$ کنیم تا ببینیم چه کسی از همه پیر تر است. به نظر شما کوئری زیر جواب می دهد؟

db.persons.aggregate([
    { $project: { _id: 0, name: 1, birthdate: { $toDate: "$dob.date" } } },
    { $sort: { birthdate: 1 } }
]).pretty()

در این کوئری گفته ام که birthdate را به صورت صعودی مرتب کن تا کمترین مقدار در اول لیست نتایج بیاید. بدین ترتیب می توانیم پیر ترین فرد را پیدا کنیم. با اجرای این کوئری نتیجه زیر برایمان برگردانده می شود (من فقط قسمتی از نتایج را می آورم):

"name" : {
    "title" : "mrs",
    "first" : "victoria",
    "last" : "hale"
},
"birthdate" : ISODate("1944-09-07T15:52:50Z")
}

"name" : {
    "title" : "mr",
    "first" : "عباس",
    "last" : "یاسمی"
},
"birthdate" : ISODate("1944-09-12T07:49:20Z")
}

"name" : {
    "title" : "miss",
    "first" : "erundina",
    "last" : "porto"
},
"birthdate" : ISODate("1944-09-13T14:58:41Z")
}

"name" : {
    "title" : "mr",
    "first" : "پرهام",
    "last" : "جعفری"
},
"birthdate" : ISODate("1944-09-16T16:03:28Z")

همانطور که می بینید تمام افراد موجود در این نتیجه، سال 1994 به دنیا آمده اند بنابراین سالمند محسوب می شوند. خانم Victoria hale در هفتم سپتامبر سال 1944 و آقای عباس یاسمی در 12 سپتامبر همان سال به دنیا آمده است. با مقایسه همین نمونه متوجه می شویم که sort به درستی کار کرده است. مسئله اینجاست که در Shell به صورت پیش فرض 20 سند اول نمایش داده می شود (در driver ها نیز به تنظیمات خودتان و کوئری زدن هایتان بستگی خواهد داشت) اما ما گفتیم که می خواهیم 10 نفر اول (پیرترین افراد) را پیدا کنیم. بنابراین باید راهی پیدا کنیم که فقط 10 نتیجه اول را نمایش بدهیم. به نظر شما راه حل چیست؟

ما می توانیم برای حل این مشکل از اپراتوری به نام limit$ استفاده کنیم. اگر یادتان باشد در فصل های اولیه این سری آموزشی به صورت خلاصه نگاهی به limit$ داشتیم و حالا می توانیم در فریم ورک aggregation از آن استفاده کنیم. استفاده از limit$ بسیار ساده است و تفاوتی ندارد که در کوئری های find یا در فریم ورک aggregation از آن استفاده کنید:

db.persons.aggregate([
    { $project: { _id: 0, name: 1, birthdate: { $toDate: "$dob.date" } } },
    { $sort: { birthdate: 1 } },
    { $limit: 10 }
]).pretty()

با اجرای کوئری بالا فقط 10 نتیجه اول برگردانده می شود بنابراین همه چیز به درستی کار می کند. همچنین برای خواناتر کردن نتایج می توانیم از اپراتور concat$ استفاده کنیم تا به جای داشتن سه فیلد جداگانه در name، یک نام ساده داشته باشیم:

db.persons.aggregate([
    { $project: { _id: 0, name: { $concat: ["$name.first", " ", "$name.last"] }, birthdate: { $toDate: "$dob.date" } } },
    { $sort: { birthdate: 1 } },
    { $limit: 10 }
]).pretty()

با اجرای این کوئری نتیجه زیر برای من نمایش داده می شود:

{ "name" : "victoria hale", "birthdate" : ISODate("1944-09-07T15:52:50Z") }
{ "name" : "عباس یاسمی", "birthdate" : ISODate("1944-09-12T07:49:20Z") }
{ "name" : "erundina porto", "birthdate" : ISODate("1944-09-13T14:58:41Z") }
{ "name" : "پرهام جعفری", "birthdate" : ISODate("1944-09-16T16:03:28Z") }
{ "name" : "eli henry", "birthdate" : ISODate("1944-09-17T15:04:13Z") }
{ "name" : "kirk brown", "birthdate" : ISODate("1944-09-18T11:03:05Z") }
{ "name" : "alexis bélanger", "birthdate" : ISODate("1944-10-02T22:56:32Z") }
{ "name" : "gina beck", "birthdate" : ISODate("1944-10-04T07:41:31Z") }
{ "name" : "sebastian olsen", "birthdate" : ISODate("1944-10-13T15:29:05Z") }
{ "name" : "lucy wilson", "birthdate" : ISODate("1944-10-25T16:27:56Z") }

همانطور که می بینید نام ها دیگر به صورت embedded document نیستند بلکه یک فیلد ساده و رشته ای می باشند. سوالی که پیش می آید این است که چطور می توانیم 10 ردیف بعدی را نمایش بدهیم؟ اگر یادتان باشد در فصل های اولیه این سری آموزشی از اپراتوری به نام skip$ صحبت کردیم که pointer را جا به جا می کرد تا از صفر شروع نکنیم بنابراین می توان گفت:

db.persons.aggregate([
    { $project: { _id: 0, name: { $concat: ["$name.first", " ", "$name.last"] }, birthdate: { $toDate: "$dob.date" } } },
    { $sort: { birthdate: 1 } },
    { $skip: 10 },
    { $limit: 10 }
]).pretty()

کوئری بالا می گوید 10 نتیجه اول را Skip کن (یعنی از آن ها رد شو) و سپس 10 نتیجه بعدی را به ما بده. با اجرای این کوئری نتیجه طبق انتظار ما خواهد بود:

{ "name" : "eva murray", "birthdate" : ISODate("1944-10-29T02:05:56Z") }
{ "name" : "elena chevalier", "birthdate" : ISODate("1944-10-31T02:56:40Z") }

        "name" : "gretchen schmidtke",
        "birthdate" : ISODate("1944-11-01T20:49:03Z")
}
{ "name" : "joseph thomas", "birthdate" : ISODate("1944-11-06T11:08:45Z") }
{ "name" : "sarah lee", "birthdate" : ISODate("1944-11-07T07:53:47Z") }

        "name" : "conrad scheepbouwer",
        "birthdate" : ISODate("1944-11-08T02:15:17Z")
}
{ "name" : "martina charles", "birthdate" : ISODate("1944-11-08T07:38:49Z") }
{ "name" : "olga blanco", "birthdate" : ISODate("1944-11-17T09:16:50Z") }
{ "name" : "elisa morales", "birthdate" : ISODate("1944-11-22T22:51:47Z") }
{ "name" : "rafael velasco", "birthdate" : ISODate("1944-11-27T07:12:20Z") }

همانطور که می بینید دیگر نشانی از خانم Victoria hale و آقای عباس یاسمی نیست بنابراین مطمئن می شویم که در 10 نتیجه دوم قرار داریم.

سوال: آیا ترتیب صدا زدن skip و limit مهم است؟ آیا حتما باید limit را بعد از skip صدا بزنیم؟

پاسخ: اگر limit را قبل از skip صدا بزنیم، هیچ نتیجه ای نمی گیریم! چرا؟ به دلیل اینکه در چنین حالتی ابتدا افراد را sort می کنیم و سپس 10 نفر اول را انتخاب می کنیم (limit اجرا می شود) سپس از این 10 نفر، 10 نفر را رد می کنیم (skip اجرا می شود) بنابراین هیچ فردی باقی نمی ماند و نتیجه ای نمی گیریم. به همین دلیل است که ترتیب stage های موجود در pipeline بسیار مهم است. این ترتیب در متد find مهم نبود و می توانستید skip و limit را به pointer خود بچسبانید اما اینجا داستان دیگری را داریم.

البته باید هشدار بدهم که ترتیب اجرای stage های pipeline به limit و skip محدود نیست بلکه تک تک دستورات ترتیب خاص خود را دارند. به طور مثال به کوئری زیر توجه کنید:

db.persons.aggregate([
    { $project: { _id: 0, name: { $concat: ["$name.first", " ", "$name.last"] }, birthdate: { $toDate: "$dob.date" } } },
    { $skip: 10 },
    { $limit: 10 },
    { $sort: { birthdate: 1 } }
]).pretty()

در اینجا من sort را در انتهای کار آورده ام. نتیجه اجرای کوئری بالا به شکل زیر است:

{ "name" : "mestan kaplangı", "birthdate" : ISODate("1951-12-17T20:03:33Z") }
{ "name" : "isolino viana", "birthdate" : ISODate("1959-03-22T14:53:41Z") }
{ "name" : "andreia arnaud", "birthdate" : ISODate("1960-01-31T05:16:10Z") }
{ "name" : "delia durand", "birthdate" : ISODate("1966-08-03T09:22:41Z") }
{ "name" : "louise graham", "birthdate" : ISODate("1971-01-21T20:36:16Z") }
{ "name" : "sandra lorenzo", "birthdate" : ISODate("1975-03-23T17:01:45Z") }
{ "name" : "anne ruiz", "birthdate" : ISODate("1982-10-09T12:10:42Z") }
{ "name" : "بنیامین سالاری", "birthdate" : ISODate("1984-03-10T22:12:43Z") }
{ "name" : "anaëlle adam", "birthdate" : ISODate("1987-10-20T11:33:44Z") }
{ "name" : "katie welch", "birthdate" : ISODate("1990-10-14T05:02:12Z") }

همانطور که می بینید تاریخ تولد افراد دیگر در سال 1940 نیست، بلکه افرادی را داریم که در 1990 به دنیا آمده اند و هنوز جوان هستند. به نظر شما مشکل کجاست؟ اگر به کوئری بالا نگاه کنید، ترتیب اجرای درخواست ها بدین شکل است:

  • ابتدا با project$ داده های مورد نظرم را دریافت کرده ام.
  • سپس 10 نفر اول را skip$ کرده ام (از آن ها رد شده ایم).
  • سپس 10 نفر بعدی را انتخاب کرده ام (limit$).
  • در نهایت فقط همین 10 نفر انتخاب شده را sort کرده ام نه کل کالکشن را!

به همین دلیل است که ترتیب اجرا شدن تمام این دستورات بسیار مهم خواهد بود.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش MongoDB توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مقالات مرتبط
آخرین سوالات کاربران
5451218 در 2 سال قبل پرسیده:
ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو