Get child object before _id in mongo - mongodb

I'm trying to get the document before the current document from an collection.
The 'problem' is that this in an child of the document.
So let's say I have this document in the 'Shops' collection:
{
name: 'Test Shop',
invoices: [
{
_id: ObjectId("5c642436dc12625a909d8115"),
date: '2019-02-13 08:05:42.087Z',
value: 0
},
{
_id: ObjectId("5c6429bcc17f3d2e4c5dfb61"),
date: '2019-02-13 14:29:16.882Z',
value: 1
},
{
_id: ObjectId("5c642b32c17f3d2e4c5dfbdd"),
date: '2019-02-13 12:35:30.275Z',
value: 2
}
]
}
I have the latest invoice object with 'value: 2'
Now I want to fetch the object before this object. The object with 'value: 1'.
I'm trying to do that with this query, but It keeps me returning the first object (I think the first result for the search)
db.getCollection('shops').find({
'invoices._id': {
$lte: ObjectId("5c642b32c17f3d2e4c5dfbdd")
}
}, {'invoices.$':1}).sort({'invoices.date':1})
Is there a good way to only fetch the last result of the search, or do a good query?

Use $slice projection
db.collection.find(
{ },
{ "invoices": { "$slice": -1 }}
)
or $elemMatch projection
db.collection.find(
{ },
{ "invoices": { "$elemMatch": { "_id": ObjectId("5c642b32c17f3d2e4c5dfbdd") }}}
)

Related

MongoDB - arrayFilters and updateMany from same document

I have the following sample of data:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: null
},
...
]
}
and I would like to update the object effectiveDate based on seniorityDate in the array of assigned where system:'Team' only:
db.collection.updateMany({},
[{
$set: {
'assigned.$[elem].effectiveDate': '$seniorityDate'
}
}], {
arrayFilters: [{
"elem.system": "Team"
}]
})
but I got the following error:
arrayFilters may not be specified for pipeline-syle updates
The expected result will be:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: '2001-01-01T00:00:00Z'
},
...
]
}
How can I achieve it?
You can't use the arrayFilters with the aggregation pipeline at the same time. While you are updating the value from another field, hence you can only achieve with aggregation pipeline.
$set - Set assigned field.
1.1. $map - Iterate element in assigned array and return new array.
1.1.1. $mergeObjects - Merge current iterated document with the document from 1.1.1.1.
1.1.1.1. Document with effectiveDate field. With the $cond operator, if matches the condition, use the seniorityDate value, else remain the existing value.
db.collection.updateMany({},
[
{
$set: {
"assigned": {
$map: {
input: "$assigned",
in: {
$mergeObjects: [
"$$this",
{
effectiveDate: {
$cond: {
if: {
$eq: [
"$$this.system",
"Team"
]
},
then: "$seniorityDate",
else: "$$this.effectiveDate"
}
}
}
]
}
}
}
}
}
])
Thanks to #rickhg12hs' suggestion, always limit the document for better performance, as you know which document/field should be updated by condition.
Hence your update query with query condition will be as below:
db.collection.updateMany({
"assigned.system": "Team"
},
[
...
])
Demo # Mongo Playground

Mongoose sort by a field in the last element of a nested array

[{
_id: '1',
logs: [
{
createdAt: "2021-04-01",
},
{
createdAt: "2021-04-02",
},
],
},{
_id: '2',
logs: [
{
createdAt: "2021-04-03",
},
{
createdAt: "2021-04-04",
},
],
}]
I want to sort by the createdAt of the last element of logs in desc order. In this example item _id:2 should be the first and item _id:1 should be the second since 2021-04-02 is 'smaller' than 2021-04-04.
I tried 'logs[logs.length - 1].createdAt': -1 and 'logs.createdAt': -1 but they don't work.
To get sorted Object with respect to the last entry in each of the array,
you need to fetch the last date from the array which can be done using $last operator.
Next I am adding the field which has the entry of the last createdAt in a new field name - sortField.
I will sort it according to the sortField.
I will unset the sortField.
I know there would be better ways to do this, but it should do the job for the time being.
db.collection.aggregate([
{
"$addFields": {
"sortField": {
"$last": "$logs.createdAt"
}
}
},
{
$sort: {
"sortField": -1
}
},
{
$unset: "sortField"
}
])

How to query mongodb to fetch results based on values nested parameters?

I am working with MongoDB for the first time.
I have a collection whose each document is roughly of the following form in MongoDB:
{
"name":[
{
"value":"abc",
"created_on":"2020-02-06 06:11:21.340611+00:00"
},
{
"value":"xyz",
"created_on":"2020-02-07 06:11:21.340611+00:00"
}
],
"score":[
{
"value":12,
"created_on":"2020-02-06 06:11:21.340611+00:00"
},
{
"value":13,
"created_on":"2020-02-07 06:11:21.340611+00:00"
}
]
}
How will I form a query so that I get the latest updated values of each field in the given document. I went through Query Embedded Documents, but I wasn't able to figure out how It is.
My expected output is:
{
"name": "xyz",
"score": "13"
}
If you always do push new/latest values to arrays name & score, then you can try below query, it would get last element from array as in general new/latest values will always be added as last element in an array :
db.collection.aggregate([
{ $addFields: { name: { $arrayElemAt: ['$name', -1] }, score: { $arrayElemAt: ['$score', -1] } } },
{ $addFields: { name: '$name.value', score: '$score.value' } }])
Test : MongoDB-Playground

MongoDB sum with match

I have a collection with the following data structure:
{
_id: ObjectId,
text: 'This contains some text',
type: 'one',
category: {
name: 'Testing',
slug: 'test'
},
state: 'active'
}
What I'm ultimately trying to do is get a list of categories and counts. I'm using the following:
const query = [
{
$match: {
state: 'active'
}
},
{
$project: {
_id: 0,
categories: 1
}
},
{
$unwind: '$categories'
},
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: 1 }
}
}
]
This returns all categories (that are active) and the total counts for documents matching each category.
The problem is that I want to introduce two additional $match that should still return all the unique categories, but only affect the counts. For example, I'm trying to add a text search (which is indexed on the text field) and also a match for type.
I can't do this at the top of the pipeline because it would then only return categories that match, not only affect the $sum. So basically it would be like being able to add a $match within the $group only for the $sum. Haven't been able to find a solution for this and any help would be greatly appreciated. Thank you!
You can use $cond inside of your $group statement:
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: { $cond: [ { $eq: [ "$categories.type", "one" ] }, 1, 0 ] } }
}
}

MongoDB aggregate - filter by subdocument

I have a mongodb collection with structure like that:
[
{
name: "name1",
instances: [{value:1, score:2}, {value:2, score:5}, {value:2.5, score:9}]
},
{
name: "name2",
instances: [{value:6, score:3}, {value:1, score:6}, {value:3.7, score:5.2}]
}
]
When I want to get all the data from a document, I use aggregate because I want each instance returned as a separate document:
db.myCollection.aggregate([{$match:{name:"name1"}}, {$unwind:"$instances"}, {$project:{name:1, value:"$instances.value", score:"$instances.score"}}])
And everything works like I want it to.
Now for my question: I want to filter the returned data by score or by value. For example, I want an array of all the subdocuments of name1 which have a value greater or equal to 2.
I tried to add to the $match object 'instances.value':{$gte:2}, but it didn't filter anything, and I still get all 3 documents for this query.
Any ideas?
After unwinding instances then again used $match as below
db.collectionName.aggregate({
"$match": {
"name": "name1"
}
}, {
"$unwind": "$instances"
}, {
"$match": {
"instances.value": {
"$gte": 2
}
}
}, {
$project: {
name: 1,
value: "$instances.value",
score: "$instances.score"
}
})
Or if you tried $match after project then used as below
db.collectionName.aggregate([{
$match: {
name: "name1"
}
}, {
$unwind: "$instances"
}, {
$project: {
name: 1,
value: "$instances.value",
score: "$instances.score"
}
}, {
"$match": {
"value": {
"$gte": 2
}
}
}])