Reputation: 314
MongoDB now supports key names which contain a period (.
) or starts with a $
. (new in version 5.0)
They introduced new operators in the aggregation framework like $getField
and $setField
(docs) to work with these kind of keys but they didn't provide any examples on how to use them in the $project
stage.
If I have the following collection (from the docs):
{ "_id" : 1, "item" : "sweatshirt", "price.usd": 45.99, qty: 300 }
{ "_id" : 2, "item" : "winter coat", "price.usd": 499.99, qty: 200 }
{ "_id" : 3, "item" : "sun dress", "price.usd": 199.99, qty: 250 }
how to project only the price.usd
field?
Upvotes: 1
Views: 4259
Reputation: 1
Use $getField operator when you want to get some specific fields from an object that is returned by an expression.
Take as example the following collection: you have a field "items" with an array of objects and a field "selectedItemId" that refers to one of the objects inside the "items" array.
[{
"_id": 111,
"items": [
{
"_id": 211,
"name": "table",
"price.usd": 45.99,
"quantity": 3
},
{
"_id": 212,
"name": "shoe",
"price.usd": 45.99,
"quantity": 3
},
],
"selectedItemId": 212,
}]
Never mind the background of how you got to this collection... You want to check the objects inside "items" and return only the name of the selected item, like that:
[{
"_id": 111,
"name": "shoe"
}]
If you have to do that in the $project stage, you could run a $filter to return the object you want, and wrap everything with the $getField operator to specify you only want the field "name":
db.collection.aggregate([
{
$project: {
_id: "$_id",
name: {
"$getField": {
field: "name", // return field "name"
input: { // from the obj returned by the expr below
$first: { // destruct array to obj as you know you only get one item anyway
$filter: {
input: "$items", // filter array "items"
as: "item", // naming every item as "item"
cond: { // by
$eq: [ // comparing the fields
"$selectedItemId",
"$$item._id"
]
}
}
}
}
}
}
}
},
])
I guess the key is to think it as a projection inside a projection.
Upvotes: 0
Reputation: 2189
That's not right. You can use $getField
in the project
stage. However, you'd need to make some additional operations if you want to preserve price.usd
key
Usage:
db.collection.aggregate([
{
"$project": {
"price": {
$getField: "price.usd"
}
}
}
])
Upvotes: 2
Reputation: 22296
These new functions can't be used in the $project
stage as it has very clear structure restrictions:
_id: <0 or false>
<field>: <1 or true>
<field>: <expression>
<field>:<0 or false>
You can either use field:0
in the $project
stage for all other fields:
db.collection.aggregate([
{
$project: {
_id: 0,
item: 0,
qty: 0
}
}
])
Or if you have too many fields / an unstructured schema that changes and you can't rely on that you could convert each document to an array using $objectToArray, filter that array to contains only the relevant keys then finally convert it back to an object using arrayToObject, like so:
db.collection.aggregate([
{
$project: {
rootAsArray: {
"$objectToArray": "$$ROOT"
}
}
},
{
$project: {
newRoot: {
$filter: {
input: "$rootAsArray",
as: "field",
cond: {
$eq: [
"$$field.k",
"price.usd"
]
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
"$arrayToObject": "$newRoot"
}
}
}
])
Upvotes: 0