زمانی که بحث از کوئری زدن به آرایه ها می شود، مسائل مختلفی در ذهن ما می آید که باید آن ها را بررسی کنیم. برای شروع این جلسه از پایگاه داده user خودم استفاده می کنم. اگر از جلسات قبل یادتان باشد کاربران زیر را تعریف کرده بودیم:
use user db.users.find().pretty()
اجرای کد بالا کاربران را به ما نمایش می دهد:
"_id" : ObjectId("5eb295a448ff1d422538a355"), "name" : "Amir", "hobbies" : [ { "title" : "programming", "frequency" : 3 }, { "title" : "Gym", "frequency" : 6 } ], "phone" : 59732894409174 } "_id" : ObjectId("5eb295a448ff1d422538a356"), "name" : "Nastaran", "hobbies" : [ { "title" : "Reading", "frequency" : 5 }, { "title" : "Movies", "frequency" : 7 } ], "phone" : "352380532895", "age" : 30 } "_id" : ObjectId("5eb299c448ff1d422538a357"), "name" : "Ahmad", "hobbies" : [ { "title" : "Counceling", "frequency" : 100 }, { "title" : "Farming", "frequency" : 7 } ], "phone" : "3131031385734943", "age" : null }
فرض کنید به دنبال داده هایی هستیم که hobby (سرگرمی) افراد در آن Gym (رفتن به باشگاه) است. به نظر شما آیا برای این کار می توانیم کد زیر را اجرا کنیم؟
db.users.find({hobbies: "Gym"}).pretty()
اگر کد بالا را اجرا کنید، هیچ خروجی نخواهید گرفت! چرا؟ من چند خط بالاتر کاربران را به همراه داده هایشان برایتان قرار داده ام. اگر به آن ها نگاه کنید، ساختار hobbies را به شکل زیر می بینید:
"hobbies" : [ { "title" : "programming", "frequency" : 3 }, { "title" : "Gym", "frequency" : 6 } ],
بنابراین Gym درون یک شیء و سپس درون یک آرایه است! روش اشتباه این است که به دنبال تساوی مطلق باشید و از کد زیر استفاده کنید:
db.users.find({hobbies: {title: "Gym"}}).pretty()
با اجرای کد بالا هیچ مقداری برایتان برگردانده نمی شود. چرا؟ به دلیل اینکه درون فیلد hobbies هیچ document ای به شکل بالا وجود ندارد. تمام document های موجود در hobbies حتما دارای فیلد های title و frequency هستند و این برابر مطلق نیست. بنابراین اگر به دنبال روش برابری مطلق بودیم باید می گفتیم:
db.users.find({hobbies: {title: "Gym", frequency: 6}}).pretty()
با این کار، کاربر Amir به من برگردانده می شد. مشکل این روش چیست؟ اولا بسیار طولانی است! اگر شیء واقعی ما ده خصوصیت دیگر نیز داشته باشد چه کار باید بکنیم؟ برای هر کوئری ده خصوصیت جداگانه بنویسیم تا فقط یک خصوصیت با مقدار مورد نظر ما match شود؟! این کار اصلا عاقلانه نیست. دوما اگر دو کاربر داشته باشیم که هر دو Gym را در hobbies خود داشته باشند، اما Frequency یکی 6 و دیگر 2 باشد، آنگاه کوئری بالا فقط یکی از آن ها را برمی گرداند!
برای حل این مشکل می توان گفت:
db.users.find({"hobbies.title": "Gym"}).pretty()
با اینکه ما چندین embedded document داریم و hobbies.title برای ساختار ما دقیق نیست (اینطور نیست که مقدار hobbies یک شیء باشد، بلکه hobbies یک آرایه است که درون آن یک شیء است که مقدار title را دارد) اما MongoDB متوجه این ساختار می شود و خودش می داند چه کار کند. بنابراین این روش نه تنها برای embedded document ها کار می کند، بلکه برای آرایه ای از embedded document ها نیز کار می کند. همچنین یادتان باشد که حتما لازم نیست به دنبال تساوی باشید، بلکه می توانید از اپراتورهای gt یا lt و غیره نیز در این موارد استفاده کنید.
اولین اپراتور مربوط به آرایه ها که در این جلسه با آن آشنا می شویم، اپراتور size$ است. در حال حاضر تمام کاربران ما تعداد سرگرمی (hobbies) یکسانی دارند: هر فرد دقیقا دو سرگرمی دارد. من یک کاربر دیگر را به این کالکشن اضافه می کنم که اینطور نباشد:
db.users.insertOne({name: "Pooya", hobbies: ["Sports", "Cooking", "Hiking"]})
این کاربر فقط یک name (نام) و hobbies (سرگرمی) دارد. البته قسمت hobbies را به صورت آرایه ای از اشیاء مختلف ننوشته ام چرا که برای کار ما مهم نیست، تنها چیزی که مهم است تعداد آیتم های موجود در hobbies می باشد. حالا می توانیم از اپراتور size$ استفاده کنیم تا کاربرانی را پیدا کنیم که تعداد سرگرمی های آن ها 3 عدد باشد:
db.users.find({hobbies: {$size: 3}}).pretty()
با این کار کاربر Pooya به ما برگردانده می شود:
"_id" : ObjectId("5eb3c250c91228985d228864"), "name" : "Pooya", "hobbies" : [ "Sports", "Cooking", "Hiking" ] }
نکته مهم در مورد این اپراتور این است که فقط برای تساوی از آن استفاده می شود، مثلا نمی توانید بگویید کسانی که سرگرمی هایشان از 3 عدد بیشتر باشد. برای چنین کاری باید روش دیگری را پیاده سازی کنید و نمی توانید از اپراتور size$ استفاده کنید.
اپراتور بعدی ما all$ است. ما برای این اپراتور از پایگاه داده movieData استفاده می کنیم:
use movieData db.movies.findOne()
با اجرای کد بالا یکی از اسناد درون این کالکشن را مشاهده می کنیم. برای من فقط قسمت genres مهم می باشد:
"genres" : [ "Drama", "Action", "Science-Fiction" ],
همانطور که می بینید در genres، سه ژانر سینمایی وجود دارد. برای هر فیلم و سریالی ممکن است 1 یا 2 یا 3 یا هر تعداد ژانر سینمایی موجود باشد. حالا فرض کنید بخواهیم فیلم هایی را پیدا کنیم که دارای ژانر های Drama و action و Science-Fiction باشند اما ترتیب آن ها مهم نباشد. به کد زیر نگاه کنید:
db.movies.find({genres: ["Drama", "Action", "Science-Fiction"]})
کد زیر فقط فیلم هایی را به ما می دهد که ترتیب genres در آن ها دقیقا به صورت Drama و Action و سپس Science-Fiction باشد. برای حل این مشکل باید از اپراتور all$ استفاده کنیم تا ترتیب دیگر مهم نباشد:
db.movies.find({genres: {$all: ["Drama", "Action", "Science-Fiction"]}})
با استفاده از این اپراتور مطمئن می شویم که ترتیب آیتم های درون genres هیچ اهمیتی ندارد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.