MongoDB sorting does not work with inner array - mongodb

I'm trying to query specific fields in my document and sort them by one of the fields, however, the engine seems to completely ignore the sort.
I use the query:
db.symbols.find({_id:'AAPL'}, {'income_statement.annual.totalRevenue':1,'income_statement.annual.fiscalDateEnding':1}).sort({'income_statement.annual.totalRevenue': 1})
This is the output:
[
{
_id: 'AAPL',
income_statement: {
annual: [
{
fiscalDateEnding: '2021-09-30',
totalRevenue: '363172000000'
},
{
fiscalDateEnding: '2020-09-30',
totalRevenue: '271642000000'
},
{
fiscalDateEnding: '2019-09-30',
totalRevenue: '256598000000'
},
{
fiscalDateEnding: '2018-09-30',
totalRevenue: '265595000000'
},
{
fiscalDateEnding: '2017-09-30',
totalRevenue: '229234000000'
}
]
}
}
]
I would expect to have the entries sorted by fiscalDateEnding, starting with 2017-09-30 ascending.
However, the order is fixed, even if I use -1 for sorting.
Any ideas?

The sort you are using is for the ordering of documents in the result set. This is different from the ordering of array elements inside the document.
For your case, if you are using a newer version of MongoDB (5.2+), you can use the $sortArray.
db.symbols.aggregate([
{
$project: {
_id: 1,
annual: {
$sortArray: {
input: "$income_statement.annual",
sortBy: {
fiscalDateEnding: 1
}
}
}
}
}
])
If you are using older version of MongoDB, you can do the followings to perform the sorting.
db.collection.aggregate([
{
"$unwind": "$income_statement.annual"
},
{
$sort: {
"income_statement.annual.fiscalDateEnding": 1
}
},
{
$group: {
_id: "$_id",
annual: {
$push: "$income_statement.annual"
}
}
},
{
"$project": {
_id: 1,
income_statement: {
annual: "$annual"
}
}
}
])
Here is the Mongo Playground for your reference.

Related

mongodb : find documents that contains the most a specific kind of property

I have a collection "MyCollection" where each document could have 0,1 or many properties named "propertyX" where X is the nth time the property appears in the document.
For example, my collection could looks like this :
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
}
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"property4":xxxxxx
"property5":xxxxxx
}
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"property4":xxxxxx
"property5":xxxxxx
"property6":xxxxxx
"property7":xxxxxx
}
I'm trying to sort documents with a mongo query by the number of propertyX they had, so in my example, the third object must be at the top of my result (7 properties), then the second one (5 properties), then the first one (only 3 properties). What I've done for the moment, is implementing it in javascript (using mongo driver) and do a count of keys with the Object.Keys(myObject).filter(key=>key.startsWith('property')) but this is taking way too much time. Is there a way to do it directly with a mongo query, thanks
You can achieve this by using the aggregation tool.
First create a new property with the number of properties in you document, so you can use it to sort on a later stage:
db.getCollection("MyCollection").aggregate([
{
$addFields: {
properties: { $objectToArray: '$$ROOT' },
}
}
]);
At this stage your documents on the pipeline would look something like this:
{
"_id":ObjectId("XXXX")
"property1":xxxxxx
"property2":xxxxxx
"property3":xxxxxx
"properties: [
{ k: "_id", v: ObjectId("XXXX")},
{ k: "property1", v: xxxxxx },
{ k: "property2", v: xxxxxx },
{ k: "property3", v: xxxxxx },
]
}
Using the $size operator you can get the length of this array:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
}
...
So now you would have a property that shows the number of keys in your document:
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"properties: 4,
}
Which would make it possible to sort the documents by properties number:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
},
{
$sort: { properties: -1 }
}
...
To finish you can get rid of the property properties with $unset:
...
{
$addFields: {
properties: { $size: { $objectToArray: '$$ROOT' }},
}
},
{
$sort: { properties: -1 }
},
{
$unset: 'properties'
},
You would end up with the following:
[
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"property4":xxxxxx,
"property5":xxxxxx,
"property6":xxxxxx,
"property7":xxxxxx,
},
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
"property4":xxxxxx,
"property5":xxxxxx,
},
{
"_id":ObjectId("XXXX"),
"property1":xxxxxx,
"property2":xxxxxx,
"property3":xxxxxx,
}
]

Delete document that has size greater than a specific value

I have a collection which contains a multiple documents whose size has increased from 16MBs or is about to reach 16MBs.
I want query that finds documents which have size greater than 10MBs and delete all of them.
I am using following to find the size of document.
Object.bsonsize(db.test.findOne({type:"auto"}))
Is there a way to embed this query inside db.test.deleteMany() query?
This following query deletes the documents with size greater than the specified size (the size is specified in bytes). This query is valid with MongoDB v4.4 or higher.
db.collection.deleteMany( {
$expr: { $gt: [ { $bsonSize: "$$ROOT" }, SIZE_LIMIT ] },
type: "auto"
} )
The following script runs for MongoDB v4.2 or earlier:
const SIZE_LIMIT = 75 // substitute your value here in bytes
let idsToDelete = [ ]
let crsr = db.collection.find()
while(crsr.hasNext()) {
let doc= crsr.next()
if (Object.bsonsize(doc) > SIZE_LIMIT) {
idsToDelete.push(doc._id)
}
}
db.collection.deleteMany( { _id: { $in: idsToDelete } } )
I think you have to do it like this:
db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $gt: 16e6 } } },
]).forEach(function (doc) {
db.test.deleteOne({ _id: doc._id });
})
Or if you prefer deleteMany:
var ids = db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $lt: 16e6 } } }
]).toArray().map(x => x._id);
db.test.deleteMany({ _id: { $in: ids } });

MongoDB aggregation: How to get the index of a document in a collection depending sorted by a document property

Assume I have a collection with millions of documents. Below is a sample of how the documents look like
[
{ _id:"1a1", points:[2,3,5,6] },
{ _id:"1a2", points:[2,6] },
{ _id:"1a3", points:[3,5,6] },
{ _id:"1b1", points:[1,5,6] },
{ _id:"1c1", points:[5,6] },
// ... more documents
]
I want to query a document by _id and return a document that looks like below:
{
_id:"1a1",
totalPoints: 16,
rank: 29
}
I know I can query the whole document, sort by descending order then get the index of the document I want by _id and add one to get its rank. But I have worries about this method.
If the documents are in millions won't this be 'overdoing' it. Querying a whole collection just to get one document? Is there a way to achieve what I want to achieve without querying the whole collection? Or the whole collection has to be involved because of the ranking?
I cannot save them ranked because the points keep on changing. The actual code is more complex but the take away is that I cannot save them ranked.
Total points is the sum of the points in the points array. The rank is calculated by sorting all documents in descending order. The first document becomes rank 1 and so on.
an aggregation pipeline like the following can get the result you want. but how it operates on a collection of millions of documents remains to be seen.
db.collection.aggregate(
[
{
$group: {
_id: null,
docs: {
$push: { _id: '$_id', totalPoints: { $sum: '$points' } }
}
}
},
{
$unwind: '$docs'
},
{
$replaceWith: '$docs'
},
{
$sort: { totalPoints: -1 }
},
{
$group: {
_id: null,
docs: { $push: '$$ROOT' }
}
},
{
$set: {
docs: {
$map: {
input: {
$filter: {
input: '$docs',
as: 'x',
cond: { $eq: ['$$x._id', '1a3'] }
}
},
as: 'xx',
in: {
_id: '$$xx._id',
totalPoints: '$$xx.totalPoints',
rank: {
$add: [{ $indexOfArray: ['$docs._id', '1a3'] }, 1]
}
}
}
}
}
},
{
$unwind: '$docs'
},
{
$replaceWith: '$docs'
}
])

Mongoose sort elements by counting numbers

I have a documents like
{
data: [{"channel":"712064846325219432","message":1019},{"channel":"712064884812021801","message":4}],
user: '290494169783205888',
},
{
data: [{"channel":"712064846325219432","message":2000},{"channel":"712064884812021801","message":500}],
user: '534099893979971584',
}
So how can I count data's message and sort this documents by descending message?
Use aggregation pipeline stages $unwind and $group to count the message for each user then sort by the total number of messages. Check the example.
db.collection.aggregate([
{
$unwind: {
path: "$data"
}
},
{
$group: {
_id: "$user",
total_message: {
$sum: "$data.message"
}
}
},
{
$sort: {
total_message: -1
}
}
])
Results:
[
{
"_id": "534099893979971584",
"total_message": 2500
},
{
"_id": "290494169783205888",
"total_message": 1023
}
]
you can use Query.sort()
For descending order you can either use -1, desc or descending
Query.sort(message: -1)

return only the last level of the embedded property that is searched in a document

I have these documents.
db.test.find({"house.floor":1})
db.test.insertMany([{
"name":"homer",
"house": {
"floor": 1,
"room":
{
"bed": "bed_pink",
"chair":"chair_pink"
}
}
},
{
"name":"marge",
"house": {
"floor": 1,
"room":
{
"bed": "bed_blue",
"chair":"chair_red"
}
}
}]
)
db.test.find({"house.room.bed":"bed_blue"})
I want to return only the value of the last level of my search. in this case:
{
"bed": "bed_blue",
"chair":"chair_red"
}
the answer I get, is the whole document, I want to advance to a certain level of the query. how can I do it?
You can use aggregation for it
db.test.aggregate([
{ $match: { "house.room.bed":"bed_blue" } },
{ $replaceRoot: { newRoot: "$house.room" } }
])
You can use aggregate together with $match and $project for this as follows:
db.test.aggregate([
{
$match: {
"house.room.bed": "bed_blue"
}
},
{
$project: {
"_id": 0,
"bed": "$house.room.bed",
"chair": "$house.room.chair"
}
}
])
Further, you can modify the $project part as needed.
Here is a demo: https://mongoplayground.net/p/Fvll0LFIvQy