MongoDB incorrect results with elemMatch and date fields - mongodb

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();

Related

MongoDB - find document whose array length is less than or equal to 5

Can't we pass an object to $size operator in mongoose? Is there any ways to query on array for length so we can fetch document which contains an array of a particular length.
Hers is Sample Document
"_id" : ObjectId("5e8c9becd1257f66c4b8cd63"),
"index" : 0,
"name" : "Aurelia Gonzales",
"isActive" : false,
"registered" : ISODate("2015-02-11T09:52:39.000+05:30"),
"age" : 20,
"gender" : "female",
"eyeColor" : "green",
"favoriteFruit" : "banana",
"company" : {
"title" : "YURTURE",
"email" : "aureliagonzales#yurture.com",
"phone" : "+1 (940) 501-3963",
"location" : {
"country" : "USA",
"address" : "694 Hewes Street"
}
},
"tags" : [
"enim",
"id",
"velit",
"ad",
"consequat"
]
}
Here is query
db.admin.aggregate([
{
$match : {tags : {$size : {$lte : 5}}}
}
])
Here is Output
{
"message" : "$size needs a number",
"ok" : 0,
"code" : 2,
"codeName" : "BadValue",
"name" : "MongoError"
}
You can't use $size like that & needed to use aggregation $size operator to do this.
Query :
db.collection.find({
$expr: { /** Allows the use of aggregation expressions within the query language */
$lte: [
{
$size: "$tags"
},
5
]
}
})
Test : MongoDB-Playground
Although if the size of the array is important enough, it could be stored in the documents and indexed to fetch much faster results.
Following a similar logic a solution could be, two stage aggregation using $addFields and $size, $lte.
db.collection.aggregate([
{
$addFields: {
sizeOfTags: {
$size: "$tags"
}
}
},
{
$match: {
sizeOfTags: {
$lte: 5
}
}
}
])

Mongodb accessing documents

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.

Plain query in mongoDB

I have a collection called constructora that has the following structure:
{
"_id" : A,
"edificio" : [
{
"_id": X,
"a" : 48,
"b" : 59
},
{
"_id": Y,
"a" : 8,
"b" : 5
},
{
"_id": Z,
"a" : 0,
"b" : -1
},
...
]
}
So, I want to make a query that returns, for each sub document (edificio) his parent's _id. An example:
{
"_id" : X,
"a" : 48,
"b" : 59
"id_constructora" : A
}
{
"_id" : Y,
"a" : 8,
"b" : 5
"id_constructora" : A
}
{
"_id" : Z,
"a" : 0,
"b" : -1
"id_constructora" : A
}
How can I do that?
EDIT
Now I'm trying using aggregate, and grouping my query by "edificio_id", so for each document in edificio I can get my desired output:
db.constructora.aggregate(
[
{ $project : { "_id" : 1, "edificio._id" : 1 } },
{ $group : { _id : "$edificio._id" } }
]
).pretty();
But it doesn't work. The output is:
...
{
"_id" : [
ObjectId("613339376430333562373466"),
ObjectId("663736363935393066656236"),
ObjectId("313933613036363364633832"),
ObjectId("653135313831633638336436")
]
}
{
"_id" : [
ObjectId("643531326231663739626465"),
ObjectId("343231386237333365356461"),
ObjectId("373461303864636138393263"),
ObjectId("386433623966653737343962"),
ObjectId("303863633366376431363335"),
ObjectId("663833343161643639376161"),
ObjectId("383833363836663532633733"),
ObjectId("396330313961353137333166"),
ObjectId("646535366662363364613837"),
ObjectId("633937613032656436653965")
]
}
You can use $unwind to break the embedded array into embedded docs, $addFields to rename and add the _id into the embedded doc followed by $replaceRoot to promote the embedded document to the top level in 3.4 mongo server.
db.constructora.aggregate([
{$unwind:"$edificio"},
{$addFields:{"edificio.id_constructora":"$_id"}},
{$replaceRoot: {newRoot: "$edificio"}}
])
More info here https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/#replaceroot-with-an-array-element

ordering fields after applying $setUnion mongoDB

I have a collection:
{
"_id" : ObjectId("5338ec2a5b5b71242a1c911c"),
"people" : [
{
"name" : "Vasya"
},
{
"age" : "30"
},
{
"weight" : "80"
}
],
"animals" : [
{
"dog" : "Sharick"
},
{
"cat" : "Barsik"
},
{
"bird" : "parrot"
}
]},{
"_id" : ObjectId("5338ec7f5b5b71242a1c911d"),
"people" : [
{
"name" : "Max"
},
{
"age" : "32"
},
{
"weight" : "78"
}
],
"animals" : [
{
"dog" : "Borbos"
},
{
"cat" : "Murka"
},
{
"bird" : "Eagle"
}
]}
then I combine two arrays "people" and "animals"
db.tmp.aggregate({$project:{"union":{$setUnion:["$people","$animals"]}}})
in the issue:
How to make the fields of each record array "result" to be displayed in a single order, and not randomly?
that is:
Wish I could find the quote ( If I can I will add it ), but it basically comes from the CTO of MongoDB and is essentially (sic) "Set's are not considered to be ordered". And very much so from a Math point of view that is true.
So you have stumbled upon one of the new features in the current (as of writing) 2.6 release candidate series. But like with it's $addToSet counterpart, the resulting set from $setUnion will not be sorted in any way.
To do this you need to $unwind and $sort and then $group again using $push, just as you always have with $addToSet. And of course you would need some common key in order to $sort on this, which your data does not.
Update: Here is the quote, and here is another.

extract subarray value in mongodb

MongoDB noob here...
I have a collection as follows...
> db.students.find({_id:22},{scores:[{type:'exam'}]}).pretty()
{
"_id" : 22,
"scores" : [
{
"type" : "exam",
"score" : 75.04996547553947
},
{
"type" : "quiz",
"score" : 10.23046475899236
},
{
"type" : "homework",
"score" : 96.72520512117761
},
{
"type" : "homework",
"score" : 6.488940333376703
}
]
}
how do I display only the quiz score via mongo shell?
You have some syntax in your original example which probably isn't doing what you expect .. that is, it looks like your intent was to only match scores for a specific type ('exam' in your example, 'quiz' by your description).
Below are some examples using the MongoDB 2.2 shell.
$elemMatch projection
You can use the $elemMatch projection to return the first matching element in an array:
db.students.find(
// Search criteria
{ '_id': 22 },
// Projection
{ _id: 0, scores: { $elemMatch: { type: 'exam' } }}
)
The result will be the matching element of the array for each document, eg:
{ "scores" : [ { "type" : "exam", "score" : 75.04996547553947 } ] }
Aggregation Framework
If you want to display more than one matching value or reshape the result document instead of returning the full matching array element, you can use the Aggregation Framework:
db.students.aggregate(
// Initial document match (uses index, if a suitable one is available)
{ $match: {
'_id': 22, 'scores.type' : 'exam'
}},
// Convert embedded array into stream of documents
{ $unwind: '$scores' },
// Only match scores of interest from the subarray
{ $match: {
'scores.type' : 'exam'
}},
// Note: Could add a `$group` by _id here if multiple matches are expected
// Final projection: exclude fields with 0, include fields with 1
{ $project: {
_id: 0,
score: "$scores.score"
}}
)
The result in this case includes would be:
{ "result" : [ { "score" : 75.04996547553947 } ], "ok" : 1 }