در جلسه قبل با کلیت فریم ورک aggregation آشنا شدیم و در نهایت به کوئری زیر رسیدیم:
db.persons.aggregate([ { $match: { gender: "female" } }, { $group: { _id: { state: "$location.state" }, totalPersons: { $sum: 1 } } } ]) .pretty()
و از شما پرسیدیم که به نظرتان خروجی این کوئری به چه شکلی خواهد بود. بدون مقدمه نتیجه اجرای کوئری بالا به شکل زیر است:
{ "_id" : { "state" : "hordaland" }, "totalPersons" : 4 } { "_id" : { "state" : "acre" }, "totalPersons" : 2 } { "_id" : { "state" : "landes" }, "totalPersons" : 1 } { "_id" : { "state" : "iğdır" }, "totalPersons" : 2 } { "_id" : { "state" : "queensland" }, "totalPersons" : 20 } { "_id" : { "state" : "buskerud" }, "totalPersons" : 5 } { "_id" : { "state" : "kars" }, "totalPersons" : 2 } { "_id" : { "state" : "southland" }, "totalPersons" : 8 } { "_id" : { "state" : "kymenlaakso" }, "totalPersons" : 9 } { "_id" : { "state" : "تهران" }, "totalPersons" : 6 } { "_id" : { "state" : "côtes-d'armor" }, "totalPersons" : 1 } { "_id" : { "state" : "konya" }, "totalPersons" : 7 } { "_id" : { "state" : "چهارمحال و بختیاری" }, "totalPersons" : 8 } { "_id" : { "state" : "baden-württemberg" }, "totalPersons" : 11 } { "_id" : { "state" : "herefordshire" }, "totalPersons" : 2 } { "_id" : { "state" : "graubünden" }, "totalPersons" : 4 } { "_id" : { "state" : "indiana" }, "totalPersons" : 3 } { "_id" : { "state" : "gaziantep" }, "totalPersons" : 3 } { "_id" : { "state" : "northamptonshire" }, "totalPersons" : 3 } { "_id" : { "state" : "marlborough" }, "totalPersons" : 12 } Type "it" for more
همانطور که مشاهده می کنید ما داده های خود را کاملا مطابق با سلیقه خودمان تغییر داده ایم! به طوری که id برابر شیء ای شده است که ایالت/استان را نمایش می دهد و totalPersons هم تمام افراد اهل آن استان/ایالت را نمایش می دهد (تعداد سند های ادغام شده را نمایش می دهد). برای اینکه این نتیجه را تست کنیم، می توانیم از یک کوئری ساده Find استفاده کنیم:
db.persons.find({"location.state": "تهران"}).pretty()
در اینجا گفته ام تمام افراد را پیدا کن که در آن ها استان مورد نظر «تهران» است. با اجرای این دستور تمام 6 سندی که نتیجه قبلی در totalPersons اعلام کرده بود، نمایش داده می شود. من یکی از آن ها را می آورم:
"_id" : ObjectId("5ec23bdddeafd616fdb3bfce"), "gender" : "male", "name" : { "title" : "mr", "first" : "طاها", "last" : "سهيلي راد" }, "location" : { "street" : "3465 سمیه", "city" : "بوشهر", "state" : "تهران", "postcode" : 95606, "coordinates" : { "latitude" : "2.6458", "longitude" : "20.1503" }, "timezone" : { "offset" : "-12:00", "description" : "Eniwetok, Kwajalein" } }, "email" : "طاها.سهيليراد@example.com", "login" : { "uuid" : "48b6612b-4dd2-4b6b-aa57-b95d0c2b00dd", "username" : "biggorilla703", "password" : "ghost1", "salt" : "eSc31sey", "md5" : "fab1bee54d17f6f6624236bb12dd3915", "sha1" : "a1c6a8c47b084dd8a74990aaddf166d94f48fea9", "sha256" : "a0beedf3cd15bff65f947b53f8a7f7c24103cc92e74e2d13fee4b8a6de4d5c70" }, "dob" : { "date" : "1992-12-14T09:42:57Z", "age" : 25 }, "registered" : { "date" : "2008-03-01T23:12:08Z", "age" : 10 }, "phone" : "009-04589994", "cell" : "0989-930-0577", "id" : { "name" : "", "value" : null }, "picture" : { "large" : "https://randomuser.me/api/portraits/men/7.jpg", "medium" : "https://randomuser.me/api/portraits/med/men/7.jpg", "thumbnail" : "https://randomuser.me/api/portraits/thumb/men/7.jpg" }, "nat" : "IR"
توجه کنید که totalPersons تعداد خانم هایی را نشان می دهد که از تهران باشند اما کوئری find ما کل افرادی را نشان می دهد که از تهران باشند بنابراین به این نکته توجه داشته باشید که شاید تعداد افراد بیشتر از مقدار totalPersons باشند اما موارد اضافی همگی مرد (male) هستند.
در ضمن احتمالا متوجه شده اید که داده های افراد ایرانی کمی عجیب است (ایمیل ها به فارسی است که نامعتبر محسوب می شود). ما به این مسئله کاری نداریم اما اگر کنجکاو هستید، این داده ها با استفاده از وب سایت زیر ایجاد شده اند:
این وب سایت کاربران fake و تصادفی می سازد (مانند متون lorem ipsum اما برای کاربران مختلف) تا کسانی که می خواهند با یک پایگاه داده تمرین کنند، به راحتی به کاربران آماده دسترسی داشته باشند. به همین دلیل است که کاربران ایرانی کاملا دقیق نیستند چرا که واقعی نیستند اما برای ما مهم نیست. نکته اصلی این است که دستورات MongoDB ما به درستی اجرا می شوند.
حالا تصور کنید که می خواهیم sort یا دسته بندی انجام بدهیم. من می خواهم که معیار این دسته بندی بر اساس totalPersons باشد تا ایالت/استان هایی را که تراکم جمعیت بیشتری داریم، در ابتدای نتایج بیاوریم. اینجاست که قدرت aggregation framework را می بینید. ما نمی توانیم با یک کوئری find و sort عادی این کار را انجام بدهیم چرا که باید در ابتدا داده ها را فیلتر کرده باشیم تا خانم های درون این استان ها را حساب کنیم. برای این کار وارد state را مرحله بعدی این فریم ورک می شویم:
db.persons.aggregate([ { $match: { gender: "female" } }, { $group: { _id: { state: "$location.state" }, totalPersons: { $sum: 1 } } }, { $sort: { totalPersons: -1 } } ]) .pretty()
ما در اینجا نتایج را بر اساس totalPersons و به صورت نزولی (descending) مرتب کرده ایم. یادتان باشد که هر مرحله از این فریم ورک، نتایج مرحله قبلی را دریافت می کند نه نتایج کالکشن یا نتایج match$ را، بنابراین با اینکه فیلد totalPersons درون داده های اصلی ما نیست باز هم در sort$ به آن دسترسی داریم چرا که Sort$ داده های اصلی را نمی گیرد بلکه داده های برگشتی از group$ را می گیرد. نتیجه اجرای کوئری بالا به شکل زیر است:
{ "_id" : { "state" : "midtjylland" }, "totalPersons" : 33 } { "_id" : { "state" : "nordjylland" }, "totalPersons" : 27 } { "_id" : { "state" : "syddanmark" }, "totalPersons" : 24 } "_id" : { "state" : "australian capital territory" }, "totalPersons" : 24 } { "_id" : { "state" : "new south wales" }, "totalPersons" : 24 } { "_id" : { "state" : "south australia" }, "totalPersons" : 22 } { "_id" : { "state" : "danmark" }, "totalPersons" : 21 } { "_id" : { "state" : "hovedstaden" }, "totalPersons" : 21 } { "_id" : { "state" : "overijssel" }, "totalPersons" : 20 } { "_id" : { "state" : "queensland" }, "totalPersons" : 20 } { "_id" : { "state" : "sjælland" }, "totalPersons" : 19 } { "_id" : { "state" : "nova scotia" }, "totalPersons" : 17 } { "_id" : { "state" : "canterbury" }, "totalPersons" : 16 } { "_id" : { "state" : "yukon" }, "totalPersons" : 16 } { "_id" : { "state" : "northwest territories" }, "totalPersons" : 16 } { "_id" : { "state" : "gelderland" }, "totalPersons" : 16 } { "_id" : { "state" : "bayern" }, "totalPersons" : 15 } { "_id" : { "state" : "tasmania" }, "totalPersons" : 15 } { "_id" : { "state" : "northern territory" }, "totalPersons" : 15 } { "_id" : { "state" : "noord-brabant" }, "totalPersons" : 14 } Type "it" for more
بنابراین sort ما کامل بوده و کار می کند. البته یکی از استان ها (چهارمین سند) نام طولانی تری دارد بنابراین شکسته است اما این تغییر ظاهر به معنی تغییر داده نیست. اگر به دقت نگاه کنید این سند نیز دقیقا به شکل بقیه سند ها است.
به هر کدام از این مراحل (match و group و sort) یک stage در aggregation pipeline می گویند. یعنی چه؟ pipeline به معنی «خط لوله» است و منظور از آن ردیف شدن stage ها در کنار هم است که یک خط لوله می سازد. در هر کدام از این stage ها داده ها تغییر می کنند و می دانیم این ها تمام مراحل موجود در فریم ورک aggregation نیستند. ما بسیاری از آن ها را در این دوره بررسی خواهیم کرد اما به همه آن ها نمی رسیم.
به طور مثال stage بعدی project$ نام دارد و به جای اینکه داده ها در یک گروه قرار دهد، تغییرات را روی تک تک این داده ها اعمال می کند. ما با مفهوم projection در کوئری های find آشنا شده ایم اما projection در فریم ورک aggregation بسیار قدرتمند تر عمل می کند. در جلسه بعد آن را بررسی خواهیم کرد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.