Mongo incorrectly sorting on array field? - mongodb

I'm trying to filter on an array field which unfortunately doesn't seem to be working correctly. Everything I've read suggests that this should work, and the sort is doing something, just not what I expected and I can't explain it.
What I'm trying to achieve is sorting on an array sub-field. I've managed to achieve most of this using the positional operator but I can't work out what the sort is doing.
db.getCollection('boards')
.find({ "lastVisited.user": "AAA" }, { name: 1, "lastVisited.$" : 1 })
.sort({ "lastVisited.0.timestamp": 1 });
This results in the following output
/* 1 */
{
"_id" : ObjectId("5b642d2cac2f544b1d48d09a"),
"lastVisited" : [
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-18T00:00:00.000Z")
}
]
}
/* 2 */
{
"_id" : ObjectId("5b6845245e102f3844d2181b"),
"lastVisited" : [
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-16T00:00:00.000Z")
}
]
}
/* 3 */
{
"_id" : ObjectId("5b6842095e102f3844d2181a"),
"lastVisited" : [
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-19T00:00:00.000Z")
}
]
}
The thing to note here is that the dates are ordered 18th then 19th then 16th which makes no sense! Can anyone explain this?
These are the documents that I've used:
/* 1 */
{
"_id" : ObjectId("5b642d2cac2f544b1d48d09a"),
"lastVisited" : [
{
"user" : "BBB",
"timestamp" : ISODate("2018-08-04T00:00:00.000Z")
},
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-18T00:00:00.000Z")
}
]
}
/* 2 */
{
"_id" : ObjectId("5b6842095e102f3844d2181a"),
"lastVisited" : [
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-19T00:00:00.000Z")
}
]
}
/* 3 */
{
"_id" : ObjectId("5b6845245e102f3844d2181b"),
"lastVisited" : [
{
"user" : "AAA",
"timestamp" : ISODate("2018-08-16T00:00:00.000Z")
}
]
}

Unfortunately you can't do this currently in Mongo, as it still uses the full document (not just the projected part) to sort on. So you'll need to use the aggregation framework instead. See an open issue https://jira.mongodb.org/browse/SERVER-4451
Here's an example with aggregation, as you want your sort to happen on matched elements
db.getCollection('stack1').aggregate([
// Initial document match (uses index, if a suitable one is available)
{ $match:
{ "lastVisited.user": "AAA" }
},
{ "$unwind":"$lastVisited"},
{ $match:{
"lastVisited.user": "AAA"
}},
{ "$sort": { "lastVisited.timestamp": 1 } }
])

Related

Mongodb Query to get the nth document

I need to create a query in mongodb that needs to return the SECOND TO THE LAST document. I am planning to use $group for this query but i dont know what aggregation function to use. I only know $first and $last.
I have an example collection below and also include the expected output. Thank you!
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84be"),
"userid" : ObjectId("5ffe702d59a9205db81fcb69"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),
"userid" : ObjectId("5ffe644f9493e05db9245192"),
"action" : "EDITPROFILE"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bc"),
"userid" : ObjectId("5ffe64949493e05db9245197"),
"action" : "DELETETRANSACTION"
} ]
"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bb"),
"userid" : ObjectId("5ffe64b69493e05db924519b"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84ba"),
"userid" : ObjectId("5ffe65419493e05db92451d4"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b9"),
"userid" : ObjectId("5ffe65689493e05db92451d9"),
"action" : "CHANGEACCESS"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),
"userid" : ObjectId("5ffe65819493e05db92451dd"),
"action" : "DELETETRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b7"),
"userid" : ObjectId("5ffe65df9493e05db92451f3"),
"action" : "EDITPROFILE",
]
OUTPUT:
{"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),"userid" : ObjectId("5ffe644f9493e05db9245192"),"action" : "EDITPROFILE"},
{"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),"userid" : ObjectId("5ffe65819493e05db92451dd"),"action" : "DELETETRANSACTION"}
You can't have two _id keys in one single object.
I've made the parent object's id to _parentId you can give it's a name anything you want except _id
Aggregation:
db.collection.aggregate([
{
$unwind: "$auditlogs"
},
{
"$project": {
"_parentId": "$_id",
"_id": "$auditlogs._id",
"action": "$auditlogs.action",
"userid": "$auditlogs.userid",
}
}
])
Playground
You can slice the array by -2 to get the last two item, then by 1 to get first one. Therefore, the array will be left the second to the last. Finally, unwind auditlogs so it can be changed from array to object which is structure that you want.
db.collection.aggregate([
{
$project: { auditlogs : { $slice: [ "$auditlogs", -2 ] } }
},
{
$project: { auditlogs : { $slice: [ "$auditlogs", 1 ] } }
},
{
$unwind: "$auditlogs"
}
])

How to combine a collection with the object of a different collection without relation in MongoDB?

I have 2 collections;
//db.cities.find({})
/* 1 */
{
"_id" : ObjectId("/*...*/"),
"Name" : "Istanbul"
}
/* 2 */
{
"_id" : ObjectId("/*...*/"),
"Name" : "Ankara"
}
/* 3 */
{
"_id" : ObjectId("/*...*/"),
"Name" : "Izmir"
}
/* 4 */
{
"_id" : ObjectId("/*...*/"),
"Name" : "Eskisehir"
}
//db.dates.find({})
/* 1 */
{
"_id" : "Turkey",
"dateTime": "2021-09-03 10:25"
}
/* 2 */
{
"_id" : "England",
"dateTime": "2021-09-03 08:25"
}
I want to combine two collections but not like inner join.
Here is the result I want to see;
//the output that I need
{
"_id" : ObjectId("/*...*/"),
"Cities": [
{
"_id" : ObjectId("/*...*/"),
"Name" : "Istanbul"
},
{
"_id" : ObjectId("/*...*/"),
"Name" : "Ankara"
},
{
"_id" : ObjectId("/*...*/"),
"Name" : "Izmir"
},
{
"_id" : ObjectId("/*...*/"),
"Name" : "Eskisehir"
}
],
"DateTime" : "2021-09-03 10:25"
}
As a result, I don't want to see the DateTime in all cities elements.
How can I get this result in MongoDB v3.2?
Note 1: The cities collection doesn't have the Country field. I can write Turkey as hardcoded in the query.
Note 2: I prefer aggregate instead of map-reduce if is it possible.
Since 3.6, MongoDB allows execute Subqueries
Try this one:
db.dates.aggregate([
{
"$lookup": {
"from": "cities",
"pipeline": [],
"as": "Cities"
}
},
{
$match: {
_id: "Turkey"
}
},
{
"$addFields": {
_id: "$$REMOVE",
"Country": "Turkey"
}
}
])
MongoPlayground

MongoDb sort on date field with $match and $project not working

I am not seeing any type of sort on my data when sorting by a "date" field in mongoDb queries. The "date_recorded" field IS a date field.
I've also tried sorting on a time_stamp field. The sort order doesn't appear to be working whether it is ascending or descending.
I cannot seem to figure out why it isn't working. I've tried it in:
- Compass using the Aggregate tab.
- Robo3t
- VSCode using NodeJs (a schema model query)
The output is always the same.
Any help to get this working will be greatly appreciated...
I've been searching Google and trying different things for about two hours now.
Here is my query:
db.getCollection('inputData').aggregate(
{ $match: { "inputData_userID": { $eq: "user1" } } }
,{ $project: { "date": 1 }}
,{ $sort: { "date_recorded": -1 }}
,function (err, docs) {}
)
This is the output:
/* 1 */
{
"_id" : "0f30df7453b6096da524d3b61ce75eb1",
"date" : "4/13/2017"
}
/* 2 */
{
"_id" : "081be472b94804ae597706aa2bc4d9f4",
"date" : "4/18/2017"
}
/* 3 */
{
"_id" : "0005933cda516a4df346bf0807ab6ca4",
"date" : "5/19/2017"
}
/* 4 */
{
"_id" : "3a67cc9a5eb0a9197fa5448773bfec88",
"date" : "4/14/2017"
}
/* 5 */
{
"_id" : "1aefe9e79faaf4d65c6194b162311e08",
"date" : "4/13/2017"
}
/* 6 */
{
"_id" : "3f4c9d65c207d5cf620a00cee062a4c8",
"date" : "4/13/2017"
}
Here is another query:
db.getCollection('inputData').find(
{ "inputData_userID": { $eq: "user1" } }
,{ "_id": 0, "date": 1, "date_recorded": 1 }
,{ $sort: { "date_recorded": -1 }}
)
Here is the output result of this query:
/* 1 */
{
"date_recorded" : ISODate("2017-04-13T08:54:24.024Z"),
"date" : "4/13/2017"
}
/* 2 */
{
"date_recorded" : ISODate("2017-04-18T22:01:20.767Z"),
"date" : "4/18/2017"
}
/* 3 */
{
"date_recorded" : ISODate("2017-05-19T00:03:03.081Z"),
"date" : "5/19/2017"
}
/* 4 */
{
"date_recorded" : ISODate("2017-04-14T06:12:55.320Z"),
"date" : "4/14/2017"
}
/* 5 */
{
"date_recorded" : ISODate("2017-04-13T23:53:22.692Z"),
"date" : "4/13/2017"
}
/* 6 */
{
"date_recorded" : ISODate("2017-04-13T08:55:38.721Z"),
"date" : "4/13/2017"
}
The output of one aggregation pipeline stage provides the input to the next stage, so you need to include date_recorded in your $project stage so you can sort on it in the $sort stage that follows. But you also need to put your stages in an array instead of providing them as separate parameters:
db.getCollection('inputData').aggregate([
{ $match: { inputData_userID: "user1" }}
,{ $project: { date: 1, date_recorded: 1 }}
,{ $sort: { date_recorded: -1 }}]
,function (err, docs) { ... }
)
Note that I also simplified your $match expression as you don't need to use $eq here.
In mongo-shell i dint have any problem in date sorting in aggregate.
this is data of date field.
db.inputData.find().pretty()
and gives sample data like this...
{
"_id" : ObjectId("5dc26c292fa53037f6b559aa"),
"date" : ISODate("2017-04-12T18:30:00Z")
}
{
"_id" : ObjectId("5dc26cb82fa53037f6b559ab"),
"date" : ISODate("2017-04-14T18:30:00Z")
}
{
"_id" : ObjectId("5dc26cf72fa53037f6b559ac"),
"date" : ISODate("2017-04-18T18:30:00Z")
}
{
"_id" : ObjectId("5dc26d2e2fa53037f6b559ad"),
"date" : ISODate("2018-04-18T18:30:00Z")
}
{ "_id" : ObjectId("5dc26d722fa53037f6b559ae"), "date" : "4/19/2018" }
{ "_id" : ObjectId("5dc26db12fa53037f6b559af"), "date" : "4/19/2019" }
{ "_id" : ObjectId("5dc26dca2fa53037f6b559b0"), "date" : "5/20/2019" }
For this inputData here is aggregate pipeline only for sorting date field.
db.inputData.aggregate([{$sort:{date:-1}}])
By running above aggregate soring data is..
{ "_id" : ObjectId("5dc26d2e2fa53037f6b559ad"), "date" : ISODate("2018-04-18T18:30:00Z") }
{ "_id" : ObjectId("5dc26cf72fa53037f6b559ac"), "date" : ISODate("2017-04-18T18:30:00Z") }
{ "_id" : ObjectId("5dc26cb82fa53037f6b559ab"), "date" : ISODate("2017-04-14T18:30:00Z") }
{ "_id" : ObjectId("5dc26c292fa53037f6b559aa"), "date" : ISODate("2017-04-12T18:30:00Z") }
{ "_id" : ObjectId("5dc26b2e2fa53037f6b559a9"), "date" : ISODate("1970-01-01T00:00:00Z") }
{ "_id" : ObjectId("5dc26dca2fa53037f6b559b0"), "date" : "5/20/2019" }
{ "_id" : ObjectId("5dc26db12fa53037f6b559af"), "date" : "4/19/2019" }
{ "_id" : ObjectId("5dc26d722fa53037f6b559ae"), "date" : "4/19/2018" }
One important thing, for date field, if you want to convert it to date from string you have to run this script..
db.inputData.find().forEach(function(doc){
doc.date = new Date(doc.date);
db.inputData.save(doc);
})
Your first query ans is.
db.getCollection('inputData').aggregate(
[{ $match: { "_id": new mongoose.Types.ObjectId(objectId) } }
,{ $project: { "date": 1 }}
,{ $sort: { "date": -1 }}]
,function (err, docs) {}
)

Counting how many times unique values occur in an array across a MongoDB collection

So I have a collection of users. The user document is a very simple document, and looks like this:
{
username: "player101",
badges: ["score10", "score100"]
}
So how can I query to see how many times each unique value in the badges array occurs across the entire collection?
Use aggregation with $unwind and $group stages, where you can sum badges with $sum arithmetic operator
db.players.aggregate([
{
$unwind: "$badges"
},
{
$group:
{
_id: "$badges",
count: { $sum: 1 }
}
}
]);
on collection players with documents
{ "username" : "player101", "badges" : [ "score10", "score100" ] }
{ "username" : "player102", "badges" : [ "score11", "score100" ] }
{ "username" : "player103", "badges" : [ "score11", "score101" ] }
{ "username" : "player104", "badges" : [ "score12", "score100" ] }
gives you the result
{ "_id" : "score101", "count" : 1 }
{ "_id" : "score11", "count" : 2 }
{ "_id" : "score12", "count" : 1 }
{ "_id" : "score100", "count" : 3 }
{ "_id" : "score10", "count" : 1 }

And not working in mongodb

i have a collection named student like
{
"_id" : ObjectId("5693b549c4fd0e0bf4782d73"),
"nameIdentity" : [
{
"name" : "a"
},
{
"name" : "b"
}
]
},
{
"_id" : ObjectId("5693b549c4fd0e0bf4782d74"),
"nameIdentity" : [
{
"name" : "a"
}
]
},
{
"_id" : ObjectId("5693b549c4fd0e0bf4782d75"),
"nameIdentity" : [
{
"name" : "a"
},
{
"name" : "b"
}
]
},
{
"_id" : ObjectId("5693b549c4fd0e0bf4782d76"),
"nameIdentity" : [
{
"name" : "b"
}
]
}
i am trying to query using 'and' but its giving me some different output.
When i query for all the nameIdentities which has only name "a" db.student.find({"nameIdentity.name":"a"}) i am expecting the output as 1 where as i am getting 3. Again when i query for names with both a and b db.student.find({"nameIdentity.name":"a"},{"nameIdentity.sourceReferenceId.sourceName":"b"}) i am expecting output as 2 but i am getting 4. Can any one suggest me where am i going wrong?
use this.
db.student.find({"nameIdentity.name":"a","nameIdentity": { $size: 1 }})
in your query
db.student.find({"nameIdentity.name":"a"}) you are searching for data which nameIdentity is "a" not only a. and in your second query use like
db.student.find({"nameIdentity.name":"a","nameIdentity.name":"b"})