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.
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 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.
How to filter the product and optimize mongodb query,
We would like to get popular products base on some conditions i.e which products are orders, view and likes.
db.products.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "_id",
"foreignField": "product_id",
"as": "orders"
}
},
{
"$addFields": {
"orderCount": {
"$size": {
"$cond": [
{
"$isArray": "$orders"
},
"$orders",
[]
]
}
}
}
},
{
"$addFields": {
"likeCount": {
"$size": {
"$cond": [
{
"$isArray": "$likes"
},
"$likes",
[]
]
}
}
}
},
{
"$addFields": {
"sumCount": {
"$sum": [
"$orderCount",
"$likeCount",
"$view"
]
}
}
},
{
$sort: {
"sumCount": -1
}
}
])
https://mongoplayground.net/p/fIG3-yHGuV6
Have to use multiple $addFields what would be best option to achieve the products that have the most orders, likes and views. please guide
thanks
I would suggest 2 corrections,
orders size does not need verification if it is an array or not condition because $lookup stage will always return in array
You can do both operations for orderCount and likeCount in a single $addFields stage
You final query would be,
db.products.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "_id",
"foreignField": "product_id",
"as": "orders"
}
},
{
"$addFields": {
"orderCount": { "$size": "$orders" },
"likeCount": {
"$size": {
"$cond": [{ "$isArray": "$likes" }, "$likes", []]
}
}
}
},
{
"$addFields": {
"sumCount": {
"$sum": ["$orderCount", "$likeCount", "$view"]
}
}
},
{ "$sort": { "sumCount": -1 } }
])
Playground
You can also use projection to minimize the code
{
"$project": {
"likes": 1,
"orderCount": {
"$size": {
"$cond": {
"if": {
"$isArray": [
"$orders"
]
},
"then": "$orders",
"else": []
}
}
},
"likeCount": {
"$size": {
"$cond": {
"if": {
"$isArray": [
"$likes"
]
},
"then": "$likes",
"else": []
}
}
},
"views": {
"$ifNull": [
"$view",
0
]
}
}
},
https://mongoplayground.net/p/qUNftLP_-PN
check the mongoplayground.
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 a MongoDB collection with documents of the following structure (non-interesting bits left out):
{
displayFieldId: "abcd",
fields: [
{
fieldId: "efgh",
value: "cake"
},
{
fieldId: "abcd",
value: "cheese"
},
....
],
....
}
I would like to run a query on this collection to fetch only the element in the fields array which fieldId matches the document's displayFieldId. The result of the query on the document above should thus be:
{
fields: [
{
fieldId: "abcd",
value: "cheese"
}
],
....
}
I constructed the following query. It does what I want, with the exception that the displayFieldValue is hard coded
db.containers.find({}, {
fields: {
$elemMatch: {
fieldId: "abcd"
}
}
});
Is there a way to make it look at the document's displayFieldId and use that value instead of the hard coded "abcd"?
The server is running MongoDB 3.2.6
If possible, I would like to do this without aggregation, but if that can't be done, then aggregation will have to do
With aggregation framework:
db.containers.aggregate([
{
"$redact": {
"$cond": [
{
"$anyElementTrue": [
{
"$map": {
"input": "$fields",
"as": "el",
"in": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{
"$project": {
"displayFieldId": 1,
"fields": {
"$filter": {
"input": "$fields",
"as": "el",
"cond": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
},
"otherfields": 1,
....
}
}
])
MongoDB 3.4:
db.containers.aggregate([
{
"$redact": {
"$cond": [
{
"$anyElementTrue": [
{
"$map": {
"input": "$fields",
"as": "el",
"in": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{
"$addFields": {
"fields": {
"$filter": {
"input": "$fields",
"as": "el",
"cond": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
}
}
])
Without aggregation framework - using $where (the slow query):
db.containers.find({
"$where": function() {
var self = this;
return this.fields.filter(function(f){
return self.displayFieldId === f.fieldId;
}).length > 0;
}
}).map(function(doc){
var obj = doc;
obj.fields = obj.fields.filter(function(f){
return obj.displayFieldId === f.fieldId;
});
return obj;
})