اپراتور elemenMatch$ و آشنایی با cursorها

ElemenMatch Operator and Familiarity with Cursors

20 اردیبهشت 1401
درسنامه درس 37 از سری دوره جامع آموزش MongoDB
MongoDB: اپراتور elemenMatch$ و آشنایی با cursor ها (قسمت 39)

اپراتور elemenMatch$

در جلسه قبل با برخی از اپراتورهای آرایه ای آشنا شدیم (all$ و size$) اما هنوز یکی از اپراتورهای اصلی باقی مانده است: elemenMatch$. برای این جلسه از پایگاه داده user استفاده می کنم بنابراین:

use user

تصور کنید که ما به دنبال تمام کاربرانی هستیم که Gym را در hobbies خود دارند و همچنین frequency آن نیز روی 6 یا بیشتر می باشد. برای این کار راه حل های مختلفی وجود دارد. به طور مثال می توانیم از and$ استفاده کنیم:

db.users.find({$and: [{"hobbies.title": "Gym"}, {"hobbies.frequency": {$gte: 6}}]}).pretty()

بدین صورت دو شرط را به and$ داده ایم و تنها یک کاربر داریم که هر دو شرط بالا در آن صحیح است (کاربر Amir). تا اینجا چیز جدیدی نداریم اما بگذارید یک کاربر دیگر به کالکشن خود اضافه کنیم:

db.users.insertOne({name: "Javad", hobbies: [{title: "Gym", frequency: 2},{title: "Driving", frequency: 4}]})

کاربر Javad نیز Gym را در سرگرمی های خود دارد و Frequency آن روی 2 تنظیم شده است. به نظر شما حالا با دستور زیر چه چیزی برگردانده می شود:

db.users.find({$and: [{"hobbies.title": "Gym"}, {"hobbies.frequency": {$gte: 3}}]}).pretty()

کد بالا می گوید Gym باید در hobbies باشد اما frequency آن باید برابر با 3 یا بیشتر از 3 باشد. با اجرای کد بالا نتیجه زیر را می گیریم:

        "_id" : ObjectId("5eb295a448ff1d422538a355"),
        "name" : "Amir",
        "hobbies" : [
                {
                        "title" : "programming",
                        "frequency" : 3
                },
                {
                        "title" : "Gym",
                        "frequency" : 6
                }
        ],
        "phone" : 59732894409174
}

        "_id" : ObjectId("5eb3e69ac91228985d228865"),
        "name" : "Javad",
        "hobbies" : [
                {
                        "title" : "Gym",
                        "frequency" : 2
                },
                {
                        "title" : "Driving",
                        "frequency" : 4
                }
        ]
}

به نظر شما چرا کاربر Javad با اینکه frequency برابر 2 دارد، هنوز هم برگردانده می شود؟ دوباره به کد زیر نگاه کنید:

db.users.find({$and: [{"hobbies.title": "Gym"}, {"hobbies.frequency": {$gte: 3}}]}).pretty()

این کد نمی گوید که Frequency مخصوص Gym باید 3 یا بیشتر باشد بلکه می گوید frequency در کل در یکی از این اسناد باید 3 یا بیشتر باشد. دو شرطی که با and$ مشخص کرده ایم لزوما مربوط به یک document واحد نیستند. بنابراین اگر نتیجه بالا را نگاه کنید، می بینید که driving برای Javad برابر 4 است که از 3 بیشتر است بنابراین آن هم برگردانده شده است. به زبان ساده مراحل اجرا شدن کوئری بالا بدین شکل است:

  • آیا Javad دارای Gym در hobbies خود است؟ بله!
  • آیا Javad دارای frequency (در هر جایی) بیشتر یا برابر 3 است؟ بله
  • بنابراین Javad نیز در این شرط صدق می کند و برگردانده می شود.

اینجاست که اپراتور elemMatch$ وارد می شود. بنابراین برای حل مشکل بالا، باید آن را به شکل زیر بنویسیم:

db.users.find({hobbies: {$elemMatch: {title: "Gym", frequency: {$gte: 3}}}}).pretty()

بدین صورت می توانیم از elemMatch استفاده کنیم، یعنی اول گفته ایم که در کدام فیلد باید جست و جو شود (hobbies) سپس شرط را به آن داده ایم. کد بالا فقط کاربر Amir را برمی گرداند.

آشنایی با Cursorها

اگر یادتان باشد در فصل های قبلی در مورد cursor ها به صورت خلاصه صحبت کرده بودیم. متد ()find برخلاف findOne به ما یک cursor می داد. چرا این موضوع برای ما اهمیت دارد و چرا اصلا باید چنین اتفاقی بیفتد؟ اگر در یک پایگاه داده واقعی از ()find استفاده کنید (بدون آرگومان) هزاران یا حتی میلیون ها سند را دریافت خواهید کرد. حتی اگر شرطی را برای find قرار بدهید در اکثر اوقات این شرط برای صد ها سند برقرار است بنابراین با همان یک دستور به صد ها یا شاید هزاران سند درخواست بزنید. چنین فرآیندی بسیار غیر استاندارد است. چرا؟ به دلیل اینکه در چنین سناریویی:

  • تمام داده ها توسط موتور پایگاه داده پیدا شده و دریافت می شوند.
  • تمام داده ها از پایگاه داده به مموری سرور شما ارسال می شوند.
  • تمام داده ها در مموری برنامه شما بارگذاری می شوند.

به همین دلیل find به جای اینکه خود داده ها را به شما بدهد، یک cursor یا pointer (به معنی «نشانگر») به شما می دهد که جایگاه داده ها را در خود دارد اما خود داده ها را ندارد. مثلا اگر دستور find را روی 1000 سند صدا بزنیم، 20 سند اول (1 تا 20) به همراه یک cursor به ما داده می شود که حاوی جایگاه 10 سند بعدی (21 تا 40) نیز می باشد. به هر کدام از این دسته داده ها یک batch می گوییم. البته این حالت برای shell است که در آن کار می کنیم. یعنی driver های MongoDB به ما 20 داده اول را نمی دهند بلکه داده ها را تک به تک برمی گردانند و کنترل آن دست خودمان است.

مثلا اگر یادتان باشد در پایگاه داده movieData دقیقا 240 فیلم و سریال داشتیم. آیا اگر دستور زیر را اجرا کنیم همه 240 سریال را دریافت می کنیم؟

use movieData
db.movies.find().pretty()

خیر! همانطور که گفتم shell به ما 20 سند اول را داده و در انتهای آن می گوید:

Type "it" for more

یعنی اگر it را ارسال کنیم، 20 سند دوم نیز ارسال می شود. برای اینکه بهتر متوجه cursor بشویم، باید متد count را روی find صدا کنیم:

db.movies.find().count()

همانطور که می دانید find یک cursor را به ما برمی گرداند بنابراین count وارد آن cursor می شود و تعداد کل اسناد را می شمارد و نهایتا عدد 240 را به ما می دهد. این اطلاعات (Cursor) در همان ابتدا در مموری بارگذاری می شوند و اینطور نیست که بعدا با دستور count دوباره از روی دیسک خوانده شوند، به همین دلیل است که سرعت آن بالا است.

توجه داشته باشید که it یک دستور مخصوص به shell است و زمانی که با driver ها کار می کنید، چیزی به نام it وجود نخواهد داشت، بلکه معمولا متدی به نام next یا شبیه آن را خواهیم داشت که برای اطلاع دقیق از آن باید به documentation رسمی driver مورد نظر خود بروید. البته در shell نیز دستور زیر را داریم:

db.movies.find().next()

این دستور به جای 20 سند، فقط یک سند (سند بعدی) را به ما می دهد.

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

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

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