במאמר הקודם דיברנו על איך MongoDB עובד עם Node.js. במאמר הזה נדבר על Aggregation עם MongoDB. מה זה Aggregation? באופן עקרוני זה פשוט חישובים על התוצאות.
יש כמה דרכים לעשות Aggregation ב-MongoDb. הראשונה היא pipeline. באופן עקרוני אנחנו מקבלים תוצאות, ומעבירים אותן דרך צינור לפונקציה שעושה עליהן חישוב. את התוצאות של הפונקציה אפשר להעביר דרך צינור נוסף וכך הלאה
מבולבלים? אל תהיו. על מנת לתרגל אנו נבנה מסד נתונים פשוט ביותר. לכל document יש מספק ששמו X, סוג שהוא A-E ושם. המסד נבנה כך:
use orders;
function generateName() {
var myArray = ['Moshe','Ran','Haim','Michal','Tamar']
var rand = myArray[Math.floor(Math.random() * myArray.length)];
return rand;
}
function generateType() {
var myArray = ['A','B','C','D','E']
var rand = myArray[Math.floor(Math.random() * myArray.length)];
return rand;
}
for (var i = 1; i < = 100; i++) {
db.testData.insert( { x : i, name : generateName(), type : generateType() } )
}
והנה דוגמה ל-find פשוט שאני מריץ:
> db.testData.find({})
{ "_id" : ObjectId("543f91343d35c56ad957aac6"), "x" : 1, "name" : "Tamar", "type" : "E" }
{ "_id" : ObjectId("543f91353d35c56ad957aac7"), "x" : 2, "name" : "Moshe", "type" : "E" }
{ "_id" : ObjectId("543f91353d35c56ad957aac8"), "x" : 3, "name" : "Moshe", "type" : "A" }
ה-Pipe הראשון שבו נשתמש הוא מסוג match וכשמו כן הוא – דרכו אני מפלטר כאלו שמתאימים לתנאי מסוים. למשל כל אחו שהם מסוג A. איך אני עושה את זה? ככה:
db.testData.aggregate({ $match: {"type": "B"} });
מתודת aggregate שדרכה אני מעביר את אופרטור match. אני יכול להעביר עוד אופרטורים. למשל:
db.testData.aggregate({ $match: {"type": "B", "name":"Moshe"} });
כך אני מקבל אלו אלו שהם גם B וגם Moshe. עד כאן לא משהו שלא ידענו. ה-match הוא סוג אחד של aggregation שאפשר להעביר לו לא מעט פרמטרים, בדיוק כפי שאנו יכולים להעביר ל-find. למשל, אנו יכולים לבקש ממנו גם שה-Type יהיה B וגם A:
db.testData.aggregate({ $match: {"type": {$in: [ 'A', 'B' ] }, "name":"Moshe"} });
איך אני יודע את זה? כל סוגי ה-pipes מפורטים בדוקומנטציה. בחלק של match נאמר במפורש שאפשר להעביר את הפרמטרים של document query שכולם נמצאים גם הם בדוקומנטציה. שם כתוב איך לעשות IN (שזה גם וגם) או NOT EQUAL וכך הלאה. בשאילתה הזו אני משתמש למשל ב-NOT IN – כל מי שקוראים לו 'משה' אבל הוא לא מסוג A או D למשל.
db.testData.aggregate({ $match: {"type": {$nin: [ 'D','A' ] }, "name":"Moshe"} });
אין טעם לעבור על כל האופרטורים, כי הם רבים מספור. אבל טוב יהיה אם תציצו בדוקומנטציה.
את מה שאספנו עם האגרגציה אנו יכולים להעביר ל-pipe נוסף. איזה? מה שמתחשק! למשל group שבמבצע קיבוץ של הנתונים.
db.testData.aggregate([{
$match: {
"type": "B"
}
}, {
$group: {
_id: '$name',
'Count': {
$sum: 1
}
}
}]);
וואו! מה הולך פה? רגע! זה פשוט! קודם כל יש לנו מערך ב-aggregate שמכיל את כל ה-pipes. האיבר הראשון הוא match. אותו אנו מכירים כבר. האיבר השני הוא group. הוא מעט יותר מסובך. ראשית אני מכניס את ה-_id שאני רוצה. במקרה הזה זה השם. אחרי כן אני מכניס את המידע שאני רוצה. למשל Count (שזה השם שבחרתי) והוא כולל את הסכימה באמצעות אופרטור sum. מה התוצאה?
{ "_id" : "Haim", "Count" : 1 }
{ "_id" : "Michal", "Count" : 4 }
{ "_id" : "Moshe", "Count" : 6 }
{ "_id" : "Tamar", "Count" : 8 }
{ "_id" : "Ran", "Count" : 2 }
אני יכול להציג עוד ועוד טורים. למשל, הממוצע של ה-x
db.testData.aggregate([{
$match: {
"type": "B"
}
}, {
$group: {
_id: '$name',
'Count': {
$sum: 1
},
'X Average': {
$avg: '$x'
}
}
}]);
רואים כמה זה פשוט? הכל נמצא בדוקומנטציה.
אפשר להמשיך ולדחוף את התוצאות לתוך pipe נוספים. למשל מיון התוצאות. או אפילו לכתוב את התוצאות לתוך collection!
db.testData.aggregate([{
$match: {
"type": "B"
}
}, {
$group: {
_id: '$name',
'Count': {
$sum: 1
},
'X Average': {
$avg: '$x'
}
}
}, {
$out: "DaResults"
}]);
הקוד שלעיל ידחוף את כל התוצאה לתוך collection שנקרא DaResults.
כל pipe הוא בעצם איבר במערך ואת כל ה-pipes אפשר למצוא בדוקומנטציה. כמה קל ונעים, לא?
ישנה עוד דרך לבצע aggregation שנקראת Map Reduce. אני לא עובר עליה במדריך הזה כי אני פשוט לא מכיר אותה מספיק טוב.
בשביל שינוי קטן של התוצאות אפשר להשתמש ב-Single Purpose Aggregation Operations שמאוד מאוד דומות ל-Pipe Aggregation רק שהן יותר פשוטות.
במאמר הבא אנו נלמד על Replications