Conditional check inside nested array in MongoDb - mongodb

After a few pipeline stages, I came up with following sample result which is one document. If the videos.views.userId contains 10, I need to indicate videos.isWatched = true else false. We can use $unwind, check the condition, then group it.
This is sample output, The original document contains a lot of field, so I just like to do with less code unless I need to unwind Is there any way to do without unwind("videos")?
[
{
"_id":"someId",
"videos":[
{
"_id":"1",
"name":"A",
"views":[
{
"userId":10,
"group":"X"
}
]
},
{
"_id":"2",
"name":"B",
"views":[
{
"userId":20,
"group":"Y"
}
]
}
],
"Assessment":[
{
"_id":"22",
"title": "Hello world"
}
]
}
]
Expected result
[
{
"_id":"someId",
"videos":[
{
_id:"1",
name:"A",
views:[
{
userId:10,
group:"X"
}
],
"isWatched":true
},
{
_id:"2",
name:"B",
views:[
{
userId:20,
group:"Y"
}
],
"isWatched":false
}
],
"Assessment":[
{
_id:"22",
title: "Hello world"
}
]
}
]

You can use $map along with $mergeObjects to add a new field to an existing array. $anyElementTrue can be used to determine whether there's any userId equal to 10:
db.collection.aggregate([
{
$addFields: {
videos: {
$map: {
input: "$videos",
in: {
$mergeObjects: [
"$$this",
{
isWatched: {
$anyElementTrue: {
$map: { input: "$$this.views", in: { $eq: [ "$$this.userId", 10 ] } }
}
}
}
]
}
}
}
}
}
])
Mongo Playground

Related

MongoDB - Modify the field name in a nested list of lists

As the title says, the field I need to modify is nested like this:
basicData.owners.relatedJson.basicData.devices.equipmentID
which owners and devices are both lists.
The object looks like this:
{
"basicData": {
"owners": [
{
"relatedJson": {
"basicData": {
"devices": [
{
"equipmentID": "abcd",
"type": "camera"
}
],
"otherFieldsBasicData": "other values",
"evenMoreFieldsBasicData": "other values"
},
"otherFieldsRelatedJson": "other values",
"evenMoreFieldsRelatedJson": "other values"
}
}
]
}
}
I want to rename equipmentID to equipmentId.
I've also asked this question, and I've been trying to create a query using that as a starting point, but with no success.
I was able to build a query that could get as far down as the devices list, but. then I wanted to call $set on that list, and I get an error because you can't call set inside $mergeObjects.
I thought there was some way I could use $[] to help iterate through the first array, but I can't get it to work. I think this approach is wrong.
This is what I've ended up with, which doesn't work:
db.myCollection.updateMany({"basicData.owners.relatedJson.basicData.devices.equipmentID": {$exists: true}},
[
{
$set: {
"basicData.owners$[].relatedJson.basicData.devices": {
$map: {
input: "$basicData.owners$[].relatedJson.basicData.devices", in: {
$mergeObjects: ["$$this",
{equipmentId: "$$this.equipmentID",}]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Any advice would be greatly appreciated.
Think you need two $map (with nested) operators.
First $map to iterate basicData.owners array while another $map is used to iterate relatedJson.basicData.devices array.
db.collection.updateMany({
"basicData.owners.relatedJson.basicData.devices.equipmentID": {
$exists: true
}
},
[
{
$set: {
"basicData.owners": {
$map: {
input: "$basicData.owners",
in: {
$mergeObjects: [
"$$this",
{
"relatedJson": {
$mergeObjects: [
"$$this.relatedJson",
{
"basicData": {
$mergeObjects: [
"$$this.relatedJson.basicData",
{
"devices": {
$map: {
input: "$$this.relatedJson.basicData.devices",
in: {
$mergeObjects: [
"$$this",
{
equipmentId: "$$this.equipmentID",
}
]
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Demo # Mongo Playground

MongoDb update array element with multiply on array keys

I have a mongo document as follows
{
"id": "mongoId",
"results": [
"score" : 80,
"details": {
"credit": 2,
"creditPoints": 40,
"creditScore": 0
}
]
}
I want to update all documents the creditScore as credit * creditPoints.
What i have tried till now is
db.match.updateMany({}, { "$set" : { "results.$[].details.creditScore": {"$mul" : { "$results.details.credit": "$results.details.creditPoints" } } }} );
Direct update is not allow to use internal fields operations, you need to use update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of results array
$multiply to multiply credit and creditPoints
$mergeObjects to merge details object with updated creditScore field
$mergeObjects to merge current object with updated details object
db.match.updateMany({},
[
{
$set: {
results: {
$map: {
input: "$results",
in: {
$mergeObjects: [
"$$this",
{
details: {
$mergeObjects: [
"$$this.details",
{
creditScore: {
$multiply: [
"$$this.details.credit",
"$$this.details.creditPoints"
]
}
}
]
}
}
]
}
}
}
}
}
])
Playground

Mongodb aggregation pass argument to element size of $sample

Hello every body here any one can help me with query below
I want to get quiz list with random amount
the amount of rendom will
base on each lesson
The problem is
mongodb not allow to pass argument to element size of $sample
Any one can give me the solution
lessonModel.aggregate([
{ $match : {'quiz.status':1 } },
{
$lookup : {
from : 'quiz',
let : { 'lesson_id' : '$_id','limit' : '$quiz.amount' },
pipeline : [
{
$match: {
$expr: {
$eq: [ "$lesson_id", "$$lesson_id" ]
}
}
},
{
$project: {
title:1,
check_list:1,
duration:1
}
},
{ $sample: { size: '$$limit' } }
],
as: 'quiz'
}
},
{$unwind: '$quiz'},
{ $replaceRoot: { newRoot: "$quiz" } }
]).exec();
The error said size argument to $sample must be a number
Here is my sample data
UPDATE
I think your main problem is to randomly pick amount number of quizs under each lesson. Since $sample is not helpful use $function (New in version MongoDB 4.4).
Solution
Inside $function operator write some logic to
Shuffle the questions (You can change it to your requirement).
Slice it to return the number(amount) of questions required.
db.lessons.aggregate([
{ $match: { "quiz.status": 1 } },
{
$lookup: {
from: "quiz",
let: { "lesson_id": "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$lesson_id", "$$lesson_id"] }
}
},
{
$project: {
title: 1,
check_list: 1,
duration: 1
}
}
],
as: "questions"
}
},
{
$project: {
quiz: {
$function: {
body: function(questions, amount) {
if (amount == 0) return [];
for (let i = questions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[questions[i], questions[j]] = [questions[j], questions[i]];
}
return questions.slice(0, amount);
},
args: ["$questions", { $ifNull: ["$quiz.amount", 0] }],
lang: "js"
}
}
}
},
{ $unwind: "$quiz" },
{ $replaceRoot: { newRoot: "$quiz" } }
]);
$sample does not support variables. A number must be specified explicitly like:
{
$sample: { size: 1 }
}
Also replace your let as shown below because last lesson document has no amount filed in the quiz object
let: {
'lesson_id': '$_id',
'limit': { $ifNull: ['$quiz.amount', 0] } // or any other number.
},
Wrong:
{
$sample: { size: "$$limit" } // Wont work!
}

How to query MongoDB for complex data

I have a table structured as follows:
db.l2vpn_fdb_database.findOne()
{
_id: ObjectId("5f5257f11324c04122714445"),
hostname: "spine01-drt-red",
l2vpn_fdb_database: {
MAC: [
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-01",
SrvID: "1",
VsiName: "EVPN",
},
{
IfIndex: "0",
MacAddr: "00-00-0C-07-AC-02",
SrvID: "0",
VsiName: "EVPN",
},
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-0A",
SrvID: "1",
VsiName: "EVPN",
},
],
},
}
I'd like to search for "MacAddr" object, could you help me figure out based on above? So essentially I'd like to be able to parse database for a MacAddress assuming it's there and be able to get "IfIndex" for further processing.
Thank you.
You can use $filter to get matched objects
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
}
}
}
])
Working Mongo playground
for Hostname with macAddr try like this,
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
},
hostname:{
$eq:['$hostname','spine01-drt-red']
}
}
}
])
This query could help you.
b.l2vpn_fdb_database.findOne({
"l2vpn_fdb_database.MAC.MacAddr": "00-00-0C-07-AC-01",
},
{
"l2vpn_fdb_database.MAC.$": 1
})
The result is the same document just with 1 element in the array
Result:
{
"_id": ObjectId("5f5257f11324c04122714445"),
"l2vpn_fdb_database": {
"MAC": [
{
"IfIndex": "1631",
"MacAddr": "00-00-0C-07-AC-01",
"SrvID": "1",
"VsiName": "EVPN"
}
]
}
}

Updating array of sub sub document with matching element

Below is my schema:
{
"_id":ObjectId("5c49c783de72ec2ec47b95d1"),
"placement":[
{
"offer":[
{
"sent_by":"John",
"comment":""
},
{
"sent_by":"Mary",
"comment":""
}
]
}
]
}
I want to update placement.offer.comment where placement.offer.sent_by is Mary but it always updates the first record. I don't want to provide a hard coded number like placement.0.offer.1.sent_by.
This should be the resulting document:
{
"_id":ObjectId("5c49c783de72ec2ec47b95d1"),
"placement":[
{
"offer":[
{
"sent_by":"John",
"comment":""
},
{
"sent_by":"Mary",
"comment":"Some comment updated"
}
]
}
]
}
You would need to use array filters to achieve that:
db.collection.update(
{ /* add additional query filters here */ },
{ $set: { "placement.$[].offer.$[o].comment": "Some updated comment" } },
{ arrayFilters: [ { "o.sent_by": "Mary" } ] }
)