Mongodb object within array within array - arrays

I'm having this JSON data structure:
{
data :
{
fields_data :
[
[
{
key1 : val
},
{
key1 : val
}
],
[
{
key2 : val
},
{
key2 : val
}
]
]
}
}
I'm having hard time to query this, I'd tried to use the in and the elemMatch in other ways but with no success.
one of the queries I've tried :
{
"data.fields_data": {
$in: [{
$in: [{
$elemMatch:
{
$elemMatch : {
"key1": "random text"
}
}
}]
}]
}
}
Any suggestions?

Well, nested $elemMatch should work here:
db.collection.find{
'data.fields_data': {
$elemMatch: {
$elemMatch: {
key1: val
}
}
}
}
But you should consider getting rid of nested array here. For example, you could use array if objects instead:
{
data: {
fields_data: [{
data: [
{ key1: val },
{ key1: val }
]}, {
data: [
{ key2: val },
{ key2: val }
]}
]
}
}
It will allow you to query your documents using simple dot notation:
{ 'data.fields_data.data.key1': val }

Related

Update nested array object value by replacing substring of that object property

I have the following set of data in my MongoDB collection called orders:
{
"_id" : ObjectId("618e0e1b17687316dcdd6246"),
"groupUID": "abc",
"orderData" : {
"charges" : {
"total" : 18480.0,
"subtotal" : 13980.0
},
"items" : [
{
"name" : "Chocolate cookies",
"imageURL": "domainURL2.com/cookies"
},
{
"name" : "Chocolate muffins",
"imageURL": "domainURL2.com/muffins"
}
]
}
}
Now I want to update the imageURL substring part of "domainURL2" to "domainURL1" field in every document of this collection. I have the following query so far:
db.orders.update(
{
"groupUID" : "abc"
},
{ "$set": { "orderData.items.$.imageURL":"myURL.com" } } )
I also have the query in JavaScript form but I want this to be in pure Mongo query. So the query below will not do it for me unfortunately.
db.getCollection("orders").find({"groupUID" : "abc"}).forEach(function(aRow) {
if (aRow.orderDetails !== undefined) {
var updated = false;
aRow.orderData.items.forEach(function(item) {
item.imageURL = item.imageURL.replace("eddress/", "noknok-app/");
})
db.getCollection("orders").save(aRow);
}
});
I want to update all records' imageURL field's substring part. I am unable to figure out the rest of the query. Can anyone please help me?
My answer may look complex (Welcome for suggestion/improvement).
Work the update with Aggegration Pipeline.
$set - Update orderData.items field.
1.1. $map - Iterate orderData.items and returns new array.
1.1.1. $mergeObjects - Merge current object and imageURL field from 1.1.1.1.
1.1.1.1. $cond - With $regexMatch to find the imageURL starts with "domainURL2.com".
1.1.1.2. If true, then replace "domainURL2.com" with "domainURL1.com".
1.1.1.3. If false, remain existing value.
db.collection.update({
"groupUID": "abc"
},
[
{
"$set": {
"orderData.items": {
$map: {
input: "$orderData.items",
in: {
$mergeObjects: [
"$$this",
{
imageURL: {
$cond: {
if: {
$regexMatch: {
input: "$$this.imageURL",
regex: "^domainURL2.com"
}
},
then: {
$concat: [
"domainURL1.com",
{
$arrayElemAt: [
{
$split: [
"$$this.imageURL",
"domainURL2.com"
]
},
-1
]
}
]
},
else: "$$this.imageURL"
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground
Another approach is using $replaceOne (suggested by #rickhg12hs) which will be much easier.
$replaceOne to replace for 1.1.1.1.
db.collection.update({
"groupUID": "abc"
},
[
{
"$set": {
"orderData.items": {
$map: {
input: "$orderData.items",
in: {
$mergeObjects: [
"$$this",
{
imageURL: {
$replaceOne: {
input: "$$this.imageURL",
find: "domainURL2.com",
replacement: "domainURL1.com"
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground ($replaceOne)

Update multiple object values inside array in MongoDB with different values without looping, but pure query

I want to update this document in MongoDB
[
{
"persons" : [
{
"name":"Javier",
"occupation":"teacher"
},
{
"name":"Juliana",
"occupation":"novelist"
},
{
"name":"Henry",
"occupation":"plumber"
}
]
}
]
let's say I have a global array variable that contains this
var peopleGlobalVar = [
{
"name":"Javier",
"occupation":"gardener"
},
{
"name":"Henry",
"occupation":"postman"
}
]
I want to update the document with the value in globalVar WITHOUT common javascript looping, so if the names match, the occupation value will change, and I'm looking for the "pure query" solution.
the code I expect:
collection.update(
{},
{
$set: {
// code ...
}
},
{
$arrayFilter: [
{
"elem.name": {
$in:
$function : {
body: function(updatedPeopleFromGlobalVariable){
let names = [];
updatedPeopleFromGlobalVariable.forEach(function(person){
names.push(person.name);
return names;
},
args: peopleGlobalVar,
lang:"js",
}
},
"multi": true
}
]
}
)
What the output should be
[
{
"persons" : [
{
"name":"Javier",
"occupation":"gardener"
},
{
"name":"Juliana",
"occupation":"novelist"
},
{
"name":"Henry",
"occupation":"postman"
}
]
}
]
Anyone have the solution to this?

Match $and based on array of filter items in MongoDB

I have a Collection in MongoDB of CatalogItems. Every CatalogItem contains a product that has an array of metafields.
In these metafields there are 2 fields that are Brand_ID and Article_No
Example CatalogItem document:
CatalogItem = {
product: {
metafields: [
{
key: "Brand_ID",
value: "317"
},
{
key: "Article_No",
value: "48630"
}
]
}
}
I have an array of filters that is used to match the CatalogItems documents based on these metafields
filter array
filter = [
{ brandId: '317', articleId: '48630' },
{ brandId: '257', articleId: 'ZSA04036' }
]
I want to return all CatalogItems that match any of the exact combinations in filter.
For example to return the stated CatalogItem I currently use this query
// Checks for { brandId: '317', articleId: '48630' }
query = {
$and: [
{ "product.metafields": { $elemMatch: { key: "Brand_ID", value: filter[0].brandId } } },
{ "product.metafields": { $elemMatch: { key: "Article_No", value: filter[0].articleId } } },
]
}
The issue that I have is that in order for me to look trough all the filter items I have to increment the filter index and rerunning the query.
For example looking for the second filter index I would have to change
filter[0].brandId to filter[1].brandId
Is there a way in Mongo to query using a predefined array of objects instead of rerunning the query multiple times?
I figured out a way to set variables to the query using $or and .forEach()
let query = {
$or: []
};
filter = [
{ brandId: '317', articleId: '48630' },
{ brandId: '257', articleId: 'ZSA04036' }
]
filter.forEach(filterItem => {
query.$or.push(
{
$and: [
{ "product.metafields": { $elemMatch: { key: "Brand_ID", value: filterItem.brandId } } },
{ "product.metafields": { $elemMatch: { key: "Article_No", value: filterItem.articleId } } },
]
}
)
})

MongoDB: update from ObjectId to string for many documents

I have a complex structure similar to:
{
type: "Data",
data: [
"item1": {...},
"item2": {...},
...
"itemN": {
"otherStructure": {
"testData": [
{
"values": {...}
},
{
"values": {
"importantKey": ObjectId("23a2345gf651")
}
}
]
}
}
]
}
For such kind of data structure I want to update data type for all these importantKeys from ObjectId into string.
I was trying queries similar to:
db.collection.updateMany(
{type:"Data"},
{$toString: "data.$[element].otherStructure.testData.$[element].values.importantKey"},
{"data.element.otherStructure.testData.element.values.importantKey": {$type: "objectId"}})
But all these tries were no successful.
So, are there any adequate solutions for updating such data?
UPDATE
Sorry for confusing, my structure is more complex:
data.content.$[element].otherStructure.testData.keys.$[element].values.$[element].meta.importantKey
All these $[element] elements means objects with list of elements.
You may use this workaround:
db.collection.aggregate([
{
$addFields: {
"data.content": {
$map: {
input: "$data.content",
as: "data",
in: {
otherStructure: {
testData: {
keys: {
$map: {
input: "$$data.otherStructure.testData.keys",
as: "testData",
in: {
"values": {
$map: {
input: "$$testData.values",
as: "values",
in: {
"meta": {
"importantObject": {
"importantKey": {
$toString: "$$values.meta.importantObject.importantKey"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
},
{$out:"collection"}
])
MongoPlayground

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