MongoDB - Replace embedded fields - database

I have the following collection:
[
{
"_id": "6021d4e8de525e00bb3623f1",
...otherFields,
"addonGroups": [
{
"_id": "6021d474143dbb00da601e6a",
...otherFields,
"addons": [
{
"name": "Nike\t珍珠",
...otherFields,
},
{
"name": "Adidas\t椰果",
...otherFields,
},
]
}
],
}
]
I want to replace the \t character for whitespace " " on all the addons name field
I have the following:
db.collection.aggregate([
{
$addFields: {
"addonGroups.addons.name": {
$replaceAll: {
input: "$addonGroups.addons.name",
find: "\t",
replacement: " "
}
}
}
}
])
I am getting this error:
query failed: (Location51746) PlanExecutor error during aggregation :: caused by :: $replaceAll requires that 'input' be a string, found: [["Nike 珍珠", "Adidas 椰果"]]

You can work with $map operator.
db.collection.aggregate([
{
$set: {
addonGroups: {
$map: {
input: "$addonGroups",
as: "addonGroup",
in: {
"$mergeObjects": [
"$$addonGroup",
{
"addons": {
$map: {
input: "$$addonGroup.addons",
as: "addon",
in: {
$mergeObjects: [
"$$addon",
{
"name": {
$replaceAll: {
input: "$$addon.name",
find: "\t",
replacement: " "
}
}
}
]
}
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground

Related

Mongodb $ (dollar sign) project query for arrays

I have the following data:
{
_id: "1"
transitions: [
{
"_id" : "11"
"name" : "Tr1"
"checkLists" : [
{ _id: "111", name: "N1"},
{ _id: "112", name: "N2"}
]
}
]
}
I used the following code to get the name N2 by query of _id:112
db.collection.findOne({ 'transitions.checkLists._id: new ObjectId("112") } }}, { 'transitions.checkLists.$': 1 })
but the result returns back both of them:
{ _id: ObjectId("1"),
transitions:
[ { checkLists:
[ { name: 'N1', _id: ObjectId("111") },
{ name: 'N2', _id: ObjectId("112") } ] } ] }
I would like to find and get only the name N2 by query of _id:112
Expected Result:
{ _id: ObjectId("1"),
transitions:
[ { checkLists:
[ { name: 'N2', _id: ObjectId("112") } ] } ] }
You can do it via the aggregation framework as follow:
db.collection.aggregate([
{
$match: {
"transitions.checkLists._id": "111"
}
},
{
"$addFields": {
"transitions": {
"$map": {
"input": "$transitions",
"as": "t",
"in": {
"$mergeObjects": [
"$$t",
{
"checkLists": {
"$filter": {
"input": "$$t.checkLists",
"as": "c",
"cond": {
$eq: [
"$$c._id",
"111"
]
}
}
}
}
]
}
}
}
}
},
{
"$addFields": {
transitions: {
$filter: {
input: "$transitions",
as: "elem",
cond: {
"$ne": [
"$$elem.checkLists",
[]
]
}
}
}
}
}
])
Explained:
Match the transitions.checkLists._id element in first stage.
Map/mergeobjects with filtered checklists to filter only the needed object from the transitions.checkLists array.
Remove the transitions elements where no checkList exists.
( this need to be done to remove elements for same document where there is no matching _id's )
Playground

mongodb: match documents without substring in array field

I'm trying to match documents without a substring in an array field, in an aggregation pipeline.
I believe I need to add a field and then do a $match on -1, but I'm stuck on $indexOfBytes to find substrings in an array field.
db.collectionName.aggregate([{$addFields:{indexOfA:{$indexOfBytes:['$arrayName.fieldName1.fieldName2','A']}}}])
I get the error:
MongoServerError: PlanExecutor error during aggregation :: caused by :: $indexOfBytes requires a string as the first argument, found: array
collection example:
[
{ arrayName: [ fieldName1: { fieldName2: 'A' } ] },
{ arrayName: [ fieldName1: { fieldName2: 'B' } ] },
{ arrayName: [ fieldName1: { fieldName2: 'C' } ] },
{ arrayName: [ ] }
]
desired result (:
[
{ arrayName: [ fieldName1: { fieldName2: 'B' } ] },
{ arrayName: [ fieldName1: { fieldName2: 'C' } ] },
{ arrayName: [ ] }
]
Maybe something like this:
db.collection.aggregate([
{
$addFields: {
arrayName: {
"$filter": {
"input": "$arrayName",
"as": "a",
"cond": {
$eq: [
{
$indexOfBytes: [
"$$a.fieldName1.fieldName2",
"A"
]
},
-1
]
}
}
}
}
},
{
$match: {
$expr: {
$ne: [
{
$size: "$arrayName"
},
0
]
}
}
}
])
Explained:
$filter arrayName elements not having A in fieldName2
$match only the non-empty arrayName arrays
playground

Concat a string prefix to the field of an object in an array in MongoDb

I have many mongoDb documents like so
{
store:"Jacks Pizza",
storeNumbers:[
{
"chef":"Giovanni",
"number":"7203305544"
}
]
},
store:"Felicias Kitchen",
storeNumbers:[
{
"chef":"Gina",
"number":"+19161214594"
}
]
I would like to append a "+1" prefix to all such numbers that don't have a +1 country code attached to them.
Here's what I have tried-
db.users.updateMany({
"storeNumbers.number" : {
$exists: true,
$ne:"",
$regex: /^(?!\+)^(?![a-zA-Z])/
}
},
[ {
$set : {
"storeNumbers.$.number" : {
"$concat": [ "+1" , "$storeNumbers.$.number"]
}
}
}
]
);
This gives me an error saying that I cannot perform concat on elements in an array.
How would you do this?
There is no straight way to do this, you can use update with aggregation pipeline starting from MongoDB 4.2,
match query if regex does not match "+1" string at the start of the number
$map to iterate loop of storeNumbers
$cond check condition if number does not match "+1" string at the start of the number and number field is not string and string type then concat "+1" before number using $concat otherwise do nothing
$mergeObjects to merge current object with new update number field
db.users.updateMany({
"storeNumbers.number": {
$exists: true,
$ne: "",
$not: {
$regex: "^\\+1"
}
}
},
[
{
$set: {
storeNumbers: {
$map: {
input: "$storeNumbers",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{
$and: [
{
$not: {
$regexMatch: {
input: "$$this.number",
regex: "^\\+1"
}
}
},
{
$ne: ["$$this.number", ""]
},
{
$eq: [{ $type: "$$this.number" }, "string"]
}
]
},
{
number: {
$concat: ["+1", "$$this.number"]
}
},
{}
]
}
]
}
}
}
}
}
])
Playground

Update value in nested array of mongo 3.5

I want to make an upsert call to update as well as insert my data in nested array of mongo db.
This is my mongo document.
{
"_id" : "575",
"_class" : "com.spyne.sharing.SpyneShareUserProject",
"spyneSharePhotoList" : [
{
"_id" : "fxLO68XyMR",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : false
},
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
]
},
{
"_id" : "nVpD0KoQAI",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : true
}
]
},
{
"_id" : "Pm0B3Q9Igv",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : true
}
]
}
]
}
Here my requirement is,
lets say i have an ID i.e. 575 (_id)
Then i will have the nested array ID i.e. fxLO68XyMR (spyneSharePhotoList._id)
Then i will have nested email id as ID i.e. chittaranjan#eventila.com (spyneSharePhotoList.spyneShareUsers._id) and selectedByClient (boolean)
Now i want is to check if this ID (spyneSharePhotoList.spyneShareUsers._id) is already present in the desired location i want to update the boolean value i.e. selectedByClient (true / false) according to that email id.
If the id is not present in the array, the it will make a new entry. as
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
in spyneShareUsers list.
Please help me to do this task. Thank you
Most likely this is not the smartest solution, but it should work:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR",
ShareUserId: "chittaranjan_new#eventila.com"
}
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$facet: {
root: [{ $match: {} }],
update: [
{ $unwind: "$spyneSharePhotoList" },
{ $match: { "spyneSharePhotoList._id": shareUserProject.PhotoListId } },
{
$set: {
"spyneSharePhotoList.spyneShareUsers": {
$concatArrays: [
{
$filter: {
input: "$spyneSharePhotoList.spyneShareUsers",
cond: { $ne: ["$$this._id", shareUserProject.ShareUserId] }
}
},
[{
_id: shareUserProject.ShareUserId,
selectedByClient: { $in: [shareUserProject.ShareUserId, "$spyneSharePhotoList.spyneShareUsers._id"] }
}]
]
}
}
}
]
}
},
{ $unwind: "$root" },
{ $unwind: "$update" },
{
$set: {
"root.spyneSharePhotoList": {
$concatArrays: [
["$update.spyneSharePhotoList"],
{
$filter: {
input: "$root.spyneSharePhotoList",
cond: { $ne: ["$$this._id", shareUserProject.PhotoListId] }
}
}
]
}
}
},
{ $replaceRoot: { newRoot: "$root" } }
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
I did not check whether all operators are available in MongoDB 3.5
My goal was to process everything in aggregation pipeline and run just a single replaceOne() at the end.
Here another solution based on $map operator:
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$set: {
spyneSharePhotoList: {
$map: {
input: "$spyneSharePhotoList",
as: "photoList",
in: {
$cond: {
if: { $eq: [ "$$photoList._id", shareUserProject.PhotoListId ] },
then: {
"_id": "$$photoList._id",
spyneShareUsers: {
$cond: {
if: { $in: [ shareUserProject.ShareUserId, "$$photoList.spyneShareUsers._id" ] },
then: {
$map: {
input: "$$photoList.spyneShareUsers",
as: "shareUsers",
in: {
$cond: {
if: { $eq: [ "$$shareUsers._id", shareUserProject.ShareUserId ] },
then: { _id: shareUserProject.ShareUserId, selectedByClient: true },
else: "$$shareUsers"
}
}
}
},
else: {
$concatArrays: [
"$$photoList.spyneShareUsers",
[ { _id: shareUserProject.ShareUserId, selectedByClient: false } ]
]
}
}
}
},
else: "$$photoList"
}
}
}
}
}
}
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
You can achieve the same result also with two updates:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR_x",
ShareUserId: "chittaranjan_new#gmail.com"
}
ret = db.collection.updateOne(
{ _id: shareUserProject.id },
{ $pull: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)
db.collection.updateOne(
{ _id: shareUserProject.id },
{ $push: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId, selectedByClient: ret.modifiedCount == 1 } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)

Updating data type to an Object in mongoDB

I have changed one of the fields of my collection in mongoDB from an array of strings to an array of object containing 2 strings. New documents get inserted without any problem, but when a get method is called to get , querying all the documents I get this error:
Failed to decode 'Students'. Decoding 'photoAddresses' errored
with: readStartDocument can only be called when CurrentBSONType is
DOCUMENT, not when CurrentBSONType is STRING.
photoAddresses is the field that was changed in Students.
I was wondering is there any way to update all the records so they all have the same data type, without losing any data.
The old version of photoAdresses:
"photoAddresses" : ["something","something else"]
This should be updated to the new version like this:
"photoAddresses" : [{photoAddresses:"something"},{photoAddresses:"something else"}]
The following aggregation queries update the string array to object array, only if the array has string elements. The aggregation operator $map is used to map the string array elements to objects. You can use any of the two queries.
db.test.aggregate( [
{
$match: {
$expr: { $and: [ { $isArray: "$photo" },
{ $gt: [ { $size: "$photo" }, 0 ] }
]
},
"photo.0": { $type: "string" }
}
},
{
$project: {
photo: {
$map: {
input: "$photo",
as: "ph",
in: { addr: "$$ph" }
}
}
}
},
] ).forEach( doc => db.test.updateOne( { _id: doc._id }, { $set: { photo: doc.photo } } ) )
The following query works with MongoDB version 4.2+ only. Note the update operation is an aggregation instead of an update. See updateMany.
db.test.updateMany(
{
$expr: { $and: [ { $isArray: "$photo" },
{ $gt: [ { $size: "$photo" }, 0 ] }
]
},
"photo.0": { $type: "string" }
},
[
{
$set: {
photo: {
$map: {
input: "$photo",
as: "ph",
in: { addr: "$$ph" }
}
}
}
}
]
)
[EDIT ADD]: The following query works with version MongoDB 3.4:
db.test.aggregate( [
{
$addFields: {
matches: {
$cond: {
if: { $and: [
{ $isArray: "$photoAddresses" },
{ $gt: [ { $size: "$photoAddresses" }, 0 ] },
{ $eq: [ { $type: { $arrayElemAt: [ "$photoAddresses", 0 ] } }, "string" ] }
] },
then: true,
else: false
}
}
}
},
{
$match: { matches: true }
},
{
$project: {
photoAddresses: {
$map: {
input: "$photoAddresses",
as: "ph",
in: { photoAddresses: "$$ph" }
}
}
}
},
] ).forEach( doc => db.test.updateOne( { _id: doc._id }, { $set: { photoAddresses: doc.photoAddresses } } ) )

Resources