Create array if not exists or append item to existing - mongodb

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

Related

Mongoose - Update multi objects inside an array by ID

Firstly, sorry for my bad English.
Secondly, I want to ask, how to multi-update my document.
I have a row structure like this:
Data Model
[
{
"id": '00001',
"exp": '192',
"items": [
{ "qty": 23, "ID": ObjectId("kieryu35261twerts73940djr") },
{ "qty": 77, "ID": ObjectId("1240a1ffuw33dbcv6ut8888zz") },
{ "qty": 1, "ID": ObjectId("5600r0e0rr67cbd60a1234y5") },
{ "qty": 5, "ID": ObjectId("32bbd0e0rr92cdb60a4386c7") }
],
"stats": [
{ "strenght": 1, "ID": ObjectId("3eruskdjfhrutiwo4059yuol3") },
{ "strenght": 2, "ID": ObjectId("3jdks0d9r2seifldvjmw0km2e") },
{ "strenght": 1, "ID": ObjectId("aslkdnasklnc2112uhnsjssad") },
{ "strenght": 5, "ID": ObjectId("1230msafmlklkmasfmcio3kms") }
]
},
{
"id": '00002',
"exp": '100',
"items": [
{ "strenght": 10, "ID": ObjectId("21312kn3kaklmasklcklasmck") },
{ "strenght": 10, "ID": ObjectId("kldsafklsajdfklmsadlkaskl") }
],
"stats": [
{ "strenght": 9, "ID": ObjectId("aslkclkamiior2oinrkl2adsa") },
{ "strenght": 0, "ID": ObjectId("asdoasjdosmdkl123123kmnsd") }
]
}
]
I want to update one document row by finding the id of the document, and multi-deep elements by ID too. Like this:
{
"id": '00001',
"exp": '555',
"items": [
{ "qty": 44, "ID": ObjectId("kieryu35261twerts73940djr") },
{ "qty": 55, "ID": ObjectId("1240a1ffuw33dbcv6ut8888zz") },
{ "qty": 66, "ID": ObjectId("5600r0e0rr67cbd60a1234y5") },
{ "qty": 77, "ID": ObjectId("32bbd0e0rr92cdb60a4386c7") }
],
"stats": [
{ "strenght": 10, "ID": ObjectId("3eruskdjfhrutiwo4059yuol3") },
{ "strenght": 20, "ID": ObjectId("3jdks0d9r2seifldvjmw0km2e") },
{ "strenght": 12, "ID": ObjectId("aslkdnasklnc2112uhnsjssad") },
{ "strenght": 54, "ID": ObjectId("1230msafmlklkmasfmcio3kms") }
]
}
And last, just for information, I do before this:
await DataModel.findOneAndUpdate(
{
"id" : idvariable // == 00001
},
{
"$set" : {
"exp" : 555,
"items": {
//update qty == 44 where ID == kieryu35261twerts73940djr
//update qty == 55 where ID == 1240a1ffuw33dbcv6ut8888zz
//update qty == 66 where ID == 5600r0e0rr67cbd60a1234y5
//update qty == 77 where ID == 32bbd0e0rr92cdb60a4386c7
},
"stats": {
//update strenght == 10 where ID == 3eruskdjfhrutiwo4059yuol3
//update strenght == 20 where ID == 3jdks0d9r2seifldvjmw0km2e
//update strenght == 12 where ID == aslkdnasklnc2112uhnsjssad
//update strenght == 54 where ID == 1230msafmlklkmasfmcio3kms
}
}
}
)
Please, I don't know how to update it with a single query update, or other technique. Thank you.
Possible, but a bit long query.
Working on the Update with Aggregation Pipeline,
$map - Iterate the items in the array and return a new array.
$switch - Switch-case (statement) to match the ID of the current iterated document and update the document if matched. If all the case conditions are failed to match, remain the existing document.
db.collection.update({
"id": idvariable// == 00001
},
[
{
"$set": {
"exp": 555,
"items": {
$map: {
input: "$items",
in: {
$switch: {
branches: [
{
case: {
$eq: [
"$$this.ID",
"kieryu35261twerts73940djr"
]
},
then: {
$mergeObjects: [
"$$this",
{
qty: 44
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"1240a1ffuw33dbcv6ut8888zz"
]
},
then: {
$mergeObjects: [
"$$this",
{
qty: 55
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"5600r0e0rr67cbd60a1234y5"
]
},
then: {
$mergeObjects: [
"$$this",
{
qty: 66
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"32bbd0e0rr92cdb60a4386c7"
]
},
then: {
$mergeObjects: [
"$$this",
{
qty: 77
}
]
}
}
],
default: "$$this"
}
}
}
},
"stats": {
$map: {
input: "$stats",
in: {
$switch: {
branches: [
{
case: {
$eq: [
"$$this.ID",
"3eruskdjfhrutiwo4059yuol3"
]
},
then: {
$mergeObjects: [
"$$this",
{
strenght: 10
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"3jdks0d9r2seifldvjmw0km2e"
]
},
then: {
$mergeObjects: [
"$$this",
{
strenght: 20
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"aslkdnasklnc2112uhnsjssad"
]
},
then: {
$mergeObjects: [
"$$this",
{
strenght: 12
}
]
}
},
{
case: {
$eq: [
"$$this.ID",
"1230msafmlklkmasfmcio3kms"
]
},
then: {
$mergeObjects: [
"$$this",
{
strenght: 54
}
]
}
}
],
default: "$$this"
}
}
}
}
}
}
])
For the items and stats with ID to be updated, make sure that you need to parse as ObjectId [Note that Mongo Playground doesn't recognize your provided IDs, I manually change the dataset for those IDs as string but the concept for the update is the same].
Sample Mongo Playground

Mongoose not updating multiple matches

[
{
"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

Updating nested array of documents as object

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"
}
]
}
]

Project multiple documents with different key nodes for subdocuments

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
]
}
}
},
[]
]
}
]
}
}
}
])

mongo query: add a new field with ranking number based on another field

I am new to mongo queries. Currently I have a collection like this, which is used to create a d3 force-directed graph.
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763
},
{
"id": "jason#gmail.com",
"count": 1,
"nodeUpdatetime": 1525447783
}
],
"links": [{
"source": "bmw#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525312111
}, {
"source": "car#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525334013
}, {
"source": "bmw#gmail.com",
"target": "car#gmail.com",
"timestamp": 1525334118
}]
}
Using a mongo query, I would like to generate the output to something like this. Basically for the nested data under "nodes", add a new field called "topn" and rank them by count from 1 to 5. The remainder values are null. Can anyone help? Thank you!
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732,
"topn": 1
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null,
"topn": 2
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742,
"topn": 4
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847,
"topn": null
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758,
"topn": 5
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763,
"topn": 3
},
..............
The following should get you what you want:
db.collection.aggregate({
$unwind: "$nodes" // flatten the "nodes" array
}, {
$sort: { "nodes.count": -1 } // sort descending by "count"
}, {
$group: { // create the original structure again - just with sorted array elements
_id: "$_id",
nodes: { "$push": "$nodes" }
}
}, {
$addFields: {
"nodes": {
$zip: { // zip two arrays together
inputs: [
"$nodes", // the first one being the existing and now sorted "nodes" array
{ $range: [ 1, 6 ] } // and the second one being [ 1, 2, 3, 4, 5 ]
],
useLongestLength: true // do not stop after five elements but instead continue using a "null" value
}
}
}
}, {
$addFields: {
"nodes": {
$map: { // transform the "nodes" array
input: "$nodes",
as: "this",
in: {
$mergeObjects: [ // by merging two objects
{ $arrayElemAt: [ "$$this", 0] }, // the first sits at array position 0
{
topn: { $arrayElemAt: [ "$$this", 1] } // the second will be a new entity witha a "topn" field holding the second element in the array
}
]
}
}
}
}
})