Mongoose query penultimate item from array of objects - arrays

I have schemas each containing an array of objects looking like this:
{name: String, _id: ObjectId}
Each schemas array has multiple items, I need to match the penultimate (next to last) items _id inside an aggregate function, which currently looks like this:
{ "$match": {$expr: {$gt: [{"$arrayElemAt": ["$path._id", -2]}, mongoose.Types.ObjectId(req.params.id)]}}},
{ "$project": {
"_id": 1,
"name": 1,
"isFile": {
"$cond": [{"$eq": [{"$ifNull":["$data", null]}, null]},false, true]
}
}
}
(the current match function is my attempt, however it does not work)
Any help would be greatly appreciated!

Answer: use $eq instead of $gt...

Related

MongoDB aggregation for finding an index of array nested object with $gt condition

I am trying to get the positional index of an array element (to push more elements at $position). I'm specifically targeting a single document (say, id=x)
Simplified document:
{
id:"x",
samples:[{"timestamp":123},{"timestamp":234},{"timestamp":345}}
}
Aggregation pipeline:
collection.aggregate([{"$match": {"id": "x"}},
{"$project": {"matchedIndex": {"$indexOfArray": ["$samples.timestamp", 234]}}}])
This works to return matchedIndex=1 .
Now I would like to find the index of 'samples' where timestamp is greater than say 235, which should return '2'
I have tried combinations of:
collection.aggregate([{"$match": {"UUID": "5fd41e35-5e49-O977-t091-6f228bc65e37"}},
{"$project": {"matchedIndex":
{"$indexOfArray": ["$samples.timestamp",
{"$gt": ["$$timestamp", 235]}]}}}])
I understand this wont work, I'm just not sure how to go about it.
The purpose is to insert many elements between timestamps. Maybe there is a better way entirely to do this. Thanks
Instead of using "$samples.timestamp" as first argument you can use $map there to transform it into an array of true and false values depending on your condition, try:
{
"$project": {
"matchedIndex": {
"$indexOfArray": [
{ $map: { input: "$samples.timestamp", in: { $gte: [ "$$this", 235 ] } } },
true
]
}
}
}
Mongo Playground

JsonPath - Filter Array and get only the first element

Im trying to filter the elements of this JSON array to return only the first element it will find.
{
"elements": [{
"urn": "urn:li:lyndaCourse:189800",
"details": {
"classifications": [{
"associatedClassification": {
"urn": "urn:li:lyndaCategory:9331",
"type": "LIBRARY"
}
},
{
"associatedClassification": {
"urn": "urn:li:lyndaCategory:8982",
"type": "SUBJECT"
}
},
{
"associatedClassification": {
"urn": "urn:li:lyndaCategory:8920",
"type": "LIBRARY"
}
}
]
}
}]
}
But this results in an EMPTY array [].
I tried this JSONPATH query in https://jsonpath.herokuapp.com/
$.elements[0].details.classifications..associatedClassification[?(#.type=='LIBRARY')][0]
Expecting to get:
[{
"urn": "urn:li:lyndaCategory:9331",
"type": "LIBRARY"
}]
Another way to filter the information is by filtering the property "classification" (without using ".."), and use "associatedClassification.type" in your filter, so you should have something like this:
$.elements[0].details.classifications[?(#.associatedClassification.type=='LIBRARY')]
With the above JSONPATH you will have all items which type is "LIBRARY" (in your example will return 2 items).
You mentioned you need only the first one of the filtered items, as far as I investigated it seems there's no posible solution to return only the first item using only JSONPATH (see the thread bellow):
https://github.com/json-path/JsonPath/issues/272
The following works on all elements:
$..classifications[?(#.associatedClassification.type=='LIBRARY')]
and returns a list of all matching associatedClassification objects.
JSONPath is expected to point inside the original document. Therefore, getting the first element from the result list would require post-processing, e.g. a separate JSONPath, e.g.
$.[0]

Mongo find position by id in an Array of Objects

I have a range of documents
{
_id: ObjectId("5e388da4df54cb8efb47e61b"),
userId:'test_user'
productId:'product_6_id'
recommendations:{
_id:123
rankedList:[
0:{id:ObjectId('product_5_id'),Name:'Product_5'},
1:{id:ObjectId('product_6_id'),Name:'Product_6'},
2:{id:ObjectId('product_3_id'),Name:'Product_3'}],
Date:'2020-02-25T05:03:55.439+00:00'
}
},
{
_id: ObjectId("5e388da4df54cb8efb47e62b"),
userId:'test_user1'
productId:'product_3_id'
recommendations:{
_id:123
rankedList:[
0:{id:ObjectId('product_1_id'),Name:'Product_1'},
1:{id:ObjectId('product_5_id'),Name:'Product_5'},
2:{id:ObjectId('product_3_id'),Name:'Product_3'}],
Date:'2020-02-25T05:03:55.439+00:00'
}
}
and I need to find each time the position of productId within the Array of objects rankedList.
Thus here the answer would be positionIndex=1 for first doc and positionIndex=2 for second document.
I am quite confused with $indexOfArray and how I should use it here with aggregate.
Yes, you need $indexOfArray. The tricky part is that recommendations.rankedList is an array of objects however MongoDB allows you to use following expression:
$recommendations.rankedList.id
which evaluates to a list of strings, ['product_5_id', 'product_6_id', 'product_3_id'] in this case so your code can look like this:
db.collection.aggregate([
{
$project: {
index: {
$indexOfArray: [ "$recommendations.rankedList.id", "$productId" ]
}
}
}
])
Mongo Playground

How do I create a query from an array with mongoose?

So I have this array with 3 IDs: ["56ba576f66b9add00e4d3082","56b9252266b9add00e4d3080","56b908f166b9add00e4d307f"]
I want to build a query that says something like that:
userModel.find({ $or:
[
{_id:"56ba576f66b9add00e4d3082"},
{_id:"56ba576f66b9add00e4d3082"},
{_id:"56ba576f66b9add00e4d3082"}
]},
{email:true});
Is there some sort of a method to use in order to produce such query with the array?
Should I be using mapReduce or the $where method or anything else? (I never used those before so I'm not sure if this is the case or not)
Perhaps I'm asking the wrong question? Any recommendations are very much welcome.
If you just have one element then just use $in:
var array = [
"56ba576f66b9add00e4d3082",
"56b9252266b9add00e4d3080",
"56b908f166b9add00e4d307f"
];
userModel.find({ "_id": { "$in": array } }, { "email": true });
An $in condition is basically an $or applied to the same field.
Try using the map function:
array = ["56ba576f66b9add00e4d3082","56b9252266b9add00e4d3080","56b908f166b9add00e4d307f"]
userModel.find({ $or: array.map(function(id) {
return {_id: id};
})}, {email:true});

Updating nested arrays in mongodb

I have a document in mongodb with 2 level deep nested array of objects that I need to update, something like this:
{
id: 1,
items: [
{
id: 2,
blocks: [
{
id: 3
txt: 'hello'
}
]
}
]
}
If there was only one level deep array I could use positional operator to update objects in it but for second level the only option I've came up is to use positional operator with nested object's index, like this:
db.objects.update({'items.id': 2}, {'$set': {'items.$.blocks.0.txt': 'hi'}})
This approach works but it seems dangerous to me since I'm building a web service and index number should come from client which can send say 100000 as index and this will force mongodb to create an array with 100000 indexes with null value.
Are there any other ways to update such nested objects where I can refer to object's ID instead of its position or maybe ways to check if supplied index is out of bounds before using it in query?
Here's the big question, do you need to leverage Mongo's "addToSet" and "push" operations? If you really plan to modify just individual items in the array, then you should probably build these arrays as objects.
Here's how I would structure this:
{
id: 1,
items:
{
"2" : { "blocks" : { "3" : { txt : 'hello' } } },
"5" : { "blocks" : { "1" : { txt : 'foo'}, "2" : { txt : 'bar'} } }
}
}
This basically transforms everything in to JSON objects instead of arrays. You lose the ability to use $push and $addToSet but I think this makes everything easier. For example, your query would look like this:
db.objects.update({'items.2': {$exists:true} }, {'$set': {'items.2.blocks.0.txt': 'hi'}})
You'll also notice that I've dumped the "IDs". When you're nesting things like this you can generally replace "ID" with simply using that number as an index. The "ID" concept is now implied.
This feature has been added in 3.6 with expressive updates.
db.objects.update( {id: 1 }, { $set: { 'items.$[itm].blocks.$[blk].txt': "hi", } }, { multi: false, arrayFilters: [ { 'itm.id': 2 }, { 'blk.id': 3} ] } )
The ids which you are using are linear number and it has to come from somewhere like an additional field such 'max_idx' or something similar.
This means one lookup for the id and then update. UUID/ObjectId can be used for ids which will ensure that you can use Distributed CRUD as well.
Building on Gates' answer, I came up with this solution which works with nested object arrays:
db.objects.updateOne({
["items.id"]: 2
}, {
$set: {
"items.$.blocks.$[block].txt": "hi",
},
}, {
arrayFilters: [{
"block.id": 3,
}],
});
MongoDB 3.6 added all positional operator $[] so if you know the id of block that need update, you can do something like:
db.objects.update({'items.blocks.id': id_here}, {'$set': {'items.$[].blocks.$.txt': 'hi'}})
db.col.update({"items.blocks.id": 3},
{ $set: {"items.$[].blocks.$[b].txt": "bonjour"}},
{ arrayFilters: [{"b.id": 3}] }
)
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#update-nested-arrays-in-conjunction-with
This is pymongo function for find_one_and_update. I searched a lot to find the pymongo function. Hope this will be useful
find_one_and_update(filter, update, projection=None, sort=None, return_document=ReturnDocument.BEFORE, array_filters=None, hint=None, session=None, **kwargs)
Example
db.pymongo_object.find_one_and_update( filter = {'id' : 1}, update= {$set: {"items.$[array1].blocks.$[array2].txt": "hi"}}, array_filters =[{"array1.id" :2}, {"array2.id": 3}])
Also see pymongo documentation.

Resources