Mongodb accessing documents - mongodb

I've the following db:
{ "_id" : 1, "results" : [ { "product" : "abc", "score" : 10 }, { "product" : "xyz", "score" : 5 } ] }
{ "_id" : 2, "results" : [ { "product" : "abc", "score" : 8 }, { "product" : "xyz", "score" : 7 } ] }
{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "xyz", "score" : 8 } ] }
I want to show the first score of each _id, i tried the following:
db.students.find({},{"results.$":1})
But it doesn't seem to work, any advice?

You can take advantage of aggregation pipeline to solve this.
Use $project in conjunction with $arrayElemAt to point to appropriate node index in the array.
So, to extract the documents of the first score, have written below query.
db.students.aggregate([ {$project: { scoredoc:{$arrayElemAt:["$results",0]}} } ]);
In case if you just wish to have scores excluding product, use $results.score as shown below.
db.students.aggregate([ {$project: { scoredoc:{$arrayElemAt:["$results.score",0]}} } ]);
Here scoredoc object will have all documents of first score element.
Hope this helps!

According to above mentioned description please try executing following query in MongoDB shell
db.students.find(
{results:
{$elemMatch:{score:{$exists:true}}}}, {'results.$.score':1}
)
According to MongoDB documentation
The positional $ operator limits the contents of an from the
query results to contain only the first element matching the query
document.
Hence in above mentioned query positional $ operator is used in projection section to retrieve first score of each document.

Related

Query on 2 properties of the same embedded object in an array

I have a Collection of objects :
db.coll.find().pretty();
{
"_id" : "1",
"elements" : [
{ "key" : "A", "value" : 10 },
{ "key" : "B", "value" : 1 },
]
},
{
"_id" : "2",
"elements" : [
{ "key" : "A", "value" : 1 },
{ "key" : "C", "value" : 33 },
]
}
I want to find the documents that contain an element with "key" equal to "A" AND its "value" is greater than 5.
Not sure if this is possible without using the aggregation framework.
Without aggregation, using $elemMatch and query will be as below :
db.coll.find({"elements":{"$elemMatch":{"$and":[{"key":"A"},{"value":{"$gt":5}}]}}}).pretty()
or if you want use aggregation then used following query for aggregation
db.coll.aggregate({"$unwind":"$elements"},{"$match":{"$and":[{"elements.key":"A"},{"elements.value":{"$gt":5}}]}}).pretty()
Here is the query
db.coll.find({elements: {$elemMatch: {key: "A", value: {$gt: 5}}}});
It uses $elemMatch operator. The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. (Refer the documentation)

mongodb queries find total number of cities in the database

Hi everyone I have a huge data that contains some information like this below:
{ "_id" : "01011", "city" : "CHESTER", "loc" : [ -72.988761, 42.279421 ], "pop" : 1688, "state" : "MA" }
{ "_id" : "01012", "city" : "CHESTERFIELD", "loc" : [ -72.833309, 42.38167 ], "pop" : 177, "state" : "MA" }
{ "_id" : "01013", "city" : "CHICOPEE", "loc" : [ -72.607962, 42.162046 ], "pop" : 23396, "state" : "MA" }
{ "_id" : "01020", "city" : "CHICOPEE", "loc" : [ -72.576142, 42.176443 ], "pop" : 31495, "state" : "MA" }
I want to be able to find the number of the cities in this database using Mongodb command. But also the database may have more than one recored that has the same city. As the example above.
I tried:
>db.zipcodes.distinct("city").count();
2015-04-25T15:57:45.446-0400 E QUERY warning: log line attempted (159k) over max size (10k), printing beginning and end ... TypeError: Object AGAWAM,BELCHERTOWN ***data*** has no method 'count'
but I didn't work with me.Also I did something like this:
>db.zipcodes.find({city:.*}).count();
2015-04-25T16:00:01.043-0400 E QUERY SyntaxError: Unexpected token .
But it didn't work also and even if does work it will count the redundant data (city). Any idea?
Instead of doing
db.zipcodes.distinct("city").count();
do this:
db.zipcodes.distinct("city").length;
and there is aggregate function, which may help you.
I have also found 1 example on aggregate (related to your query).
If you want to add condition, then you could refer $gte / $gte (aggregation) and/or $lte / $lte (aggregation)
See, if that helps.
You can also use the aggregation framework for this. The aggregation pipeline has two $group operator stages; the first groups the documents by city and the last calculates the total distinct documents from the previous stream:
db.collection.aggregate([
{
"$group": {
"_id": "$city"
}
},
{
"$group": {
"_id": 0,
"count": { "$sum": 1 }
}
}
]);
Output:
/* 1 */
{
"result" : [
{
"_id" : 0,
"count" : 3
}
],
"ok" : 1
}

MongoDB incorrect results with elemMatch and date fields

I have the following document
{
"_id" : ObjectId("52da43cd6f0a61e8a5059aaf"),
"assignments" : [
{
"project" : "abc",
"start" : ISODate("2012-12-31T18:30:00Z"),
"end" : ISODate("2013-06-29T18:30:00Z")
},
{
"project" : "efg",
"start" : ISODate("2013-06-30T18:30:00Z"),
"end" : ISODate("2014-03-30T18:30:00Z")
}
],
"eid" : "123",
"name" : "n1",
"uid" : "u1"
}
i am trying to find all the assignments starting after a certain date with the following query. but the returned data is confusing and doesnt look correct. pl help me understand what am i doing wrong.
> db.test.find( {uid:'u1'},{ assignments: { $elemMatch: {end: {$gte: new Date(2011,5,1) } } }} ).pretty();
{
"_id" : ObjectId("52da43cd6f0a61e8a5059aaf"),
"assignments" : [
{
"project" : "abc",
"start" : ISODate("2012-12-31T18:30:00Z"),
"end" : ISODate("2013-06-29T18:30:00Z")
}
]
}
should it return both the projects?
The $elemMatch operator limits the results to the first matching array element per document.
If you want to return all matching array elements you can use the Aggregation Framework:
db.courses.aggregate(
// Find matching documents (would benefit from an index on `{uid: 1}`)
{ $match : {
uid:'u1'
}},
// Unpack the assignments array
{ $unwind : "$assignments" },
// Find the assignments ending after given date
{ $match : {
"assignments.end": { $gte: ISODate("2011-05-01") }
}}
)
$elemMatch limits projections in the following way.
Say you have a collection with these documents:
{ "_id" : 1, "prices" : [ 50, 60, 70 ] }
{ "_id" : 2, "prices" : [ 80, 90, 100 ] }
db.t.find({},{'prices':{$elemMatch:{$gt:50}}})
Returns:
{ "_id" : 1, "prices" : [ 60 ] }
{ "_id" : 2, "prices" : [ 80 ] }
The projection is curtailed to the first element that is matched, rather than all elements that are matched.
If you were expecting a result like this:
{ "_id" : 1, "prices" : [ 60,70] }
{ "_id" : 2, "prices" : [ 80, 90, 100 ] }
It is not possible with projections which only supports $elemMatch, $ and $slice.
For example, you can use, for example, $all in the projection or any other smart projection.
If you want to shape the document, match, filter, project with an unwind and wind (via grouping).
aggregate([ {$unwind:'$prices'},{$match:{'prices':{$gt:50}}},{$group:{_id:'$_id',prices:{$push:'$prices'}}}])
This will result in what we're looking for.
{ "_id" : 2, "prices" : [ 80, 90, 100 ] }
{ "_id" : 1, "prices" : [ 60, 70 ] }
You can take this pattern and use it for your situation.
With that query you are searching documents with uid = u1 and projecting the results to show only the first element that has a date greater than Date(2011,5,1). Check $elemMatch projection operator
I think you need something like this:
db.test.find(
{
uid:'u1'
"assignments.end": {$gte: new Date(2011,5,1)}
}).pretty();

Find maximum date from multiple embedded documents

One of many documents in my collection is like below:
{ "_id" :123,
"a" :[
{ "_id" : 1,
"dt" : ISODate("2013-06-10T19:38:42Z")
},
{ "_id" : 2,
"dt" : ISODate("2013-02-10T19:38:42Z")
}
],
"b" :[
{ "_id" : 1,
"dt" : ISODate("2013-02-10T19:38:42Z")
},
{ "_id" : 2,
"dt" : ISODate("2013-23-10T19:38:42Z")
}
],
"c" :[
{ "_id" : 1,
"dt" : ISODate("2013-03-10T19:38:42Z")
},
{ "_id" : 2,
"dt" : ISODate("2013-13-10T19:38:42Z")
}
]
}
I want to find the maximum date for the whole document (a,b,c).
The solution i have right now is, I loop through all root _id then do a $match in aggregation framework for each a, b, c for every root document. this sounds very inefficient, any better ideas?
Your question is very very similar to this question. Check it out.
As with that solution, your issue can be handled with MongoDB's aggregation framework, using the $project and $cond operators to repeatedly flatten your documents while preserving a max value at each step.

What's the $unwind operator in MongoDB?

This is my first day with MongoDB so please go easy with me :)
I can't understand the $unwind operator, maybe because English is not my native language.
db.article.aggregate(
{ $project : {
author : 1 ,
title : 1 ,
tags : 1
}},
{ $unwind : "$tags" }
);
The project operator is something I can understand, I suppose (it's like SELECT, isn't it?). But then, $unwind (citing) returns one document for every member of the unwound array within every source document.
Is this like a JOIN? If yes, how the result of $project (with _id, author, title and tags fields) can be compared with the tags array?
NOTE: I've taken the example from MongoDB website, I don't know the structure of tags array. I think it's a simple array of tag names.
The thing to remember is that MongoDB employs an "NoSQL" approach to data storage, so perish the thoughts of selects, joins, etc. from your mind. The way that it stores your data is in the form of documents and collections, which allows for a dynamic means of adding and obtaining the data from your storage locations.
That being said, in order to understand the concept behind the $unwind parameter, you first must understand what the use case that you are trying to quote is saying. The example document from mongodb.org is as follows:
{
title : "this is my title" ,
author : "bob" ,
posted : new Date () ,
pageViews : 5 ,
tags : [ "fun" , "good" , "fun" ] ,
comments : [
{ author :"joe" , text : "this is cool" } ,
{ author :"sam" , text : "this is bad" }
],
other : { foo : 5 }
}
Notice how tags is actually an array of 3 items, in this case being "fun", "good" and "fun".
What $unwind does is allow you to peel off a document for each element and returns that resulting document.
To think of this in a classical approach, it would be the equivilent of "for each item in the tags array, return a document with only that item".
Thus, the result of running the following:
db.article.aggregate(
{ $project : {
author : 1 ,
title : 1 ,
tags : 1
}},
{ $unwind : "$tags" }
);
would return the following documents:
{
"result" : [
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "fun"
},
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "good"
},
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "fun"
}
],
"OK" : 1
}
Notice that the only thing changing in the result array is what is being returned in the tags value. If you need an additional reference on how this works, I've included a link here.
$unwind duplicates each document in the pipeline, once per array element.
So if your input pipeline contains one article doc with two elements in tags, {$unwind: '$tags'} would transform the pipeline to be two article docs that are the same except for the tags field. In the first doc, tags would contain the first element from the original doc's array, and in the second doc, tags would contain the second element.
consider the below example to understand this
Data in a collection
{
"_id" : 1,
"shirt" : "Half Sleeve",
"sizes" : [
"medium",
"XL",
"free"
]
}
Query -- db.test1.aggregate( [ { $unwind : "$sizes" } ] );
output
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "medium" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "XL" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "free" }
As per mongodb official documentation :
$unwind Deconstructs an array field from the input documents to output a document for each element. Each output document is the input document with the value of the array field replaced by the element.
Explanation through basic example :
A collection inventory has the following documents:
{ "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] }
{ "_id" : 2, "item" : "EFG", "sizes" : [ ] }
{ "_id" : 3, "item" : "IJK", "sizes": "M" }
{ "_id" : 4, "item" : "LMN" }
{ "_id" : 5, "item" : "XYZ", "sizes" : null }
The following $unwind operations are equivalent and return a document for each element in the sizes field. If the sizes field does not resolve to an array but is not missing, null, or an empty array, $unwind treats the non-array operand as a single element array.
db.inventory.aggregate( [ { $unwind: "$sizes" } ] )
or
db.inventory.aggregate( [ { $unwind: { path: "$sizes" } } ]
Above query output :
{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }
Why is it needed?
$unwind is very useful while performing aggregation. it breaks complex/nested document into simple document before performaing various operation like sorting, searcing etc.
To know more about $unwind :
https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
To know more about aggregation :
https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
Let me explain in a way corelated to RDBMS way. This is the statement:
db.article.aggregate(
{ $project : {
author : 1 ,
title : 1 ,
tags : 1
}},
{ $unwind : "$tags" }
);
to apply to the document / record:
{
title : "this is my title" ,
author : "bob" ,
posted : new Date () ,
pageViews : 5 ,
tags : [ "fun" , "good" , "fun" ] ,
comments : [
{ author :"joe" , text : "this is cool" } ,
{ author :"sam" , text : "this is bad" }
],
other : { foo : 5 }
}
The $project / Select simply returns these field/columns as
SELECT author, title, tags FROM article
Next is the fun part of Mongo, consider this array tags : [ "fun" , "good" , "fun" ] as another related table (can't be a lookup/reference table because values has some duplication) named "tags". Remember SELECT generally produces things vertical, so unwind the "tags" is to split() vertically into table "tags".
The end result of $project + $unwind:
Translate the output to JSON:
{ "author": "bob", "title": "this is my title", "tags": "fun"},
{ "author": "bob", "title": "this is my title", "tags": "good"},
{ "author": "bob", "title": "this is my title", "tags": "fun"}
Because we didn't tell Mongo to omit "_id" field, so it's auto-added.
The key is to make it table-like to perform aggregation.