I have a collection called 'A' with thousands of documents in it. Here grades is the array of sub documents which consist attribute questions which is array of number like below
{ "_id" : 1,
"grades" : [
{ type: "quiz", questions: [ 10, 8, 5 ] },
{ type: "quiz", questions: [ 8, 9, 6 ] },
{ type: "hw", questions: [ 5, 4, 3 ] },
{ type: "exam", questions: [ 25, 10, 23, 0 ] }
]
}
In order to add additional feature we have to modify the format of questions and make it array of documents which has two fields one is number (which will inherit the old data) and another one is attempts(by default it will be empty array and may have value 'P'- Pass or 'F'- Fail). After this transformation the document should look like below
{ "_id" : 1,
"grades" : [
{
type: "quiz",
questions:[
{ number: 10, attempts: ['F', 'P'] },
{ number: 8, attempts: [] },
{ number: 5, attempts: ['P'] }
]
},
{
type: "quiz",
questions:
[
{ number: 8, attempts: ['F', 'P'] },
{ number: 9, attempts: [] },
{ number: 6, attempts: ['P'] }
]
},
{
type: "hw",
questions:
[
{ number: 5, attempts: ['F', 'P'] },
{ number: 4, attempts: [] },
{ number: 3, attempts: ['P'] }
]
},
{
type: "exam",
questions:
[
{ number: 25, attempts: ['F', 'P'] },
{ number: 10, attempts: [] },
{ number: 23, attempts: ['P'] },
{ number: 0, attempts: ['P', 'P'] }
]
}
]
}
Appreciate for all the help
You can test yourself here: https://mongoplayground.net/p/ZEp_vk5hhf7
Brief explanation:
You have to use $map two times.
First you have to loop the $grades field, because he is an array.
As you are looping through $grades array, you have to loop through the $questions field, because he is an array too.
Query:
db.collection.aggregate([
{
"$project": {
"grades": {
"$map": {
"input": "$grades",
"as": "g",
"in": {
"type": "$$g.type",
"questions": {
"$map": {
"input": "$$g.questions",
"as": "q",
"in": {
"number": "$$q",
"attempts": []
}
}
}
}
}
}
}
}
])
Result:
[
{
"_id": 1,
"grades": [
{
"questions": [
{
"attempts": [],
"number": 10
},
{
"attempts": [],
"number": 8
},
{
"attempts": [],
"number": 5
}
],
"type": "quiz"
},
{
"questions": [
{
"attempts": [],
"number": 8
},
{
"attempts": [],
"number": 9
},
{
"attempts": [],
"number": 6
}
],
"type": "quiz"
},
{
"questions": [
{
"attempts": [],
"number": 5
},
{
"attempts": [],
"number": 4
},
{
"attempts": [],
"number": 3
}
],
"type": "hw"
},
{
"questions": [
{
"attempts": [],
"number": 25
},
{
"attempts": [],
"number": 10
},
{
"attempts": [],
"number": 23
},
{
"attempts": [],
"number": 0
}
],
"type": "exam"
}
]
}
]
Related
[
{
"id": 1,
"items": [
{
id: 15,
score: 10
},
{
id: 14,
score: 100
},
{
id: 12,
score: 1
}
]
},
{
"id": 2,
"items": []
}
]
Now, I try to update items whose id is 14,15 & used the following query.
db.collection.update({
"items.id": {
$in: [
14,
15
]
}
},
{
$set: {
"items.$.score": 444
}
},
{
multi: true
}
)
but it updated only the first match in items that is that is id with 15, what can be wrong?
[
{
"_id": ObjectId("5a934e000102030405000000"),
"id": 1,
"items": [
{
"id": 15,
"score": 444
},
{
"id": 14,
"score": 100
},
{
"id": 12,
"score": 1
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"id": 2,
"items": []
}
]
Use arrayFilters with targeted array elements:
here is the doc
db.collection.update({
"id": 1
},
{
"$set": {
"items.$[ele].score": 20
}
},
{
arrayFilters: [
{
"ele.id": {
"$in": [
15,
14
]
}
}
]
})
see play ground code https://mongoplayground.net/p/cdgu1aqLwsI
You can do it with Positional identifiers $[]
db.collection.update({
"items.id": {
$in: [
14,
15
]
}
},
{
$set: {
"items.$[element].score": 444
}
},
{
arrayFilters: [
{
"element.id": {
$in: [
14,
15
]
}
}
],
multi: true
})
try it here
I need to add items to a specific subobject in my collection. Is is possible to do this using just the update method considering that the array may not exist in some objects?
Here's a sample structure:
[
{
"key": 1,
"someValue": "abc",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 1,
"value": 29
},
{
"paymentNumber": 2,
"value": 22
}
]
}
]
},
{
"key": 2,
"someValue": "xyz",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 3,
"value": 10
},
{
"paymentNumber": 4,
"value": 1
},
{
"paymentNumber": 5,
"value": 5
}
]
}
]
}
]
And here's the syntax I'm using:
db.collection.update({
"installments.payments.paymentNumber": 4
},
{
"$push": {
"installments.payments": [
{
"receipts": [
{
"receiptNumber": 1
}
]
}
]
}
})
This is causing this error:
fail to run update: multiple write errors: [{write errors: [{Cannot create field 'payments' in element {installments: [ { number: 1.0, payments: [ { paymentNumber: 3.0, value: 10.0 }, { paymentNumber: 4.0, value: 1.0 }, { paymentNumber: 5.0, value: 5.0 } ] } ]}}]}, {<nil>}]
Is it possible?
Here's the Mongo Playground link
And here's the expected result:
[
{
"key": 1,
"someValue": "abc",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 1,
"value": 29
},
{
"paymentNumber": 2,
"value": 22
}
]
}
]
},
{
"key": 2,
"someValue": "xyz",
"installments": [
{
"number": 1,
"payments": [
{
"paymentNumber": 3,
"value": 10
},
{
"paymentNumber": 4,
"value": 1,
"receipts": [{
"receiptNumber": 1
}]
},
{
"paymentNumber": 5,
"value": 5
}
]
}
]
}
]
To update nested arrays, the filtered positional operator $[identifier] identifies the array elements that match the arrayFilters conditions for an update operation.
Try the following query to $push in nested array:
db.collection.update({
"installments.payments.paymentNumber": 4,
},
{
"$push": {
"installments.$.payments.$[payments].receipts": {
"receiptNumber": 1
}
}
},
{
"arrayFilters": [
{
"payments.paymentNumber": 4
}
],
multi: true
})
MongoDB Playground
I have two documents as shown. Their common factor is a node in the subdocument (type,veg_type). I have also added the same common node to each individual document (udf_type, udf_veg_type).One is a legacy data (with key node veggies) and another is new data (with key node vegetables).
How do I project the combined data of vegetables and veggies(in key node vegs) without the type and veg_type nodes? I use user_id for matching.
Intended Output
{
"user_id": 31,
"veggies": [
{
"udf_type": "green_vegetables",
"tot": 28560,
"itms": [
{
"num": 1,
"itm_det": {
"name": "spinach",
"qty": 18
}
}
],
"chksum": "d1583afab3a04f4b32589cfa64392765n78782ff60a0e0dc24b295868083"
},
{
"udf_type": "vegetables",
"tot": 2860,
"itms": [
{
"num": 1,
"itm_det": {
"name": "onion",
"qty": 1
}
}
],
"chksum": "e497c7b288e50e3be4c6bc676e4c849e4n5645n64a2d77748e185d7a1bce8c"
},
{
"udf_veg_type": "green_vegetables",
"tot": 2352000,
"itms": [
{
"num": 1,
"itm_det": {
"name": "kale",
"qty": 18
}
}
],
"chksum": "87b239cd9b39baa48b4564b5754009a131f542622ba018f37cd1fdb5"
}
]
}
{
"_id" : ObjectId("1"),
"user_id": 31,
"veggies": [
{
"type": "green_vegetables",
"desc": [
{
"udf_type": "green_vegetables",
"tot": 28560,
"itms": [
{
"num": 1,
"itm_det": {
"name": "spinach",
"qty": 18
}
}
],
"chksum": "d1583afab3a04f4b32589cfa64392765n78782ff60a0e0dc24b295868083"
}
]
},
{
"type": "vegetables",
"desc": [
{
"udf_type": "vegetables",
"tot": 2860,
"itms": [
{
"num": 1,
"itm_det": {
"name": "onion",
"qty": 1
}
}
],
"chksum": "e497c7b288e50e3be4c6bc676e4c849e4n5645n64a2d77748e185d7a1bce8c"
}
]
}
]
}
{
"_id" : ObjectId("2"),
"user_id": 31,
"vegetables": [
{
"veg_type": "green_vegetables",
"desc": [
{
"udf_veg_type": "green_vegetables",
"tot": 2352000,
"itms": [
{
"num": 1,
"itm_det": {
"name": "kale",
"qty": 18
}
}
],
"chksum": "87b239cd9b39baa48b4564b5754009a131f542622ba018f37cd1fdb5"
}
]
}
]
}
Assuming that the desc arrays always have just one item, try this:
db.collection.aggregate([
{
$match: {
"user_id": 31 // change this into the user_id variable
}
},
{
$group: {
_id: "$user_id",
veggies: {
$max: "$veggies"
},
vegetables: {
$max: "$vegetables"
}
}
},
{
$project: {
"user_id": "$_id",
"veggies": {
$concatArrays: [
{
$cond: [
"$veggies",
{
$map: {
input: "$veggies",
in: {
$arrayElemAt: [
"$$this.desc",
0
]
}
}
},
[]
]
},
{
$cond: [
"$vegetables",
{
$map: {
input: "$vegetables",
in: {
$arrayElemAt: [
"$$this.desc",
0
]
}
}
},
[]
]
}
]
}
}
}
])
Here's the structure part of my collection:
_id: ObjectId("W"),
names: [
{
number: 1,
subnames: [ { id: "X", day: 1 }, { id: "Y", day: 10 }, { id: "Z", day: 2 } ],
list: ["A","B","C"],
day: 1
},
{
number: 2,
day: 5
},
{
number: 3,
subnames: [ { id: "X", day: 8 }, { id: "Z", day: 5 } ],
list: ["A","C"],
day: 2
},
...
],
...
I use this request:
db.publication.aggregate( [ { $match: { _id: ObjectId("W") } }, { $group: { _id: "$_id", SizeName: { $first: { $size: { $ifNull: [ "$names", [] ] } } }, names: { $first: "$names" } } }, { $unwind: "$names" }, { $sort: { "names.day": 1 } }, { $group: { _id: "$_id", SzNames: { $sum: 1 }, names: { $push: { number: "$names.number", subnames: "$names.subnames", list: "$names.list", SizeList: { $size: { $ifNull: [ "$names.list", [] ] } } } } } } ] );
but I would now use $sort for my names array AND my subnames array to obtain this result (subnames may not exist) :
_id: ObjectId("W"),
names: [
{
number: 2,
SizeList: 0,
day: 5
},
{
number: 3,
subnames: [ { id: "Z", day: 5 }, { id: "X", day: 8 } ],
list: ["A","C"],
SizeList: 2,
day: 2
},
{
number: 1,
subnames: [ { id: "X", day: 1 }, { id: "Z", day: 2 }, { id: "Y", day: 10 } ],
list: ["A","B","C"],
SizeList: 3,
day: 1
}
...
],
...
Can you help me ?
You can do this, but with great difficulty. I for one would gladly vote for an inline version of $sort along the lines of the $map operator. That would makes things so much easier.
For now though you need to de-construct and re-build the arrays after sorting. And you have to be very careful about this. Hence make false arrays with a single entry before processing $unwind:
db.publication.aggregate([
{ "$project": {
"SizeNames": {
"$size": {
"$ifNull": [ "$names", [] ]
}
},
"names": { "$ifNull": [{ "$map": {
"input": "$names",
"as": "el",
"in": {
"SizeList": {
"$size": {
"$ifNull": [ "$$el.list", [] ]
}
},
"SizeSubnames": {
"$size": {
"$ifNull": [ "$$el.subnames", [] ]
}
},
"number": "$$el.number",
"day": "$$el.day",
"subnames": { "$ifNull": [ "$$el.subnames", [0] ] },
"list": "$$el.list"
}
}}, [0] ] }
}},
{ "$unwind": "$names" },
{ "$unwind": "$names.subnames" },
{ "$sort": { "_id": 1, "names.subnames.day": 1 } },
{ "$group": {
"_id": {
"_id": "$_id",
"SizeNames": "$SizeNames",
"names": {
"SizeList": "$names.SizeList",
"SizeSubnames": "$names.SizeSubnames",
"number": "$names.number",
"list": "$names.list",
"day": "$names.day"
}
},
"subnames": { "$push": "$names.subnames" }
}},
{ "$sort": { "_id._id": 1, "_id.names.day": 1 } },
{ "$group": {
"_id": "$_id._id",
"SizeNames": { "$first": "$_id.SizeNames" },
"names": {
"$push": { "$cond": [
{ "$ne": [ "$_id.names.SizeSubnames", 0 ] },
{
"number": "$_id.names.number",
"subnames": "$subnames",
"list": "$_id.names.list",
"SizeList": "$_id.names.SizeList",
"day": "$_id.names.day"
},
{
"number": "$_id.names.number",
"list": "$_id.names.list",
"SizeList": "$_id.names.SizeList",
"day": "$_id.names.day"
}
]}
}
}},
{ "$project": {
"SizeNames": 1,
"names": {
"$cond": [
{ "$ne": [ "$SizeNames", 0 ] },
"$names",
[]
]
}
}}
])
You can kind of "hide away" the original empty array from the inner document as shown, but it's really difficult to remove all presence of the outer "names" array without pulling a similar conditional array "push" technique, and that really isn't a practical approach.
If all of this is just about sorting array elements in individual documents though, the aggregation framework should not be the tool to do this. It can be done as shown, but per document this is much easier to do in client side code.
Output:
{
"_id" : ObjectId("54b5cff8102f292553ce9bb5"),
"SizeNames" : 3,
"names" : [
{
"number" : 1,
"subnames" : [
{
"id" : "X",
"day" : 1
},
{
"id" : "Z",
"day" : 2
},
{
"id" : "Y",
"day" : 10
}
],
"list" : [
"A",
"B",
"C"
],
"SizeList" : 3,
"day" : 1
},
{
"number" : 3,
"subnames" : [
{
"id" : "Z",
"day" : 5
},
{
"id" : "X",
"day" : 8
}
],
"list" : [
"A",
"C"
],
"SizeList" : 2,
"day" : 2
},
{
"number" : 2,
"SizeList" : 0,
"day" : 5
}
]
}
I have documents in a MongoDB 'playground' collection following the below "schema" :
{
"_id": ObjectId("54423b40c92f9fffb486a6d4"),
"ProjectFileId": 1,
"SourceLanguageId": 2,
"TargetSegments": [
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
},
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
}
]
}
And the following update query:
db.playground.update({
$and: [
{
"TargetSegments.Colors": {
$exists: true
}
},
{
"ProjectFileId": 1
},
{
"SourceLanguageId": 2
},
{
"TargetSegments": {
$elemMatch: {
"LanguageId": 1
}
}
}
]
},
{
$set: {
"TargetSegments.$.Segment": null,
"TargetSegments.$.Colors": [],
"TargetSegments.$.Widths": [],
"TargetSegments.$.Heights": []
}
},
false, true)
After the execution of the query the result is:
{
"_id": ObjectId("54423b40c92f9fffb486a6d4"),
"ProjectFileId": 1,
"SourceLanguageId": 2,
"TargetSegments": [
{
"LanguageId": 1,
"Segment": null,
"Colors": [],
"Heights": [],
"Widths": []
},
{
"LanguageId": 1,
"Segment": "Something",
"Colors": [
1,
2,
3
],
"Heights": [
1,
2,
3
],
"Widths": [
1,
2,
3
]
}
]
}
As you can see, only the first element of the "TargetSegments" array is updated.
How can I update all the elements of the TargetSegments array in one update query?
Its because you are using $ operator: The positional $ operator identifies an element (not multi) in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
You can use below code to do it:
db.playground.find({
$and: [
{
"TargetSegments.Colors": {
$exists: true
}
},
{
"ProjectFileId": 1
},
{
"SourceLanguageId": 2
},
{
"TargetSegments": {
$elemMatch: {
"LanguageId": 1
}
}
}
]
}).forEach(function(item)
{
var targets = item.TargetSegments;
for(var index = 0; index < targets.length; index++)
{
var target = targets[index];
target.Segment = null,
target.Colors= [],
target.Widths= [],
target.Heights= []
}
db.playground.save(item);
});