I have this Object:
{
"_id":"1",
"a":"b",
"b":"wanted value"
},
{
"_id":"2",
"a":"c",
"c":"wanted value 2"
}
how can i get the value of a and make it the property with which i project to get "wanted value"/"wanted value 2" ?
wanted output:
{
"_id":"1",
"b":"wanted value"
},
{
"_id":"2",
"c":"wanted value 2"
}
Use $objectToArray to convert the root document into an array of k-v tuples then use $reduce to process the array.
db.collection.aggregate([
{
"$addFields": {
"arr": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$addFields": {
"v": {
"$reduce": {
"input": "$arr",
"initialValue": null,
"in": {
"$cond": {
"if": {
$eq: [
"$$this.k",
"a"
]
},
"then": "$$this.v",
"else": "$$value"
}
}
}
}
}
},
{
"$set": {
"arr": {
"$filter": {
"input": "$arr",
"as": "tuple",
"cond": {
$eq: [
"$$tuple.k",
"$v"
]
}
}
}
}
},
{
"$set": {
"result": {
"$arrayToObject": "$arr"
}
}
},
{
"$replaceRoot": {
"newRoot": "$result"
}
}
])
Here is the Mongo playground for your reference.
Related
I have the following aggregation pipeline running in the latest version of mongoDB and pymongo:
[
{
"$project": {
"union": {
"$setUnion": [
"$query_a",
"$query_b"
]
}
}
},
{
"$unwind": "$union"
},
{
"$group": {
"_id": "$union.ID",
"date_a": {
"$addToSet": "$union.date_a"
},
"date_b": {
"$addToSet": "$union.date_b"
}
}
},
{
"$unwind": "$date_a"
},
{
"$unwind": "$date_b"
},
{
"$project": {
"_id": 1,
"date_a": "$date_a",
"date_b": "date_b",
"diff": {
"$subtract": [
{
"$toInt": "$date_b"
},
{
"$toInt": "$date_a"
}
]
}
}
},
{
"$match": {
"diff": {
"$gt": 0,
"$lte": 20
}
}
},
]
This gives the union of the 2 pipelines query_a and query_b. After this union I want to get an intersection on ID with the pipeline query_c: (query_a UNION query_b) INTERSECTION query_c.
For this playground example the desired output would be:
[
{
"ID": "c80ea2cb-3272-77ae-8f46-d95de600c5bf",
},
{
"ID": "cdbcc129-548a-9d51-895a-1538200664e6",
}
]
You could change and augment your pipeline a little to get your desired output.
db.collection.aggregate([
{
"$project": {
"union": {
// do the intersection here
"$filter": {
"input": {
"$setUnion": [
"$query_a",
"$query_b"
]
},
"as": "elem",
"cond": {
// only take IDs in query_c
"$in": ["$$elem.ID", "$query_c.ID"]
}
}
}
}
},
{
"$unwind": "$union"
},
{
"$group": {
"_id": "$union.ID",
"date_a": {
"$addToSet": "$union.date_a"
},
"date_b": {
"$addToSet": "$union.date_b"
}
}
},
{
"$unwind": "$date_a"
},
{
"$unwind": "$date_b"
},
{
"$project": {
"diff": {
"$subtract": [
{
"$toInt": "$date_b"
},
{
"$toInt": "$date_a"
}
]
}
}
},
{
"$match": {
"diff": {
"$gt": 0,
"$lte": 20
}
}
},
{ // get unique _id's
"$group": {
"_id": "$_id"
}
},
{ // rename _id to ID
"$project": {
"_id": 0,
"ID": "$_id"
}
}
])
Try it on mongoplayground.net.
You can do it with:
Updating first $project stage to also project an array of IDs from query_c.
Using $set as a second stage where you would filter out all items from the union of query_a and query_b, that does not have ID that's in query_c.
You can do it like this:
{
"$project": {
"union": {
"$setUnion": [
"$query_a",
"$query_b"
]
},
"query_c": {
"$map": {
"input": "$query_c",
"in": "$$this.ID"
}
}
}
},
{
"$set": {
"union": {
"$filter": {
"input": "$union",
"cond": {
"$in": [
"$$this.ID",
"$query_c"
]
}
}
}
}
},
The rest of your Aggregation pipeline can remain the same.
Working example
I have the following aggregation that is supported by MongoDB 5 but not 4.4. How can I write this in v4.4?
Aggregation Pipeline (V5):
"$ifNull": [
{
"$getField": {
"field": "prices",
"input": {
"$first": "$matchedUsers"
}
}
},
[]
]
Here's a MongoDB Playground for the same.
This pipeline should work in version 4.4:
db.datasets.aggregate([
{
"$lookup": {
"from": "users",
"localField": "assignedTo",
"foreignField": "id",
"as": "matchedUsers"
}
},
{
"$addFields": {
"cgData": {
"$first": "$matchedUsers"
}
}
},
{
"$addFields": {
"cgData": {
"$first": {
"$filter": {
"input": {
"$ifNull": [
"$cgData.prices",
[]
]
},
"as": "currentPrice",
"cond": {
"$and": [
{
"$gte": [
"$firstBillable",
"$$currentPrice.beginDate"
]
},
{
$or: [
{
$eq: [
{
$type: "$$currentPrice.endDate"
},
"missing"
]
},
{
"$lt": [
"$firstBillable",
"$$currentPrice.endDate"
]
}
]
}
]
}
}
}
}
}
},
{
"$addFields": {
cgPrice: "$cgData.price"
}
},
{
"$project": {
cgData: 0,
"matchedUsers": 0
}
}
])
In this, a new $addFields stage is added, to get first element of matchedUsers array.
{
"$addFields": {
"cgData": {
"$first": "$matchedUsers"
}
}
}
Then we use $ifNull like this:
{
"$ifNull": [
"$cgData.prices",
[]
]
}
See it working here.
Data:
{
"_id": "test1",
"orderStatus": "shipped",
"history": {
"pending": {startAt: '2021/03/16'},
"shipped": {startAt: '2021/03/18'},
}
}
Is it possible to access sub document by another field?
I'd like to get current order status startAt in an aggregation pipeline, for example:
db.aggregate([{$addFields: { currentStartAt: "history.$orderStatus" }}])
but it doesn't work.
Try this one:
db.collection.aggregate([
{ $set: { history: { $objectToArray: "$history" } } },
{ $set: { history: { $filter: { input: "$history", cond: { $eq: ["$orderStatus", "$$this.k"] } } } } },
{ $project: { currentStartAt: { $first: "$history.v.startAt" } } }
])
Within a mongo shell you can also do this one:
var field = db.collection.findOne({}, { orderStatus: 1 }).orderStatus;
var field = "$history." + field + ".startAt";
db.collection.aggregate([
{ $project: { currentStartAt: field } }
])
This also works but I have no idea about performance, let me know how it performs.
db.collection.aggregate([
{
"$addFields": {
"currentStartAt": {
"$arrayElemAt": [
{
"$map": {
"input": {
"$filter": {
"input": {
"$objectToArray": "$history"
},
"as": "el",
"cond": {
"$eq": [
"$orderStatus",
"$$el.k"
]
}
}
},
"in": "$$this.v.startAt"
}
},
0
]
}
}
},
{
"$project": {
"currentStartAt": 1
}
}
])
Another query doing same thing
db.collection.aggregate([
{
"$addFields": {
"currentStartAt": {
"$filter": {
"input": {
"$objectToArray": "$history"
},
"cond": {
"$eq": [
"$orderStatus",
"$$this.k"
]
}
}
}
}
},
{
"$project": {
"currentStartAt": {
"$first": "$currentStartAt.v.startAt"
}
}
}
])
I have below data. I want to find value=v2 (remove others value which not equals to v2) in the inner array which belongs to name=name2. How to write aggregation for this? The hard part for me is filtering the nestedArray which only belongs to name=name2.
{
"_id": 1,
"array": [
{
"name": "name1",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
},
{
"name": "name2",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
}
]
}
And the desired output is below. Please note the value=v1 remains under name=name1 while value=v1 under name=name2 is removed.
{
"_id": 1,
"array": [
{
"name": "name1",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
},
{
"name": "name2",
"nestedArray": [
{
"value": "v2"
}
]
}
]
}
You can try,
$set to update array field, $map to iterate loop of array field, check condition if name is name2 then $filter to get matching value v2 documents from nestedArray field and $mergeObject merge objects with available objects
let name = "name2", value = "v2";
db.collection.aggregate([
{
$set: {
array: {
$map: {
input: "$array",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this.name", name] }, //name add here
{
nestedArray: {
$filter: {
input: "$$this.nestedArray",
cond: { $eq: ["$$this.value", value] } //value add here
}
}
},
{}
]
}
]
}
}
}
}
}
])
Playground
You can use the following aggregation query:
db.collection.aggregate([
{
$project: {
"array": {
"$concatArrays": [
{
"$filter": {
"input": "$array",
"as": "array",
"cond": {
"$ne": [
"$$array.name",
"name2"
]
}
}
},
{
"$filter": {
"input": {
"$map": {
"input": "$array",
"as": "array",
"in": {
"name": "$$array.name",
"nestedArray": {
"$filter": {
"input": "$$array.nestedArray",
"as": "nestedArray",
"cond": {
"$eq": [
"$$nestedArray.value",
"v2"
]
}
}
}
}
}
},
"as": "array",
"cond": {
"$eq": [
"$$array.name",
"name2"
]
}
}
}
]
}
}
}
])
MongoDB Playground
How we can use toUpper with array field, I have the following query which compare array field 'locations' with an array of camel case items, now my problem is how we can convert locations field values to upper case and then compare with array.
var array = ["KABUL","KAPISA","WARDAK","LOGAR","PARWAN","BAGHLAN","NANGARHAR","LAGHMAN",
"BAMYAN","PANJSHER","KHOST","GHAZNI","KUNARHA","PAKTYA","PAKTIKA","KUNDUZ",
"NOORISTAN","SAMANGAN","TAKHAR","DAYKUNDI","BADAKHSHAN","BALKH","GHOR",
"UROZGAN","FARYAB","ZABUL","SAR-E-PUL","NIMROZ","JAWZJAN","HELMAND","BADGHIS",
"KANDAHAR","FARAH","HERAT"];
db.getCollection('test').aggregate([
{ "$project": {
"locations": {
"$map": {
"input": {
"$setIntersection": ["$locations", array ]
},
"in": { "k": "$$this", "v": 1 }
}
}
}},
{ "$unwind": "$locations" },
{ "$group": {
"_id": "$locations.k",
"v": { "$sum": "$locations.v" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"obj": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$obj" }
}}
])
locations field is like :
"locations" : [
"Afghanistan",
"Kabul",
.....
],
Using $map to transform "each" element of course:
{ "$project": {
"locations": {
"$map": {
"input": {
"$setIntersection": [
{ "$map": { "input": "$locations", "in": { "$toUpper": "$$this" } } },
array
]
},
"in": { "k": "$$this", "v": 1 }
}
}
}},