Removing Dynamic Fields by Association in MongoDB Aggregation - node.js

I'm trying to display a MongoDB aggregation result via react chartjs. in aggregation, I can remove one field whose value is static via the set operator. is there a way to remove a second field by an association whose value is dynamic? in the example below, {"A": "N"} denotes the field that is readily removed by the set operator, whereas {"A_count":1} denotes the corresponding dynamic field that I am trying to remove.
starting aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"},{"A":"N"}],
"A_count_set":[{"A_count":0},{"A_count":1}]
}]
set operation for static field removal
{$set: {
A_set: {
$filter: {
input: "$A_set",
as: "x",
cond: { "$ne": [ "$$x", {"A":"N"}] }
}
}
}}
current aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"}],
"A_count_set":[{"A_count":0},{"A_count":1}]
}]
target aggregation output
[{
"_id":"Fubar",
"A_set":[{"A":"Y"}],
"A_count_set":[{"A_count":0}]
}]

$project merge two array with the same position
$set filter array
$addFields recover the original array
$project remove the merge array
aggregate
db.collection.aggregate([
{
$project: {
anotherValue: {
$map: {
input: {
$range: [
0,
{
$size: "$A_set"
}
]
},
as: "idx",
in: {
$mergeObjects: [
{
$arrayElemAt: [
"$A_set",
"$$idx"
]
},
{
$arrayElemAt: [
"$A_count_set",
"$$idx"
]
}
]
}
}
}
}
},
{
$set: {
anotherValue: {
$filter: {
input: "$anotherValue",
as: "x",
cond: {
"$ne": [
"$$x.A",
"N"
]
}
}
}
}
},
{
$addFields: {
"A_set": {
$map: {
input: "$anotherValue",
as: "a",
in: {
"A": "$$a.A"
}
}
},
"A_count_set": {
$map: {
input: "$anotherValue",
as: "a",
in: {
"A_count": "$$a.A_count"
}
}
}
}
},
{
"$project": {
"anotherValue": 0
}
}
])
mongoplayground

Related

Update last element of a nested array in mongodb

I have tried the below query of mongoose which does not seem to work:
Model.findOneAndUpdate(
{
name: origin
},
{
$set: {
'field1.$[id1].field2.-1': "value"
}
},
{
arrayFilters: [
{ 'id1.userId': "customerId" }
],
new: true
}
);
Note: field1 and field2 are arrays
The negative indexes are not accepted by MongoDB which is causing problems.
You may consider using the $set (aggregation) operator and use double $map operator:
db.collection.aggregate([
{ $match: { name: "myname" } }
{
$set: {
field1: {
$map: {
input: "$field1",
in: {
$cond: {
if: { $ne: [ "$$this.userId", "customerId" ] },
then: "$$this",
else: {
$mergeObjects: [
"$$this",
{
field2: {
$concatArrays: [
{ $slice: [ "$$this.field2", { $add: [ { $size: "$$this.field2" }, -1 ] } ] },
["value"]
]
}
}
]
}
}
}
}
}
}
}
])
Mongo Playground
Apply the $set operator together with the $ positional operator in your update to change the name field.
The $ positional operator will identify the correct element in the array to update without explicitly specifying the position of the element in the array, thus your final update statement should look like:
db.collection.update(
{ "friends.u.username": "michael" },
{ "$set": { "friends.$.u.name": "hello" } }
)
Answer taken from - https://stackoverflow.com/a/34431571

Searching in nested arrays Mongodb

I have a mongodb with some JSON data which includes and nested arrays. I am
trying to make a query to count how many documents have a specific
value. For example here is how my json data looks:
{
"_id" : ObjectId("5ecb815bf4b8512918224e71"),
"array1" : [
{
"_id" : ObjectId("5ecb815bf4b8512918224e85"),
"xxxx" : "1450",
"yyyy" : 83,
"array2" : [
{
"_id" : ObjectId("5ecb815bf4b8512918224e88"),
"aaaa" : "1470420945276",
},
{...},
{...}]
}
The query that i am trying is the following:
db.example.aggregate([
{
$project: {
value1: {
$filter: {
input: "$array1",
as: "array",
cond: { $eq: [ "$$array.array2.aaaa" , "1470420945276" ] }
}
}
}
},
{
$project: {
value1Count: { $size: "$value1" }
}
}
])
But doesnt work and returns that value1Count=0. It looks like it doesnt nnavigate into the array2 to
read the value of the 'aaaa'. Any help?
You were almost close to getting the desired value. The problem is $$array.array2.aaaa returns an array value, so we can't use $eq here. Instead, we should use $in operator.
db.example.aggregate([
{
$project: {
value1: {
$filter: {
input: "$array1",
as: "array",
cond: {
$in: [
"1470420945276",
"$$array.array2.aaaa"
]
}
}
}
}
},
{
$project: {
value1Count: {
$size: "$value1"
}
}
}
])
MongoPlayground | Alternative solution

Mongo DB Remove Field in Array inside an Array

I have following structure of my document:
{
"daily": [
{
"general": [
{
"status": false,
"_id": "5e728265f4796c0017203662",
"title": "Sport"
},...
]
}
]
}
I would like to pull the the document inside the "general" Array by it's "_id".
I tried several things but nothing seems to work for me.
Every help will be appreciate :)
The update query to pull the sub-document from the nested array field general using the _id:
ID = "5e728265f4796c0017203662"
db.collection.update(
{ "daily.general._id": ID },
{ $pull: { "daily.$.general": { _id: ID } } }
)
This can only be done in Mongo 4.2+, where they introduced pipeline'd updates.
Now we can use aggregation expressions to update documents:
db.collection.updateOne(
{},
[
{
$set: {
"daily": {
$map: {
input: "$daily",
as: "item",
in: {
"general": {
$filter: {
input: "$$item.general",
as: "datum",
cond: {$ne: ["$$datum._id", "5e728265f4796c0017203662"]}
}
}
}
}
}
}
}
]);
Or if objects in daily have more than just the general field you can do it like this:
db.collection.updateOne(
{},
[
{
$set: {
"daily": {
$map: {
input: "$daily",
as: "item",
in: {
$mergeObjects: [
"$$item",
{
"general": {
$filter: {
input: "$$item.general",
as: "datum",
cond: {$ne: ["$$datum._id", "5e728265f4796c0017203662"]}
}
}
}
]
}
}
}
}
}
]);
Unfortunately for any other Mongo version this is not possible, you'll have to restructure your data or do it in code.

Mongodb lookup array of elements with combined result

So these are my two documents
Order document:
{
"_id":"02a33b9a-284c-4869-885e-d46981fdd679",
"context":{
"products":[
{
"id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00"
}
],
},
}
Product document:
{
"_id":"e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"context":{
"name": "My Product",
"image": "someimage"
},
}
So I'm trying to do a lookup for a products in order document, but the result should contain combined fields, like so:
"products":[
{
"_id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00",
"name": "My Product",
"image": "someimage"
}
],
Not sure how to do this, should I do it outside of the lookup, or inside? This is my aggregation
Orders.aggregate([
{
"$lookup":{
"from":"products",
"let":{
"products":"$context.products"
},
"pipeline":[
{
"$match":{
"$expr":{
"$in":[
"$_id",
"$$products.id"
]
}
}
},
{
"$project":{
"_id":0,
"id":1,
"name":"$context.name"
}
}
],
"as":"mergedProducts"
}
},
{
"$project":{
"context":"$context",
"mergedProducts":"$mergedProducts"
}
},
]);
You need to run that mapping outside of $lookup by running $map along with $arrayElemAt to get single pair from both arrays and then apply $mergeObjects to get one object as a result:
db.Order.aggregate([
{
$lookup: {
from: "products",
localField: "context.products.id",
foreignField: "_id",
as: "productDetails"
}
},
{
$addFields: {
productDetails: {
$map: {
input: "$productDetails",
in: {
_id: "$$this._id",
name: "$$this.context.name"
}
}
}
}
},
{
$project: {
_id: 1,
"context.products": {
$map: {
input: "$context.products",
as: "prod",
in: {
$mergeObjects: [
"$$prod",
{ $arrayElemAt: [ { $filter: { input: "$productDetails", cond: { $eq: [ "$$this._id", "$$prod.id" ] } } }, 0 ] }
]
}
}
}
}
}
])
Mongo Playground
The goals of the last step is to take take two arrays: products and productDetails (the output of $lookup) and find matches between them. We know there's always one match so we can get only one item $arrayElemAt 0. As an output of $map there will be single array containing "merged" documents.

How to do case insensitive filtering in mongoose?

I just started working on MongoDB and below is my code. I need to ignore casing while filtering data how to do it?
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
data: {
$filter: {
input: "$data",
as: "el",
cond: {
$in: [
"$$el",
[
"Min", //need to ignore casing
"max",
"gg"
]
]
}
}
}
}
}
])
Thanks in advance
You can use the $toLower operator to convert the filter element to lowercase then ensure the array elements in the conditional expression are all in lowercase:
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
data: {
$filter: {
input: "$data",
as: "el",
cond: {
$in: [
// Convert the input element to lower case
{ "$toLower": "$$el" },
// Ensue all the elements of the array below are in lower case
[
"min",
"max",
"gg"
]
]
}
}
}
}
}
])

Resources