Complete a MongoDB query to group and sum fields - mongodb

I'm doing an app which simulates a hotel check in, where the user registers a lot of client's data. Among this data, I have this:
{
"client_name" : "John Doe",
"client_id" : "xxxxxxxx"
...
"company" : "CocaCola",
"hotel_hq" : "New York",
"lodging_days" : 5,
...
}
One of the functions that should have the app is to show the list of hotel headquarters that the company attends, and the number of days that the company use in every HQ.
So, I need a query that returns me something like this:
{"company" : "CocaCola", "hotel_hq" : ["New York", "California", "Orlando"], "lodging_days" : [5, 10, 8]}
I make, with blood sweat and tears, this query:
db.clients.aggregate(
{
$group: {
_id: '$company',
hotel_hq : {$push:'$hotel_hq'},
lodging_days : {$push:'$lodging_days' }
}
})
And it's the closest I've been, because that returns me this:
{"_id" : "CocaCola", "hotel_hq": ["New York", "New York", "California", "Orlando", "Orlando", "Orlando", "Orlando"], "lodging_days" : [5, 8, 10, 8, 9, 2, 3]}
The hotel HQ are sometimes repeated because differents clients of the same company stayed in the same HQ, or the same client does it more than one time.
Obviously, I can change $push to $addToSet, but the result going to be:
{"_id" : "CocaCola", "hotel_hq": ["New York", "California", "Orlando"], "lodging_days" : [5, 8, 10, 9, 2, 3]}
Which is cool for the hotel_hq, but no for the lodging_days, I try with a $sum, but I don't know how to say Mongo to sum only the 'lodging_days' of a repeated 'hotel_hq'.

hope this works..
db.test3.aggregate(
[
{
$group:{_id:{company:"$company",hotel_hq:"$hotel_hq"},
"Days":{$sum:"$lodging_days"}}
},
{
$group:{_id:"$company",hotel_hq:{$push:"$_id.hotel_hq"},
Days:{$push:"$Days"}}
}
]
)

Related

Mongodb multiple subdocument

I need a collection with structure like this:
{
"_id" : ObjectId("5ffc3e2df14de59d7347564d"),
"name" : "MyName",
"pays" : "de",
"actif" : 1,
"details" : {
"pt" : {
"title" : "MongoTime PT",
"availability_message" : "In stock",
"price" : 23,
"stock" : 1,
"delivery_location" : "Portugal",
"price_shipping" : 0,
"updated_date" : ISODate("2022-03-01T20:07:20.119Z"),
"priority" : false,
"missing" : 1,
},
"fr" : {
"title" : "MongoTime FR",
"availability_message" : "En stock",
"price" : 33,
"stock" : 1,
"delivery_location" : "France",
"price_shipping" : 0,
"updated_date" : ISODate("2022-03-01T20:07:20.119Z"),
"priority" : false,
"missing" : 1,
}
}
}
How can i create an index for each subdocument in 'details' ?
Or maybe it's better to do an array ?
Doing a query like this is currently very long (1 hour). How can I do ?
query = {"details.pt.missing": {"$in": [0, 1, 2, 3]}, "pays": 'de'}
db.find(query, {"_id": false, "name": true}, sort=[("details.pt.updated_date", 1)], limit=300)
An array type would be better, as there are advantages.
(1) You can include a new field which has values like pt, fr, xy, ab, etc. For example:
details: [
{ type: "pt", title : "MongoTime PT", missing: 1, other_fields: ... },
{ type: "fr", title : "MongoTime FR", missing: 1, other_fields: ... },
{ type: "xy", title : "MongoTime XY", missing: 2, other_fields: ... },
// ...
]
Note the introduction of the new field type (this can be any name representing the field data).
(2) You can also index on the array sub-document fields, which can improve query performance. Array field indexes are referred as Multikey Indexes.
The index can be on a field used in a query filter. For example, "details.missing". This key can also be part of a Compound Index. This can help a query filter like below:
{ pays: "de", "details.type": "pt", "details.missing": { $in: [ 0, 1, 2, 3 ] } }
NOTE: You can verify the usage of an index in a query by generating a Query Plan, applying the explain method on the find.
(3) Also, see Embedded Document Pattern as explained in the Model One-to-Many Relationships with Embedded Documents.

Add ranking system in returned query document?

My User collection contains the following documents:
{"_id" : 1, point_count: "12"},
{"_id" : 2, point_count: "19"},
{"_id" : 3, point_count: "13"},
{"_id" : 4, point_count: "1233"},
{"_id" : 5, point_count: "1"},
... and about 1000 more
My question is: Is it possible to show ranking of each user based on point_count field when I search by id? let say I use this query to find user with id = 4
db.User.find({_id: 4})
let assume highest point_count in my entire collection is 1233, I'm hoping to get this result
{"_id" : 4, point_count: "1233", rank: 1}
rank 1 because 1233 is the highest point_count
or when I search user with id = 5, my expect result should be
{"_id" : 5, point_count: "1", rank: 1000..something}
1000..something because it is the lowest rank and there are around 1000+ id in the document.
Thank you all so much for helping me here!

Get a count of documents in an aggregation under specific requirement

In my collection, each document represents a user-generated quiz, and includes an array field for tags, i.e. History, Science, Math, etc. I am trying to get a count of documents associated with each tag.
The below aggregation results in a unique tag list that look like this: {tags:["History", "Science", "Math"]}
db.quizzes.aggregate([
{$unwind: "$tags"},
{$group: {_id:null, tgs: {$addToSet: "$tags"}}},
{$project: {_id:0, tags: "$tgs"}},
])
However, can the above aggregation also get a count of the number of documents that contains each tag? For example if there were 3 History quizzes, 2 Science quizzes, and 5 Math quizzes, the result would look like this: {tags:[{tag: "History", count: 3}, {tag: "Science", count: 2}, {tag: "Math", count:5}]}
Thanks in advance for any tips.
Edited to include collection documents:
{
"_id" : ObjectId("57d8ccd573099cb013b462b5"),
"title" : "Presidential Trivia",
"quiz" : "[{\"question\":\"How many presidents were members of the Whig party?\",\"choices\":[\"Two\",\"Three\",\"Four\"],\"correct\":\"2\"},{\"question\":\"Who was the first president to be impeached?\",\"choices\":[\"Warren Harding\",\"Andrew Johnson\",\"Andrew Jackson\"],\"correct\":\"1\"},{\"question\":\"How many presidents died during their presidency?\",\"choices\":[\"Four\",\"Six\",\"Eight\"],\"correct\":\"2\"},{\"question\":\"How many presidents had no party affiliation?\",\"choices\":[\"One\",\"Two\",\"Three\"],\"correct\":\"0\"},{\"question\":\"Who was the only president to serve two non-consecutive terms, making him both the 22nd and 24th president?\",\"choices\":[\"John Quincy Adams\",\"Grover Cleveland\",\"Theodore Roosevelt\"],\"correct\":\"1\"}]",
"correctArray" : "[\"2\",\"1\",\"2\",\"0\",\"1\"]",
"author" : "jake2",
"createTime" : ISODate("2016-09-14T04:06:45.118Z"),
"likes" : 0,
"avgScore" : 0,
"plays" : 3,
"private" : "0",
"tags" : [
"US Presidents",
"American History",
"History"
]
}
{
"_id" : ObjectId("57d8d08973099cb013b462b6"),
"title" : "Finance Quiz",
"quiz" : "[{\"question\":\"Which of these involves the analysis of of a business's financial statements, often used in stock valuation?\",\"choices\":[\"Fundamental Analysis\",\"Technical Analysis\",\"P/E ratio\"],\"correct\":\"0\"},{\"question\":\"What was the name of the bond purchasing program started by the U.S. Federal Reserve in response to the 2008 financial crisis?\",\"choices\":[\"Stimulus Package\",\"Quantitative Easing\",\"Mercantilism\"],\"correct\":\"1\"},{\"question\":\"Which term describes a debt security issued by a government, company, or other entity?\",\"choices\":[\"Bond\",\"Stock\",\"Mutual fund\"],\"correct\":\"0\"},{\"question\":\"Which of these companies has the largest market capitalization (as of October 2015)?\",\"choices\":[\"Ford Motors\",\"Apple\",\"Bank of America\"],\"correct\":\"1\"},{\"question\":\"Which of these is a measure of the size of an economy?\",\"choices\":[\"Purchasing Power Index\",\"Unemployment Rate\",\"Gross Domestic Product\"],\"correct\":\"2\"}]",
"correctArray" : "[\"0\",\"1\",\"0\",\"1\",\"2\"]",
"author" : "jake2",
"createTime" : ISODate("2016-09-14T04:22:33.756Z"),
"tags" : [
"Finance"
],
"likes" : 0,
"avgScore" : 0,
"plays" : 10,
"private" : "0"
}
{
"_id" : ObjectId("57d8d24073099cb013b462b8"),
"title" : "Astronomy Pop Quiz",
"quiz" : "[{\"question\":\"Which of the following are currently (as of November 2015) used by scientists as observational evidence of the existence of dark matter?\",\"choices\":[\"Gravitational Lensing\",\"Specimens of dark matter collected by NASA\",\"Anomalies in planetary orbits\"],\"correct\":\"0\"},{\"question\":\"Which of these emits the most energy?\",\"choices\":[\"Stars\",\"Quasars\",\"Black Holes\"],\"correct\":\"1\"},{\"question\":\"What is it called when light or electromagnetic radiation from an object is increased in wavelength?\",\"choices\":[\"The Jupiter Effect\",\"Redshift\",\"The Observer's Differential\"],\"correct\":\"1\"},{\"question\":\"Who was the first human in space?\",\"choices\":[\"Yuri Gagarin\",\"Alan Shepard\",\"John Glenn\"],\"correct\":\"0\"},{\"question\":\"Which of these is the most dense?\",\"choices\":[\"The Sun\",\"A neutron star\",\"Earth\"],\"correct\":\"1\"}]",
"correctArray" : "[\"0\",\"1\",\"1\",\"0\",\"1\"]",
"author" : "Bertram",
"createTime" : ISODate("2016-09-14T04:29:52.636Z"),
"tags" : [
"Astronomy"
],
"likes" : 1,
"avgScore" : 0,
"plays" : 5,
"private" : "0"
}
{
"_id" : ObjectId("57d8d3c173099cb013b462ba"),
"title" : "Film Trivia",
"quiz" : "[{\"question\":\"Who directed The Godfather trilogy?\",\"choices\":[\"John Huston\",\"Francis Ford Coppola\",\"Martin Scorsese\"],\"correct\":\"1\"},{\"question\":\"What year was the first Ocscar awarded?\",\"choices\":[\"1923\",\"1927\",\"1932\"],\"correct\":\"1\"},{\"question\":\"As of 2010, this and Schindler's List (1993) are the only films to win Best Picture, Director and Screenplay at the Golden Globes, BAFTAs and the Oscars.\",\"choices\":[\"Rain Man\",\"Slumdog Millionaire\",\"Titanic\"],\"correct\":\"1\"},{\"question\":\"In Casablanca, why can't Rick return to America?\",\"choices\":[\"He is indebted to the mob.\",\"He was deported.\",\"No reason is given.\"],\"correct\":\"2\"},{\"question\":\"What was the highest-grossing Western of all time?\",\"choices\":[\"Django Unchained\",\"True Grit\",\"Dances with Wolves\"],\"correct\":\"2\"}]",
"correctArray" : "[\"1\",\"1\",\"1\",\"2\",\"2\"]",
"author" : "Pappy2",
"createTime" : ISODate("2016-09-14T04:36:17.950Z"),
"tags" : [
"Movies"
],
"likes" : 1,
"avgScore" : 0,
"plays" : 8,
"private" : "0"
}
{
"_id" : ObjectId("57ea7f67a58303f01a585e55"),
"title" : "US History Concepts",
"quiz" : "[{\"question\":\"\",\"choices\":[\"\",\"\",\"\"]}]",
"correctArray" : "[]",
"author" : "martha",
"createTime" : ISODate("2016-09-27T14:17:11.627Z"),
"tags" : [
"US History",
"History"
],
"likes" : 0,
"avgScore" : 0,
"plays" : 1,
"private" : "0"
}
You can try the following aggregation pipeline.
db.quizzes.aggregate([
{"$unwind":"$tags"},
{"$group":{"_id":"$tags", count:{$sum:1}}},
{"$project":{"_id":0, "tags":{"tag":"$_id","count":"$count"}}},
{"$group":{"_id":null, "tags":{"$push":"$tags"}}},
{"$project":{"_id":0, tags:1}}
])

Update a document (quickly) - MongoDB

Consider this MongoDB document:
{
"_id" : "RMa.103",
"official_name" : "Real Madrid Club de Fùtbol",
"country" : "Spain",
"started_by" : {
"day" : 6,
"month" : 3,
"year" : 1902
},
"stadium" : {
"name" : "Santiago Bernabeu",
"capacity" : 85454
},
"palmarès" : {
"La Liga" : 32,
"Copa del Rey" : 19,
"Supercopa de Espana" : 9,
"UEFA Champions League" : 10,
"UEFA Europa League" : 2,
"UEFA Super Cup" : 2,
"FIFA Club World cup" : 4
},
"uniform" : "white"
}
I forgot to insert an important information of the team: the common name.
So, I updated the document:
[1] db.team.update({_id:"RMa.103"}, {$set:{common_name:"Real Madrid"}})
In this way, the new information is added at the end of the document, instead I want it after the official_name:
{
"_id" : "RMa.103",
"official_name" : "Real Madrid Club de Fùtbol",
"common_name" : "Real Madrid"
.......
.......
.......
}
Now, I know that updating the document with the following method, I have the common name of the team in the right location:
db.team.update(
{_id:"RMa.103"}, {$set:{ "_id" : "RMa.103",
"official_name": "Real Madrid Club de Fùtbol",
common_name:"Real Madrid", "country" : "Spain",
"started_by" : { "day" : 6, "month" : 3, "year" : 1902 },
"stadium" : { "name" : "Santiago Bernabeu", "capacity" : 85454 },
"palmarès" : { "La Liga" : 32, "Copa del Rey" : 19, "Supercopa de Espana" : 9,
"UEFA Champions League" : 10, "UEFA Europa League" : 2, "UEFA Super Cup" : 2,
"FIFA Club World cup" : 4 }, "uniform" : "white", "common_name" : "Real Madrid" }})
I have to update a lot of documents and this kind of operation is very difficult and boring from the shell of the prompt. Are there faster methods to do this update? For example is it possible to change the update method of the [1]?
From the JSON documentation :
An object is an unordered set of name/value pairs.
So there is absolutely no way to enforce any kind of key order in JSON therefore it's the same for MongoDB documents.
It's useless : any MongoDB driver will give you the corresponding value with a given key. The key order is pointless.
No, not really. You should not have to have it in a specific order. When querying from a application, it won't care what order your values are in. For example:
db.collection("team").find({offical_name:"Real Madrid Club de Fùtbol"},function(err,results)
{
/*Gets the first result (should only be one)
and prints the common name*/
console.log(results[0].common_name);
});
If you are wondering why they would not have a way to order it for human eyes, you must remember that databases are not designed to be interacted with directly. They are supposed to be in conjunction with an application that uses them and presents the data in a human friendly manner.
I hope I answered your question sufficiently, if not please comment below and I will explain more.

Updating array with push and slice

I have just started to play with MongoDB and have some questions about how I update my documents in the database. I insert two documents in my db with
db.userscores.insert({name: 'John Doe', email: 'john.doe#mail.com', levels : [{level: 1, hiscores: [90, 40, 25], achivements: ['capture the flag', 'it can only be one', 'apple collector', 'level complete']}, {level: 2, hiscores: [30, 25], achivements: ['level complete']}, {level: 3, hiscores: [], achivements: []}]});
db.userscores.insert({name: 'Jane Doe', email: 'jane.doe#mail.com', levels : [{level: 1, hiscores: [150, 90], achivements: ['Master of the universe', 'capture the flag', 'it can only be one', 'apple collector', 'level complete']}]});
I check if my inserting worked with the find() command and it looks ok.
db.userscores.find().pretty();
{
"_id" : ObjectId("5358b47ab826096525d0ec98"),
"name" : "John Doe",
"email" : "john.doe#mail.com",
"levels" : [
{
"level" : 1,
"hiscores" : [
90,
40,
25
],
"achivements" : [
"capture the flag",
"it can only be one",
"apple collector",
"level complete"
]
},
{
"level" : 2,
"hiscores" : [
30,
25
],
"achivements" : [
"level complete"
]
},
{
"level" : 3,
"hiscores" : [ ],
"achivements" : [ ]
}
]
}
{
"_id" : ObjectId("5358b47ab826096525d0ec99"),
"name" : "Jane Doe",
"email" : "jane.doe#mail.com",
"levels" : [
{
"level" : 1,
"hiscores" : [
150,
90
],
"achivements" : [
"Master of the universe",
"capture the flag",
"it can only be one",
"apple collector",
"level complete"
]
}
]
}
How can I add/update data to my userscores? Lets say I want to add a hiscore to user John Doe on level 1. How do I insert the hiscore 75 and still have the hiscore array sorted? Can I limit the number of hiscores so the array only contains 3 elements? I have tried with
db.userscores.aggregate(
// Initial document match (uses name, if a suitable one is available)
{ $match: {
name : 'John Doe'
}},
// Expand the levels array into a stream of documents
{ $unwind: '$levels' },
// Filter to 'level 1' scores
{ $match: {
'levels.level': 1
}},
// Add score 75 with cap/limit of 3 elements
{ $push: {
'levels.hiscore':{$each [75], $slice:-3}
}}
);
but it wont work, the error I get is "SyntaxError: Unexpected token [".
And also, how do I get the 10 highest score from all users on level 1 for example? Is my document scheme ok or can I use a better scheme for storing users hiscores and achivements on diffrent levels for my game? Is there any downsides on quering or performance using they scheme above?
You can add the score with this statement:
db.userscores.update(
{ "name": "John Doe", "levels.level": 1 },
{ "$push": { "levels.$.hiscores": 75 } } )
This will not sort the array as this is only supported if your array elements are documents.
In MongoDB 2.6 you can use sorting also for non-document arrays:
db.userscores.update(
{ "name": "John Doe", "levels.level": 1 },
{ "$push": { "levels.$.hiscores": { $each: [ 75 ], $sort: -1, $slice: 3 } } } )