MongoDB push into nested array without using indices - node.js

I'm looking to $push something into a nested array, of which the parent array matches a simple property condition:
Here's how my document looks:
{
name: "Foo",
boardBucket: {
currentBoardId: 1234,
items: [ <- looking to push into `boardItems` of an `item` in this Array
{
boardId: 1234, <- that has `boardId: 1234`
boardItems: [ "barItem", "deyItem" ] <- Final Array I want to push to
}
]
}
}
So I'd like to push "fooItem" in boardItems of item that has boardId: 1234
Option 1: I can use dot notation and access by index
I can certainly do a $push by using dot.notation which uses the index of the item like so:
this.update({ '$push': {"boardBucket.items.0.boardItems": "fooItem" } });
But what if I don't know the index?
How can I push into boardItems of item with boardId: 1234 without using the indices (using the boardId instead)?
Note:
I'm using mongoose as the db driver
I'd like to avoid using mongoose's save() cause it tends to be buggy + it seems to keep a copy of the object locally which i'd like to avoid
Just direct update() mongo queries are what I'm after
I'd certainly like to avoid any type of whole-document fetching to perform this update as my documents are huge in size

I think this should do the trick:
this.update(
{"boardBucket.items": {$elemMatch: { boardId: "1234"}}},
{'$push': {"boardBucket.items.boardItems": "fooItem" }}
);

(Sorry for not sampling in the first place, was on a rush then)
db.myDb.insert({
name: "Foo",
boardBucket: {
currentBoardId: 1234,
items: [
{
boardId: 1234,
boardItems: [ "barItem", "deyItem" ]
},
{
boardId: 1235,
boardItems: [ "dontPushToThisOne" ]
}
]
}
});
db.myDb.insert({
name: "Foo2",
boardBucket: {
currentBoardId: 1236,
items: [
{
boardId: 1236,
boardItems: [ "dontPushToThisOne" ]
}
]
}
});
db.myDb.update(
{ "boardBucket.currentBoardId":1234,
"boardBucket.items.boardId":1234},
{ "$push" : {"boardBucket.items.$.boardItems":"fooItem"} }, {multi:1} );

Related

how can i sort data with a array element in mongodb without using unwind

this is my sample data in this I have a userId and a array "watchHistory", "watchHistory" array contains the list of videos that is watched by the user :
{
"_id": "62821344445c30b35b441f11",
"userId": 579,
"__v": 0,
"watchHistory": [
{
"seenTime": "2022-05-23T08:29:19.781Z",
"videoId": 789456,
"uploadTime": "2022-03-29T12:33:35.312Z",
"description": "Biography of Indira Gandhi",
"speaker": "andrews",
"title": "Indira Gandhi",
"_id": "628b45df775e3973f3a670ec"
},
{
"seenTime": "2022-05-23T08:29:39.867Z",
"videoId": 789455,
"uploadTime": "2022-03-31T07:37:39.712Z",
"description": "What are some healthy food habits to stay healthy",
"speaker": "morris",
"title": "Healthy Food Habits",
"_id": "628b45f3775e3973f3a670"
},
]
}
I need to match the userId and after that i need to sort it with "watchHistory.seenTime", seenTime field indicates when the user saw the video. so i need to sort like the last watched video should come first in the list.
I don't have permission to use unwind so can any one help me from this. Thank you.
If you are using MongoDB version 5.2 and above, you can use $sortArray operator in an aggregation pipeline. Your pipeline should look something like this:
db.collection.aggregate(
[
{"$match":
{ _id: '62821344445c30b35b441f11' }
},
{
"$project": {
_id: 1,
"userId": 1,
"__v": 1,
"watchHistory": {
"$sortArray": { input: "$watchHistory", sortBy: { seenTime: -1 }}
}
}
}
]
);
Please modify the filter for "$match" stage, according to the key and value you need to filter on. Here's the link to the documentation.
Without using unwind, it's not possible to do it via an aggregation pipeline, but you can use update method and $push operator, as a workaround like this:
db.collection.update({
_id: "62821344445c30b35b441f11"
},
{
$push: {
watchHistory: {
"$each": [],
"$sort": {
seenTime: -1
},
}
}
})
Please see the working example here

How to project specific fields from a queried document inside an array?

here is the document
formId: 123,
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
amount:250,
phone:123456789
},
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"success",
amount:250,
phone:123456791
}
]
I used $elemMatch to filter the array such that I get only the matched object.
const response = await Form.findOne({ formId:123 }, {
_id:0,
title: 1,
eventDate: 1,
responses: {
$elemMatch: { orderId: 98422 },
},
})
But this returns all the fields inside the object present in the array "responses".
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
amount:250,
phone:123456789
}
]
But I want only specific fields to be returned inside the object like this
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
}
]
How can i do that ?
Query
aggregation way to keep some members and edit them also
map on responses if orderId matches keep the fields you want, the others are replaced with null
filter to remove those nulls (members that didnt match)
here 2 matches if you want to keep only one member of the array you can use
[($first ($filter ...)]
*$elemMatch that you used can be combined with the $ project operator to avoid the aggregation, but with $ operator we get all the matching member (here you want only some fields so i think aggregation is the way)
Playmongo
aggregate(
[{"$match": {"formId": {"$eq": 123}}},
{"$project":
{"_id": 0,
"title": 1,
"eventDate": 1,
"responses":
{"$map":
{"input": "$responses",
"in":
{"$cond":
[{"$eq": ["$$this.orderId", 98422]},
{"name": "$$this.name",
"email": "$$this.email",
"paymentStatus": "$$this.paymentStatus"},
null]}}}}},
{"$set":
{"responses":
{"$filter":
{"input": "$responses", "cond": {"$ne": ["$$this", null]}}}}}])

updateOne nested Array in mongodb

I have a group collection that has the array order that contains ids.
I would like to use updateOne to set multiple items in that order array.
I tried this which updates one value in the array:
db.groups.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b'}}
)
That correctly updates (or sets) the array value with index 0.
However, I want to set more array values and updateOne also supports a pipeline so I tried this:
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1'}}
]
)
This does NOTHING if the order array is empty. But if it's not, it replaces every element in the order array with an object { 0: 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1' }.
I don't understand that behavior.
In the optimal case I would just do
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1'}},
{$set: {'order.1': 'otherid'}},
{$set: {'order.2': 'anotherone'}},
]
)
And that would just update the order array with the values.
What is happening here and how can I achieve my desired behavior?
The update by index position in the array is only supported in regular update queries, but not in aggregation queries,
They have explained this feature in regular update query $set operator documentation, but not it aggregation $set.
The correct implementation in regular update query:
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
{
$set: {
'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1',
'order.1': 'otherid',
'order.2': 'anotherone'
}
}
)
If you are looking for only an aggregation query, it is totally long process than the above regular update query, i don't recommend that way instead, you can format your input in your client-side language and use regular query.
If you have to use aggregation framework, try this (you will have to pass array of indexes and array of updated values separately):
$map and $range to iterate over the order array by indexes
$cond and $arrayElemAt to check if the current index is in the array of indexes that has to be updates. If it is, update it with the same index from the array of new values. If it is not, keep the current value.
NOTE: This will work only if the array of indexes that you want to update starts from 0 and goes up (as in your example).
db.collection.update({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{
"$set": {
"order": {
"$map": {
input: {
$range: [
0,
{
$size: "$order"
}
]
},
in: {
$cond: [
{
$in: [
"$$this",
[
0,
1,
2
]
]
},
{
$arrayElemAt: [
[
"b6386841-2ff7-4d90-af5d-7499dd49ca4b1",
"otherid",
"anotherone"
],
"$$this"
]
},
{
$arrayElemAt: [
"$order",
"$$this"
]
}
]
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/P4irM9Ouyza

How to update specific object inside the array?

I have a data that looks like below in MongoDB
{
_id: aasdfeasfeasdf,
todo: [
{_todoIde: 333, _with: []},
{_todoIde: 111, _with: []},
]
}
I want to $addToSet value to _todoIde: 333's _with like {_todoIde: 333, _with: [aaaa]},. How can I do it?
.updateOne(
{_id},
{ $addToSet: {}}
)
I got to the document but I can't specify that _todoIde: 333 to update just that one.
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array,
.updateOne(
{ _id: "aasdfeasfeasdf", "todo._todoIde": 333 },
{
$addToSet: {
"todo.$._with": "aaaa"
}
}
)
Playground
You have to add an extra condition to specify the todoIde
Try this:
db.collection.update(
{$and:[{_id: typeId},{'todo._todoIde': 333}]},
{$set: { "todo._todoIde.$._with":[a,b,c]}},
);

Aggregate using $size without exception if array doesn't exist [duplicate]

Trying to create a MongoDB data source with icCube. The idea is to return the size of an array as a new field. Something like :
$project:
{
"people": 1,
"Count myFieldArray" : {$size : "$myFieldArray" }
}
But I'm getting for some records the following error :
The argument to $size must be an Array, but was of type: EOO
Is there a way that size is 0 if the field is empty or not an array (getting rid of the error) ?
You can use the $ifNull operator here. It seems the field is either not an array or not present by the given error:
{ "$project": {
"people": 1,
"Count": {
"$size": { "$ifNull": [ "$myFieldArray", [] ] }
}
}}
Also you might want to check for the $type in your $match in case these do exist but are not an array.
From MongoDB 3.2 and newer, you can use $isArray to check if your field is an array along with the $cond operator to return the field on evaluating with $isArray:
{ "$project": {
"people": 1,
"myFieldArrayCount": {
"$size": {
"$cond": [
{ "$isArray": "$myFieldArray" },
"$myFieldArray",
[]
]
}
}
}}
Alternative solution would be to eliminate the documents with nulls using
$match: {myFieldArray: { $elemMatch: { $exists: true } }}
Also, document fields which are used as arguments to $size by '$' reference (here: "$myFieldArray") must also be the part of projections.
$project:
{
"people": 1,
"myFieldArray":1,
"Count myFieldArray" : {$size : "$myFieldArray" }
}

Resources