MongoDB cannot sort the _id? - mongodb

I have a simple order table in mongoDB
{
"_id" : NumberInt(2),
"bar" : "Maggie Choos Bar"
},
{
"_id" : NumberInt(3),
"bar" : "Corona Bar"
{
I want to find the BIGGEST "_id" number in the table
db.getCollection("order").find({}).sort({"$_id":-1}).limit(1);
But no matter if I sort 1 or -1 I keep getting the result with _id : 2
Any ideas?

The field name prefixed with a dollar sign, like $_id, is for references in an aggregation pipeline. For a sort document, use just the field name:
db.getCollection("order").find({}).sort({"_id":-1}).limit(1);

Related

Is it possible to key a single sort on multiple fields in MongoDB?

I have a simple music database that contains information about CDs (Artist, Album Title, Release Year, etc.). Basically, I want to sort my query by Artist, then Release Year (which is easy to do) but in the case where a CD does not have an Artist, I would like the sort to key on the Album Title.
As an example, for the following CDs:
> db.music.find()
{ "_id" : ObjectId("581d3e073a4d1f0d73a8650b"), "Type" : "CD", "Artist" : "Korn", "Title" : "Korn", "ReleaseYear" : 1994 }
{ "_id" : ObjectId("581d3e283a4d1f0d73a8650c"), "Type" : "CD", "Artist" : "Korn", "Title" : "Life is Peachy", "ReleaseYear" : 1996 }
{ "_id" : ObjectId("581d3efd3a4d1f0d73a8650d"), "Type" : "CD", "Title" : "Big Daddy", "ReleaseYear" : 1999 }
{ "_id" : ObjectId("581d3f3c3a4d1f0d73a8650e"), "Type" : "CD", "Title" : "Aerosmith", "ReleaseYear" : 1973, "Artist" : "Aerosmith" }
I would want it sorted into:
Aerosmith - Aerosmith (1973)
Big Daddy (1999)
Korn - Korn (1994)
Korn - Life is Peachy (1996)
Here is what I have tried:
> db.music.find().sort({[Artist:1,Title:1],ReleaseYear:1})
2016-11-04T22:10:39.317-0400 E QUERY [thread1] SyntaxError: missing ] in computed property name #(shell):1:29
> db.music.find().sort({[Artist,Title]:1,ReleaseYear:1})
2016-11-04T22:10:50.564-0400 E QUERY [thread1] SyntaxError: missing ] in computed property name #(shell):1:29
> db.music.find().sort({{Artist:1,Title:1},ReleaseYear:1})
2016-11-04T22:11:12.040-0400 E QUERY [thread1] SyntaxError: invalid property id #(shell):1:22
I COULD put in a SortName field that contains either the Artist or the Title, but would like to avoid putting that into the document directly.
If you want the sorting to be efficient on large data sets and you serve the query in OLTP-style workloads, a materialized sorting key is your best choice, as you can put an index for sorting on it. The MongoDB sort operator does not natively support ad-hoc, user-defined sort keys.
If it is okay for the query to consume more time, e.g. for analytics, you can employ MongoDB's aggregation framework, in particular, the two operators:
$project which allows the creation of new fields in the aggregation pipeline
$ifNull that returns a non-null value (first argument) or a fallback expression (second argument)
Using those, your sort query can be written as:
db.music.aggregate([
{ $project:
{ "Artist": 1,
"Title": 1,
"ReleaseYear": 1,
"SortName": { "$ifNull" : [ "$Artist", "$Title" ] },
}
},
{ $sort: { "SortName": 1, "ReleaseYear": 1} }
]);
You can add any additional pipeline steps to this, for example, to first filter the data ($match).

Using $last on Mongo Aggregation Pipeline

I searched for similar questions but couldn't find any. Feel free to point me in their direction.
Say I have this data:
{ "_id" : ObjectId("5694c9eed4c65e923780f28e"), "name" : "foo1", "attr" : "foo" }
{ "_id" : ObjectId("5694ca3ad4c65e923780f290"), "name" : "foo2", "attr" : "foo" }
{ "_id" : ObjectId("5694ca47d4c65e923780f294"), "name" : "bar1", "attr" : "bar" }
{ "_id" : ObjectId("5694ca53d4c65e923780f296"), "name" : "bar2", "attr" : "bar" }
If I want to get the latest record for each attribute group, I can do this:
> db.content.aggregate({$group: {_id: '$attr', name: {$last: '$name'}}})
{ "_id" : "bar", "name" : "bar2" }
{ "_id" : "foo", "name" : "foo2" }
I would like to have my data grouped by attr and then sorted by _id so that only the latest record remains in each group, and that's how I can achieve this. BUT I need a way to avoid naming all the fields that I want in the result (in this example "name") because in my real use case they are not known ahead.
So, is there a way to achieve this, but without having to explicitly name each field using $last and just taking all fields instead? Of course, I would sort my data prior to grouping and I just need to somehow tell Mongo "take all values from the latest one".
See some possible options here:
Do multiple find().sort() queries for each of the attr values you
want to search.
Grab the original _id of the $last doc, then do a findOne() for each of those values (this is the more extensible option).
Use the $$ROOT system variable as shown here.
This wouldn't be the quickest operation, but I assume you're using this more for analytics, not in response to a user behavior.
Edited to add slouc's example posted in comments:
db.content.aggregate({$group: {_id: '$attr', lastItem: { $last: "$$ROOT" }}}).

Meteor/Mongo nested arrays update

I'm new to meteor/mongo/js as a stack and I'm getting lost in JSON arrays and referencing them. Based another SO answer ( and the docs) I think I am close...
A document in the Orders collection, the document has nested arrays.
Order -> orderLines -> lineItems:
Sample doc:
{
"_id" : "27tGpRtMWYqPpFkDN",
"orderLines" : [
{
"lineId" : 1,
"name" : "Cheese & Pickle",
"instructions" : "",
"lineItems" : [
{
"name" : "Cheddar Cheese",
"quantity" : 1
},
{
"name" : "Branston Pickle",
"quantity" : 1
},
{
"name" : "Focaccia Roll",
"quantity" : 1
}
]
}
]
}
What I'm trying to do from the meteor/mongo shell:
Add "instructions" of "foo" to orderLines where lineId = 1
Put a new item on lineItems array
This appears to hang...
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN","orderLines.lineId":"1", {$set: {"orderLines.$.instructions":"foo"}})
...
This doesn't like the identifier in the query
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":"1"}, {$push:{"orderLines.$.lineItems":" { "name" : "butter", "quantity" : 1}"}});
2015-10-27T16:09:54.489+0100 SyntaxError: Unexpected identifier
Thanks all for your comments... but I found out some answers, posted for reference
Item 1 - using $set on a value within an array
This was failing due to two typos, one missing closing } at the end of the query, second was quoting the value "1" for itemId in the query.
This works:
db.orders.update({_id:"27tGpRtMWYqPpFkDN", orderLines.lineId":1},
{$set: {"orderLines.$.instructions":"foo"}})
I also realised that when I said "It appears to hang" it is the cli waiting for a valid statement, so hints at a missing } or )!
Item 2 - using $push to add data to an array - 2 levels nested
This was failing due to quoting around the array data
db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":1 },
{$push:{"orderLines.$.lineItems": { "name" : "butter", "quantity" : 1} }})
Nested Arrays: can use $ positional operator
What I want to do next is use $set on an item in the second level array, and this would require using the $ positional operator twice:
db.orders.update({"orderLines.lineId":1, lineItems.name:"Cheddar Cheese"},
{$set: {"orderLines.$.lineItems.$.quantity": 2}})
This throws an error:
Too many positional (i.e. '$') elements found in path
There is an open MongoDB enhancement request for this but it's been open since 2010
$push adds a new element to an array. You're merely trying to set the value of a particular key in an array element.
Try:
db.orders.update({ _id: "27tGpRtMWYqPpFkDN", "orderLines.lineId": 1},
{ $set: { "orderlines.$.instructions": "foo" }})
docs

Find oldest/youngest post in mongodb collection

I have a mongodb collection with many fields. One field is 'date_time', which is in an ISO datetime format, Ex: ISODate("2014-06-11T19:16:46Z"), and another field is 'name'.
Given a name, how do I find out the oldest/youngest post in the collection?
Ex: If there are two posts in the collection 'data' :
[{'name' : 'John', 'date_time' : ISODate("2014-06-11T19:16:46Z")},
{'name' : 'John', 'date_time' : ISODate("2015-06-11T19:16:46Z")}]
Given the name 'John' how do I find out the oldest post in the collection i.e., the one with ISODate("2014-06-11T19:16:46Z")? Similarly for the youngest post.
Oldest:
db.posts.find({ "name" : "John" }).sort({ "date_time" : 1 }).limit(1)
Newest:
db.posts.find({ "name" : "John" }).sort({ "date_time" : -1 }).limit(1)
Index on { "name" : 1, "date_time" : 1 } to make the queries efficient.
You could aggregate it as below:
Create an index on the name and date_time fields, so that the
$match and $sort stage operations may use it.
db.t.ensureIndex({"name":1,"date_time":1})
$match all the records for the desired name(s).
$sort by date_time in ascending order.
$group by the name field. Use the $first operator to get the first
record of the group, which will also be the oldest. Use the $last
operator to get the last record in the group, which will also be the
newest.
To get the entire record use the $$ROOT system variable.
Code:
db.t.aggregate([
{$match:{"name":"John"}},
{$sort:{"date_time":1}},
{$group:{"_id":"$name","oldest":{$first:"$$ROOT"},
"youngest":{$last:"$$ROOT"}}}
])
o/p:
{
"_id" : "John",
"oldest" : {
"_id" : ObjectId("54da62dc7f9ac597d99c182d"),
"name" : "John",
"date_time" : ISODate("2014-06-11T19:16:46Z")
},
"youngest" : {
"_id" : ObjectId("54da62dc7f9ac597d99c182e"),
"name" : "John",
"date_time" : ISODate("2015-06-11T19:16:46Z")
}
}
db.t.find().sort({ "date_time" : 1 }).limit(1).pretty()

MongoDB Why this error : can't append to array using string field name: comments

I have a DB structure like below:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"content" : "xxx"
}
]
}
I update a new subdocument in the comments feild. It is OK.
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
after that the DB structure:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"comments" : [
{
"id" : 3,
"content" : "xxx"
}
],
"content" : "xxx"
}
]
}
But when I update a new subdocument in the comment field that _id is 3, There is a error:
db.test.update(
{"_id" : 1, "comments.comments.id" : 3},
{$push : {"comments.comments.$.comments" : {id : 4, content:"xxx"}}}
)
error message:
can't append to array using string field name: comments
Well, it makes total sense if you think about it. MongoDb has the advantage and the disadvantage of solving magically certain things.
When you query the database for a specific regular field like this:
{ field : "value" }
The query {field:"value"} makes total sense, it wouldn't in case value is part of an array but Mongo solves it for you, so in case the structure is:
{ field : ["value", "anothervalue"] }
Mongo iterates through all of them and matches "value" into the field and you don't have to think about it. It works perfectly.. at only one level, because it's impossible to guess what you want to do if you have multiple levels
In your case the first query works because it's the case in this example:
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
Matches _id in the first level, and comments._id at the second level, it gets an array as a result but Mongo is able to solve it.
But in the second case, think what you need, let's isolate the where clause:
{"_id" : 1, "comments.comments.id" : 3},
"Give me from the main collection records with _id:1" (one doc)
"And comments which comments inside have and id=3" (array * array)
The first level is solved easily, comments.id, the second is not possible due comments returns an array, but one more level is an array of arrays and Mongo gets an array of arrays as a result and it's not possible to push a document into all the records of the array.
The solution is to narrow your where clause to obtain an unique document in comments (could be the first one) but it's not a good solution because you never know what is the position of the document you're looking for, using the shell I think the only option to be accurate is to do it in two steps. Check this query that works (not the solution anyway) but "solves" the multiple array part fixing it to the first record:
db.test.update(
{"_id" : 1, "comments.0.comments._id" : 3},
{$push : {"comments.0.comments.$.comments" : {id : 4, content:"xxx"}}}
)