How to replace/insert a subdocument in nested array in MongoDB in just one command - mongodb

I have a collection persons with an array of answers:
[{ _id: ObjectId("5abd0550dcc44451cf3272ef"), "name": "Mike",
"answers": [ { "questionId": 118, "aaa": "xyz", "bbb": "xyz" } ] },
{ _id: ObjectId("5abd0550dcc44451cf3272ab"), "name": "John",
"answers": [ { "questionId": 101, "aaa": "xyz", "ccc": "xyz" } ] },
{ _id: ObjectId("5abd0550dcc44451cf327212"), "name": "Els",
"answers": [ { "questionId": 101, "aaa": "qrt", "ccc": "qrt" } ] },
{ _id: ObjectId("5abd0550dcc44451cf32724d"), "name": "Josefien",
"answers": [
{ "questionId": 109, "sss": "xyz", "ttt": "xyz" },
{ "questionId": 110, "nnn": "xyz", "mmm": "xyz" },
{ "questionId": 111, "kkk": "xyz", "lll": "xyz" },
]}]
Now I have a new answer from one person. When the questionId already exists I want to replace the whole subdocument answer. If not I want to add a new subdocument answer.
I tried this, but doesn't work, giving an error if it's a new subdocument:
db.persons.update(
{ _id: ObjectId("5abd0550dcc44451cf3272ef"), "answers.questionId": answer.questionId },
{ $set: { "answers.$": answer } },
{ upsert: true }
)
This is working fine:
db.persons.update(
{ _id: ObjectId("5abd0550dcc44451cf3272ef") },
{ $pull: { answers: { questionId: answer.questionId } } }
);
db.persons.update(
{ _id: ObjectId("5abd0550dcc44451cf3272ef") },
{ $addToSet : { answers: answer } }
)
But is there a way to do this in just one command?
Imagine that the persons collection has 10 millions of documents in real life. Also every person gives many new answers. In that case one transaction above two would be highly preferred.

This should work:
db.persons.update(
{ _id: ObjectId("5abd0550dcc44451cf3272ef"), "answers.questionId": answer.questionId },
{ $set: { 'answers.$': answer }}
);

Related

mongodb update and push array of objects

I have this simple collection of students:
{
"_id": "btv7865reVGlksabv",
"students": [
{
"name": "John",
"age": 30
},
{
"name": "Henry",
"age": 25
}
]
}
Now I want to push new students into this array:
const newStudents = [
{
"name": "Mike",
"age": 22
},
{
"name": "Kim",
"age": 20
}
]
What I tried so far is:
Students.update(
{
"_id": "btv7865reVGlksabv"
},
{
$push: {
"students": newStudents
}
}
);
The above query doesn't update my collection for some reason. Can anyone help me correct this query?
Chain up $push with $each
db.collection.update({
"_id": "btv7865reVGlksabv"
},
{
$push: {
"students": {
$each: [
{
"name": "Mike",
"age": 22
},
{
"name": "Kim",
"age": 20
}
]
}
}
})
Mongo Playground
Maybe something like this:
db.collection.update({},
[
{
$addFields: {
students: {
$concatArrays: [
"$students",
[
{
name: "New1",
age: "New1"
},
{
name: "New2",
age: "New2"
}
]
]
}
}
}
])
Explained:
Use $addFileds->$concatArrays to update via aggregation pipeline ( 4.2+) to add the elements from your new array to the already existing array ...
Playground

Mongodb querying for all min values that match criteria and indexing

Suppose I had the following:
[
{
"team": "A",
"age": 1,
"name": "Abe"
},
{
"team": "A",
"age": 5,
"name": "Apple"
},
{
"team": "B",
"age": 1,
"name": "Ben"
},
{
"team": "B",
"age": 2,
"name": "Bon"
},
{
"team": "C",
"age": 5,
"name": "Cherry"
}
]
I have the following query:
They must be in either TeamA or TeamB.
After filtering, I want only the youngest.
So in this example, it would return only Abe and Ben.
Preferably, I want it done in a single query. My guess is I have to use an aggregation pipeline, something like
db.People.aggregate(
[
$match: { $or: [{ team: 'A' }, { team: 'B' }] },
// some more stuff
);
Question1: I'm not sure what the next step would be. Could someone point me in the right direction?
Question2:
There may be a million records and I was thinking of adding two index:
Index on Team. I'm thinking this will allow it to filter Teams of interest.
Index on Age so that it can grab only the mins.
Would these indexes help or what kind of indexes should I be looking into?
**Edit: I'm getting closer, but I'm only interested in the records themselves. **
db.collection.aggregate([
{
$match: {
$or: [
{
team: "A"
},
{
team: "B"
}
]
}
},
{
$group: {
_id: "$age",
items: {
$push: "$$ROOT"
}
}
},
{
$sort: {
_id: 1
}
},
{
$limit: 1,
}
])

How to project nested array elements with custom field name in MongoDB

I have a users collection in MongoDB with this sample row:
[
{
"_id": 1,
"name": "John Doe",
"comments": [
{
"_id": 100,
"content": "Comment 100",
"post": "1000"
},
{
"_id": 101,
"content": "Comment 101",
"post": "1000"
}
]
}
]
I want to project users and comments data after converting _id fields into id. So I used the following query:
db.users.aggregate([
{
$project: {
_id: 0,
id: '$_id',
name:1
comments: {
_id: 0,
id: '$_id',
content: 1,
},
}
}
])
Now the users _id field is successfully converted. But the comments id field is equal to _id of users collection instead of comments.
[
{
"id": 1,
"name": "john Doe",
"comments": [
{
"id": 1,
"content": "comment 100"
},
{
"id": 1,
"content": "comment 101"
},
]
}
]
How can I achieve the right result.
you should use $map in project like this
https://mongoplayground.net/p/H4pueuejYdf
db.collection.aggregate([
{
"$project": {
_id: 0,
id: "$_id",
name: 1,
comments: {
"$map": {
"input": "$comments",
"as": "c",
"in": {
id: "$$c._id",
content: "$$c.content",
post: "$$c.post"
}
}
}
}
}
])

How to group data and get and get all the field back in mongodb [duplicate]

This question already has an answer here:
How to group documents with specific field in aggregation of mongodb
(1 answer)
Closed 1 year ago.
I have collection in a mongodb and want to group invoice by modified and get all the fields in those objects.
{
modified: "11/02/2020",
stocks: [
{
product:{
name:"Milk",
price: 20
}
quantity: 2,
paid: true
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Sugar",
price: 50
}
quantity: 1,
paid: false
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Butter",
price: 10
}
quantity: 5,
paid: false
}
]
}
So I tried:
db.collection.aggregate([{
$group: {
_id: "$modified",
records: { $push: "$$ROOT" }
}
}
])
But It reaggregate on the modifier field being pushed into the records generating duplicates
Demo - https://mongoplayground.net/p/4AGuo5zfF4V
Use $group
db.collection.aggregate([
{
$group: { _id: "$grade", records: { $push: "$$ROOT" } }
}
])
Output
[
{
"_id": "A",
"records": [
{
"_id": ObjectId("5a934e000102030405000000"),
"grade": "A",
"name": "John",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"grade": "A",
"name": "John1",
"subject": "English"
}
]
},
{
"_id": "B",
"records": [
{
"_id": ObjectId("5a934e000102030405000002"),
"grade": "B",
"name": "JohnB",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000003"),
"grade": "B",
"name": "JohnB1",
"subject": "English"
}
]
}
]

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground