آرایه‌ها و اپراتور unwind$ در فریم‌ورک aggregation

$unwind Arrays and Operators in the Aggregation Framework

26 اردیبهشت 1401
درسنامه درس 72 از سری دوره جامع آموزش MongoDB
MongoDB: آرایه ها و اپراتور unwind$ در فریم ورک aggregation (قسمت 74)

ما به stage های مختلفی در فریم ورک aggregation پرداخته ایم اما حالا باید به سمت اپراتور ها و stage هایی برویم که با آرایه ها سر و کار دارند. به همین خاطر من داده هایی را از قبل برایتان آماده کرده ام:

[
  {
    "name": "Max",
    "hobbies": ["Sports", "Cooking"],
    "age": 29,
    "examScores": [
      { "difficulty": 4, "score": 57.9 },
      { "difficulty": 6, "score": 62.1 },
      { "difficulty": 3, "score": 88.5 }
    ]
  },
  {
    "name": "Manu",
    "hobbies": ["Eating", "Data Analytics"],
    "age": 30,
    "examScores": [
      { "difficulty": 7, "score": 52.1 },
      { "difficulty": 2, "score": 74.3 },
      { "difficulty": 5, "score": 53.1 }
    ]
  },
  {
    "name": "Maria",
    "hobbies": ["Cooking", "Skiing"],
    "age": 29,
    "examScores": [
      { "difficulty": 3, "score": 75.1 },
      { "difficulty": 8, "score": 44.2 },
      { "difficulty": 6, "score": 61.5 }
    ]
  }
]

شما می توانید این داده ها را در یک فایل ذخیره کرده و با دستور mongoimport آن ها را وارد پایگاه داده خود کنید یا اینکه آن ها را کپی کرده و یک کالکشن جدید به نام friends بسازید. حتما می دانید که با insertMany می توانیم این کار را انجام بدهیم. البته باید برای کار با دستوراتی که به شکل بالا در چند خط نوشته شده اند، توضیحاتی بدهم. شما برای اجرای این دستورات چند راه دارید:

  • آسان ترین راه این است که تمام کد را کپی کرده و سپس در ترمینال خود بگویید )friends.insertMany یعنی پرانتز انتهایی را قرار ندهید. سپس کد ها را paste کرده و سپس پرانتز نهایی را بگذارید. در نهایت کلید enter را بزنید تا داده ها وارد شوند.
  • می توانید به سایت هایی مانند https://codebeautify.org/remove-extra-spaces رفته تا تمام white space ها را حذف کنید. مثلا من داده های بالا را به آن دادم و حالا کد من به شکل زیر در آمده است:
db.friends.insertMany([ { "name": "Max", "hobbies": ["Sports", "Cooking"], "age": 29, "examScores": [ { "difficulty": 4, "score": 57.9 }, { "difficulty": 6, "score": 62.1 }, { "difficulty": 3, "score": 88.5 } ] }, { "name": "Manu", "hobbies": ["Eating", "Data Analytics"], "age": 30, "examScores": [ { "difficulty": 7, "score": 52.1 }, { "difficulty": 2, "score": 74.3 }, { "difficulty": 5, "score": 53.1 } ] }, { "name": "Maria", "hobbies": ["Cooking", "Skiing"], "age": 29, "examScores": [ { "difficulty": 3, "score": 75.1 }, { "difficulty": 8, "score": 44.2 }, { "difficulty": 6, "score": 61.5 } ] } ])

در نهایت مسئله به خود شما مربوط است. پس از اجرای این دستور مثل همیشه از mongodb یک تاییدیه می گیرید. در فریم ورک aggregation می توان کار های مختلفی با آرایه ها انجام داد بنابراین بهتر است بدون مقدمه شروع کنیم. در قدم اول می خواهم داده هایمان را بر اساس سن group$ کرده و سپس hobbies را در هم ادغام کنیم تا ببینیم هر سرگرمی متعلق به چند نفر است:

db.friends.aggregate([
    { $group: { _id: { age: "$age" }, allHobbies: { $push: "$hobbies" } } }
]).pretty()

اپراتور push$ به ما اجازه می دهد که مقادیر مورد نظر خودمان را در یک آرایه وارد کنیم؛ این مقادیر ممکن است از فیلد های موجود باشد و ممکن است مقادیری باشد که خودمان به صورت دستی وارد کرده ایم. من در کد بالا گفته ام که کل سند ها را بر اساس age گروه بندی کن و سپس فیلد جدیدی به نام allHobbies را تعریف کرده ام. این فیلد، push$ را دارد که به ازای هر کدام از سند ها، فیلد hobbies را درون allHobbies قرار می دهد. با اجرای کوئری بالا نتیجه به شکل زیر خواهد بود:

        "_id" : {
                "age" : 30
        },
        "allHobbies" : [
                [
                        "Eating",
                        "Data Analytics"
                ]
        ]
}

        "_id" : {
                "age" : 29
        },
        "allHobbies" : [
                [
                        "Sports",
                        "Cooking"
                ],
                [
                        "Cooking",
                        "Skiing"
                ]
        ]
}

همانطور که مشخص است ما دو گروه داریم (گروه سنی 30 و 29 سال) که هر کدام فیلد allHobbies را دارد. نکته جالب اینجاست که allHobbies یک آرایه است که درون خود آرایه های مختلفی دارد. به نظر شما چرا چنین ساختاری به وجود آمده است؟ همانطور که گفتم push$ مخصوص کار با آرایه است بنابراین هر زمان که از آن استفاده کنید یک آرایه ساخته می شود. به همین دلیل است که allHobbies یک آرایه است. همچنین ما در کوئری خود گفته بودیم که فیلد hobbies را به allHobbies اضافه کن و می دانیم که hobbies خودش یک آرایه است بنابراین hobbies درون allHobbies می شود آرایه در آرایه!

حالا اگر بخواهیم به جای اینکه آرایه hobbies را درون allHobbies قرار بدهیم، مقادیر درونش را داخل allHobbies قرار بدهیم چطور؟ برای این کار نیاز به یک stage جدید داریم اما قبل از آن باید با اپراتور unwind$ (به معنی از هم باز کردن) آشنا شوید. به کوئری زیر نگاه کنید:

db.friends.aggregate([
    { $unwind: "$hobbies" }
]).pretty()

در این کوئری ساده گفته ایم که فیلد hobbies را unwind کن، یعنی از هم باز کن. با اجرای آن نیز نتیجه زیر را می گیریم:

        "_id" : ObjectId("5ec48cfe35361420f36550a7"),                      
        "name" : "Max",                                                    
        "hobbies" : "Sports",                                              
        "age" : 29,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 4,                                  
                        "score" : 57.9                                     
                },                                                         
                {                                                          
                        "difficulty" : 6,                                  
                        "score" : 62.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 3,                                  
                        "score" : 88.5                                     
                }                                                          
        ]                                                                  
}                                                                          
                                                                           
        "_id" : ObjectId("5ec48cfe35361420f36550a7"),                      
        "name" : "Max",                                                    
        "hobbies" : "Cooking",                                             
        "age" : 29,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 4,                                  
                        "score" : 57.9                                     
                },                                                         
                {                                                          
                        "difficulty" : 6,                                  
                        "score" : 62.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 3,                                  
                        "score" : 88.5                                     
                }                                                          
        ]                                                                  
}                                                                          
                                                                           
        "_id" : ObjectId("5ec48cfe35361420f36550a8"),                      
        "name" : "Manu",                                                   
        "hobbies" : "Eating",                                              
        "age" : 30,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 7,                                  
                        "score" : 52.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 2,                                  
                        "score" : 74.3                                     
                },                                                         
                {                                                          
                        "difficulty" : 5,                                  
                        "score" : 53.1                                     
                }                                                          
        ]                                                                  
}                                                                          
                                                                           
        "_id" : ObjectId("5ec48cfe35361420f36550a8"),                      
        "name" : "Manu",                                                   
        "hobbies" : "Data Analytics",                                      
        "age" : 30,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 7,                                  
                        "score" : 52.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 2,                                  
                        "score" : 74.3                                     
                },                                                         
                {                                                          
                        "difficulty" : 5,                                  
                        "score" : 53.1                                     
                }                                                          
        ]                                                                  
}                                                                          
                                                                           
        "_id" : ObjectId("5ec48cfe35361420f36550a9"),                      
        "name" : "Maria",                                                  
        "hobbies" : "Cooking",                                             
        "age" : 29,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 3,                                  
                        "score" : 75.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 8,                                  
                        "score" : 44.2                                     
                },                                                         
                {                                                          
                        "difficulty" : 6,                                  
                        "score" : 61.5                                     
                }                                                          
        ]                                                                  
}                                                                          
                                                                           
        "_id" : ObjectId("5ec48cfe35361420f36550a9"),                      
        "name" : "Maria",                                                  
        "hobbies" : "Skiing",                                              
        "age" : 29,                                                        
        "examScores" : [                                                   
                {                                                          
                        "difficulty" : 3,                                  
                        "score" : 75.1                                     
                },                                                         
                {                                                          
                        "difficulty" : 8,                                  
                        "score" : 44.2                                     
                },                                                         
                {                                                          
                        "difficulty" : 6,                                  
                        "score" : 61.5                                     
                }                                                          
        ]                                                                  
}                                                                         

آیا متوجه کار unwind شدید؟ unwind تک تک اعضای آرایه hobbies را از درون این آرایه خارج کرده است و از آنجایی که ما دستور حذف چیزی را نداده ایم، مجبور به تکرار سند ها شده است تا هر فرد یا سند با تک تک hobbies خود ذکر شود. با این تفاسیر حالا می توانیم کوئری قبلی خود را تکمیل کنیم تا allHobbies مقادیر hobbies را بگیرد بدون اینکه آرایه های تو در تو داشته باشیم:

db.friends.aggregate([
    { $unwind: "$hobbies" },
    { $group: { _id: { age: "$age" }, allHobbies: { $push: "$hobbies" } } }
]).pretty()

یعنی در ابتدا تک تک مقادیر درون آرایه hobbies را از آن خارج کرده و سپس تمام سند ها را group می کنیم تا ببینیم در هر رده سنی، چه سرگرمی (hobby) هایی وجود دارد. نتیجه اجرای کوئری بالا به شکل زیر است:

"_id" : {                
    "age" : 29       
},                       
"allHobbies" : [         
    "Sports",        
    "Cooking",       
    "Cooking",       
    "Skiing"         
]                        
}                                
                     
"_id" : {                
    "age" : 30       
},                       
"allHobbies" : [         
    "Eating",        
    "Data Analytics" 
]                        
}

دقیقا همان چیزی که می خواستیم! تنها مشکل اینجاست که آرایه allHobbies دارای مقادیر تکراری است. هدف ما این بود که ببینیم در هر رده سنی چه سرگرمی هایی وجود دارد بنابراین وجود مقادیر تکراری هیچ کمکی به ما نمی کند. برای حل این مشکل نیز اپراتور دیگری به نام addToSet$ وجود دارد که جایگزین push$ است:

db.friends.aggregate([
    { $unwind: "$hobbies" },
    { $group: { _id: { age: "$age" }, allHobbies: { $addToSet: "$hobbies" } } }
]).pretty()

تفاوت addToSet و push در این است که push با مقادیر تکراری مشکلی ندارد و آن ها را نیز اضافه می کند اما addToSet مقادیر تکراری را نادیده می گیرد. نتیجه اجرای کوئری بالا به شکل زیر است:

"_id" : {
    "age" : 29
},
"allHobbies" : [
    "Cooking",
    "Skiing",
    "Sports"
]

"_id" : {
    "age" : 30
},
"allHobbies" : [
    "Eating",
    "Data Analytics"
]

حالا مقادیر تکراری حذف شده اند و به مقصودمان رسیده ایم!

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

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

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