MongoDB: extra object created when updating with $addToSet - arrays

I'm encountering a strange behavior in MongoDB.
This is the schema of the documents in my collection:
{
"_id" : ObjectId("62bf10951fecaed4dba275b1"),
"name" : "Rack 1",
"type" : "rack",
"positions" : [
{
"number" : 1
},
{
"number" : 2
},
{
"number" : 3,
"nodes" : [
{
"number" : 1
}
]
},
{
"number" : 4,
"nodes" : [
{
"number" : 1
},
{
"number" : 2
},
{
"number" : 3
}
]
},
]
}
I would like to run two queries:
create a new position, if it doesn't exist yet
create a new object in the nodes array
I have created two queries so far:
db.getCollection('locations').updateOne(
{ _id: ObjectId("62bf10951fecaed4dba275b1") },
{ $addToSet: { 'positions': { number: 5 } } }
)
db.getCollection('locations').updateOne(
{ _id: ObjectId("62bf10951fecaed4dba275b1"), 'positions.number': 5 },
{ $addToSet: { 'positions.$.nodes': { number: 1 } } }
)
The second query creates a new element in nodes. BUT it also creates a new element in the positions array.
This is the erroneous object:
{
"_id" : ObjectId("62bf10951fecaed4dba275b1"),
"name" : "Rack 1",
"type" : "rack",
"positions" : [
{
"number" : 1
},
{
"number" : 2
},
{
"number" : 3,
"nodes" : [
{
"number" : 1
}
]
},
{
"number" : 4,
"nodes" : [
{
"number" : 1
},
{
"number" : 2
},
{
"number" : 3
}
]
},
{
"number" : 5,
"nodes" : [
{
"number" : 1
}
]
},
{
"number" : 5 <<<<<< THIS IS WRONG
},
]
}
Is there something I need to know or is simply the wrong query?
Many thanks

The two queries may run at different lengths of time and should not be executed synchronously.
Please try using some async/await style ordering to ensure that the second query does indeed execute when the first query is finished.
e.g. in javascript:
const myFunction = async () => {
await db.getCollection('locations').updateOne(
{ _id: ObjectId("62bf10951fecaed4dba275b1") },
{ $addToSet: { 'positions': { number: 5 } } }
)
await db.getCollection('locations').updateOne(
{ _id: ObjectId("62bf10951fecaed4dba275b1"), 'positions.number': 5 },
{ $addToSet: { 'positions.$.nodes': { number: 1 } } }
)
}
myFunction()

Related

MongoDB nested filters application

I have data in mongodb with multiple fields, I am trying to filter data on basis of a field named create_date and then trying to fetch totalrecordscount along with further filtering the data. Following is the data structure:
"_id" : ObjectId("62a886a76034628f8028e8dc"),
"create_time" : "18:53:01",
"close_date" : "2022-05-09",
"close_time" : "13:34:43",
"country_code" : "US",
"closed_case" : 1,
"resolution_days" : 8,
"status_code" : "5",
"state_code" : "1",
"issue_resolved_flag" : "Yes",
"incident_created_by" : "09D4A6BB-C51E-EB11-A813-000D3A58F938",
"incident_modified_by" : "A3CBC776-DF3C-E711-810B-E0071B7284D1",
"modifiedon" : "2022-05-09 13:34:46.0",
"row_insertion_dttm" : "2022-06-14 02:58:21.202",
"data_source_category" : "CASE",
"resolution_duration_minutes" : 5060,
"create_date" : "2022-05-01",
"repeat_case_different_issue7_day" : 0,
"repeat_case_same_issue_7day" : 0,
"scr_7day" : 1,
"ocr_7day" : 0,
"csat_status" : "no"
I am able to aggregate the data on basis of create date and fetch the totalrecordscount for a particular date using following command :
country_code:1
}},
{
$match:{create_date:{$gt:"2022-06-01"}}
},
{$group:{ _id: {datebasis: "$create_date"},
TotalRecordscount: { $sum: 1 },
}
},
])
The output is: {
"_id" : {
"datebasis" : "2022-06-17"
},
"TotalRecordscount" : 13254.0
}
/* 2 */
{
"_id" : {
"datebasis" : "2022-06-14"
},
"TotalRecordscount" : 16688.0
}
/* 3 */
{
"_id" : {
"datebasis" : "2022-06-09"
},
"TotalRecordscount" : 15478.0
}
But my ask is to further group the data to get the number of records on a particular date for fields like "scr_7day" equals to 0 or "resolution_duration_minutes" < 1440.
Can you help me in achieving this?
Assume you solve the date string logic as mentioned in the comment, my answer just focuses on your question.
You can work with $count and $cond operators to calculate the documents by condition.
db.collection.aggregate([
{
$group: {
_id: {
datebasis: "$create_date"
},
TotalRecordscount: {
$sum: 1
},
scr_7dayIsZero: {
$sum: {
$cond: {
if: {
$eq: [
"$scr_7day",
0
]
},
then: 1,
else: 0
}
}
},
resolution_duration_minutesLessThan1440: {
$sum: {
$cond: {
if: {
$lt: [
"$resolution_duration_minutes",
1440
]
},
then: 1,
else: 0
}
}
}
}
}
])
Sample Mongo Playground

MongoDB: retrieve only certain properties of a document after matching

I have a MongoDB collection called books.
An example of a document is:
{
"_id" : ObjectId("62bf10951fecaed4dba275b1"),
"name" : "Library 1",
"positions" : [
{
"number" : 2,
"nodes" : [
{
"number" : 2,
"bookId" : "6254674d3711f90bd8e76036"
},
{
"number" : 1,
"bookId" : "621e9b5aa7951d0be4516c18"
}
]
},
{
"number" : 1,
"nodes" : [
{
"number" : 1,
"bookId" : "6254674d3711f90bd8e76037"
},
{
"number" : 3,
"bookId" : "6254674d3711f90bd8e76039"
},
{
"number" : 2,
"bookId" : "6254674d3711f90bd8e76035"
}
]
}
]
}
I need to run a query that, based on a book ID, returns the name, the positions.number and the positions.node.number.
For example, if I search for the ID 6254674d3711f90bd8e76035, it should return:
{
_id: ObjectId("62bf10951fecaed4dba275b1"),
name: "Library 1",
positions: {
number: 1,
nodes: {
number: 2
}
}
}
So far, this is what I came out with:
db.getCollection('books').aggregate([
{ $match: { "positions.nodes.bookId": "6254674d3711f90bd8e76035" } },
{ $project: { name: 1, "positions.number": 1, "positions.nodes.number": 1 } }
])
Unfortunately, this returns every single node. I might need something that says:
"Select name, position.number, position.nodes.number where bookId = 6254674d3711f90bd8e76035"
Any help is appreciated.
Thanks
Solved:
db.getCollection('books').aggregate([
{
$match: {"positions.nodes.books": "6254674d3711f90bd8e76035"}
},
{
$unwind: "$positions"
},
{
$unwind: "$positions.nodes"
},
{
$match: {"positions.nodes.books": "6254674d3711f90bd8e76035"}
},
])

Querying Arrays in MongoDB

I have a Mongo collection called players and in each document there are two entries called transactions and autographs. Both are arrays with objects inside containing a timestamp.
How can I use this db.collection.count( { timestamp: {$gt: 1585526400000} }) to display how many have been inputed into the db in the last 7 days?
Assuming the following two simplified records are in your database:
{
"_id" : ObjectId("5e8b8b66c1f8161eeeab762f"),
"transactions" : [
{
"timestamp" : ISODate("2020-04-05T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-04-02T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-04-01T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-01-06T00:25:20.202Z")
}
]
},
{
"_id" : ObjectId("5e8b9008b29982222cd38888"),
"transactions" : [
{
"timestamp" : ISODate("2020-04-04T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-02-03T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-02-01T00:25:20.202Z")
},
{
"timestamp" : ISODate("2020-02-06T00:25:20.202Z")
}
]
}
Then you can get the count of the array elements which your condition as follows:
db.getCollection('players').aggregate([{
$project: {
transactionsCount: {
$size: {
$filter: {
input: "$transactions",
as: "item",
cond: {
$gte: ["$$item.timestamp", ISODate('2020-03-30 00:00:00.000Z')]
}
}
}
}
}
}
])
Result:
{
"_id" : ObjectId("5e8b8b66c1f8161eeeab762f"),
"transactionsCount" : 3
}
{
"_id" : ObjectId("5e8b9008b29982222cd38888"),
"transactionsCount" : 1
}

Find specific field in an array of embedded documents

I'm trying to find specific element of array in my MongoDB document, but with all my tries query returns all array. How can I get the right object?
My collection's document looks like:
{
"Achievements": [
{
"AchievementID": 1,
"AchievementEventID": 0
},
{
"AchievementID": 2,
"AchievementEventID": 1
}
],
"Buildings": [
{
"BuildingID": 1,
"BuildingType": "type1"
},
{
"BuildingID": 2,
"BuildingType": "type1"
},
]
}
I tried to get only one element of my Achievements array:
db.data.find({'Achievements.AchievementEventID': 0})
I expected to get only element with AchievementEventID equal to 0:
{
"AchievementID": 1,
"AchievementEventID": 0
}
But I got whole Achievements array.
How I could get only specific element?
You can use the Aggregation query like in the following example. The sample achive collection has 3 documents:
{
"_id" : 1,
"Achievements" : [
{
"AchievementID" : 1,
"AchievementEventID" : 0
},
{
"AchievementID" : 2,
"AchievementEventID" : 1
}
],
"Buildings" : [
{
"BuildingID" : 1,
"BuildingType" : "type1"
},
{
"BuildingID" : 2,
"BuildingType" : "type1"
}
]
}
{
"_id" : 2,
"Achievements" : [
{
"AchievementID" : 2,
"AchievementEventID" : 2
}
],
"Buildings" : [
{
"BuildingID" : 2,
"BuildingType" : "type2"
}
]
}
{
"_id" : 3,
"Achievements" : [
{
"AchievementID" : 31,
"AchievementEventID" : 1
}
],
"Buildings" : [
{
"BuildingID" : 3,
"BuildingType" : "type3"
}
]
}
The Query:
db.achive.aggregate( [
{ $project: { Buildings: 0} },
{ $unwind: "$Achievements" },
{ $match: { "Achievements.AchievementEventID": { $eq: 1 } } }
])
=>
{ "_id" : 1, "Achievements" : { "AchievementID" : 2, "AchievementEventID" : 1 } }
{ "_id" : 3, "Achievements" : { "AchievementID" : 31, "AchievementEventID" : 1 } }
I added the _id field to identify the documents selected.
Updated Query:
db.achive.aggregate( [
{ $unwind: "$Achievements" },
{ $match: { "Achievements.AchievementEventID": { $eq: 1 } } },
{ $project: { AchievementID: "$Achievements.AchievementID", _id: 0} },
])
=>
{ "AchievementID" : 2 }
{ "AchievementID" : 31 }
You can use $elemMatch for that
db.data.find(
{ 'Achievements.AchievementEventID': 0},{Buildings:0, Achievements: {$elemMatch: {AchievementEventID: 0}}}
)
If the above solution doesn't for you then try the below one:
db.data.find({"Achievements.AchievementEventID": 0}, {Buildings: 0, 'Achievements.$': 1});
You can use aggregate instead of a find like this
db.data.aggregate(
[{'$unwind':'$Achievements'},{$match:{
'Achievements.AchievementEventID':0
}},{$project:{
'AchievementID':'$Achievements.AchievementID',
'AchievementEventID':'$Achievements.AchievementEventID'
}}]
);

Calculate weight and skip a specific element Mongodb - Aggregation Framework

I have a collection with array elements containing A,B or C values. I have to calculate a weight of each element value.
The logic of this weight is sample :
We give 1.0 as weight of the last element (dfferente to C), and others 0
If the last element of the array is C, we give it to the previous last element (different to C).
if there is 1 element in the array (A,B or C) we give it 1 as a weight.
This is how my collection looking like :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C"
},
{
"value" : "A
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "A"
},
{
"value" : "A"
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"myArray" : [
{
"value" : "A"
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "C"
},
{
"value" : "C"
}
]
}
I did some aggregation to make it happen but I can here just to affect 1 to the last (different to C)
and (if the last is C I give 1 to the last one -1) so I have to fixe the case of having C in the last and last-1 and last -2 .. last-n
so I have to fix it to affect 1 to the last one different to C
db.col.aggregate([{'$addFields':{
'myArray_temp':{
'$switch':{
'branches':[{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$eq':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},2]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-2]},'weight':1}],
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':0}]
]}
},
{
'case':{'$eq':[{'$size':'$myArray'},1]},
'then':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
},
{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$ne':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},1]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':1}]
]}
}
],
'default':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
}
}
}}])
The results should be :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C",
"weight": 0
},
{
"value" : "A" ,
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 0
},
{
"value" : "A",
"weight": 0
},
{
"value" : "A",
"weight": 1
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"total" : 4.5,
"myArray" : [
{
"value" : "A",
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 1 // here we give 1 to the last differente to C
},
{
"value" : "C" ,
"weight": 0 // my code affect 1 here cause it find C in the last and affect to the last-1 element.
},
{
"value" : "C" ,
"weight": 0
}
]
}
We have to skip all the last (C) elements and give the 1 weight to the last not-C element.
Thank you in advance!
Longest aggregation in my career but seems to be working:
db.collection.aggregate([
{
$addFields : {
size: { $size: "$myArray" },
reversed: {
$reverseArray: "$myArray"
}
}
},
{
$addFields: {
otherThanC: {
$filter: {
input: "$reversed",
as: "item",
cond: { $ne: [ "$$item.value", "C" ] }
}
}
}
},
{
$addFields: {
firstOtherThanCIndex : {
$indexOfArray: [ "$reversed", { $arrayElemAt: [ "$otherThanC", 0 ] } ]
}
}
},
{
$unwind: {
path: "$reversed",
includeArrayIndex: "arrayIndex"
}
},
{
$addFields: {
weight: {
$switch: {
branches: [
{ case: { $eq: [ "$size", 1 ] }, then: 1 },
{ case: { $and: [ { $eq: [ "$arrayIndex", 0 ] }, { $eq: [ { $size: "$otherThanC" }, 0 ] } ] } , then: 1},
{ case: { $eq: [ "$arrayIndex", "$firstOtherThanCIndex" ] }, then: 1 }
],
default: 0
}
}
}
},
{
$group: {
_id: "$_id",
myArrayReversed: {
$push: {
value: "$reversed.value",
weight: "$weight"
}
}
}
},
{
$project: {
_id: 1,
myArray: { $reverseArray: "$myArrayReversed" }
}
}
])
Brief description of each pipeline stage:
We need to add two extra fields: $size of and array and second array with reversed items
Second and third steps are to find first (last) item not equal to C using $filter and $arrayElemAt which returns first matching index
Then we can $unwind our reversed array using special syntax which adds index of array when unwinding
That is the moment when we can calculate weight using $switch - simply setting 1 if array has one element or indexes are matching and zero otherwise
Then we just need to reshape the data: grouping back by _id and reversing the array

Resources