Mongo Update Query on Array Index value matching - mongodb

student collection look like below.
[{
"_id" : NumberLong(1),
"studentId" : "70133",
"subjects" : [
"Mathematics",
"Biology and Zoology"
]
},
{
"_id" : NumberLong(2),
"studentId" : "70134",
"subjects" : [
"Mathematics",
"English"
]
},
{
"_id" : NumberLong(3),
"studentId" : "70135",
"subjects" : [
"English",
"Mathematics",
"Chemistry"
]
}]);
I want to write a query which will update the student collection subject array values like this:
If subject matches English have to be updated to ENG
If subject matches Biology and Zoology have to be updated to BAZ
If subject matches Mathematics have to be updated to MAT
If subject matches Chemistry have to be updated to CHM
After update documents should look like below. How Can I achieve this.
[{
"_id" : NumberLong(1),
"studentId" : "70133",
"subjects" : [
"MAT",
"BAZ"
]
},
{
"_id" : NumberLong(2),
"studentId" : "70134",
"subjects" : [
"MAT",
"ENG"
]
},
{
"_id" : NumberLong(3),
"studentId" : "70135",
"subjects" : [
"ENG",
"MAT",
"CHM"
]
}]);

This has to be done with multiple updates like:
db.collection.update(
{ "subjects" : { $in : [ "Mathematics" ] } },
{ $set: { "subjects.$" : "MAT" } },
{ multi: true }
)
See this page for more information regarding updating multiple document
if multi is set to true, the db.collection.update() method updates all documents that meet the criteria. The multi update operation may interleave with other read/write operations.
https://docs.mongodb.com/master/reference/method/db.collection.update/index.html#multi-parameter

Related

MongoDB query for documents with nested objects and array

I am having trouble with mongodb querying my object document.
I have the following document:
{
"_id" : ObjectId("1"),
"name" : "Bob",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
},
{
"_id" : ObjectId("2"),
"name" : "John",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"1901-08-01"
]
}
]
}
I am trying to get the document where fields.date[0].values is equal to 2022-08-01.
How can I achieve that? Thank you.
You are looking for a nested element value which having some specific value/date.
Since your document is incomplete so, I am creating one for illustrating the solution.
db.employees.insertMany([{
"name" : "Bob",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
}},
{
"name" : "John",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"1901-08-01"
]
}
]
}
},
{
"name" : "Eve",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
}},
]);
Now we can find the specific value (2022-08-01 as per your requirement) from the nested array/element inside the document. Since the value resides inside "DATE" which is inside the "fields". So we can get that value easily by calling "fields.DATE".
For example you can write some code like this :
db.employees.find({"fields.DATE":{"$elemMatch": {"values": "2022-08-01"}}})
From the above code you will get a result like this.
{ "_id" : ObjectId("62e94392389644cb3fc9c81f"), "name" : "Bob", "fields" : { "DATE" : [ { "fromDate" : null, "values" : [ "2022-08-01" ] } ] } }
{ "_id" : ObjectId("62e94392389644cb3fc9c821"), "name" : "Eve", "fields" : { "DATE" : [ { "fromDate" : null, "values" : [ "2022-08-01" ] } ] } }
Hope this will help you to solve this issue. Happy coding:)

Embed root field in a subdocument within an aggregation pipeline

Maybe someone can help me with Mongo's Aggregation Pipeline. I am trying to put an object in another object but I'm new to Mongo and ist very difficult:
{
"_id" : ObjectId("5888a74f137ed66828367585"),
"name" : "Unis",
"tags" : [...],
"editable" : true,
"token" : "YfFzaoNvWPbvyUmSulXfMPq4a9QgGxN1ElIzAUmSJRX4cN7zCl",
"columns" : [...],
"description" : "...",
"sites" : {
"_id" : ObjectId("5888ae2f137ed668fb95a03d"),
"url" : "www.....de",
"column_values" : [
"University XXX",
"XXX",
"false"
],
"list_id" : ObjectId("5888a74f137ed66828367585")
},
"scan" : [
{
"_id" : ObjectId("5888b1074e2123c22ae7f4d3"),
"site_id" : ObjectId("5888ae2f137ed668fb95a03d"),
"scan_group_id" : ObjectId("5888a970a7f75fbd49052ed6"),
"date" : ISODate("2017-01-18T16:00:00Z"),
"score" : "B",
"https" : false,
"cookies" : 12
}
]
}
I want to put every object in the "scan"-array into "sites". So that it looks like this:
{
"_id" : ObjectId("5888a74f137ed66828367585"),
"name" : "Unis",
"tags" : [...],
"editable" : true,
"token" : "YfFzaoNvWPbvyUmSulXfMPq4a9QgGxN1ElIzAUmSJRX4cN7zCl",
"columns" : [...],
"description" : "...",
"sites" : {
"_id" : ObjectId("5888ae2f137ed668fb95a03d"),
"url" : "www.....de",
"column_values" : [
"University XXX",
"XXX",
"false"
],
"list_id" : ObjectId("5888a74f137ed66828367585"),
"scan" : [
{
"_id" : ObjectId("5888b1074e2123c22ae7f4d3"),
"site_id" : ObjectId("5888ae2f137ed668fb95a03d"),
"scan_group_id" : ObjectId("5888a970a7f75fbd49052ed6"),
"date" : ISODate("2017-01-18T16:00:00Z"),
"score" : "B",
"https" : false,
"cookies" : 12
}
]
}
}
Is there a step in the aggregation pipeline to perform this task?
With a single pipeline I don't see any other way but specifying each field individually as:
db.collection.aggregate([
{
"$project": {
"name": 1, "tags": 1,
"editable": 1,
"token": 1, "columns": 1,
"description": 1,
"sites._id": "$sites._id",
"sites.url": "$sites.url" ,
"sites.column_values": "$sites.column_values" ,
"sites.list_id": "$sites.list_id",
"sites.scan": "$scan"
}
}
])
With MongoDB 3.4 and newer, you can use the $addFields pipeline step instead of specifying all fields using $project. The advantage is that it adds new fields to documents and outputs documents that contain all existing fields from the input documents and the newly added fields:
db.collection.aggregate([
{
"$addFields": {
"sites._id": "$sites._id",
"sites.url": "$sites.url" ,
"sites.column_values": "$sites.column_values" ,
"sites.list_id": "$sites.list_id",
"sites.scan": "$scan"
}
}, { "$project": { "scan": 0 } }
])

MongoDB finding all subdocuments where subDocument _ids like "string"

Sample document structure is like:
{
"_id" : "https://docs.mongodb.org/manual",
"collection" : {
"_id" : "collection",
"urls" : [
"https://docs.mongodb.org/manual/c1",
"https://docs.mongodb.org/manual/c2"
]
},
"collectionNew" : {
"_id" : "collection1",
"urls" : [
"https://docs.mongodb.org/manual/c1New",
"https://docs.mongodb.org/manual/c2New"
]
},
"log" : {
"_id" : "log",
"urls" : [
"https://docs.mongodb.org/manual/l1",
"https://docs.mongodb.org/manual/l2"
]
}
}
I have multiple such documents in my collection.
After finding document by
db.AutoSearch.find({"_id": "https://docs.mongodb.org/manual"})
i want to search all subdocuments under this with name like "collection"
SOLUTION
I changed my document structure to:
{
"_id" : "https://docs.mongodb.org/manual",
"contents" : [
{
"_id" : "connect",
"urls" : [
"https://docs.mongodb.org/manual/search/?query=connect"
]
},
{
"_id" : "connection",
"urls" : [
"https://docs.mongodb.org/manual/search/?query=connection",
"https://docs.mongodb.org/manual/search/?query=connection%20pymongo"
]
}
{
"_id" : "list",
"urls" : [
"https://docs.mongodb.org/manual/search/?query=listfiles",
"https://docs.mongodb.org/manual/search/?query=listdatabases",
"https://docs.mongodb.org/manual/search/?query=listcommands"
]
},
{
"_id" : "log",
"urls" : [
"http://docs.mongodb.org/manual/tutorial/rotate-log-files/",
"http://docs.mongodb.org/manual/reference/log-messages/"
]
},
{
"_id" : "index",
"urls" : [
"https://docs.mongodb.org/manual/search/?query=index-related%20commands"
]
}
]}
Then by using mongoDB aggregate query
db.AutoSearch.aggregate([{$match:{ "_id": "https://docs.mongodb.org/manual"}},
{$unwind: '$contents'},
{$match:{"contents._id":/connect/}}])
I got the desired output.

Redact in mongodb seems obscure to me

I'm fighting with redact right now and I'm not sure to understand it.
I just read the docs and tried to use redact on a collection grades (it comes from mongodb online training)
A document in the collection "grades" looks like this :
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [
{
"type" : "exam",
"score" : 57.92947112575566
},
{
"type" : "quiz",
"score" : 21.24542588206755
},
{
"type" : "homework",
"score" : 68.19567810587429
},
{
"type" : "homework",
"score" : 67.95019716560351
},
{
"type" : "homework",
"score" : 18.81037253352722
}
]
}
I use the following query :
db.grades.aggregate([
{ $match: { student_id: 0 } },
{
$redact: {
$cond: {
if: { $eq: [ "$type" , "exam" ] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
}
]
);
With this query, each type an exam is found, this sub document should be excluded. And it works, the result is:
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [
{
"type" : "quiz",
"score" : 21.24542588206755
},
{
"type" : "homework",
"score" : 68.19567810587429
},
{
"type" : "homework",
"score" : 67.95019716560351
},
{
"type" : "homework",
"score" : 18.81037253352722
}
]
}
but if I invert the condition, I expect that only exams are kept in the result :
if: { $eq: [ "$type" , "exam" ] },
then: "$$DESCEND",
else: "$$PRUNE"
however the result is empty.
I don't understand why subdocument of type "exam" are not included.
The $redact stage starts at the root document and its fields, and only when that document fulfills the condition to $$DESCEND, it examines the sub-documents included in that document. That means the first thing $redact does with your document is examine this:
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [] // Some array. I will look at this later.
}
It doesn't even find a type field here, so $eq: [ "$type" , "exam" ] is false. What did you tell $redact to do when the condition is false? else: "$$PRUNE", so the whole document is pruned before the sub-documents are examined.
As a workaround, test if $type is either "exam" or doesn't exist. You didn't explicitly ask for a working solution, so I will leave it as an exercise to you to figure out how to do this.

MongoDB: query nested array by more than one condition

Say this is one item from my users collection:
{
"_id" : "5545f4c4d0dd52c355a99fbe",
"name" : "Dollie James",
"device" : "iOS",
"gender" : "Female",
"wallet" : [
{
"store" : "All Saints",
"balance" : "$196.11",
"date" : "2014-02-22T22:09:38 -10:00",
"tags" : [
"Tshirt",
"Summer"
]
},
{
"store" : "Nike",
"balance" : "$367.76",
"date" : "2014-04-18T14:44:30 -10:00",
"tags" : [
"Shoes"
]
}
]
}
This record was returned from the following query:
db.users.findOne({$and:[{"wallet.tags" : "Tshirt"}, {"wallet.store":"Nike"}]})
However this is not the result I want because Tshirt and Nike are not in the same object in the wallet array. It seems that mongo is doing a query on the entire array. How can I target this query to only return my two conditions that are in the same object within the array?
You don't need $and (as it will apply conditions independently) but $elemMatch. From the doc:
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
In your specific case:
> db.wallet.find(
{wallet: { $elemMatch:{"tags" : "Tshirt", "store":"Nike"}}}
).pretty()
{
"_id" : "5545f4c4d0dd52c355a99f00",
"name" : "Working example",
"device" : "iOS",
"gender" : "Female",
"wallet" : [
{
"store" : "Nike",
"balance" : "$196.11",
"date" : "2014-02-22T22:09:38 -10:00",
"tags" : [
"Tshirt",
"Summer"
]
}
]
}