Related
I am having a similar collection
db={
collectionA: [
{
"id": ObjectId("63b7c24c06ebe7a8fd11777b"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"isProdApproved": false,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
{
_id: ObjectId("63b7c2fd06ebe7a8fd117781"),
iApproved: false
},
{
_id: ObjectId("63b7c2fd06ebe7a8fd117782"),
iApproved: false
}
]
},
{
"productIndex": 2,
"isProdApproved": false,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
{
_id: ObjectId("63b7c2fd06ebe7a8fd117781"),
iApproved: false
},
{
_id: ObjectId("63b7c2fd06ebe7a8fd117783"),
iApproved: false
}
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
]
}
I want to find the productOwner whose _id is 63b7c2fd06ebe7a8fd117781 in the productOwners and update the isApproved and isprodApproved to true. Other data will remain as it is.
I have tried this but it is only updating the first occurance
db.collectionA.update(
{
_id: ObjectId('63b7c24c06ebe7a8fd11777b'),
'products.productOwners._id': ObjectId('63b7c2fd06ebe7a8fd117781'),
},
{ $set: { 'products.$.productOwners.$[x].isApproved': true } },
{ arrayFilters: [{ 'x._id': ObjectId('63b7c2fd06ebe7a8fd117781') }] }
);
This one should work:
db.collection.updateMany({},
[
{
$set: {
products: {
$map: {
input: "$products",
as: "product",
in: {
$cond: {
if: { $eq: [{ $type: "$$product.productOwners" }, "array"] },
then: {
$mergeObjects: [
"$$product",
{ isProdApproved: { $in: [ObjectId("63b7c2fd06ebe7a8fd117781"), "$$product.productOwners._id"] } },
{
productOwners: {
$map: {
input: "$$product.productOwners",
as: 'owner',
in: {
$mergeObjects: [
"$$owner",
{ iApproved: { $eq: ["$$owner._id", ObjectId("63b7c2fd06ebe7a8fd117781")] } }
]
}
}
}
}
]
},
else: "$$product"
}
}
}
}
}
}
]
)
However, the data seem to be redundant. Better update only products.productOwners.iApproved and then derive products.isProdApproved from nested elements:
db.collection.aggregate([
{
$set: {
products: {
$map: {
input: "$products",
as: "product",
in: {
$cond: {
if: { $eq: [{ $type: "$$product.productOwners" }, "array"] },
then: {
$mergeObjects: [
"$$product",
{ isProdApproved: { $anyElementTrue: ["$$product.productOwners.iApproved"] } },
]
},
else: "$$product"
}
}
}
}
}
}
])
I have a document like this:
this is my example data is attached below,
[
{
"_id": ObjectId("6218b836405919280c209f7e"),
"projectId": ObjectId("6218a31f405919280c209e18"),
"accountId": ObjectId("621888e852bd8836c04b8f82"),
"personalIdRoot": [
{
"_id": ObjectId("6221e7514195b43f24c9953f"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c99540"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdFill": [
{
"_id": ObjectId("6221e7514195b43f24c9953d"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953e"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdCap": [
{
"_id": ObjectId("6221e7514195b43f24c9953b"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953c"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"aps": ObjectId("6218bc18405919280c209f8e"),
}
]
i used this example data for my aggregate query.
my aggregate query is attached below:
please find below code:
db.getCollection('funds').aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$facet: {
"results": [
{
$group: {
_id: 0,
values: { "$addToSet": "$personalIdRoot.welderId" },
values: { "$addToSet": "$personalIdFill.welderId" },
values: { "$addToSet": "$personalIdCap.welderId" }
}
},
],
}
}
])
my result:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
[
ObjectId("6218b48c405919280c209f6c")
],
[
ObjectId("6218b2e4405919280c209f68")
],
[
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
]
}
]
}
But i need my result in a single query:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
}
]
}
I have a result of array of array collections.
but i need it in a single array. like above result
Thanks in advance.
It sounds like what you want is:
db.collection.aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$project: {
values: {
$setUnion: [
"$personalIdCap.personalId",
"$personalIdFill.personalId",
"$personalIdRoot.personalId"
]
}
}
}
])
See how it works on the playground example
I have a collection like this:
{
'_id' : ObjectId('6251f8556e75125f9260f333'),
'name': 'jojo',
'profile': 'jojo profile',
'date': ISODate("2022-04-09T21:18:40.473Z"),
'look': [
{ 'art': 'group-id', 'data': 'alma', 'dt': '1'},
{ 'art': 'called', 'data': 'central', 'dt': '1'},
{ 'art': 'access-time', 'data': 108000, 'dt': '1'}
]
'answer': [
{ 'art': 'rate-id', 'data': 'limit1', 'dt': '1'},
{ 'art': 'protocol', 'data': 'tcp', 'dt': '1'}
]
},
{
'_id' : ObjectId('6251f8306e75125f9260f332'),
'name': 'dodo',
'profile': 'dodo profile',
'date': ISODate("2022-04-09T15:20:58.562Z"),
'look': [
{ 'art': 'group-id', 'data': 'alma', 'dt': '1'},
{ 'art': 'called', 'data': 'central', 'dt': '1'},
]
'answer': [
{ 'art': 'rate-id', 'data': 'limit1', 'dt': '1'},
]
},
{
'_id' : ObjectId('6251a5113700ba4a0a59c48f'),
'name': 'kaka',
'profile': 'kaka profile',
'date': ISODate("2022-04-09T15:22:25.816Z"),
'look': [
{ 'art': 'access-time', 'data': 50400, 'dt': '1'}
]
'answer': [
{ 'art': 'protocol', 'data': 'tcp', 'dt': '1'}
]
}
and I was expecting an output like this:
{
'_id' : ObjectId('6251f8556e75125f9260f333'),
'name': 'jojo',
'profile': 'jojo profile',
'date': ISODate("2022-04-09T21:18:40.473Z"),
'goup': 'alma', // filter by 'group-id' and put value of data field
'called': 'central', // filter by 'called' and put value of data field
'accessTime': 108000, // filter by 'access-time' and put value of data field
'rate': 'limi1', // filter by 'rate-id' and put value of data field
'protocol': 'tcp', // filter by 'protocol' and put value of data field
},
{
'_id' : ObjectId('6251f8306e75125f9260f332'),
'name': 'dodo',
'profile': 'dodo profile',
'date': ISODate("2022-04-09T15:20:58.562Z"),
'goup': 'alma',
'called': 'central',
'accessTime': '', // set blank data if not exist
'rate': 'limi1',
'protocol': '', // set blank data if not exist
},
{
'_id' : ObjectId('6251a5113700ba4a0a59c48f'),
'name': 'kaka',
'profile': 'kaka profile',
'date': ISODate("2022-04-09T15:22:25.816Z"),
'goup': '', // set blank data if not exist
'called': '', // set blank data if not exist
'accessTime': 50400,
'rate': '', // set blank data if not exist
'protocol': 'tcp',
}
I've searched here but couldn't find an answer that matches the problem I'm facing, probably because of the wrong keywords.
Since I'm new to mongodb, I'm confused about how to solve the query I want. How can I achieve this? Please help me...
You would require an aggregate operation that has a pipeline with the following key operators and stages:
$map: an operator to transform the look and answer arrays into documents with just mapped k and v fields, crucial for obtaining a hash map with the following operator
$arrayToObject: this allows the above to be possible i.e. converting an array into a single document
$mergeObjects: combine top level fields i.e. _id, date, name, profile together with the converted documents above
$replaceWith: pipeline stage to replace the root document with the specified document from above
Overall, your pipeline should follow:
const first = {
$first: {
$split: ['$$this.art', '-']
}
};
const keyExpression = {
$cond: [
{ $eq: [first, 'access'] },
'accessTime',
first
]
};
const pipeline = [
{ $replaceWith: {
$mergeObjects: [
{
_id: '$_id',
date: '$date',
name: '$name',
profile: '$profile',
protocol: '',
group: '',
called: '',
rate: '',
accessTime: '',
},
{ $arrayToObject: {
$map: {
input: '$look',
in: { k: keyExpression, v: '$$this.data' }
}
} },
{ $arrayToObject: {
$map: {
input: '$answer',
in: { k: keyExpression, v: '$$this.data' }
}
} }
]
} }
]
Mongo Playground
For this you should use the aggregation framework of mongo db, because will require complex operations to get the data in the shape that you want.
https://www.mongodb.com/docs/manual/aggregation/
Every aggregation is an array of stages and every stage does something specific.
I used the next stages:
addFields: Allows you to add new fields to the response of every document, so if you don't have group in the document, that will add or replace it.
project: Allows you remove some fields of a document. In a projection stage if you set an attribute as 0 that will remove that attribute from the response.
Also I used some operators:
filter: this allows you to filter data of an element that is an array
arrayElemenAt: receives an array and return the position specified
The pipeline:
[
{
"$addFields":{
"group":{
"$arrayElemAt":[
{
"$filter":{
"input":"$look",
"as":"item",
"cond":{
"$eq":[
"$$item.art",
"group-id"
]
}
}
},
0
]
},
"called":{
"$arrayElemAt":[
{
"$filter":{
"input":"$look",
"as":"item",
"cond":{
"$eq":[
"$$item.art",
"called"
]
}
}
},
0
]
},
"accessTime":{
"$arrayElemAt":[
{
"$filter":{
"input":"$look",
"as":"item",
"cond":{
"$eq":[
"$$item.art",
"access-time"
]
}
}
},
0
]
},
"rate":{
"$arrayElemAt":[
{
"$filter":{
"input":"$answer",
"as":"item",
"cond":{
"$eq":[
"$$item.art",
"rate-id"
]
}
}
},
0
]
},
"protocol":{
"$arrayElemAt":[
{
"$filter":{
"input":"$answer",
"as":"item",
"cond":{
"$eq":[
"$$item.art",
"protocol"
]
}
}
},
0
]
}
}
},
{
"$addFields":{
"group":"$group.data",
"called":"$called.data",
"accessTime":"$accessTime.data",
"rate":"$rate.data",
"protocol":"$protocol.data"
}
},
{
"$project":{
"look":0,
"answer":0
}
}
]
This is quite cumbersome with the current structure, as for each field you have to convert the object to an array, filter it then convert it back, here's how it looks:
db.collection.aggregate([
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
{
_id: "$_id",
name: "$name",
profile: "$profile",
date: "$date",
},
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
$objectToArray: {
$ifNull: [
{
"$arrayElemAt": [
{
$filter: {
input: {
$ifNull: [
"$look",
[]
]
},
cond: {
$eq: [
"$$this.art",
"group-id"
]
}
}
},
0
]
},
{
art: ""
}
]
}
},
cond: {
$eq: [
"$$this.k",
"data"
]
}
}
},
in: {
k: "goup",
v: "$$this.v"
}
}
}
},
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
$objectToArray: {
$ifNull: [
{
"$arrayElemAt": [
{
$filter: {
input: {
$ifNull: [
"$look",
[]
]
},
cond: {
$eq: [
"$$this.art",
"called"
]
}
}
},
0
]
},
{
art: ""
}
]
}
},
cond: {
$eq: [
"$$this.k",
"data"
]
}
}
},
in: {
k: "called",
v: "$$this.v"
}
}
}
},
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
$objectToArray: {
$ifNull: [
{
"$arrayElemAt": [
{
$filter: {
input: {
$ifNull: [
"$look",
[]
]
},
cond: {
$eq: [
"$$this.art",
"access-time"
]
}
}
},
0
]
},
{
art: ""
}
]
}
},
cond: {
$eq: [
"$$this.k",
"data"
]
}
}
},
in: {
k: "access-time",
v: "$$this.v"
}
}
}
},
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
$objectToArray: {
$ifNull: [
{
"$arrayElemAt": [
{
$filter: {
input: {
$ifNull: [
"$answer",
[]
]
},
cond: {
$eq: [
"$$this.art",
"rate-id"
]
}
}
},
0
]
},
{
art: ""
}
]
}
},
cond: {
$eq: [
"$$this.k",
"data"
]
}
}
},
in: {
k: "rate",
v: "$$this.v"
}
}
}
},
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
$objectToArray: {
$ifNull: [
{
"$arrayElemAt": [
{
$filter: {
input: {
$ifNull: [
"$answer",
[]
]
},
cond: {
$eq: [
"$$this.art",
"protocol"
]
}
}
},
0
]
},
{
art: ""
}
]
}
},
cond: {
$eq: [
"$$this.k",
"data"
]
}
}
},
in: {
k: "protocol",
v: "$$this.v"
}
}
}
}
]
}
}
}
])
Mongo Playground
If you're using Mongo version 5+, then you can use $getField to simplify the syntax a little bit, here's how one field would look like in this syntax:
goup: {
$getField: {
field: 'data',
input: {
'$arrayElemAt': [
{
$filter: {
input: {
$ifNull: [
'$look',
[],
],
},
cond: {
$eq: [
'$$this.art',
'group-id',
],
},
},
},
0,
],
},
},
},
I have aggregation like this:
Produk.aggregate([
{
$lookup: {
from: "kis_m_kategoriproduks",
localField: "idSubKategori",
foreignField: "subKategori._id",
as: "kategori",
},
},
{ $unwind: "$kategori" },
{ $sort: { produk: 1 } },
{
$project: {
_id: 0,
id: "$id",
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
idSubKategori: "$idSubKategori",
subKategori: "$kategori.subKategori",
},
},
},
])
current result is :
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
},
{
"nama": "Drink",
"_id": "6195bc258ee419e6a9b832a8"
}
]
}
}
]
}
I only want to display data in subKategori that the _id match with idSubKategori. this what I expected:
{
"status": "success",
"data": [
{
"dataKategori": {
"idKategori": "6195bbec8ee419e6a9b8329d",
"kategori": "Kuliner",
"idSubKategori": "6195bc0f8ee419e6a9b832a2",
"subKategori": [
{
"nama": "Food",
"_id": "6195bc0f8ee419e6a9b832a2"
}
]
}
}
]
}
here is my $kategori schema:
const schema = mongoose.Schema(
{
kategori: {
type: String,
required: true,
unique: true,
},
subKategori: [
{
id: mongoose.Types.ObjectId,
nama: String,
},
],
},
{
timestamps: false,
}
);
any suggestion?
I fix the problem by add $filter inside $project like this:
dataKategori: {
idKategori: "$kategori._id",
kategori: "$kategori.kategori",
subKategori: {
$arrayElemAt: [
{
$filter: {
input: "$kategori.subKategori",
as: "sub",
cond: { $eq: ["$$sub._id", "$idSubKategori"] },
},
},
0,
],
},
},
reference: https://stackoverflow.com/a/42490320/6412375
I have these documents:
[
{
'_id': 1,
'role': [
{ // keep this document
'plan': 'free',
'date': ISODate('2020-01-01')
},
{
'plan': 'free',
'date': ISODate('2020-01-02')
},
{
'plan': 'free',
'date': ISODate('2020-01-03')
},
{ // keep this document
'plan': 'pro',
'date': ISODate('2020-01-04')
},
{
'plan': 'pro',
'date': ISODate('2020-01-05')
},
{
'plan': 'pro',
'date': ISODate('2020-01-06')
},
{ // keep this document
'plan': 'free',
'date': ISODate('2020-01-08')
},
{
'plan': 'free',
'date': ISODate('2020-01-09')
}
]
},
{
'_id': 2,
'role': [
{ // keep this document
'plan': 'pro',
'date': ISODate('2020-02-05')
},
{
'plan': 'pro',
'date': ISODate('2020-02-06')
},
{ // keep this document
'plan': 'free',
'date': ISODate('2020-02-07')
},
{
'plan': 'free',
'date': ISODate('2020-02-08')
},
{
'plan': 'free',
'date': ISODate('2020-02-09')
},
{ // keep this document
'plan': 'pro',
'date': ISODate('2020-02-10')
},
{
'plan': 'pro',
'date': ISODate('2020-02-11')
},
{
'plan': 'pro',
'date': ISODate('2020-02-12')
}
]
}
]
So I have to filter documents, based on the change of the value of plan field.
I always want to keep the first occurence, but the next document will only be kept if the value of plan field has changed (e.g. free changed to pro, or pro changed to free).
Obs.: I have more distinct values for the plan field (e.g. premium, admin etc), but I only got two documents for the example.
I believe this operation might be an overkill if done on huge dataset & dataset having role array with lots of objects in it. You can try below aggregation query :
db.collection.aggregate([
/** As `role` field already exists `$addFields` will overwrite with new value */
{
$addFields: {
role: {
$let: {
vars: {
data: {
$reduce: {
input: { $slice: [ "$role", 1, { $size: "$role" } ] }, /** array input without first object */
initialValue: { roleObjs: [ { $arrayElemAt: [ "$role", 0 ] } ], plan: { $arrayElemAt: [ "$role.plan", 0 ] } }, /** Pick first object & first object's plan as initial values */
in: {
roleObjs: { $cond: [ { $eq: [ "$$this.plan", "$$value.plan" ] }, "$$value.roleObjs", { $concatArrays: [ "$$value.roleObjs", [ "$$this" ] ] } ] }, /** Conditional check & merge new object to array or return holding array as is */
plan: { $cond: [ { $eq: [ "$$this.plan", "$$value.plan" ] }, "$$value.plan", "$$this.plan" ] }
}
}
}
},
in: "$$data.roleObjs" /** Return newly formed `roleObjs` array in local variable */
}
}
}
}
])
Test : mongoplayground
Here is an aggregation with the desired result:
db.collection.aggregate( [
{
$addFields: {
plans: {
$reduce: {
input: "$role",
initialValue: [],
in: { $concatArrays: [ "$$value", [ "$$this.plan" ] ] }
}
}
}
},
{
$addFields: {
role: {
$reduce: {
input: { $range: [ 0, { $subtract: [ { $size: "$role" }, 1 ] } ] },
initialValue: { prevPlan: { $arrayElemAt: [ "$plans", 0 ] }, roles: [ { $arrayElemAt: [ "$role", 0 ] } ] },
in: {
$cond: [ { $eq: [ { $arrayElemAt: [ "$plans", "$$this"] }, "$$value.prevPlan" ] },
{ prevPlan: { $arrayElemAt: [ "$plans", "$$this"] },
roles: { $concatArrays: [ "$$value.roles", [ ] ] }
},
{ prevPlan: { $arrayElemAt: [ "$plans", "$$this" ] },
roles: { $concatArrays: [ "$$value.roles", [ { $arrayElemAt: [ "$role", "$$this" ] } ] ] }
}
]
}
}
}
}
},
{
$project: { role: "$role.roles" }
}
] )