تا این قسمت مباحث بسیاری از CRUD را یاد گرفته ایم اما هنوز دو مبحث مهم تا پایان این فصل باقی مانده است. اولین مبحث، مفهوم projection است. فرض کنید در پایگاه داده خود اطلاعاتی را دارید اما فقط قسمتی از آن اطلاعات را می خواهید:
مثلا در این تصویر انواع و اقسام داده ها را داریم اما فقط نام و سن را می خواهیم. شاید دریافت کردن تمام داده ها و سپس فیلتر کردن آن ها درون برنامه خودتان (با هر زبانی که نوشته شده باشد) مشکلی نداشته باشد اما در داده های بزرگ تر دچار مشکل می شویم. اگر تمام داده ها را بگیریم یعنی از منابع سیستم استفاده کرده ایم اما این منابع صرف دریافت داده هایی شده اند که اصلا به آن ها نیاز نداریم. اینجاست که 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 رخ می دهد، یعنی قبل از اینکه داده ها به سمت شما ارسال شوند، فیلتر شده و فقط داده های مورد نیاز ارسال خواهند شد. این دقیقا همان چیزی است که ما می خواهیم. یعنی داده های ناخواسته به سمت برنامه ما ارسال نشوند. در صورتی که اگر داده ها را با زبان برنامه نویسی فیلتر می کردیم، به هر حال ارسال می شدند و منابع سیستم را هدر می دادیم.
ما تا این قسمت با رشته ها، اعداد و مقادیر Boolean کار کرده ایم اما دو نوع داده دیگر را نیز می توانید در پایگاه داده خود، داشته باشید. embedded document یکی از ویژگی های مهم در MongoDB است که در فصل های آینده بیشتر در مورد آن ها صحبت خواهیم کرد اما فعلا باید بررسی مختصری از آن ها داشته باشیم.
در تصویر بالا مربع صورتی یک document است. embedded document یعنی یک document می تواند یک document دیگر را درون خود داشته باشد (مستطیل های زرد) و خود آن document ها می توانند document های دیگری را در خود داشته باشند (مستطیل های نارنجی). بنابراین شما می توانید document های درون یک collection را به صورت تو در تو تنظیم کنید. زمانی که بحث از embedded document ها می شود باید دو محدودیت را در نظر بگیرید:
نوع داده دیگری که می توانید درون پایگاه داده خود داشته باشید، آرایه ها هستند.
آرایه ها می توانند هر نوع داده ای را ذخیره کنند بنابراین می توانید 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 برگردانده می شوند.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.