Query for Partial Object in Array - MongoDB - mongodb

I have a simple structured document like this:
"people" : [
{
"id" : "6241863",
"amount" : 5
}
],
People can contain more than one element. I've managed to get this to work:
db.village.findOne({"people": {"$in": [{"id": "6241863", "amount": 5}]}})
But I want to ignore amount and search for any document containing people with id 6241863 and any amount.

According to the advanced query documentation, you can mix array value queries with dot notation for reaching into objects. Here's an example using your schema:
$ mongo
MongoDB shell version: 2.1.0
connecting to: test
> db.users.save({_id: 1, friends: [{id: 2, name: 'bob'}]})
> db.users.find({friends: {id: 2, name: 'bob'}})
{ "_id" : 1, "friends" : [ { "id" : 2, "name" : "bob" } ] }
> db.users.find({'friends.id': 2})
{ "_id" : 1, "friends" : [ { "id" : 2, "name" : "bob" } ] }
> db.users.find({'friends.name': 'bob'})
{ "_id" : 1, "friends" : [ { "id" : 2, "name" : "bob" } ] }
> db.users.find({'friends.name': 'ted'})
>

Try with that
db.village.findOne({"people" : { "$in" : [{ "id" : "6241863" , "amount" : { "$ne" : null } }]}})

Related

Query mongo collection as an object (not array)

I would like to aggregate my collection (with mongoose) to get all documents in the following form:
{
_id1: doc1,
_id2: doc2,
....
}
instead of the usual array ([doc1, doc2, ...]). it allows me to easily access docs by their id.
Thanks!
//actual code output form mongo shell
//prepare data
db.mycoll2.insertMany([
{_id:1,"docarr":["doc1","doc2","doc3"]},
{_id:2,"docarr":["doc4","doc5","doc6"]}
]
);
> db.mycoll2.find();
{ "_id" : 1, "docarr" : [ "doc1", "doc2", "doc3" ] }
{ "_id" : 2, "docarr" : [ "doc4", "doc5", "doc6" ] }
//use aggregate to represent array documents linewise
> db.mycoll2.aggregate([
... {$unwind: "$docarr"}
... ]);
//ouput document from array, you can access each document via _id and docarr field label name
{ "_id" : 1, "docarr" : "doc1" }
{ "_id" : 1, "docarr" : "doc2" }
{ "_id" : 1, "docarr" : "doc3" }
{ "_id" : 2, "docarr" : "doc4" }
{ "_id" : 2, "docarr" : "doc5" }
{ "_id" : 2, "docarr" : "doc6" }
>

showing derived mongo documents based on values of an array in a single mongo db record

I have a mongo db record in a table car with structure like given below
name: Ferrari
color: [ red, blue, green ]
How would I run a find query in mongo db that would display one record each for the values present in color ?
Sample Output:
name: Ferrari
color: red
name: Ferrari
color: blue
name: Ferrari
color: green
It better if you take a look about $unwind, it's actually what you looking for. Here is example how to use it.
Let's say you have collection that include this:
[
{ "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
{ "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
{ "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
{ "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
{ "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
]
you can get the result for same _id but each size from sizes , so you can do it by running one of the two options:
db.inventory2.aggregate( [ { $unwind: "$sizes" } ] )
db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )
then you will get:
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
But what important to you is to get custom result but other object.. You can use $match to complete you query, using aggregate you can get the specific result. Lets say you want to find the item "ABC" and split all sizes for that specific item:
db.inventory2.aggregate( [
//Stage one - find items named "ABC"
{$match:{"item":"ABC"}},
//Stage two - split result of sizes array
{ $unwind: "$sizes" }
] )
The result for that query is:
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
By using aggregate you can query by steps and make changes in your result, but since you using some extra filters, the result will take more time from normal query.

Project only some fields of array items in sub document

How can I project only particular fields of items in array in sub document?
Consider the following (simplified) example:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"name" : "Altima",
"body" : {
"type" : 2,
"maxprice" : 31800.00,
"minprice" : 21500.00
}
},
{
"name" : "Maxima",
"body" : {
"type" : 2,
"maxprice" : 39200.00,
"minprice" : 28800.00
}
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"name" : "Accord",
"body" : {
"type" : 2,
"maxprice" : 34100.00,
"minprice" : 20400.00
}
},
{
"name" : "Civic",
"body" : {
"type" : 3,
"maxprice" : 27900.00,
"minprice" : 19800.00
}
}
]
}
After aggregation, I'd like to get the following output:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"type" : 2,
"minprice" : 21500.00
},
{
"type" : 2,
"minprice" : 28800.00
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"type" : 2,
"minprice" : 20400.00
},
{
"type" : 3,
"minprice" : 19800.00
}
]
}
So it basically gets all documents, all fields of documents, all items in models array, BUT only some fields of the array items in models. Please help.
You need to $project the "models" field using the $map operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"models": {
"$map": {
"input": "$models",
"as": "m",
"in": {
"type": "$$m.body.type",
"minprice": "$$m.body.minprice"
}
}
}
}}
])
$unwind is your friend
First you can basically filter the (non nested) fields you want.
var projection = {$project:{name:'$name', models:'$models'}};
db.dum.aggregate(projection)
Foreach of your models, you issue a document
var unwindModels = {$unwind:{'$models'}}
db.dum.aggregate(projection, unwindModels)
The idea is that every document issued from your models field will be regrouped later on via the _id field.
Foreach document, you only keep the (sub)fields you want
var keepSubFields = {$project:{name:'$name', type:'$models.body.type', minprice:'$models.body.minprice'}}
db.dum.aggregate(projection, unwindModels, keepSubFields)
Then you reaggregate your models as an array (thanks to the _id of each record which tracks the original record)
var aggregateModels = {$group:{_id:'$_id', name:{$last:'$name'}, models:{$push:{type:'$type', minprice:'$minprice'}}}}
db.dum.aggregate(projection, unwindModels, keepSubFields, aggregateModels)
note1: Here we can use $last because our primary key is not _id but <_id, name>. ($first would be good too)
note2: we refer type by $type, because when you iterate the collection on the aggregateModels stage, your record is of the form
<_id, name, type, minprice>

MongoDB MapReduce - How to populate an array in reduce function?

I have a MovieRatings database with columns userId, movieId, movie-categoryId, reviewId, movieRating and reviewDate.
In my mapper I want to extract userId -> (movieId, movieRating)
And then in the reducer I want to group all movieId, movieRating pair by user.
Here is my attempt:
Map function:
var map = function() {
var values={movieId : this.movieId, movieRating : this.movieRating};
emit(this.userId, values);}
Reduce function:
var reduce = function(key,values) {
var ratings = [];
values.forEach(function(V){
var temp = {movieId : V.movieId, movieRating : V.movieRating};
Array.prototype.push.apply(ratings, temp);
});
return {userId : key, ratings : ratings };
}
Run MapReduce:
db.ratings.mapReduce(map, reduce, { out: "map_reduce_step1" })
Output: db.map_reduce_step1.find()
{ "_id" : 1, "value" : { "userId" : 1, "ratings" : [ ] } }
{ "_id" : 2, "value" : { "userId" : 2, "ratings" : [ ] } }
{ "_id" : 3, "value" : { "userId" : 3, "ratings" : [ ] } }
{ "_id" : 4, "value" : { "userId" : 4, "ratings" : [ ] } }
{ "_id" : 5, "value" : { "userId" : 5, "ratings" : [ ] } }
{ "_id" : 6, "value" : { "userId" : 6, "ratings" : [ ] } }
{ "_id" : 7, "value" : { "userId" : 7, "ratings" : [ ] } }
{ "_id" : 8, "value" : { "userId" : 8, "ratings" : [ ] } }
{ "_id" : 9, "value" : { "userId" : 9, "ratings" : [ ] } }
{ "_id" : 10, "value" : { "userId" : 10, "ratings" : [ ] } }
{ "_id" : 11, "value" : { "userId" : 11, "ratings" : [ ] } }
{ "_id" : 12, "value" : { "userId" : 12, "ratings" : [ ] } }
{ "_id" : 13, "value" : { "userId" : 13, "ratings" : [ ] } }
{ "_id" : 14, "value" : { "userId" : 14, "ratings" : [ ] } }
{ "_id" : 15, "value" : { "movieId" : 1, "movieRating" : 3 } }
{ "_id" : 16, "value" : { "userId" : 16, "ratings" : [ ] } }
I am not getting the expected output. In fact, this output makes no sense to me!
Here is the python equivalent of what I am trying to do in the reducer (just in case the purpose of reducer wasn't clear above) :
def reducer_ratings_by_user(self, user_id, itemRatings):
#Group (item, rating) pairs by userID
ratings = []
for movieID, rating in itemRatings:
ratings.append((movieID, rating))
yield user_id, ratings
Edit 1 #chridam
Here is an outline of what I really want to do here :
Movies.csv file looks like :
userId,movieId,movie-categoryId,reviewId,movieRating,reviewDate
1,1,1,1,5,7/12/2000
2,1,1,2,5,7/12/2000
3,1,1,3,5,7/12/2000
4,1,1,4,4,7/12/2000
5,1,1,5,4,7/12/2000
6,1,1,6,5,7/15/2000
1,2,1,7,4,7/25/2000
8,1,1,8,4,7/28/2000
9,1,1,9,3,8/3/2000
...
...
I import this into mongoDB :
mongoimport --db SomeName --collection ratings --type csv --headerline --file Movies.csv
Then I am trying to apply the map-reduce function as define above. After that I will export it back to a csv by doing somethig like :
mongoexport --db SomeName --collection map_reduce_step1 --csv --out movie_ratings_out.csv --fields ...
This movie_ratings_out.csv file should be like :
userId, movieId1, rating1, movieId2, rating2 ,...
1,1,5,2,4
...
...
So each row contains all the (movie,rating) pair for every user.
Edit 2
Sample :
db.ratings.find().pretty()
{
"_id" : ObjectId("57f4a0dd9cb74fc4d344a40f"),
"userId" : 4,
"movieId" : 1,
"movie-categoryId" : 1,
"reviewId" : 4,
"movieRating" : 4,
"reviewDate" : "7/12/2000"
}
{
"_id" : ObjectId("57f4a0dd9cb74fc4d344a410"),
"userId" : 5,
"movieId" : 1,
"movie-categoryId" : 1,
"reviewId" : 5,
"movieRating" : 4,
"reviewDate" : "7/12/2000"
}
{
"_id" : ObjectId("57f4a0dd9cb74fc4d344a411"),
"userId" : 4,
"movieId" : 2,
"movie-categoryId" : 1,
"reviewId" : 6,
"movieRating" : 5,
"reviewDate" : "7/15/2000"
}
{
"_id" : ObjectId("57f4a0dd9cb74fc4d344a412"),
"userId" : 4,
"movieId" : 3,
"movie-categoryId" : 1,
"reviewId" : 2,
"movieRating" : 5,
"reviewDate" : "7/12/2000"
}
...
Then after MapReduce expected output json is :
{
"_id" : ....,
"userId" : 4,
"movieList" : [ {
"movieId" : 2
"movieRating" : 5
},
{
"movieId" : 1
"movieRating" : 4
}
...
]
}
{
"_id" : ....,
"userId" : 5,
"movieList" : ...
}
...
You just need to run an aggregation pipeline which consists of a $group stage that summarize documents. This groups input documents by a specified identifier expression and applies the accumulator expression(s). The $group pipeline operator is similar to the SQL's GROUP BY clause. In SQL, you can't use GROUP BY unless you use any of the aggregation functions. The same way, you have to use an aggregation function in MongoDB as well. You can read more about the aggregation functions here.
The accumulator operator you would need to create the movieList array is $push.
Another pipeline which follows after the $group stage is the $project operator which is used to select or reshape each document in the stream, include, exclude or rename fields, inject computed fields, create sub-document fields, using mathematical expressions, dates, strings and/or logical (comparison, boolean, control) expressions - similar to what you would do with the SQL SELECT clause.
The last step is the $out pipeline which writes the resulting documents of the aggregation pipeline to a collection. It must be the last stage in the pipeline.
So as a result, you can run the following aggregate operation:
db.ratings.aggregate([
{
"$group": {
"_id": "$userId",
"movieList": {
"$push": {
"movieId": "$movieId",
"movieRating": "$movieRating",
}
}
}
},
{
"$project": {
"_id": 0, "userId": "$_id", "movieList": 1
}
},
{ "$out": "movie_ratings_out" }
])
Using the sample 5 documents above, the sample output if you query db.getCollection('movie_ratings_out').find({}) would yield:
/* 1 */
{
"_id" : ObjectId("57f52636b9c3ea346ab1d399"),
"movieList" : [
{
"movieId" : 1.0,
"movieRating" : 4.0
}
],
"userId" : 5.0
}
/* 2 */
{
"_id" : ObjectId("57f52636b9c3ea346ab1d39a"),
"movieList" : [
{
"movieId" : 1.0,
"movieRating" : 4.0
},
{
"movieId" : 2.0,
"movieRating" : 5.0
},
{
"movieId" : 3.0,
"movieRating" : 5.0
}
],
"userId" : 4.0
}

Project only a certain field (which is an array) in a find() operation and in the same time only get the first 3 elements from that array

How can I project only a certain field (which is an array) in a find() operation and in the same time only get the first 3 elements from that array?
something like the following :
db.test.find({}, {products: 1, products: {$slice :3}})
"products: 1" to return only the products in the results
"products: {$slice :3}" to get the first 3 products from the array
Apparently i can either get all the fields of the document with the array sliced, or get only the projects but with all the elements of the array. I cant do both in the same time.
mongodb version: 3.2
If I catch you correctly, here is my test, please correct me if anything I missing.
> db.test.find({})
{ "_id" : ObjectId("56c70b588543cbc57664541d"),
"attr1": "val1",
"attr2": "val2",
"products" : [
{ "id" : 1, "name" : "apple" },
{ "id" : 2, "name" : "banana" },
{ "id" : 3, "name" : "pear" },
{ "id" : 4, "name" : "uu" } ] }
> db.test.find({}, {products: 1, products: {$slice: 2}, attr1: 0, attr2: 0})
{ "_id" : ObjectId("56c70b588543cbc57664541d"),
"products" : [ { "id" : 1, "name" : "apple" },
{ "id" : 2, "name" : "banana" } ] }
It seems db.test.find({}, {products: 1, products: {$slice :3}, attr1: 0, attr2: 0}) could work well as you mentioned.
Another way could be done through $slice in aggregation.
> db.te.aggregate([{$project: {products: {$slice: ['$products', 0, 2]}}}])
{ "_id" : ObjectId("56c70b588543cbc57664541d"),
"products" : [ { "id" : 1, "name" : "apple" },
{ "id" : 2, "name" : "banana" } ] }