If I have this collection
[
{
"_id": "637cbf94b4741277c3b53c6c",
"text": "outter",
"username": "test1",
"address": [
{
"text": "inner",
"username": "test2",
"_id": "637cbf94b4741277c3b53c6e"
}
],
"__v": 0
}
]
and would like to search for the nested document by _id and return all of the nested document. If I do
db.collection.find({
_id: "637cbf94b4741277c3b53c6c"
},
{
address: {
$eq: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
I get
query failed: (Location16020) Expression $eq takes exactly 2 arguments. 1 were passed in.
Playground link
Question
Can anyone see what I am doing wrong?
use $elemMatch and also you have extra unneeded brackets. try
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
Edit: if you only want to return the address add projection like this
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
},
{
_id: 0,
address: 1
})
One option is to use find:
db.collection.find({},
{
_id: 0,
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
See how it works on the playground example
The other option is to use aggregation pipeline:
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"637cbf94b4741277c3b53c62",
"$address._id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$address",
cond: {
$eq: [
"$$this._id",
"637cbf94b4741277c3b53c6e"
]
}
}
}
}
}
}
])
See how it works on the playground example
Related
I have a query result from a mongoose aggregation query that I need to further process, or at best do it in the aggregation itself.
The aggregation looks like this
result = await TokenBalance.aggregate([
{
$match: {
$and: [
{ ethervalue: { $gte: minBalance } },
{
ethervalue: { $lte: maxBalance }
}
]
}
},
{ $limit:limit }
])
This returns an array of Objects of this format
{
"_id": "61013d6dda7d7c0015af5ccf",
"balances": [
{
"address": "0x1fc3ddeb035310930a444c0fa59c01618d5902af",
"symbol": "HBTC",
"balance": 5.21419339e-10,
"usdvalue": 0.000020969961637162402
},
{
"address": "0x1fc3ddeb035310930a444c0fa59c01618d5902af",
"symbol": "NSBT",
"balance": 1.258566,
"usdvalue": 27.427343477595258
},
{
"address": "0x1fc3ddeb035310930a444c0fa59c01618d5902af",
"symbol": "CRV",
"balance": 517.985955847106,
"usdvalue": 806.7017064052314
},
{
"address": "0x1fc3ddeb035310930a444c0fa59c01618d5902af",
"symbol": "USDT",
"balance": 0.003469,
"usdvalue": 0.003470159747979122
}
],
"address": "0x1fc3ddeb035310930a444c0fa59c01618d5902af",
"ethervalue": 0.7604598621232733,
"createdAt": "2021-07-28T11:20:13.927Z",
"updatedAt": "2021-07-28T11:20:13.927Z",
"__v": 0
},
What I need, is the "balances" property to be processed as grouped by symbol and for each of these symbols sum the balance and usdvalue fields.
I would prefer this do be done in the aggregation if possible, but I can not seem to get it right, even not in pure nodejs.
I want the result to be like this:
[
{
symbol: USDC, balance: xxx, usdvalue: yyy
},
{
symbol: USDT, balance: zzz, usdvalue: jjj
}
]
You can use the below approach,
$unwind to deconstruct the balances array
$group by symbol and sum balance and usdvalue
$addFields to rename _id field to symbol and and remove _id field
result = await TokenBalance.aggregate([
{
$match: {
$and: [
{ ethervalue: { $gte: minBalance } },
{ ethervalue: { $lte: maxBalance } }
]
}
},
{ $unwind: "$balances" },
{
$group: {
_id: "$balances.symbol",
balance: { $sum: "$balances.balance" },
usdvalue: { $sum: "$balances.usdvalue" }
}
},
{
$addFields: {
symbol: "$_id",
_id: "$$REMOVE"
}
},
{ $limit:limit }
])
Playground
Structure:
[
{
"_id": "12",
"title": "Vanella Icream",
"contain":"sugar",
"details": [
{
"flavour": "Vanella"
},
{
"weight": "6KG"
}
]
},
{
"_id": "3",
"title": "Pretzels",
"contain":"salt",
"details": [
{
"flavour": "Wheat"
},
{
"weight": "2KG"
}
]
}
]
Expected Output
details:
{
"flavour": [
"Vanella",
"Wheat",
],
"weight": [
"6KG",
"2KG",
]
}
More Important: I've to do this without details.flavour or details.weight.
I’ve tried with $addToSet, $filter $group but I’m not getting results like that. anyone please suggest.
Mongo Playground: https://mongoplayground.net/p/SXw3jaRDzLy
$project to show required field
$unwind deconstruct the details array
$objectToArray convert details object to array key-value format
$unwind deconstruct the details array
$group by details key and construct the array of values
$group by null and construct the array of details in key-value format
$arrayToObject convert above result from key-value format array of object to an object
db.collection.aggregate([
{
$project: {
_id: 0,
details: 1
}
},
{ $unwind: "$details" },
{
$project: {
details: { $objectToArray: "$details" }
}
},
{ $unwind: "$details" },
{
$group: {
_id: "$details.k",
v: { $addToSet: "$details.v" }
}
},
{
$group: {
_id: null,
details: {
$push: { k: "$_id", v: "$v" }
}
}
},
{
$project: {
_id: 0,
details: { $arrayToObject: "$details" }
}
}
])
Playground
I have the following document structure
{
"_id": "60b7b7c784bd6c2a1ca57f29",
"user": "607c58578bac8c21acfeeae1",
"exercises": [
{
"executed_reps": [8,7],
"_id": "60b7b7c784bd6c2a1ca57f2a",
"exercise_name": "Push up"
},
{
"executed_reps": [5,5],
"_id": "60b7b7c784bd6c2a1ca57f2b",
"exercise_name": "Pull up"
}
],
}
In aggregation, I am trying to sum all the executed_reps so the end value in this example should be 25 (8+7+5+5).
Here is the code I have so far:
const exerciseStats = await UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$group: {
_id: null,
totalReps: {
$sum: {
$reduce: {
input: '$exercises.executed_reps',
initialValue: '',
in: { $add: '$$this' },
},
},
},
},
},
]);
This gives a result of 5 for totalReps. What am I doing wrong?
Well 10 minutes later I found the solution myself:
UserWorkout.aggregate([
{
$match: {
user: { $eq: ObjectId(req.query.user) },
},
},
{ $unwind: '$exercises' },
{
$project: {
total: { $sum: '$exercises.executed_reps' },
},
},
{
$group: {
_id: null,
totalExercises: { $sum: '$total' },
},
},])
Had to use $project first. :)
You can do it like this:
const exerciseStats = await UserWorkoutaggregate([
{
"$addFields": {
"total": {
"$sum": {
"$map": {
"input": "$exercises",
"as": "exercise",
"in": {
"$sum": "$$exercise.executed_reps"
}
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/5_fsPgSh8EP
I have the following object in the format _id: userID and a Mongoose query:
const obj = {
111: "222",
333: "444",
555: "666"
};
Model.aggregate([
{
$addFields: {
userID: ???
}
}
]);
I need a way to add a userID field to each document based on the object. So let's say the collection has this data:
[
{
_id: "111",
...
},
{
_id: "333",
...
},
{
_id: "555",
...
}
]
The document should end up looking like this:
[
{
_id: "111",
userID: "222",
...
},
{
_id: "333",
userID: "444",
...
},
{
_id: "555",
userID: "666",
...
}
]
How can I do this?
You need to start with $objectToArray to get your dynamic object as an array of keys and values. Then you can use $filter to find matching pair, $arrayElemAt to get single element:
db.collection.aggregate([
{
$addFields: {
userId: {
$let: {
vars: {
match: {
$arrayElemAt: [
{
$filter: {
input: { $objectToArray: { 111: "222", 333: "444", 555: "666" } },
cond: { $eq: [ "$$this.k", { $toString: "$_id" } ] }
}
},
0
]
}
},
in: "$$match.v"
}
}
}
}
])
Mongo Playground
I want a query in MongoDB that does the following:
A document returned profession.organization property can be the string 'bank'.
A document returned profession.city property can be the string 'NY'.
However, when the document has 'bank' for 'profession.organization' AND 'NY' for 'profession.city' the document must be excluded.
To sum up a document can have city 'NY' or organization 'bank' but must be excluded if it has these properties at the same time.
What I have tried so far.
targetObj = await db.db(MDBC.db).collection(MDBC.pC)
.aggregate([{
$match: {
$and: [
{ 'profession.organization': { $ne: 'bank' } },
{ 'profession.city': { $ne: 'NY' } }
]
}
}, { $sample: { size: 1 } }]).next();
Try this one:
targetObj = await db.db(MDBC.db).collection(MDBC.pC)
.aggregate([
{
$match: {
$and: [
{
$or: [
{ "profession.organization": "bank" },
{ "profession.city": "NY" }
]
},
{
$or: [
{
"profession.organization": "bank",
"profession.city": { $ne: "NY" }
},
{
"profession.organization": { $ne: "bank" },
"profession.city": "NY"
}
]
}
]
}
},
{ $sample: {size: 1} } ]).next();
MongoPlayground
I think the query which is used in the accepted answer could be much shorter.
db.collection.aggregate([
{
$match: {
$or: [
{
"profession.organization": "bank",
"profession.city": {
$ne: "NY"
}
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": "NY"
}
]
}
},
{
$sample: {
size: 1
}
}
])
and one more thing I want to say is if you also want to include documents that don't have either of those two properties then you should use this:
db.collection.aggregate([
{
$match: {
$or: [
{
"profession.organization": "bank",
"profession.city": {
$ne: "NY"
}
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": "NY"
},
{
"profession.organization": {
$ne: "bank"
},
"profession.city": {
$ne: "NY"
}
}
]
}
},
{
$sample: {
size: 1
}
}
])
this will also include documents like-
{
"profession": {
"organization": "someBank",
"city": "notNA"
}
}
Unfortunately, at the time of writing, there is no matching operator in MongoDB to help out with this. But you can achieve a similar result by combining the $or and $ne operators. Try something like this for your query:
const query = {
$or: [
{
// Include all non-banks outside of NY.
'profession.city': { $ne: 'NY' },
'profession.organization': { $ne: 'bank' }
},
{
// Include all non-banks in NY.
'profession.city': 'NY',
'profession.organization': { $ne: 'bank' }
},
{
// Include all banks outside of NY.
'profession.city': { $ne: 'NY' },
'profession.organization': 'bank'
}
]
};