Pushing objects on a specific multidimensional mongoDb collection - arrays

i'm fairly new to the mongoDb query language and I'm struggeling with following scenario.
We have a multidimensional dataset that is comprised of:
n users
n projects for each users
n time_entries for each project
What I am trying to achieve is: I would like to push/update a time_entry of a specific project using a collection.update.
Note each pid should be unique for a user
The collection structure I am using looks as follows:
{
"_id" : ObjectId("5d6e33987f8d7f00c063ceff"),
"date" : "2019-01-01",
"users" : [
{
"user_id" : 1,
"projects" : [
{
"pid" : 1,
"time_entries" : [
{
"duration" : 1,
"start" : "2019-08-29T09:54:56+00:00"
}
]
},
{
"pid" : 2,
"time_entries" : []
}
]
},
{
"user_id" : 2,
"projects" : [
{
"pid" : 3,
"time_entries" : []
}
]
}
]
}
I'm currently able to update all projects of a given user using:
"users.$.projects.$[].time_entries"
yet I'm not able to target a specific project, due to the fact the structure contains 2 nesting levels and using multiple $ positional operator is not yet permitted in MongoDb.
"users.$.projects.$.time_entries"
Below is my full query example:
db.times.update(
{ 'users' : { $elemMatch : { 'projects' : { $elemMatch : { 'pid' : 153446871 } } } } },
{ "$push":
{
"users.$.projects.$[].time_entries":
{
"duration" : 5,
"start" : "2019-08-29T09:54:56+00:00"
}
}
}
);
Are there other ways to achieve the same result?
Should I flatten the array so I only use 1 $ positional operator?
Are there other methods to push items on a multidimensional array?
Should this logic be handled on a code level and not a Database level?

You'll need to use the Positional Filtered Operator to achieve that:
db.times.update(
{},
{
$push: {
"users.$[].projects.$[element].time_entries":{
"duration" : 5,
"start" : "2019-08-29T09:54:56+00:00"
}
}
},
{
arrayFilters: [{"element.pid":1}],
multi: true
}
)
This query will push data to the array time_entries for every pid = 1 it finds.
This will give you the result below:
{
"_id" : ObjectId("5d6e33987f8d7f00c063ceff"),
"date" : "2019-01-01",
"users" : [
{
"user_id" : 1,
"projects" : [
{
"pid" : 1,
"time_entries" : [
{
"duration" : 1,
"start" : "2019-08-29T09:54:56+00:00"
},
{
"duration" : 5.0,
"start" : "2019-08-29T09:54:56+00:00"
}
]
},
{
"pid" : 2,
"time_entries" : []
}
]
},
{
"user_id" : 2,
"projects" : [
{
"pid" : 3,
"time_entries" : []
}
]
}
]
}

Related

Navigate thought a lots of array and update value of object with mongo query

Hey guys I'm trying to update a value that is in an array in MongoDB, am trying to use the mongo queries but is not working, am following the next documentation from Mongo doc
this one is the array:
{
"_id" : "605e3d9b9ef219de662113d0",
"distribution" : [
{
"floor" : 1,
"rooms" : [
{
"number" : 301,
"beds" : [
{
"number" : 818,
"status" : "Vacante Sucia"
},
{
"number" : 819,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
},
{
"number" : 302,
"beds" : [
{
"number" : 820,
"status" : "Vacante Sucia"
},
{
"number" : 821,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
},
{
"number" : 303,
"beds" : [
{
"number" : 822,
"status" : "Vacante Sucia"
},
{
"number" : 823,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
}
]
}
],
"name" : "Meteorologia",
"code" : "METEO"
}
this one is the query that is using in mongoDB to update the status from the bed 801, room 301, floor 1:
in the arrayFilters i specified the index 0 to get the first element of the arrays
db.getCollection('establishments_copy').findAndModify({query: { code: "METEO"}, update: { $set: { "distribution.$[i].rooms.$[i].beds.$[i].status": "TEST"}}, arrayFilters: [{"i.rooms": 0, "i.beds": 0, "i.status": 0}]})
they are returning me the collection but without changes, is possible to navigate validating not for the index just with the values.
for example using the next query:
db.getCollection('establishments_copy').findAndModify({query: { code: 'METEO', distribution: { $elemMatch: { floor: 1, 'rooms.number': 301, 'rooms.beds.number': 818}}}, update: { $set: { '...': 'CHANGED'}}})
thanks!
You just need to create separate condition as per sub document's field name,
f for floor field in distribution array
r for number field in rooms array
b for number field in beds array
db.getCollection('establishments_copy').findAndModify({
query: { code: "METEO"},
update: {
$set: {
"distribution.$[f].rooms.$[r].beds.$[b].status": "TEST"
}
},
arrayFilters: [
{ "f.floor": 1 },
{ "r.number": 301 },
{ "b.number": 818 }
]
})
Playground

Update array at specific index by other filed in MongoDB

I have a collection, consist of name and data.
data is an array with 2 elements, each element is the object with code and qty.
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "a",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "c",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
I want to update the code of the first element to name of its document. The result I want is
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "Anna",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "James",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
I just google to find how to:
update array at a specific index (https://stackoverflow.com/a/34177929/11738185)
db.Collection.updateMany(
{ },
{
$set:{
'data.0.code': '$name'
}
}
)
But the code of the first element in data array is a string '$name', not a value (Anna, James)
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "$name",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "$name",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
update a field by the value of another field. It takes me to use pipeline updating (https://stackoverflow.com/a/37280419/11738185): the second param of updateMany is array (pipeline)
db.Collection.updateMany(
{ },
[{
$set:{
'data.0.code': '$name'
}
}]
)
and It adds field 0 to each element in data array
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"0" : {
"code" : "Anna"
},
"code" : "a",
"qty" : 3
},
{
"0" : {
"code" : "Anna"
},
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"0" : {
"code" : "James"
},
"code" : "c",
"qty" : 5
},
{
"0" : {
"code" : "James"
},
"code" : "d",
"qty" : 6
}
]
}
I can't find the solution for this case. Could anyone to help me? How can I update array at fixed index by other field. Thanks for reading!
1. update array at a specific index
You can't use internal fields as value of another fields, it will work only when you have external value to update like { $set: { "data.0.code": "Anna" } }.
2. update a field by the value of another field
Update with Aggregation pipeline can't allow to access data.0.code syntax.
You can try using $reduce in update with aggregation pipeline,
$reduce to iterate loop of data array, set empty array in initialValue of reduce, Check condition if initialValue array size is zero then replace code with name and merge with current object using $mergeObjects, else return current object,
$concatArrays to concat current object with initialValue array
db.collection.update({},
[{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
$cond: [
{ $eq: [{ $size: "$$value" }, 0] },
{ $mergeObjects: ["$$this", { code: "$name" }] },
"$$this"
]
}
]
]
}
}
}
}
}],
{ multi: true }
)
Playground
I think easier would be another way.
Just save the model before and use it for updating after
var annaModel = nameModel.findOne({_id: "605c666a15d2612ed0afedd2" })
nameModel.findOneAndUpdate({_id: "605c666a15d2612ed0afedd2"},{$set:{'data.0.code': annaModel.name}})

Need help in querying mongodb

I have a a few documents that have the following structure. See attached image.
document structure
Each document includes an array of 'FileMeta' objects and each FileMeta object includes an array of 'StatusHistory' objects. I'm trying to get only the FileMetas that contain StatusCode equal to 4 and that the TimeStamp is greater than a certain datetime.
Tried the following query but it only returns the first FileMeta element of each document.
db.getCollection('Collection').find({'ExternalParams.RequestingApplication':'aaa.bbb'},
{ "FileMeta": { $elemMatch: { "StatusHistory":{ $elemMatch:{ "StatusCode": 4, "TimeStamp": { $gt: ISODate("2020-06-28T11:02:26.542Z")} } } } }} )
What am I doing wrong?
here is the document structure:
{
"_id" : ObjectId("5ef84e2ec08abf38b0043ab4"),
"FileMeta" : [
{
"StatusHistory" : [
{
"StatusCode" : 0,
"StatusDesc" : "New File",
"TimeStamp" : ISODate("2020-06-28T11:00:46.286Z")
},
{
"StatusCode" : 2,
"StatusDesc" : "stby",
"TimeStamp" : ISODate("2020-06-28T11:02:20.400Z")
},
{
"StatusCode" : 4,
"StatusDesc" : "success",
"TimeStamp" : ISODate("2020-06-28T11:02:26.937Z")
}
]
},
{
"StatusHistory" : [
{
"StatusCode" : 0,
"StatusDesc" : "New File",
"TimeStamp" : ISODate("2020-06-28T11:00:46.286Z")
},
{
"StatusCode" : 2,
"StatusDesc" : "stby",
"TimeStamp" : ISODate("2020-06-28T11:02:20.617Z")
},
{
"StatusCode" : 4,
"StatusDesc" : "success",
"TimeStamp" : ISODate("2020-06-28T11:02:26.542Z")
}
]
}
],
}
I want to return only the FileMeta objects that include a StatusHistory that match the following conditions: StatusCode = 4 and TimeStamp > SomeDateTime
Sorry for the delay, mate, I've been quite busy lately. Hope you already solved your problem. Anyway, I think that I found the solution.
As you can see on this link, the example shows that by default the $elemMatch operator returns the whole array in case of match on any element.
For instance, consider the following collection:
{ _id: 1, results: [ { product: "abc", score: 10 }, { product: "xyz", score: 5 } ] }
{ _id: 2, results: [ { product: "abc", score: 8 }, { product: "xyz", score: 7 } ] }
{ _id: 3, results: [ { product: "abc", score: 7 }, { product: "xyz", score: 8 } ] }
If you do the following query, for example:
db.survey.find(
{ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } }
)
The output will be:
{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "xyz", "score" : 8 } ] }
Not:
{ "_id" : 3, "results" : [{ "product" : "xyz", "score" : 8 }]}
That said, if you want to return only the document in the array that matches the specified query, you must use the db.collection.aggregate() function with the $unwind and $match operator.
The query below shall give you what you want.
Query:
db.collection.aggregate([
{"$unwind" : "$FileMeta"},
{"$unwind" : "$FileMeta.StatusHistory"},
{
"$match" : {
"FileMeta.StatusHistory.StatusCode" : 4,
"FileMeta.StatusHistory.TimeStamp" : {"$gte" : ISODate("2020-06-28T11:02:26.937Z")}
}
}
]).pretty()
Result:
{
"_id" : ObjectId("5ef84e2ec08abf38b0043ab4"),
"FileMeta" : {
"StatusHistory" : {
"StatusCode" : 4,
"StatusDesc" : "success",
"TimeStamp" : ISODate("2020-06-28T11:02:26.937Z")
}
}
}
One last tip. Consider changing your modeling to something that looks like the unwinded document, and remember that one document should be equivalent to one row in a normal relational database. So avoid storing information that should be on "several rows" on a single document.
Useful links:
The $elemMatch operator.
The $unwind operator.

Sort in mongoDB using a specific order

I am currently looking to sort the sub document, Clients, in a particular order based on an array.
The mongoDB structure is
{
"_id" : "1033",
"Name" : "Test",
"Clients" : [
{
"Id" : 1033,
"Types" : [
{
"Class" : "C",
"Service" : null
},
{
"Class" : "B",
"Service" : null
}
]
},
{
"Id" : 156136,
"Types" : [
{
"Class" : "A",
"Service" : null
},
{
"Class" : "B",
"Service" : null
},
{
"Class" : "C",
"Service" : null
},
{
"Class" : "D",
"Service" : null
}
]
}
]
}
I need the above document displayed in the order based on a array like [B, A, D, C]
So that the output would be as below:
{
"_id" : "1033",
"Name" : "Test",
"Clients" : [
{
"Id" : 1033,
"Types" : [
{
"Class" : "B",
"Service" : null
},
{
"Class" : "C",
"Service" : null
}
]
},
{
"Id" : 156136,
"Types" : [
{
"Class" : "B",
"Service" : null
},
{
"Class" : "A",
"Service" : null
},
{
"Class" : "D",
"Service" : null
},
{
"Class" : "C",
"Service" : null
}
]
}
]
}
Could you please help me on how to achieve this?
I am currently using MongoDB Driver for .Net
Custom ordering is possible via aggregation as specified here.
I've solved my problem with custom sorting , using $addFields and $indexOfArray aggregations.
let imagine that my document has two fields: BouqetId, Name
[
{ "BouqetId" : 2, "Name" : "Name2"},
{ "BouqetId" : 16, "Name" : "Name16"},
{ "BouqetId" : 25, "Name" : "Name25"},
{ "BouqetId" : 15, "Name" : "Name15"},
{ "BouqetId" : 125, "Name" : "Name125"},
{ "BouqetId" : 258, "Name" : "Name258"},
{ "BouqetId" : 127, "Name" : "Name127"}
...
]
and I want to search for Bouqet with Ids [258,15,2,16] and get them with this order.
1/ I filter my collection using $in operator in $match aggregation to get the required documents: view step one in code.
2/ I add a field using $addFields aggregation named Order and assign to it the index of the BouqetId in the searched array using $indexOfArray aggregation.
3/ finally I sort them using the newly added Order field.
4/ I get my custom-ordered result. I can remove the order field but it is ok for now.
here and exmaple in Nodejs :
var db = client.db("MyDatabase");
var collection = db.collection("Bouqets");
var pipeline = [
{
"$match": {
"BouqetId": {
"$in": [
258,
15,
2,
16
]
}
}
},
{
"$addFields": {
"Order": {
"$indexOfArray": [
[
258,
15,
2,
16
],
"$BouqetId"
]
}
}
},
{
"$sort": {
"Order": 1.0
}
}
];
var cursor = collection.aggregate(pipeline);
Here my result :
[
{ "BouqetId" : 258, "Name" : "Name258" , "Order" : 0},
{ "BouqetId" : 15, "Name" : "Name15", "Order" : 1},
{ "BouqetId" : 2, "Name" : "Name2", "Order" : 2},
{ "BouqetId" : 16, "Name" : "Name16", "Order" : 3}
]
here the same example in c# :
IMongoClient client = new MongoClient("mongodb://host:port/");
IMongoDatabase database = client.GetDatabase("MyDatabase");
IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>("Bouqets");
var options = new AggregateOptions()
{
AllowDiskUse = false
};
// my array
var searchArray = new int[] {258, 15, 2, 16};
PipelineDefinition<BsonDocument, BsonDocument> pipeline = new BsonDocument[]
{
new BsonDocument("$match", new BsonDocument()
.Add("BouqetId", new BsonDocument()
.Add("$in", new BsonArray(searchArray)
)
)),
new BsonDocument("$addFields", new BsonDocument()
.Add("Order", new BsonDocument()
.Add("$indexOfArray", new BsonArray()
.Add(new BsonArray(searchArray)
)
.Add("$BouqetId")
)
)),
new BsonDocument("$sort", new BsonDocument()
.Add("Order", 1.0))
};
var result = collection.Aggregate(pipeline, options).ToList();
Custom ordering is not possible in MongoDB. You can either sort ascending or descending.

Mongodb filtering deeply nested arrays with aggregation framework

Thank you in advance for looking at this...
My document:
{
"_id" : { "$oid" : "550b2873e9dd90068070c31b" },
"dateCreated" : { "$date" : 1426794611867 },
"sections" : [
{
"_id" : { "$oid" : "550b2881e9dd90068070c31d" },
"index" : 0,
"name" : "Section 2",
"slides" : [
{
"_id" : { "$oid" : "550b288ce9dd90068070c321" },
"index" : 0,
"status" : "Unpublished"
},
{
"_id" : { "$oid" : "55105b87e9dd90068033ba4a" },
"index" : 1,
"status" : "Published"
}
]
},
{
"_id" : { "$oid" : "550b287ae9dd90068070c31c" },
"index" : 1,
"name" : "Section 1",
"slides" : [
{
"_id": { "$oid": "550b2888e9dd90068070c31f" },
"index" : 0,
"status": "Unpublished"
},
{
"_id" : { "$oid" : "550b288be9dd90068070c320" },
"index" : 1,
"status" : "Unpublished"
}
]
}
]
}
and the desired result where we return only sections with at least one published slide containing all the published slides
{
"_id" : { "$oid" : "550b2873e9dd90068070c31b" },
"dateCreated" : { "$date" : 1426794611867 },
"sections" : [
{
"_id" : { "$oid" : "550b2881e9dd90068070c31d" },
"index" : 0,
"name" : "Section 2",
"slides" : [
{
"_id" : { "$oid" : "55105b87e9dd90068033ba4a" },
"index" : 1,
"status" : "Published"
}
]
}
]
}
So far I have this:
col.aggregate
([
{$match : {'name': name}},
{$unwind:'$sections'},
{$unwind:'$sections.slides'},
{$match:{'$sections.slides.status': "Published"}},
{$group:{_id:'$_id', slides:{$push:'$slides'}}}
])
I am having a hard time wrapping my head around the grouping, specifically with nesting each slides array in its parent section array. Also, I would like to omit any empty sections.
I have never used the aggregate method before but I believe this is the correct approach. The mongo docs are a bit sparse when it comes to deeply nested arrays.
Read about $redact stage of MongoDB's pipeline.
At first iteration I did this:
.aggregate({
$redact: {
$cond: {
if: { $eq: ["$status", "Unpublished"] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
}, {
$redact: {
$cond: {
if: { $eq: ["$slides", []] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
})
You can try to rewrite it in more elegant way. I'm pretty sure there is a way to make it with single $redact stage, but you have to skim over the manual and find proper operators. Hope it'll help. Good luck.

Resources