Image this data structure :
{
_id: 1
...
sections: [
{
Title: 'hello world',
Title2: '',
},
{
Title: 'goodbye',
Title2: ''
}
]
}
I need to update all Title2 from Title.
I tried things like :
db...update(
{ },
[{ $set:
{
"sections.Title2": "sections.Title",
}
}])
but without success. Also tried with updateMany and some variants like sections.$.Title2.
Thank you for any help
You can do via update + aggregation pipleine(mongod 4.2+) & $map as follow:
db.collection.update({
sections: {
$exists: true
}
},
[
{
$addFields: {
"sections": {
$map: {
input: "$sections",
as: "s",
in: {
"Title": "$$s.Title",
"Title2": "$$s.Title"
}
}
}
}
}
],
{
multi: true
})
Explained:
Find and replace the existing sections with mapped the necessary array values for Title2 to Title.
Add the option {multi:true} to update all documents in collection
playground
Improved version2:
db.collection.update({
sections: {
$exists: true
}
},
[
{
$addFields: {
"sections": {
$map: {
input: "$sections",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
"Title2": "$$s.Title"
}
]
}
}
}
}
}
],
{
multi: true
})
Explained:
In this version you merge the changed value with the subdocument so you dont need to add every of the other fields that are nto going to be changed.
playground2
Related
In MongoDB I have collection of users. There I have such document:
{
_id: ObjectId("619365c54a7d53716438933e"),
name: 'Chris',
hobbies: [
{ title: 'Sports', frequency: 5 },
{ title: 'Cooking', fequency: 5 },
{ title: 'Hiking', frequency: 1 }
],
isSporty: true,
cookMaster: true,
age: 35 }
So, it has array value on hobbies and there during insert I made a mistake. When I inserted hobby Cooking, I added key fequeny instead of frequency. Now I want to fix this. In Mong exists such operator as $rename. I tried:
db.users.updateMany({name:"Chris"}, {$rename: {"hobbies.fequency": "frequency"}})
But got error. How can I rich and change this key properly?
Simply do an update with a $set to correct the field name with $map.
db.collection.update({},
[
{
"$set": {
"hobbies": {
"$map": {
"input": "$hobbies",
"as": "h",
"in": {
title: "$$h.title",
frequency: {
"$ifNull": [
"$$h.frequency",
"$$h.fequency"
]
}
}
}
}
}
}
])
Here is the Mongo playground for your reference.
$set - $map hobbies array field, with $mergeObject to add new field frequency.
$unset - Remove field for fequency in hobbies array field.
db.collection.update({
name: "Chris"
},
[
{
$set: {
"hobbies": {
$map: {
input: "$hobbies",
in: {
$mergeObjects: [
"$$this",
{
frequency: "$$this.fequency"
}
]
}
}
}
}
},
{
$unset: "hobbies.fequency"
}
])
Sample Mongo Playground
Given this aggregation pipeline:
[
{
$addFields: {
_myVar: "x"
}
},
{
$match: {
array: "x"
}
}
]
How can the field with value x only be set once?
For example, this does not work, it times out:
[
{
$addFields: {
_myVar: "x"
}
},
{
$match: {
$expr: {
$in: [
"$_myVar", "$array"
]
}
}
}
]
The variable needs to be available throughout the pipeline, so only using the value in the $match stage as condition is not a solution.
What is the solution?
You can do something like this here i added two fields and checking if _myArray has _myVar, this is just to explain how can you check... in your case you have to replace _myArray with your actual array against which you want t to match
[{
$addFields: {
_myVar: "x",
_myArray: ['X', 'Y', 'x']
}
}, {
$addFields: {
has: {
$in: ["$_myVar", "$_myArray"]
}
}
}, {
$match: {
has: true
}
}]
I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
Playground
I have a nested array structured like this
{
"_id": "5dff75968102f11e20ae888e",
"docID": "Employees",
"data": [
[
"234768237",
"Value1",
],
[
"234768238",
"Value2"
],
[
"234768239",
"Value3"
]
]
}
Inside the nested data array, first element is employee ID, Ex: 234768237.
I would want to pass an array of employee ID's and delete the matching ones, for ex : [234768237, 234768238]
What i have tried so far
let employeeIDArray = [234768237, 234768238];
collection.updateOne(
{ docID: "Employees" },
{
$pull: {
"data.$[]": { "0": { $each: employeeIDArray } }
}
}
);
You're close, try:
let employeeIDArray = [234768237, 234768238];
collection.updateOne(
{ docID: "Employees" },
{
$pull: {
"data": { $in: employeeIDArray }
}
}
);
The $each modifier is only available for $push and $addToSet actions, read more about it here.
EDIT:
For Nested Array use $pullAll instead of $pull like so:
collection.updateOne(
{ docID: "Employees" },
{
$pullAll: {
"data.$[]": employeeIDArray
}
});
If you are using MongoDB's latest version then you can use below query to update using aggregation pipeline:
collection.updateOne(
{ docId: "Employees" },
[ {
$set: {
data: {
$filter: {
input: "$data",
cond: { $in: [ { $arrayElemAt: [ "$$this", 0 ] }, [ "234768237", "234768238" ] ] } }
}
}
} ],
{ upsert: true }
)
I have an array which looks like this
const posts = [{ _id: '1', viewsCount: 52 }, ...]
Which corresponds to mongodb documents in posts collection
{
_id: '1',
title: 'some title',
body: '....',
}, ...
I want to perform an aggregation which would result in documents fetched from the posts collection to have a viewsCount field. I'm not sure how I should form my aggregation pipeline:
[
{ $match: {} },
{ $addFields: { viewsCount: ?? } }
]
UPDATE
So far the following code almost does the trick:
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } } },
]
But viewsCount in this case turns to be an object, so I guess I need to add $project
UPDATE
I've found out one possible solution which is to use $addFields stage twice - overriding the first viewsCount
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } } },
{ $addFields: { viewsCount: '$viewsCount.viewsCount' } }
]
But is there a better/more concise solution?
UPDATE
This pipeline actually works correct:
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ postsIds, '$_id' ] } ] } } },
{ $addFields: { viewsCount: '$viewsCount.viewsCount' } }
]
I have updated the second stage by replacing posts with postsIds
To have a more concise solution (one-stage) you can use $let operator which lets you to define temporary variable that can be then used inside your expression, try:
db.posts.aggregate([
{ $addFields: {
viewsCount: {
$let: {
vars: { viewsCountObj: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } },
in: "$$viewsCountObj.viewsCount"
}
} }
}
])