Nested Query on Array in MongoDB collection - arrays

I have below User collection and I want find/update query to update nested array elements.
{
_id:"000-0000-0001",
Roles :{
0000-0000-0011: //EngagementId
["0000-0000-0111", "0000-0000-0112", "0000-0000-3333"],//RoleId
"0000-0000-0012" :
["0000-0000-0121", "0000-0000-0112"]
}
},
{
_id:"000-0000-0002",
Roles :{
"0000-0000-0021" : [ "0000-0000-0222", "0000-0000-0112"],
"0000-0000-0022" : [ "0000-0000-0121", "0000-0000-0112"],
"0000-0000-0022" : [ "0000-0000-0121", "0000-0000-0112", "0000-0000-3333"]
}
}
Requirement: I want to pull RoleId 0000-0000-3333 if the array have combination of 0000-0000-3333 and 0000-0000-0112
Below is expected result :
{
_id:"000-0000-0001",
Roles :{
"0000-0000-0011" : ["0000-0000-0111", "0000-0000-0112"],
"0000-0000-0012" : ["0000-0000-0121", "0000-0000-0112"]
}
},
{
_id:"000-0000-0002"
Roles :{
"0000-0000-0021" : [ "0000-0000-0222", "0000-0000-0112"],
"0000-0000-0022" : [ "0000-0000-0121", "0000-0000-0112"],
"0000-0000-0022" : [ "0000-0000-0121", "0000-0000-0112"]
}
}
Note : Find/update will work on Key:value or if it is nested then key.key:value, but in above example we have key.value.[values]:$pull(value) and that's the challaenge.

The data model necessitates a complicated update with a pipeline.
Here' one way to do it.
db.collection.update({
"Roles": {"$exists": true}
},
[
{
"$set": {
"Roles": {
"$arrayToObject": {
"$map": {
"input": {"$objectToArray": "$Roles"},
"as": "roleKV",
"in": {
"k": "$$roleKV.k",
"v": {
"$cond": [
{
"$and": [
{"$in": ["0000-0000-0112", "$$roleKV.v"]},
{"$in": ["0000-0000-3333", "$$roleKV.v"]}
]
},
{
"$filter": {
"input": "$$roleKV.v",
"cond": {"$ne": ["$$this", "0000-0000-3333"]}
}
},
"$$roleKV.v"
]
}
}
}
}
}
}
}
],
{"multi": true}
)
Try it on mongoplayground.net.

Related

Mongo Query to modify the existing field value with new value + array of objects

I want to update many documents based on the condition in MongoDB.
MODEL is the collection which has the document with below information.
"info": [
{
"field1": "String1",
"field2": "String2"
},
{
"field1": "String1",
"field2": "String_2"
}
],
"var": "x"
I need to update all the "String1" value of field1 with "STRING_NEW". I used the below query to update but not working as expected.
db.model.updateMany(
{ "info.field1": { $exists: true } },
[
{ "$set": {
"info": {
"$map": {
"input": "$info.field1",
"in": {
"$cond": [
{ "$eq": ["$$this.field1", "String1"] },
"STRING_NEW",
$$this.field1
]
}
}
}
} }
]
)
Please have a look and suggest if anything is to be modified in the above query.
Solution 1
With the update with aggregation pipeline, you should iterate the object in info array and update the iterated object by merging the current object with new field1 field via $mergeObjects.
db.model.updateMany({
"info.field1": "String1"
},
[
{
"$set": {
"info": {
"$map": {
"input": "$info",
"in": {
"$cond": [
{
"$eq": [
"$$this.field1",
"String1"
]
},
{
$mergeObjects: [
"$$this",
{
"field1": "STRING_NEW"
}
]
},
"$$this"
]
}
}
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2
Can also work with $[<identifier>] positional filtered operator and arrayFilters.
db.model.updateMany({
"info.field1": "String1"
},
{
"$set": {
"info.$[info].field1": "STRING_NEW"
}
},
{
arrayFilters: [
{
"info.field1": "String1"
}
]
})
Demo Solution 2 # Mongo Playground

How can I find subdocument using Mongoose?

I have defined a model like this.
const ShotcountSchema = new Schema({
shotCountId : {
type : ObjectId,
required : true,
ref : 'member-info'
},
userId : {
type : String,
required : true,
unique : true
},
shot : [{
shotId : {
type : String,
required : true,
unique : true
},
clubType : {
type : String,
required : true
}
createdAt : {
type : Date,
default : Date.now
}
}]
});
If you perform a query to find subdocuments based on clubType as follows, only the results of the entire document are output.
For example, if I write the following code and check the result, I get the full result.
const shotCount = await ShotcountSchema.aggregate([
{
$match : { shotCountId : user[0]._id }
},
{
$match : { 'shot.clubType' : 'driver' }
}
]);
console.log(shotCount[0]); // Full result output
I would like to filter the subdocuments via clubType or createdAt to explore. So, I want these results to be printed.
{
_id: new ObjectId("61d67f0a74ec8620f34c57ed"),
shot: [
{
shotId: 'undefinedMKSf*Tf#!qHxWpz1hPzUBTz%',
clubType: 'driver',
shotCount: 20,
_id: new ObjectId("61d67f0a74ec8620f34c57ef"),
createdAt: 2022-01-06T05:32:58.391Z
}
]
}
How should I write the code?
db.collection.aggregate([
{
"$match": {
_id: ObjectId("61d67f0a74ec8620f34c57ed"),
"shot.clubType": "driver",
shot: {
$elemMatch: {
$and: [
{
"createdAt": {
$gte: ISODate("2022-01-07T05:32:58.391Z")
}
},
{
"createdAt": {
$lte: ISODate("2022-01-09T05:32:58.391Z")
}
}
]
}
}
}
},
{
"$set": {
shot: {
"$filter": {
"input": "$shot",
"as": "s",
"cond": {
$and: [
{
"$eq": [
"$$s.clubType",
"driver"
]
},
{
"$gte": [
"$$s.createdAt",
ISODate("2022-01-07T05:32:58.391Z")
]
},
{
"$lte": [
"$$s.createdAt",
ISODate("2022-01-09T05:32:58.391Z")
]
}
]
}
}
}
}
}
])
mongoplayground

Group and Sum multi-dimensional array after unwinding elements

I have a multidimensional array below is my JSON data, I want the output mentioned below. Thanks for the help. I have tried two methods, 1. project with map there I am unable to group the data.
2. tried with unwind and group there unable to get the inside errorField array ("recordCount" : 73).
By using groupBy key needs to group by the results, if filed key matches & inside field--> detail key matches we need to merge those do document.
My JSON:
[
{
"_id":{
"$oid":"60f5c60fbc43cb00965ac641"
},
"groupBy":{
"$oid":"60f5c60fbc43cb00965ac641"
},
"employer":{
"$oid":"60d0e4001a6ccd764cb26df4"
},
"parameters":{
"begin":"2020-01-01",
"end":"2020-12-31"
},
"recordCount":74,
"errorFields":[
{
"field":"employeeStatus",
"recordCount":62,
"errorDetails":[
{
"recordIds":[
"10000986",
"10000990",
"10001020"
],
"detail":"Active employees should not have term date. Termed employees should have term date.",
"recordCount":3
},
{
"recordIds":[
"10001032"
],
"detail":"Stale pay period data (no new check for over 30 days from queried end date)",
"recordCount":1
}
]
},
{
"field":"ytdGrossWages.ytdTotalGrossWages",
"recordCount":8,
"errorDetails":[
{
"recordIds":[
"10001211",
"10001269",
"10001328",
"10001395"
],
"detail":"YTD total does not equal sum of YTD wage buckets",
"recordCount":4
}
]
}
],
"timestamp":{
"$date":"2021-07-19T18:35:59.031Z"
},
"__v":0
},
{
"_id":{
"$oid":"60f5c615bc43cb00965ac647"
},
"groupBy":{
"$oid":"60f5c60fbc43cb00965ac641"
},
"employer":{
"$oid":"60d0e4001a6ccd764cb26df4"
},
"parameters":{
"begin":"2020-01-01",
"end":"2020-12-31"
},
"recordCount":11,
"errorFields":[
{
"field":"employeeStatus",
"recordCount":11,
"errorDetails":[
{
"recordIds":[
"10003644",
"10003680"
],
"detail":"Active employees should not have term date. Termed employees should have term date.",
"recordCount":2
},
{
"recordIds":[
"10003667",
"10003694",
"10003807",
"10003789"
],
"detail":"Stale pay period data (no new check for over 30 days from queried end date)",
"recordCount":4
}
]
},
{
"field":"ssn",
"recordCount":2,
"errorDetails":[
{
"recordIds":[
"10003667"
],
"detail":"The ssn field is required.",
"recordCount":1
},
{
"recordIds":[
"10003694"
],
"detail":"The ssn must be 9 digits.",
"recordCount":1
}
]
},
{
"field":"employeeHomeAddressCountry",
"recordCount":1,
"errorDetails":[
{
"recordIds":[
"10003694"
],
"detail":"The employeeHomeAddressCountry field is required.",
"recordCount":1
}
]
}
],
"timestamp":{
"$date":"2021-07-19T18:36:05.135Z"
},
"__v":0
}
]
I want output like this:
{
"_id" : ObjectId("60f5c60fbc43cb00965ac641"),
"errorFields" : [
{
"field" : "employeeStatus",
"recordCount" : 73,
"errorDetails" : [
{
"recordIds" : [
"10001032",
"10003667",
"10003694",
"10003807",
"10003789"
],
"detail" : "Stale pay period data (no new check for over 30 days from queried end date)",
"recordCount" : 5
},
{
"recordIds" : [
"10000986",
"10000990",
"10001020",
"10001031",
"10001035"
],
"detail" : "Active employees should not have term date. Termed employees should have term date.",
"recordCount" : 5
}
]
},
{
"field" : "ytdGrossWages.ytdTotalGrossWages",
"recordCount" : 8,
"errorDetails" : [
{
"recordIds" : [
"10001211",
"10001269",
"10001328",
"10001395"
],
"detail" : "YTD total does not equal sum of YTD wage buckets",
"recordCount" : 8
}
]
},
{
"field" : "ssn",
"recordCount" : 2,
"errorDetails" : [
{
"recordIds" : [
"10003667"
],
"detail" : "The ssn field is required.",
"recordCount" : 1
},
{
"recordIds" : [
"10003694"
],
"detail" : "The ssn must be 9 digits.",
"recordCount" : 1
}
]
},
{
"field":"employeeHomeAddressCountry",
"recordCount":1,
"errorDetails":[
{
"recordIds":[
"10003694"
],
"detail":"The employeeHomeAddressCountry field is required.",
"recordCount":1
}
]
}
]
}
Here is the mycode method 1:
db.collection.aggregate([
{ $match: { groupBy: ObjectId("60f5c60fbc43cb00965ac641") } },
{ "$project": {
"_id": "$groupBy",
"errorFields": { "$map": {
"input": "$errorFields",
"as": "ef",
"in": {
"field": "$$ef.field",
"recordCount": {
$sum:"$$ef.recordCount"
},
"errorDetails": { "$map": {
"input": "$$ef.errorDetails",
"as": "ed",
"in": {
"detail": "$$ed.detail",
"recordIds": { "$map": {
"input": "$$ed.recordIds",
"as": "ri",
"in": {
$concat: [ "$$ri"]
}
}},
"recordCount": {
$size:"$$ed.recordIds"
}
}
}}
}
}}
}}
]).pretty()
Here is the mycode method 2:
db.collection.aggregate([
{ $match: { groupBy: ObjectId("60f5c60fbc43cb00965ac641") } },
{ $unwind: "$errorFields" },
{ $unwind: "$errorFields.errorDetails" },
{ $unwind: "$errorFields.errorDetails.recordIds" },
{ "$group": {
"_id": {
"_id": "$groupBy",
"errorFields": {
"field": "$errorFields.field",
"errorDetails": {
"detail": "$errorFields.errorDetails.detail"
}
}
},
"recordIds": {
"$push" : "$errorFields.errorDetails.recordIds",
},
"Idscount": { $sum: 1 }
}},
{ "$group": {
"_id": {
"_id": "$_id._id",
"errorFields": {
"field": "$_id.errorFields.field"
}
},
"errorDetails": {
"$push": {
"recordIds": "$recordIds",
"detail": "$_id.errorFields.errorDetails.detail",
"recordCount" : "$Idscount"
}
}
}},
{ "$group": {
"_id": 0,
"errorFields": {
"$push": {
"field": "$_id.errorFields.field",
"recordCount": "$fieldCount",
"errorDetails": "$errorDetails"
}
}
}}
]).pretty()

How to query on embedded documents

{
"_id" : ObjectId("5fa919a49bbe481d117506c9"),
"isDeleted" : 0,
"productId" : 31,
"references" : [
{
"_id" : ObjectId("5fa919a49bbe481d117506ca"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5fa919a49bbe481d117506ba")
},
{
"_id" : ObjectId("5fa91cc7d7d52f1e389dee1f"),
"languageCode" : "ar",
"languageId" : 2,
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
}
],
"createdAt" : ISODate("2020-11-09T10:27:48.859Z"),
"updatedAt" : ISODate("2020-11-09T10:27:48.859Z"),
"__v" : 0
},
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3a"),
"isDeleted" : 0,
"productId" : 21,
"references" : [
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3b"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
}
],
"createdAt" : ISODate("2020-10-29T11:44:29.852Z"),
"updatedAt" : ISODate("2020-10-29T11:44:29.852Z"),
"__v" : 0
}
This is my mongoDB collection in which i store the multilingual references to product collection. In productId are the references to product Collection. Now If we have ar in our request, then we will only have the productId of ar languageCode. If that languageCode does not exist then we will have en langCode productId.
For Example if the user pass ar then the query should return
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
I have tried using $or with $elemMatch but I am not able to get the desired result. Also i am thinking of using $cond. can anyone help me construct the query.
We can acheive
$facet helps to categorized the incoming documents
In the arArray, we get all documents which has"references.languageCode": "ar" (This document may or may not have en), then de-structure the references array, then selecting the "references.languageCode": "ar" only using $match. $group helps to get all productIds which belong to "references.languageCode": "ar"
In the enArray, we only get documents which have only "references.languageCode": "en". Others are same like arArray.
$concatArrays helps to concept both arArray,enArray arrays
$unwind helps to de-structure the array.
$replaceRoot helps to make the Object goes to root
Here is the mongo script.
db.collection.aggregate([
{
$facet: {
arAarray: [
{
$match: {
"references.languageCode": "ar"
}
},
{
$unwind: "$references"
},
{
$match: {
"references.languageCode": "ar"
}
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
],
enArray: [
{
$match: {
$and: [
{
"references.languageCode": "en"
},
{
"references.languageCode": {
$ne: "ar"
}
}
]
}
},
{
$unwind: "$references"
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
]
}
},
{
$project: {
combined: {
"$concatArrays": [
"$arAarray",
"$enArray"
]
}
}
},
{
$unwind: "$combined"
},
{
"$replaceRoot": {
"newRoot": "$combined"
}
}
])
Working Mongo playground
You can test this solution to see if it is useful for you question:
db.collection.aggregate([
{
$addFields: {
foundResults:
{
$cond: {
if: { $in: ["ar", "$references.languageCode"] }, then:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'ar'] },
]
}
}
}
, else:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'en'] },
]
}
}
}
}
}
}
},
{ $unwind: "$foundResults" },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$foundResults"] } } },
{ $project: { _id: 0, "productId": 1 } }
])

Simple calculation on array of arrays

I am having the following collection:
{
"price" : [
55800000,
62800000
],
"surface" : [
81.05,
97.4
],
}
I would want to calculate the price/m2. I have tried the following but got the following error: "$divide only supports numeric types, not array and array".
db.entries.aggregate(
[
{ $project: { url: 1, pricePerSquareMeter: { $divide: [ "$price", "$surfaces" ] } } }
]
)
Would you know how to solve this? Ultimately would want to have an array like this:
{
"price" : [
55800000,
62800000
],
"surface" : [
81.05,
97.4
],
"pricePerSquareMeter" : [
688463.91,
644763.86
]
}
Important: The price and surface should also be ordered so that the calculation is valid.
You can use below aggregation
db.collection.aggregate([
{ "$addFields": {
"pricePerSquareMeter": {
"$map": {
"input": { "$range": [0, { "$size": "$price" }] },
"in": {
"$divide": [
{ "$arrayElemAt": ["$price", "$$this"] },
{ "$arrayElemAt": ["$surface", "$$this"] }
]
}
}
}
}}
])
MongoPlayground
You can use below aggregation
db.entries.aggregate([
{ $addFields: {
pricePerSquareMeter: {
$map: {
input: '$price',
as: 'item',
in: {
$divide: [
"$$item",
{ $arrayElemAt: [
"$surface",
{ "$indexOfArray": ["$price", "$$item"] }
]}
]
}
}
},
}}])

Resources