آشنایی با slice و projection در آرایه‌ها در MongoDB

rojection in Arrays in MongoDB

22 اردیبهشت 1401
درسنامه درس 40 از سری دوره جامع آموزش MongoDB
042-MongoDB-full-course-tutorial-2020

projection در آرایه ها

در قسمت قبل در مورد projection به طور کلی صحبت کردیم اما حالت خاصی در مورد projection وجود دارد که مربوط به آرایه ها است و باید قسمتی از زمان خود را روی آن بگذاریم. ساختار هر کدام از داده های ما برای هر سریال به شکل زیر است:

        "id" : 21,                                                                                                      
        "url" : "http://www.tvmaze.com/shows/21/the-last-ship",                                                         
        "name" : "The Last Ship",                                                                                       
        "type" : "Scripted",                                                                                            
        "language" : "English",                                                                                         
        "genres" : [                                                                                                    
                "Drama",                                                                                                
                "Action",                                                                                               
                "Thriller"                                                                                              
        ],                                                                                                              
        "status" : "Running",                                                                                           
        "runtime" : 60,                                                                                                 
        "premiered" : "2014-06-22",                                                                                     
        "officialSite" : "http://www.tntdrama.com/shows/the-last-ship",                                                 
        "schedule" : {                                                                                                  
                "time" : "21:00",                                                                                       
                "days" : [                                                                                              
                        "Sunday"                                                                                        
                ]                                                                                                       
        },                                                                                                              
        "rating" : {                                                                                                    
                "average" : 7.8                                                                                         
        },                                                                                                              
        "weight" : 100,                                                                                                 
        "network" : {                                                                                                   
                "id" : 14,                                                                                              
                "name" : "TNT",                                                                                         
                "country" : {                                                                                           
                        "name" : "United States",                                                                       
                        "code" : "US",                                                                                  
                        "timezone" : "America/New_York"                                                                 
                }                                                                                                       
        },                                                                                                              
        "webChannel" : null,                                                                                            
        "externals" : {                                                                                                 
                "tvrage" : 33158,                                                                                       
                "thetvdb" : 269533,                                                                                     
                "imdb" : "tt2402207"                                                                                    
        },                                                                                                              
        "image" : {                                                                                                     
                "medium" : "http://static.tvmaze.com/uploads/images/medium_portrait/164/412464.jpg",                    
                "original" : "http://static.tvmaze.com/uploads/images/original_untouched/164/412464.jpg"                
        },                                                                                                              
        "summary" : "<p>Their mission is simple: Find a cure. Stop the virus. Save the world. When a global pandemic wip
es out eighty percent of the planet's population, the crew of a lone naval destroyer must find a way to pull humanity fr
om the brink of extinction.</p>",                                                                                       
        "updated" : 1536575637,                                                                                         
        "_links" : {                                                                                                    
                "self" : {                                                                                              
                        "href" : "http://api.tvmaze.com/shows/21"                                                       
                },                                                                                                      
                "previousepisode" : {                                                                                   
                        "href" : "http://api.tvmaze.com/episodes/1499133"                                               
                },                                                                                                      
                "nextepisode" : {                                                                                       
                        "href" : "http://api.tvmaze.com/episodes/1499134"                                               
                }                                                                                                       
        }                                                                                                               
}                                                                                                                       

بنابراین برای هر سریال چند ساختار آرایه ای وجود دارد. حالا تصور کنید که به دنبال سریال هایی هستیم که ژانر درام دارند. این کار بسیار ساده ای است که آن را در اوایل این فصل یاد گرفتیم:

db.movies.find({genres: "Drama"}).pretty()

کوئری بالا کل داده های هر سریالی را که دارای ژانر درام باشد، برای ما برمی گرداند اما اگر بخواهیم فقط همین قسمت Drama را دریافت کنیم چطور؟ ما می دانیم که این کار بر عهده projection است اما زمانی که بدین شکل با آرایه ها کار می کنیم باید از روش خاصی استفاده کنیم:

db.movies.find({genres: "Drama"}, {"genres.$": 1}).pretty()

استفاده از علامت $ در اینجا به معنی first match است یعنی اولین ژانری که بر اساس فیلتر پاس داده شده صحیح می باشد، به جای $ قرار می گیرد. نتیجه اجرای کوئری بالا به شکل زیر است (من فقط چند مورد را می آورم تا شلوغ نشود):

{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6507"), "genres" : [ "Drama" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6508"), "genres" : [ "Drama" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6509"), "genres" : [ "Drama" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650a"), "genres" : [ "Drama" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650b"), "genres" : [ "Drama" ] }

باید به مفهوم first match توجه ویژه ای داشته باشید چرا که در هر حالتی، اولین نتیجه، برابر با علامت $ خواهد بود. مثلا به کوئری پیچیده تر زیر نگاه کنید:

db.movies.find({genres: {$all: ["Drama", "Horror"]}}, {"genres.$": 1}).pretty()

اگر یادتان باشد اپراتور all$ برای ترتیب مهم نبود بنابراین کوئری بالا می گوید سریال هایی را پیدا کن که در آن ها ژانر درام (Drama) و وحشت (Horror) وجود داشته باشد اما مهم نیست که اول Drama باشد یا Horror. نتیجه اجرای این کوئری به شکل زیر است (من فقط چند مورد را می آورم تا شلوغ نشود):

{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650d"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6511"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6513"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6514"), "genres" : [ "Horror" ] }

دو نکته اصلی برای این نتیجه حائز اهمیت است:

  • چرا Horror را گرفتیم؟ به دلیل اینکه برای All ترتیب مهم نیست. از طرفی اولین سریالی که بررسی شده است دارای Horror بوده بنابراین Horror می شود first match (اولین نتیجه) و برگردانده می شود.
  • چرا فقط Horror را گرفتیم؟ به دلیل اینکه علامت $ در $.genres فقط می تواند یک عنصر را بگیرد که اولین نتیجه یا First match است و فقط همان را برمی گرداند.

البته ما می توانیم مقادیری را دریافت کنیم که اصلا کوئری ما آن ها را هدف نگرفته است! به طور مثال به کوئری زیر توجه کنید:

db.movies.find({genres: "Drama"}, {genres: {$elemMatch: {$eq: "Horror"}}}).pretty()

در اینجا می گوییم سریال های ژانر درام را پیدا کن اما در projection از elemMatch$ استفاده کرده ام که قبلا با آن آشنا شدیم. elemMatch$ یک شرط را می گیرد و بررسی می کند که آیا شرط تعریف شده برای عناصر مختلف صحیح است یا خیر. پس از اجرای این کوئری shell مثل همیشه 20 نتیجه اول را برمی گرداند که بدین شکل هستند:

{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6507") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6508") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6509") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650a") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650b") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650c") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650d"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650e") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650f") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6510") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6511"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6512") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6513"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6514"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6516") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6517"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6518") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6519") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c651b") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c651d") }

چرا بعضی از این اسناد برگردانده شده خالی هستند؟ به دلیل اینکه کوئری ما در دو مرحله کار می کند:

  • ابتدا سریال هایی را پیدا کن که ژانر Drama دارند (مرحله 1).
  • از بین سریال های پیدا شده (در مرحله 1) ژانر وحشت (Horror) را بیرون بکش (مرحله دوم).

بنابراین اگر سریالی در مرحله اول پیدا شود که در مرحله دوم شکست بخورد (Drama باشد اما Horror نداشته باشد)، یک سند خالی برایمان برگردانده می شود چرا که چیزی را بیرون کشیده ایم که اصلا وجود ندارد. در ضمن فیلتر شما در find می تواند هر چیزی را هدف بگیرد و نیازی نیست حتما فیلتر و projection از یک فیلد باشند. مثلا:

db.movies.find({"rating.average": 9}, {genres: {$elemMatch: {$eq: "Horror"}}}).pretty()

یعنی rating هایی که دقیقا 9 باشند. خروجی کوئری بالا به شکل زیر است:

{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c650c") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6522") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6575") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65a1") }

بنابراین هیچ کدام از سریال هایی که دارای rating برابر با 9 بوده اند از ژانر Horror نیستند. برایتان مثال دیگری می زنم:

db.movies.find({"rating.average": {$gt: 9}}, {genres: {$elemMatch: {$eq: "Horror"}}}).pretty()

یعنی rating باید بیشتر از 9 باشد. خروجی این کوئری به شکل زیر است:

{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c6520"), "genres" : [ "Horror" ] }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c655a") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65a7") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65af") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65b1") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65d4") }
{ "_id" : ObjectId("5e8d82a8fed9c9b1e19c65db") }

بنابراین فقط یکی از سریال هایی که دارای امتیاز بالای 9 است از ژانر وحشت می باشد.

نکته بعدی ما در مورد اپراتور slice$ است. slice به معنی «قطعه» یا «قطعه کردن» است و احتمالا از نام آن حدس می زنید که کارش چیست:

db.movies.find({"rating.average": {$gt: 9}}, {genres: {$slice: 2}, name: 1}).pretty()

من در اینجا گفته ام که سریال هایی با امتیاز بالای 9 را پیدا کن و سپس با projection مشخص کرده ام که باید دارای genres و name باشند. مسئله اینجاست که به جای قرار دادن 1 یا 0 برای genres از slice$ استفاده کرده ام که genres را «قطعه کرده» و فقط دو ژانر اول را به ما تحویل می دهد. نتیجه اجرای کوئری بالا به شکل زیر است (من فقط دو مورد را می آورم تا شلوغ نشود):

  "_id" : ObjectId("5e8d82a8fed9c9b1e19c65d4"),
  "name" : "Rick and Morty",                   
  "genres" : [                                 
          "Comedy",                            
          "Adventure"                          
  ]                                            
                                               
                                               
  "_id" : ObjectId("5e8d82a8fed9c9b1e19c65db"),
  "name" : "Stargate SG-1",                    
  "genres" : [                                 
          "Action",                            
          "Adventure"                          
  ]                                            

آیا متوجه شدید؟ بسیاری از سریال ها 3 یا 4 ژانر مختلف دارند اما ما فقط دو ژانر اول را می بینیم چرا که به slice$ عدد 2 را داده ایم. این دستور پیشرفته تر نیز می شود. مثلا:

db.movies.find({"rating.average": {$gt: 9}}, {genres: {$slice: [1, 2]}, name: 1}).pretty()

اگر به جای یک عدد، یک آرایه به slice$ بدهیم، عنصر اول تعداد عناصر skip شده در آرایه genres و عنصر دوم تعداد عناصر برگردانده شده از genres بعد از عنصر Skip شده است؛ یعنی به genres نگاه کن و یک عنصر (عنصر اول - چرا که نادیده گرفتن از اولین عنصر شروع می شود) را نادیده بگیر، سپس دو عنصر بعد از آن (عناصر دوم و سوم) را برگردان. نتیجه اجرای کوئری بالا به شکل زیر است:

"_id" : ObjectId("5e8d82a8fed9c9b1e19c65d4"), 
 "name" : "Rick and Morty",                    
 "genres" : [                                  
         "Adventure",                          
         "Science-Fiction"                     
 ]                                             
                                               
                                               
 "_id" : ObjectId("5e8d82a8fed9c9b1e19c65db"), 
 "name" : "Stargate SG-1",                     
 "genres" : [                                  
         "Adventure",                          
         "Science-Fiction"                     
 ]                                             

با مقایسه کردن نتایج این کوئری و کوئری قبلی متوجه موضوع می شوید. مثلا در هر دو کوئری سریال rick and morty را داریم. کل قسمت genres این سریال به شکل زیر است:

"genres" : [
                "Comedy",
                "Adventure",
                "Science-Fiction"
]

از طرفی کوئری اخیر ما نتیجه زیر را برگردانده است:

"genres" : [                                  
         "Adventure",                          
         "Science-Fiction"                     
 ]                                             

بنابراین دقیقا طبق انتظار ما، اولین عنصر (Comedy) را نادیده گرفته است و دو عنصر بعدی یا همان عناصر دوم و سوم (Adventure و Science-Fiction) را برگردانده است. بنابراین یادتان باشد که projection برای آرایه ها نمی تواند عدد های 0 یا 1 را بگیرد بلکه یا باید از slice$ یا از elemMatch$ یا از $ استفاده کنید.

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

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

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