How to get object value with dynamic key in $project in mongoDB - javascript

I want to retrieve a value from an object with a dynamic key
[
{
"_id": 1,
"item": "sweatshirt",
"price": {
"INR": 45.99
},
"currency": 'INR'
}
]
db.collection.aggregate([
{
"$project": {
"pricenew": "$price.currency"
}
}
])
If I do price.INR it will work fine but here I have currency dynamic, so I want something like price.currency but here currency is coming like "INR" and it gives no data.
I really appreciate any help you can provide.

You need to convert the price object to an array using $objectToArray, filter it and then convert it back, like so:
db.collection.aggregate([
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
"$objectToArray": "$price"
},
cond: {
$eq: [
"$$this.k",
"$currency"
]
}
}
},
in: {
k: "pricenew",
v: "$$this.v"
}
}
}
},
{
_id: "$_id"
}
]
}
}
}
])
Mongo Playground

Related

Mongoose/MongoDB: How can I $inc only the first value I get from an array?

I have a Mongoose Schema that looks like this:
{
_id: ObjectID,
storage: [{
location: String,
storedFood: [{
code: String,
name: String,
weight: Number
}]
}]
}
And for example in storedFood can be the same Food twice. But I only want to update one of the weights of these items.
This is my code to $inc all of the items.... How can I reduce this to only one?
try {
const deletedFoodFromStorage = await User.updateOne(
{_id: user, "storage.location": location},
{ $inc: {"storage.$.storedFood.$[food].weight": -weight}},
{ arrayFilters: [ { "food.code": code } ]},
);
res.json(deletedFoodFromStorage);
} catch(err) {
res.status(400).json('Error: ' + err)
}
Should have been a simple one. Only way I found at the moment is not simple:
db.collection.update(
{_id: user, "storage.location": location},
[
{$set: {
newItem: {
$reduce: {
input: {$getField: {
input: {$first: {$filter: {
input: "$storage",
as: "st",
cond: {$eq: ["$$st.location", location]}
}}},
field: "storedFood"
}},
initialValue: [],
in: {$concatArrays: [
"$$value",
{$cond: [
{$and: [
{$eq: ["$$this.code", code]},
{$not: {$in: [code, "$$value.code"]}}
]},
[{$mergeObjects: [
"$$this",
{weight: {$subtract: ["$$this.weight", weight]}}
]}],
["$$this"]
]
}
]
}
}
}
}},
{$set: {
storage: {
$map: {
input: "$storage",
in: {$cond: [
{$eq: ["$$this.location", location]},
{$mergeObjects: ["$$this", {storedFood: "$newItem"}]},
"$$this"
]}
}
},
newItem: "$$REMOVE"
}}
])
See how it works on the playground example
Borrowing liberally from nimrod serok's answer, here's one way to do it with a single pass through all the arrays. I suspect this can be improved, at least for clarity.
db.collection.update({
_id: user,
"storage.location": location
},
[
{
"$set": {
"storage": {
"$map": {
"input": "$storage",
"as": "store",
"in": {
"$cond": [
{"$ne": ["$$store.location", location]},
"$$store",
{
"$mergeObjects": [
"$$store",
{
"storedFood": {
"$getField": {
"field": "theFoods",
"input": {
"$reduce": {
"input": "$$store.storedFood",
"initialValue": {
"incOne": false,
"theFoods": []
},
"in": {
"$cond": [
"$$value.incOne",
{
"incOne": "$$value.incOne",
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
["$$this"]
]
}
},
{
"$cond": [
{"$ne": ["$$this.code", code]},
{
"incOne": "$$value.incOne",
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
["$$this"]
]
}
},
{
"incOne": true,
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
[
{
"$mergeObjects": [
"$$this",
{"weight": {"$add": ["$$this.weight", -weight]}}
]
}
]
]
}
}
]
}
]
}
}
}
}
}
}
]
}
]
}
}
}
}
}
])
Try it on mongoplayground.net.
Ty for your help!
The final solution for me was to restructure addStoredFood-Function to make the storedFood unique and only add weight to it instead of adding another objects.
This makes my old update-Function work aswell.

Convert $objectToArray map element to String

I have a collection in database that I am trying to retrieve some data from it , the query is working fine when $orderID has string elements , but is failing when $orderID has some numbers in array , and it is throwing
query failed: (Location40395) PlanExecutor error during aggregation :: caused by :: $arrayToObject requires an array of key-value pairs, where the key must be of type string. Found key type: double
I think there must be some old data when we were saving orderID as a number so that is why it is failing from some range of dates
Query
{
"Order_Details": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$orderID",
"$total_value_of_order"
]
}
}
}
},
"as": "el",
"in": {
"orderID": "$$el.k",
"total_value_of_order": "$$el.v"
}
}
}
}
I am trying to typecast el.k to string I am using $toString but can't seem to work , the way I am trying it is
{
"as": "el",
"in": {
"orderID": {
"$toString": "$$el.k"
},
"total_value_of_order": "$$el.v"
}
}
Example collection
[
{
"_id": ObjectId("5e529ee5f8647eb59e5620a2"),
"visitID": "dVmy7flXFHzzkn9HiMt8IoWvthoTZW",
"date": ISODate("2022-02-08T16:29:13.413Z"),
"control": true,
"orderID": [
122343242
],
"target": "test",
"total_value_of_order": [
60
]
}
]
You are close, the approach is fine. you just have a couple of syntax issues.
The major thing that needs to change is the input for $arrayToObject, currently your input looks like this:
[[number, number], [number, number]]
However $arrayToObject expects input in a certain format:
[{k: string, v: value}]
So this it what we'll add, like so:
db.collection.aggregate([
{
$project: {
"Order_Details": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
$map: {
input: {
"$zip": {
"inputs": [
"$orderID",
"$total_value_of_order"
]
}
},
in: {
k: {
$toString: {
"$arrayElemAt": [
"$$this",
0
]
}
},
v: {
"$arrayElemAt": [
"$$this",
1
]
}
}
}
}
}
},
"as": "el",
"in": {
"orderID": "$$el.v",
"total_value_of_order": "$$el.k"
}
}
}
}
}
])
Mongo Playground
Notice the "orderid" format changes to string which affects it's structure, I recommend just switching between the k and v in the pipeline, like this

MongoDB lookup and map 2 arrays of result

There are 2 array fields after I looked up in MongoDB aggregation pipeline.
the first one
[
{
"colorId": "60828a1b216b0972da695f2a",
"name": "Exellent",
"description": "Great work"
}
]
and the second one
[
{
"_id": "60828a1b216b0972da695f2a",
"colorName": "Green",
"hexColorCodes": "#2D9D78",
"sequence": 1,
"isActivated": true,
"created_at": "2021-04-23T08:49:31.729Z",
"updated_at": "2021-04-23T08:49:31.729Z",
"__v": 0,
"isDefault": true
}
]
the result I want is
[
{
"colorId": "60828a1b216b0972da695f2a",
"name": "Exellent",
"description": "Great work",
"colorName": "Green",
"hexColorCodes": "#2D9D78"
}
]
then I want to map colorName and hexColorCodes to the first array. Here is my aggregate pipeline
db.collection.aggregate([
{
$lookup: {
from: "color_tags",
localField: "colors.colorId",
foreignField: "_id",
as: "tempColors",
},
},
{
$addFields: {
stages3: {
$map: {
input: "$colors",
in: {
$mergeObjects: [
"$$this",
{
$arrayElemAt: [
"$tempColors",
{
$indexOfArray: [
"$tempColors._id",
"$$this.colors.colorId",
],
},
],
},
],
},
},
},
},
}
])
but the result is not what I expected. It mapped with incorrect id. Please suggest.
$map to iterate loop of first array
$filter to iterate loop of second array and match colorId with _id and return matching result
$arrayElemAt to get first matching element
$mergeObjects to merge current object with return result from second array
{
$project: {
first: {
$map: {
input: "$first",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
$arrayElemAt: [
{
$filter: {
input: "$second",
cond: { $eq: ["$$this._id", "$$f.colorId"] }
}
},
0
]
}
]
}
}
}
}
}
If you want to result specific fields then add a $project stage at the end,
{
$project: {
"first.colorId": 1,
"first.name": 1,
"first.description": 1,
"first.colorName": 1,
"first.hexColorCodes": 1
}
}
Playground

how to use lookup on array object mongodb

I'm new on mongodb. so I try design the schema for my collection is like below
all the ObjectId is not real
stockIn documents
{
serial:"stk0001",
date:'2021-06-11',
productInTransation:[
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb2"
qty:2
},
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb1",
qty:2
}
]
}
and I have a products collection
[
{
_id:"60ae220b066b8d9861118cb5",
name:"sepatu"
},
{
_id:"60ae220b066b8d9861118cb4",
name:"sendal"
}
]
so what I expect from those documents is just like below
{
serial:"stk0001",
date:'2021-06-11',
productInTransation:[
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb2"
qty:2,
product:
{
_id:"60ae220b066b8d9861118cb5",
name:"sepatu"
},
},
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb1",
qty:2,
product:
{
_id:"60ae220b066b8d9861118cb4",
name:"sendal"
}
}
]
}
this collection is just simplified from the real case.
and the problem I don't know how to do a query on mongodb, so the output will same as the expected. thank's for any help
You can use $lookup
$unwind to deconstruct the array
$lookup to join collections
$ifNull to make sure this doesn't give any NPE when we take from first element from the joined array using $arrayElemAt
$group to reconstruct the array
Here is the code
db.stockIn.aggregate([
{ $unwind: "$productInTransation" },
{
"$lookup": {
"from": "products",
"localField": "productInTransation.productId",
"foreignField": "_id",
"as": "productInTransation.product"
}
},
{
"$addFields": {
"productInTransation.product": {
"$ifNull": [ { "$arrayElemAt": [ "$productInTransation.product", 0 ] }, [] ]
}
}
},
{
"$group": {
"_id": "$_id",
"date": { "$first": "$date" },
"serial": { "$first": "$serial" },
"productInTransation": { $push: "$productInTransation" }
}
}
])
Working Mongo playground

How do I get all attributes which are numeric types in mongo db?

I need to extract all the attributes which are of numeric types. For example, if the different attributes are
{
age: 32
gender: "female"
year: 2020
name: "Abc"
}
My query should return ["age","year"]
I think the below query should help you out.
db.test.aggregate([
// Remove this `$limit` stage if your Collection schema is dynamic and you want to process all the documents instead of just one
{
"$limit": 1
},
{
"$project": {
"arrayofkeyvalue": {
"$filter": {
"input": {"$objectToArray":"$$ROOT"},
"as": "keyValPairs",
"cond": {
"$in": [{"$type": "$$keyValPairs.v"}, ["double", "int", "long"]],
// Change the above line to the following to get only `int` keys instead of `int, double` and `long`:
// "$eq": [{"$type": "$$keyValPairs.v"}, "int"],
}
}
}
}
},
{
"$group": {
"_id": null,
"unique": {"$addToSet": "$arrayofkeyvalue.k"}
}
},
{
"$project": {
"_id": 0,
"intKeyNames": {
"$reduce": {
input: "$unique",
initialValue: [],
in: {$setUnion : ["$$value", "$$this"]}
}
}
}
},
])
The above query result will be something like this:
{
"intKeyNames" : [
"_id",
"abc",
"paymentMonth",
"paymentYear",
"value"
]
}

Categories

Resources