How to automatically add all fields except one when doing aggregation? - mongodb

I have a mongodb collection which contains objects which have multiple properties (possibly a lot). One of this is an array of another object type, and this type has a boolean property StateChanged.
I want to make a query returning all the records from this collection, and filter the array to only get documents with StateChanged = true.
Here is what I already did:
db.getCollection('Cycles').aggregate([
{
$project: {
_id: 0,
// Here I could add Field1: 1, Field2: 1,...
'Subcycles' : {
$filter : {
input: '$Subcycles',
as : 'sub',
cond: { $eq: ['$$sub.StateChanged',true]}
}
}
}
}
])
However this only brings me the "Subcycles" collection.
What I want is to include other fields in the root document.
I could specify them manually in the projection (like Field1: 1, Field2: 1,...), but as there can be a lot of fields, I was wondering if there exists a way to bring them all automatically.
Thanks in advance !

You can use $addFields instead of $project. It will automatically replace the new field with the existing field.
db.getCollection("Cycles").aggregate([
{ "$addFields": {
"Subcycles": {
"$filter": {
"input": "$Subcycles",
"as": "sub",
"cond": { "$eq": ["$$sub.StateChanged", true] }
}
}
}},
{ "$project" : { "_id": 0 }}
])

You can use $addFields and then use $project to exclude _id field:
db.getCollection('Cycles').aggregate([
{
$addFields: {
'Subcycles' : {
$filter : {
input: '$Subcycles',
as : 'sub',
cond: { $eq: ['$$sub.StateChanged',true]}
}
}
}
},
{
$project: {
_id: 0
}
}
])

Related

Moving element from one array to another, using the same Mongo UpdateOne query

I have the following data model:
"finances" : {
"futurePostings" : [
{
"description" : "test",
"orderId" : ObjectId("614702b9e98e83bc5d7d3d62")
}
],
"balance" : []
}
Then, I'm trying to move the element inside futurePosting to balance. I could remove it from futurePostings, but I can't figure out if would be possible to use the positional $ operator (or any other command) to push this same document inside balance, via the same query.
db.collection.updateOne(
{
"finances.futurePostings.orderId": ObjectId(orderId),
},
{
$push: { "finances.balance": ?? },
$pull: {
"finances.futurePostings": { orderId: ObjectId(orderId) },
},
}
);
Is it possible?
It is not possible in a regular update query, but you can try update with aggregation pipeline starting from MongoDB 4.2,
pull from futurePostings
$filter to iterate loop of futurePostings array and check not equal to condition to remove provided orderId
push into balance
$filter to iterate loop of futurePostings array and check equal to condition and filter matching orderId element
$concatArrays to concat current balance array and new element from above filtered result
db.collection.updateOne(
{ "finances.futurePostings.orderId": ObjectId(orderId) },
[{
$set: {
"finances.futurePostings": {
$filter: {
input: "$finances.futurePostings",
cond: {
$ne: ["$$this.orderId", ObjectId(orderId)]
}
}
},
"finances.balance": {
$concatArrays: [
"$finances.balance",
{
$filter: {
input: "$finances.futurePostings",
cond: {
$eq: ["$$this.orderId", ObjectId(orderId)]
}
}
}
]
}
}
}]
)
Playground

find and update with pull and get the pulled document in return

Here is my collection
[
{_id:1,
persons:[{name:"Jack",age:12},{name:"Ma",age:13}]
}
]
I want to remove {name:"Jack",age:12} in persons by pull but I also want after pulling is completed I will be returned the pulled {name:"Jack",age:12}. How can I do this?
I want to do it like this
db.getCollection('test').findOneAndUpdate({_id:1},{$pull:{"persons":{name:"Jack"}}},
{projection:{"person":{
$filter : {
input: "$persons",
as : "ele",
cond : {$eq : ["$$ele.name","Jack"]}
}
}}})
You can use $reduce, because $filter will return array, also aggregation array operators will support from MongoDB 4.4,
db.getCollection('test').findOneAndUpdate({_id:1},
{ $pull: { "persons": { name: "Jack" } } },
{
projection: {
"person":{
$reduce: {
input: "$persons",
initialValue: {},
in: { $cond: [{$eq: ["$$this.name","Jack"]}, "$$this", "$$value"] }
}
}
}
}
)
Playground

MongoDB - Array of object filter with condition

I have a json file that look like this in my collection :
[
{
"change":"00001",
"patchset":"4"
},
//etc
]
Two different object can have the same "change" properties.
So first I want to group them by "change" properties and inside this group I want the highest value of the "patchset" properties. I have managed to do this easily with this command
db.collections.aggregate([{$group:{_id:"$change",patchset_max:{$max:"$patchset"}}}])
but then, and this is where I lost it, with this max patchset, I want to get all the objects where object.patchset = max_patchset but still in the group array.
I tried with $filter and $match and then nested $group but nothing works,
Do you guys have any idea ?
Thanks
You need to use $$ROOT which is a special variable that represents whole document to get all the items for each group and then you can use $addFields to overwrite existing array and $filter to get only those docs that have patchset equal to patchset_max. Try:
db.collection.aggregate([
{
$group: {
_id: "$change",
patchset_max:{$max:"$patchset"},
docs: { $push: "$$ROOT" }
}
},
{
$addFields: {
docs: {
$filter: {
input: "$docs",
as: "doc",
cond: {
$eq: [ "$patchset_max", "$$doc.patchset" ]
}
}
}
}
}
])
Sample playground here
EDIT Answer above is correct, but if on Mongo 3.2, here's an alternative
db.collection.aggregate([{
$group: { _id: "$change", patchset: { $push: "$$ROOT" }, patchset_max:{ $max:"$patchset" } }
},{
$project: {
patchset: {
$filter: {
input: '$patchset',
as: 'ps',
cond: { $eq: ['$$ps.patchset', '$patchset_max'] }
}
}
}
}])

Renaming Field Within an Array in MongoDB

I need to update the name of field in a collection. The problem is that the field in question is within an array. So I'm trying to determine the correct way do this. I tried this to accomplish renaming the field that exists within the "plans" array: :
db.customers.updateMany( {}, { $rename: { "plans" : { "subscriptionType": "membershipType" } } } );
But this won't work. What's the correct way to handle this kind of transformation of a field within an array?
The data looks like this:
{
_id: 123,
prop1: value,
prop2: value,
prop3: value,
plans: [
subscriptionType: value,
otherProp: value,
otherProp: value
]
}
You can use Aggregation Framework's $addFields to override plans field and $map operator to rename field inside an array. Then you can use $out to override existing collection:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
membershipType: "$$plan.subscriptionType",
otherField: "$$plan.otherField",
otherField2: "$$plan.otherField2"
}
}
}
}
},
{
$out: "customers"
}
])
Alternatively you can do that dynamically. In this solution you don't have to explicitly specify other field names:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
$mergeObjects: [
{ membershipType: "$$plan.subscriptionType" },
{
$arrayToObject: {
$filter: {
input: { $objectToArray: "$$plan" },
as: "plan",
cond: { $ne: [ "$$plan.k", "subscriptionType" ] }
}
}
}
]
}
}
}
}
},
{
$out: "customers"
}
])
Using $objectToArray to $filter out old key-value pair and the using $mergeObjects to combine that filtered object with new renamed field.

mongodb adding up values in array based on some condition

I can't get my head around a mongodb aggregation framework construction that adds up some values for each "_id" field documents... IF those values exist for the field "Wert".
E.g I have a document with _id field and a conditional ProduktTeilsummeDemonstrator":[] or "ProduktTeilsummeDemonstrator":[{Wert:342},{Wert:142}] that array can be empty or not, if it is empty, I want to add a new field "ProduktTeilsumme":0, else, I want to add up all values in that array to the new field...
The data that I have looks like this:
[{"_id":230,"ProduktSummeDemonstrator":713,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null},{"_id":855,"ProduktSummeDemonstrator":1744,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null},{"_id":767,"ProduktSummeDemonstrator":1010,"ProduktTeilsummeDemonstrator":[{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":24},{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":102},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":15},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":20},{"Zeitstempel":"2018-07-09T15:07:32.472Z","Wert":90},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":104},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":29},{"Zeitstempel":"2018-07-09T14:52:32.472Z","Wert":94},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":33},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":245},{"Zeitstempel":"2018-07-09T14:52:32.473Z","Wert":243},{"Zeitstempel":"2018-07-09T15:07:32.473Z","Wert":11}],"ProduktTeilsumme":null},{"_id":9,"ProduktSummeDemonstrator":94,"ProduktTeilsummeDemonstrator":[],"ProduktTeilsumme":null}]
I tried out different things with $reduce or $cond expressions, but somehow it won't add up: (Previously before that calculation stage I am grouping by ID and also filtering based on some time field condition..)
{
$project: {
ProduktSummeDemonstrator: "$ProduktSummeDemonstrator",
ProduktTeilsummeDemonstrator: {
$filter: {
input: "$res",
as: "res",
cond: { $and: [
{ $gte: ["$$res.Zeitstempel", new Date(req.params.start) ] },
{ $lte: ["$$res.Zeitstempel", new Date(req.params.end) ] }
] }
}
},
ProduktTeilsumme: {/*
$reduce: {
input: "$ProduktTeilsummeDemonstrator",
initialValue:0,
in: {
$add: ["$$value","$$this.Wert"]
}
} */
$cond: {
if: { $eq: [ "", "$ProduktTeilsummeDemonstrator" ] },
then: 0,
else: {
$reduce: {
input: "$ProduktTeilsummeDemonstrator",
initialValue: 0,
in: {
$add: ["$$value","$$this.Wert"]
}
}
}
}
}
}
}
at least for "_id":767 I should get some values back, but I am getting "null" always.
You have to use multiple project stages if you want to keep both the array and added value. One for $filtering ProduktTeilsummeDemonstrator followed by adding up array values.
Something like
[
{"$project":{
"ProduktSummeDemonstrator":1,
"ProduktTeilsummeDemonstrator":{
"$filter":{
"input":"$ProduktTeilsummeDemonstrator",
"as":"res",
"cond":{
"$and":[
{"$gte":["$$res.Zeitstempel", new Date(req.params.start)]},
{"$lte":["$$res.Zeitstempel", new Date(req.params.end)]}
]
}
}
}
}},
{"$project":{
"ProduktSummeDemonstrator":1,
"ProduktTeilsummeDemonstrator":1,
"ProduktTeilsumme":{"$sum":"$ProduktTeilsummeDemonstrator.Wert"}
}}
]