Mongodb aggregate use field reference - mongodb

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"
}
}
}
])

Related

Get value of a specific field using aggregation in MongoDB

My document:
[
{
"_id": "5f969419d40c1580f2d4aa36",
"users": {
"foo#bar.com": "baz",
"foo2#bar.com": "baz2"
}
},
{
"_id": "5f9694d4d40c1580f2d4aa38",
"users": {
"baz#test.com": "foo"
}
}
]
If i use this aggregate, i get two users. Ok. But how can i get only the value of "foo#bar.com"?
Test in https://mongoplayground.net/p/3kW2Rw6fSjh
db.collection.aggregate([
{
"$project": {
"users": {
"$objectToArray": "$users"
}
}
},
{
"$match": {
"users.k": "foo#bar.com"
}
},
{
"$project": {
"users": {
"$arrayToObject": "$users"
}
}
}
])
You can add a $filter stage after the $match stage:
{
$set: {
users: {
$filter: {
input: "$users",
cond: {
$eq: [
"$$this.k",
"foo2#bar.com"
]
}
}
}
}
},
See how it works on the playground example

MongoDB 5 version Aggregation convert to 4.4 version

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.

How to get a column of nested array

I have a document like
{_id:xxx, xyz:[[1,2,3],[1,2,3],[1,2,3]]}
Now I want to query the first column data
x:[1,1,1]
How could I do it?
I know I can make the xyz
[{x:1,y:2,z:3},{x:1,y:2,z:3},{x:1,y:2,z:3}]
and use
find({},{x:'$xyz.x'})
db.collection.aggregate([
{
$set: {
xyz: {
"$map": {
"input": "$xyz",
"as": "m",
"in": {
x: { $first: "$$m" },
y: { $arrayElemAt: [ "$$m", 1 ] },
z: { $last: "$$m" }
}
}
}
}
},
{
$project: {
x: "$xyz.x"
}
}
])
mongoplayground
db.collection.aggregate([
{
$set: {
x: {
"$map": {
"input": "$xyz",
"as": "m",
"in": {
$first: "$$m"
}
}
}
}
},
{
"$unset": "xyz"
}
])
mongoplayground

how to project by a value as a property in mongodb?

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.

MongoDB: Select element from array based on another property in the document

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;
})