آشنایی با مفهوم Projection و داده‌های تو در تو

Projection And Nested Data in MongoDB

06 بهمن 1400
درسنامه درس 10 از سری دوره جامع آموزش MongoDB
MongoDB: آشنایی با مفهوم projection و داده های تو در تو (قسمت 10)

آشنایی با مفهوم projection

تا این قسمت مباحث بسیاری از CRUD را یاد گرفته ایم اما هنوز دو مبحث مهم تا پایان این فصل باقی مانده است. اولین مبحث، مفهوم projection است. فرض کنید در پایگاه داده خود اطلاعاتی را دارید اما فقط قسمتی از آن اطلاعات را می خواهید:

خلاصه ی مفهوم Projection در MongoDB
خلاصه مفهوم Projection در MongoDB

مثلا در این تصویر انواع و اقسام داده ها را داریم اما فقط نام و سن را می خواهیم. شاید دریافت کردن تمام داده ها و سپس فیلتر کردن آن ها درون برنامه خودتان (با هر زبانی که نوشته شده باشد) مشکلی نداشته باشد اما در داده های بزرگ تر دچار مشکل می شویم. اگر تمام داده ها را بگیریم یعنی از منابع سیستم استفاده کرده ایم اما این منابع صرف دریافت داده هایی شده اند که اصلا به آن ها نیاز نداریم. اینجاست که projection به کمک ما می آید.

به طور مثال در passengers اطلاعات زیادی راجع به کاربران داریم اما من می خواهم فقط نام مسافران (passenger به معنی مسافر است) را دریافت کنم. اگر یادتان باشد آرگومان اول find همان فیلتر است و آرگومان دوم options. ما می توانیم در آرگومان دوم از projection استفاده کنیم:

db.passengers.find({},{name: 1}).pretty()

من فیلتر را خالی گذاشته ام تا از بین تمام document ها انتخاب شود. سپس برای آرگومان دوم گفته ام خصوصیت name را از بین document ها انتخاب کن. عدد 1 یعنی خصوصیت name در نتایج برگردانده شده حضور داشته باشد. با اجرای کد بالا نتیجه زیر را می گیریم:

        "_id" : ObjectId("5e8423be6d27a360fec5c8cb"),
        "name" : "Max Schwarzmueller"
}
{ "_id" : ObjectId("5e8423be6d27a360fec5c8cc"), "name" : "Manu Lorenz" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8cd"), "name" : "Chris Hayton" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8ce"), "name" : "Sandeep Kumar" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8cf"), "name" : "Maria Jones" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d0"), "name" : "Alexandra Maier" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d1"), "name" : "Dr. Phil Evans" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d2"), "name" : "Sandra Brugge" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d3"), "name" : "Elisabeth Mayr" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d4"), "name" : "Frank Cube" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d5"), "name" : "Karandeep Alun" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d6"), "name" : "Michaela Drayer" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d7"), "name" : "Bernd Hoftstadt" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d8"), "name" : "Scott Tolib" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8d9"), "name" : "Freddy Melver" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8da"), "name" : "Alexis Bohed" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8db"), "name" : "Melanie Palace" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8dc"), "name" : "Armin Glutch" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8dd"), "name" : "Klaus Arber" }
{ "_id" : ObjectId("5e8423be6d27a360fec5c8de"), "name" : "Albert Twostone" }
Type "it" for more

با این حساب چرا id درون نتایج برگشتی است؟ id یک فیلد خاص در MongoDB است بنابراین همیشه به صورت پیش فرض در نتایج حضور خواهد داشت مگر آنکه خودتان آن را غیر فعال کنید. برای دریافت نکردن id باید صریحا به MongoDB بگویید که آن را نمی خواهید:

db.passengers.find({},{name: 1, _id: 0}).pretty()

بنابراین عدد 1 یعنی درون نتایج باشد و عدد 0 یعنی درون نتایج نباشد. زمانی که یک فیلد خاص را درون options تعیین می کنید (در اینجا name) پیش فرض این است که به غیر از name و id چیزی برگردانده نشود بنابراین اگر id را نمی خواهید باید دستور آن را صریحا مشخص کنید. حالا دستور بالا می گوید فقط و فقط name را برگردان. نتیجه این کد به شکل زیر است:

{ "name" : "Max Schwarzmueller" }
{ "name" : "Manu Lorenz" }
{ "name" : "Chris Hayton" }
{ "name" : "Sandeep Kumar" }
{ "name" : "Maria Jones" }
{ "name" : "Alexandra Maier" }
{ "name" : "Dr. Phil Evans" }
{ "name" : "Sandra Brugge" }
{ "name" : "Elisabeth Mayr" }
{ "name" : "Frank Cube" }
{ "name" : "Karandeep Alun" }
{ "name" : "Michaela Drayer" }
{ "name" : "Bernd Hoftstadt" }
{ "name" : "Scott Tolib" }
{ "name" : "Freddy Melver" }
{ "name" : "Alexis Bohed" }
{ "name" : "Melanie Palace" }
{ "name" : "Armin Glutch" }
{ "name" : "Klaus Arber" }
{ "name" : "Albert Twostone" } 

نکته مهم این است که فرآیند projection روی سرورِ MongoDB رخ می دهد، یعنی قبل از اینکه داده ها به سمت شما ارسال شوند، فیلتر شده و فقط داده های مورد نیاز ارسال خواهند شد. این دقیقا همان چیزی است که ما می خواهیم. یعنی داده های ناخواسته به سمت برنامه ما ارسال نشوند. در صورتی که اگر داده ها را با زبان برنامه نویسی فیلتر می کردیم، به هر حال ارسال می شدند و منابع سیستم را هدر می دادیم.

document های embed شده (تو در تو)

ما تا این قسمت با رشته ها، اعداد و مقادیر Boolean کار کرده ایم اما دو نوع داده دیگر را نیز می توانید در پایگاه داده خود، داشته باشید. embedded document یکی از ویژگی های مهم در MongoDB است که در فصل های آینده بیشتر در مورد آن ها صحبت خواهیم کرد اما فعلا باید بررسی مختصری از آن ها داشته باشیم.

مفهوم کلی embedded documents در MongoDB
مفهوم کلی embedded documents در MongoDB

در تصویر بالا مربع صورتی یک document است. embedded document یعنی یک document می تواند یک document دیگر را درون خود داشته باشد (مستطیل های زرد) و خود آن document ها می توانند document های دیگری را در خود داشته باشند (مستطیل های نارنجی). بنابراین شما می توانید document های درون یک collection را به صورت تو در تو تنظیم کنید. زمانی که بحث از embedded document ها می شود باید دو محدودیت را در نظر بگیرید:

  • شما نمی توانید بیشتر از 100 سطح تو در تو داشته باشید. این محدودیت فقط برای اطلاع شما گفته شده است؛ 100 مرحله بسیار زیاد است و حتی در بزرگترین وب سایت های دنیا نیز بیشتر از 5 مرحله به ندرت دیده می شود.
  • حداکثر حجم مجاز برای هر document در MongoDB دقیقا 16 مگابایت است. شاید در نگاه اول 16 مگابایت عدد کوچکی به نظر برسد اما توجه داشته باشید که ما در پایگاه داده فقط متن ذخیره می کنیم و هیچکس درون MongoDB یا MySQL یا پایگاه های داده دیگر، فایل ذخیره نمی کند. ذخیره فایل روی Storage سرور شما انجام خواهد شد و وب سایت هایی که فایل های زیادی برای دانلود دارند، معمولا از یک سرور میزبانی فایل (دانلود) جداگانه استفاده می کنند. بنابراین 16 مگابایت متن بسیار زیاد خواهد بود و امکان ندارد به چنین محدودیتی برخورد کنید.

نوع داده دیگری که می توانید درون پایگاه داده خود داشته باشید، آرایه ها هستند.

مفهوم کلی embedded arrays در MongoDB
مفهوم کلی embedded arrays در MongoDB

آرایه ها می توانند هر نوع داده ای را ذخیره کنند بنابراین می توانید document های خود را درون این آرایه ها قرار بدهید. بگذارید این دو مبحث را به صورت عملی به شما نشان بدهم.

ما در collection ای به نام flightData دو شیء پرواز داریم و حالا می خواهیم خصوصیت جدیدی به نام status (وضعیت - منظور وضعیت پرواز) را تعریف کنیم:

db.flightData.updateMany({}, {$set: {status: {description: "on-time", lastUpdated: "1 hour ago"}}})

آرگومان اول که فیلتر است را خالی گذاشته ام تا هر دو document هدف گرفته شوند. برای آرگومان دوم خصوصیت status را تعریف کرده ام اما این بار به جای پاس دادن رشته، یک document یا شیء دیگر را به عنوان مقدار به آن داده ام که خودش دارای دو خصوصیت description و lastUpdated می باشد. با اجرای دستور بالا پیام زیر را می گیریم:

{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }

یعنی همه چیز موفقیت آمیز بوده است. حالا با دستور ()find.pretty نتیجه را مشاهده می کنیم:

db.flightData.find().pretty()

نتیجه:

        "_id" : ObjectId("5e8418e16d27a360fec5c8c9"),
        "departureAirport" : "MUC",
        "arrivalAirport" : "SFO",
        "aircraft" : "Airbus A380",
        "distance" : 12000,
        "intercontinental" : true,
        "status" : {
                "description" : "on-time",
                "lastUpdated" : "1 hour ago"
        }
}

        "_id" : ObjectId("5e8418e16d27a360fec5c8ca"),
        "departureAirport" : "LHR",
        "arrivalAirport" : "TXL",
        "aircraft" : "Airbus A320",
        "distance" : 950,
        "intercontinental" : false,
        "status" : {
                "description" : "on-time",
                "lastUpdated" : "1 hour ago"
        }

همانطور که در این کد می بینید، status خودش دارای یک document است و به این قابلیت embedded documents (به معنی document های تو در تو یا nested) می گوییم. البته شما می توانید document های تو در توی بیشتری (تا 100 بار) ایجاد کنید:

db.flightData.updateMany({}, {$set: {status: {description: "on-time", lastUpdated: "1 hour ago", details: {responsible: "Amir"}}}})

حالا اگر از find.pretty استفاده کنیم، نتیجه به شکل زیر خواهد بود:

        "_id" : ObjectId("5e8418e16d27a360fec5c8c9"),
        "departureAirport" : "MUC",
        "arrivalAirport" : "SFO",
        "aircraft" : "Airbus A380",
        "distance" : 12000,
        "intercontinental" : true,
        "status" : {
                "description" : "on-time",
                "lastUpdated" : "1 hour ago",
                "details" : {
                        "responsible" : "Amir"
                }
        }
}

        "_id" : ObjectId("5e8418e16d27a360fec5c8ca"),
        "departureAirport" : "LHR",
        "arrivalAirport" : "TXL",
        "aircraft" : "Airbus A320",
        "distance" : 950,
        "intercontinental" : false,
        "status" : {
                "description" : "on-time",
                "lastUpdated" : "1 hour ago",
                "details" : {
                        "responsible" : "Amir"
                }
        }
}

حالا بیایید مثالی از آرایه ها را نیز ببینیم. من کد زیر را برای شما مثال می زنم:

db.passengers.updateOne({name: "Albert Twostone"}, {$set: {hobbies: ["sports", "cooking"]}})

یعنی مسافری به نام Albert Twostone را از collection ای به نام passengers پیدا کن، سپس خصوصیت hobbies (سرگرمی ها) را برایش تعریف کن که یک آرایه باشد. من sports و cooking را برایش انتخاب کرده ام. حالا نتیجه را با find.pretty می بینیم:

        "_id" : ObjectId("5e8423be6d27a360fec5c8de"),
        "name" : "Albert Twostone",
        "age" : 68,
        "hobbies" : [
                "sports",
                "cooking"
        ]

همانطور که می بینید یک آرایه با دو رشته است. البته الزامی به استفاده از رشته ها نیست، شما می توانید درون این آرایه، آرایه های دیگر یا حتی document های دیگری را داشته باشید.

سوال: چطور به محتوای این آرایه ها یا document های تو در تو دسترسی داشته باشیم؟

این سوال، بسیار به جا اما بسیار ساده است. من ابتدا دسترسی به آرایه ها را توضیح می دهم. به کد زیر نگاه کنید:

db.passengers.findOne({name: "Albert Twostone"}).hobbies

با اجرای این کد، نتیجه زیر را دریافت می کنید:

[ "sports", "cooking" ]

حتی می توانیم این خصوصیت را به عنوان یک فیلتر در نظر بگیریم:

db.passengers.find({hobbies: "sports"}).pretty()

نتیجه اجرای این کد، پیام زیر است:

        "_id" : ObjectId("5e8423be6d27a360fec5c8de"),
        "name" : "Albert Twostone",
        "age" : 68,
        "hobbies" : [
                "sports",
                "cooking"
        ]

حالا برای دسترسی به اشیاء می توان گفت:

db.flightData.find({"status.description": "on-time"}).pretty()

یعنی درون خصوصیت status یک شیء دیگر به نام description داریم. آن هایی را پیدا کن که این خصوصیت در آن ها برابر on-time باشد. توجه کنید که هر زمان از این ساختار استفاده کردید (استفاده از نقطه برای دسترسی به اشیاء تو در تو) باید مانند کد بالا تمام آن قسمت را درون double quotation بگذارید. بنابراین کد زیر غلط است:

db.flightData.find({status.description: "on-time"}).pretty()

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

        "_id" : ObjectId("5e8418e16d27a360fec5c8c9"),   
        "departureAirport" : "MUC",                     
        "arrivalAirport" : "SFO",                       
        "aircraft" : "Airbus A380",                     
        "distance" : 12000,                             
        "intercontinental" : true,                      
        "status" : {                                    
                "description" : "on-time",              
                "lastUpdated" : "1 hour ago",           
                "details" : {                           
                        "responsible" : "Amir"          
                }                                       
        }                                               
}                                                       
                                                        
        "_id" : ObjectId("5e8418e16d27a360fec5c8ca"),   
        "departureAirport" : "LHR",                     
        "arrivalAirport" : "TXL",                       
        "aircraft" : "Airbus A320",                     
        "distance" : 950,                               
        "intercontinental" : false,                     
        "status" : {                                    
                "description" : "on-time",              
                "lastUpdated" : "1 hour ago",           
                "details" : {                           
                        "responsible" : "Amir"          
                }                                       
        }                                               
}                                                       

از آنجایی که مقدار description در هر دو on-time بود، هر دو document برگردانده شده اند. شما می توانید از همین ساختار استفاده کنید و جلوتر بروید:

db.flightData.find({"status.details.responsible": "Amir"}).pretty()

با اجرای این دستور، باز هم دو document برگردانده می شوند.

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

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

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