can anyone please help I want to query mongo to unwind the array,and i am using mongdb native driver My collection document is as follow,Also please ignore my objectId its just for sample
{
"_id":ObjectId(123),
"name":"Sam",
"age":20,
"hobbiesDetail":[
{
"description":"FootBall",
"level":"20%"
},
{
"description":"Cricket",
"level":"80%"
}
]
},
{
"_id":ObjectId(124),
"name":"Ted",
"age":26,
"hobbiesDetail":[
{
"description":"FootBall",
"level":"20%"
}
]
}
And my expected output is
[
{
"name":"Sam",
"age":20,
"hobbies":"Football,Cricket"
},
{
"name":"Ted",
"age":26,
"hobbies":"Football"
}
]
I just want to unwind my array and add a comma between hobbies description in one query, Thanx for any help
All you need is a $projection with the $map array operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"age": 1,
"hobbies": {
"$map": {
"input": "$hobbiesDetail",
"as": "hobby",
"in": "$$hobby.description"
}
}
}}
])
Related
I have a Mongo collection of states, where each state contains an array of cities:
{
"_id":"636d1137cf1e57408486f795",
"state":"new york",
"cities":[
{
"cityid":"62bd8fa5396ba8aef4ad1041",
"name":"Yonkers"
},
{
"cityid":"62bd8fa5396ba8aef4ad1043",
"name":"Syracuse"
}
]
}
I need an update query that will lowercase every cities.name in the collection.
I can do an update with a literal value e.g.
db.states.updateMany(
{},
{ $set: { "cities.$[].name" : "some_value" } }
)
... , but I need the value to be based on the existing value. The closest I can get is something like this (but that doesn't work -- FieldPath field names may not start with '$')
db.states.updateMany(
{},
{ $set: { "cities.$[].name" : { $toLower: "cities.$[].name"} } }
)
You can chain up $map and $mergeObjects to perform the update. Put it in an aggregation pipeline in update.
db.collection.update({},
[
{
$set: {
cities: {
"$map": {
"input": "$cities",
"as": "c",
"in": {
"$mergeObjects": [
"$$c",
{
"name": {
"$toLower": "$$c.name"
}
}
]
}
}
}
}
}
])
Mongo Playground
I have the below 2 documents from a post collection. How to get only key value pairs from "post" object. The match condition will be using "post_id".
{
"_id":"1001",
"post":{
"country_name":"India",
"state_name":"Maharashtra",
"city_name":"Mumbai",
"duration":"10",
"country":[
{
"name":"india"
}
],
"site":[
{
"site_no":"101",
"code":"Taj",
"name":"santacruz"
}
]
},
"post_id":"abcd123"
}
{
"_id":"1002",
"post":{
"country_name":"India",
"state_name":"Karnataka",
"city_name":"Bangalore",
"duration":"20",
"country":[
{
"name":"india"
}
],
"site":[
{
"site_no":"201",
"code":"COLES",
"name":"Coles Park"
}
]
},
"post_id":"abcd234"
}
The expected result is:
"abcd123":{
"country_name":"India",
"state_name":"Maharashtra",
"city_name":"Mumbai",
"duration":"10"
}
"abcd234" : {
"country_name":"India",
"state_name":"Karnataka",
"city_name":"Bangalore",
"duration":"20"
}
I'm able to filter for one object, but for the bulk and with good performance, can you help me to solve this.
You can try this aggregate query:
The trick here is to create an object with keys k and v to define the key and value for the next stage.
The key will be post_id and the value the object with desired values.
In this case values has to be dinamically created so you can:
Parse $post object to an array, which allows you to filter values inside.
$filter that values to not get arrays or objects using $type.
Then convert again the array to object using $arrayToObject.
And the last step is to use $replaceRoot so you can get your desired output.
db.collection.aggregate([
{
"$project": {
"k": "$post_id",
"v": {
"$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$post" },
"cond": {
"$not": [
{
"$in": [
{ "$type": "$$this.v" },
[ "object", "array" ]
]
}
]
}
}
}
}
}
},
{
"$replaceRoot": {
"newRoot": { "$arrayToObject": [ [ { k: "$k", v: "$v" } ] ] }
}
}
])
Example here
I have a collection with many documents containing shipping prices:
{
"_id": {
"$oid": "5f7439c3bc3395dd31ca4f19"
},
"adapterKey": "transport1",
"pricegrid": {
"10000": 23.66,
"20000": 23.75,
"30000": 23.83,
"31000": 43.5,
"40000": 44.16,
"50000": 49.63,
"60000": 50.25,
"70000": 52,
"80000": 56.62,
"90000": 59,
"100000": 62.5,
"119000": 68.85,
"149000": 80,
"159000": 87,
"179000": 94,
"199000": 100.13,
"249000": 118.5,
"299000": 138.62,
"999000": 208.63
},
"zones": [
"25"
],
"franco": null,
"tax": 20,
"doc_created": {
"$date": "2020-09-30T07:54:43.966Z"
},
"idConfig": "0000745",
"doc_modified": {
"$date": "2020-09-30T07:54:43.966Z"
}
}
In pricegrid, all the properties can be different from one grid to another.
I'd like to update all the prices in the field "pricegrid" (price * 1.03 + 1).
I tried this :
db.shipping_settings.updateMany(
{ 'adapterKey': 'transport1' },
{
$mul: { 'pricegrid.$': 1.03 },
$inc: { 'pricegrid.$': 1}
}
)
Resulting in this error :
MongoServerError: Updating the path 'pricegrid.$' would create a conflict at 'grille.$'
So I tried with only $mul (planning on doing $inc in another query) :
db.livraison_config.updateMany(
{ 'adapterKey': 'transport1' },
{
$mul: { 'pricegrid.$': 1.03 }
}
)
But in that case, I get this error :
MongoServerError: The positional operator did not find the match needed from the query.
Could you please direct me on the correct way to write the request ?
You can use an aggregation pipeline in an update. $objectToArray pricegrid to convert it into an array of k-v tuple first. Then, do a $map to perform the computation. Finally, $arrayToObject to convert it back.
db.collection.update({
"adapterKey": "transport1"
},
[
{
$set: {
pricegrid: {
"$objectToArray": "$pricegrid"
}
}
},
{
"$set": {
"pricegrid": {
"$map": {
"input": "$pricegrid",
"as": "p",
"in": {
"k": "$$p.k",
"v": {
"$add": [
{
"$multiply": [
"$$p.v",
1.03
]
},
1
]
}
}
}
}
}
},
{
$set: {
pricegrid: {
"$arrayToObject": "$pricegrid"
}
}
}
])
Here is the Mongo playground for your reference.
You can do it with Aggregation framework:
$objectToArray - to transform pricegrid object to array so you can iterate of its items
$map to iterate over array generated in previous step
$sum and multiply to perform mathematical operations
$arrayToObject to transform updated array back to object
db.collection.update({
"adapterKey": "transport1"
},
[
{
"$set": {
"pricegrid": {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$pricegrid"
},
"in": {
k: "$$this.k",
v: {
"$sum": [
1,
{
"$multiply": [
"$$this.v",
1.02
]
}
]
}
}
}
}
}
}
}
],
{
"multi": true
})
Working example
I might be wrong, but it looks like there's currently no support for this feature - there's actually an open jira-issue that addresses this topic. Doesn't look like this is going to be implemented though.
I am facing a problem with MongoDB query. Our collection named is products and the data is placed something like this.
{
"_id":"61b823681975ba537915cb0c",
"salesInfo":{
"_id":"61b823681975ba537915c23c",
"salesDate":[
{
"_id":"61b3aa4a7b04b30cd0a76b06",
"salesQuantity":100,
"soldPieces":36,
"holdPieces":0
},
{
"_id":"61b3aa4a7b04b30cd0a75506",
"salesQuantity":100,
"soldPieces":36,
"holdPieces":0
}
]
}
}
I want to add a new field named percentageSold inside an array of objects, and the value should be the calculation of the following formula ((soldPieces + holdPieces) / salesQuantity * 100).
My query is this but it is returning null for the percentageSold property.
db.products.aggregate( [
{
$addFields: {
"salesInfo.salesDate.percentageSold": {$divide: [{$add: ["$salesDate.soldPieces", "$salesDate.holdPieces"]}, {$multiply: ["$salesDate.salesQuantity", 100]}]}
}
}
])
As salesInfo.salesDate is an array field, you need to to use array operator like $map to perform element-wise operation.
db.products.aggregate([
{
$addFields: {
"salesInfo.salesDate": {
"$map": {
"input": "$salesInfo.salesDate",
"as": "s",
"in": {
"_id": "$$s._id",
"salesQuantity": "$$s.salesQuantity",
"soldPieces": "$$s.soldPieces",
"holdPieces": "$$s.holdPieces",
"percentageSold": {
$divide: [
{
$add: [
"$$s.soldPieces",
"$$s.holdPieces"
]
},
{
$multiply: [
"$$s.salesQuantity",
100
]
}
]
}
}
}
}
}
}
])
Here is the Mongo playground for your reference.
I struggle writing Mongo Queries. I can never understand why it doesn't return what I expect and yes I am reading the documentation but apparently to dumb to understand.
In Compass I write this query for a State collection with a document array of cities.
{"Cities.CityName":"Denver"}
This returns to me the State of Colorado document with ALL the cities in the document array. My sample below just shows one city document but they are all there.
{
"_id": {"$oid":"6146ada531696ee91a3f9fa4"},
"StateName": "Colorado",
"StateCode": "CO",
"Cities": [{
"_id": {"$oid":"6146ada531696ee91a3f5a50"},
"CityName": "Denver",
"Latitude": "39.55666000",
"Longitude": "-104.89609000"
}...]
}
OK so I'm thinking clearly we matched on the CityName now just project the _id of the City document.
{"Cities._id":1}
But this always returns to me the State document id NOT the matched City Document _id.
What am I doing wrong?
1) You can use $ (projection) or $elemMatch (projection):
use case: The positional $ operator limits the contents of an <array> to return the first element that matches the query condition on the array.
ex:
{ "Cities.$": 1 } or {"Cities": { "$elemMatch": { "CityName": "Denver" } } }
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[{
"_id":{"$oid":"6146ada531696ee91a3f5a50"},
"CityName":"Denver",
"Latitude":"39.55666000",
"Longitude":"-104.89609000"
}]
}
Playground
Note: You have to specify the required result fields in the projection!
2) You can use $filter aggregation operator, supported from MongoDB 4.4 in find() method:
use case: Selects a subset of an array to return based on the specified condition. Returns an array with only those elements that match the condition. The returned elements are in the original order.
ex:
{
"Cities": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
}
}
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[{
"_id":{"$oid":"6146ada531696ee91a3f5a50"},
"CityName":"Denver",
"Latitude":"39.55666000",
"Longitude":"-104.89609000"
}...{}]
}
Playground
Note: You have to specify the required result fields in the projection!
2.1) You can use $map aggregation operator to select only _ids from the Cities array:
use case: Applies an expression to each item in an array and returns an array with the applied results.
ex:
{
"Cities": {
"$map": {
"input": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
},
"in": "$$this._id"
}
}
}
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[
{"$oid":"6146ada531696ee91a3f5a50"},
{"$oid":"6146ada531696ee91a3f5a51"},
....
]
}
Playground
Note: You have to specify the required result fields in the projection!
3) You can use aggregation method aggregate() for more customization:
- ex:
$match to check query condition
$addFields to add or format the existing properties
$filter and $map i have explained in 2) point
db.collection.aggregate([
{ "$match": { "Cities.CityName": "Denver" } },
{
"$addFields": {
"Cities": {
"$map": {
"input": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
},
"in": "$$this._id"
}
}
}
}
])
Playground
result:
[
{
"Cities": [
ObjectId("6146ada531696ee91a3f5a50")
],
"StateCode": "CO",
"StateName": "Colorado",
"_id": ObjectId("6146ada531696ee91a3f9fa4")
}
]