I have a schema like below:
[
{
"_id": 1,
"showResult": true,
"subject": "History",
},
{
"_id": 2,
"showResult": false,
"subject": "Math",
}
]
and an object in JS like below:
result = {
"History": 22,
"Math": 18
}
I am using aggregate to process query, in between i need to find score based on subject field in the document if showResult field is true i.e to access result variable inside query as map result[$subject]
My query:
db.collection.aggregate([
{
"$project": {
_id: 1,
"score":{$cond: { if: { $eq: [ "$showResult", true ] }, then: subjectObj[$subject], else: null }}
}
}
])
can this be done in MongoDB, i want result like below:
{
_id: 1,
score: 22
}
I think query is little costly than JS code, but i am adding the query if it will help you as per your question,
$match showResult is true
$project to show required fields, $reduce to iterate loop of result after converting from object to array using $objectToArray, check condition if subject match then return matching score
let result = {
"History": 22,
"Math": 18
};
db.collection.aggregate([
{ $match: { showResult: true } },
{
$project: {
_id: 1,
score: {
$reduce: {
input: { $objectToArray: result },
initialValue: 0,
in: {
$cond: [{ $eq: ["$$this.k", "$subject"] }, "$$this.v", "$$value"]
}
}
}
}
}
])
Playground
Related
I have collection: bookSchema as:
[
{
_id: ObjectId("637d05dc32428ed75ea08d09"),
book_details: {
book_name: "random123",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d0673ce0f17f6c473dee2"),
book_details: {
book_name: "random321",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d069a3d597c8458ebe4ec"),
book_details: {
book_name: "random676",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d06c05b32d503007bcb54"),
book_details: {
book_name: "random999",
book_auth: "Saurav"
}
}
]
Desired O/P to show as:
{
score_ambr: 3,
score_saurabh: 1
}
For this I tried as:
db.bookSchema.aggregate([
{
"$group": {
"_id": {
"$eq": [
"$book_details.book_auth",
"Amber"
]
},
"score_ambr": {
"$sum": 1
}
},
},
{
"$group": {
"_id": {
"$eq": [
"$book_details.book_auth",
"Saurav"
]
},
"score_saurabh": {
"$sum": 1
}
},
}
])
I tried using $group to as I want to group all the matching documents in one and use $count to give the number of count for the matching documents but it doesn't seem to be working and gives the O/P as
O/P:
[
{
"_id": false,
"score_sau": 2
}
]
MongoDB Playground: https://mongoplayground.net/p/cZ64KwAmwlv
I don't know what mean 3 and 1 in your example but if I've understood correctly you can try this query:
The trick here is to use $facet to create "two ways" in the aggregation. One option will filter by Amber and the other one by Saurav.
And then, as values are filtered, you only need yo know the size of the array generated.
db.collection.aggregate([
{
"$facet": {
"score_ambr": [
{
"$match": {
"book_details.book_auth": "Amber"
}
}
],
"score_saurabh": [
{
"$match": {
"book_details.book_auth": "Saurav"
}
}
]
}
},
{
"$project": {
"score_ambr": {
"$size": "$score_ambr"
},
"score_saurabh": {
"$size": "$score_saurabh"
}
}
}
])
Example here
Note that in this way you avoid to use $group.
It looks like what you want is two group twice and create a dynamic key from the book_details.book_auth:
db.bookSchema.aggregate([
{$group: {_id: "$book_details.book_auth", count: {$sum: 1}}},
{$group: {
_id: 0,
data: {$push: {
k: {$concat: ["score_", {$toLower: "$_id"}]},
v: {$sum: "$count"}
}}
}},
{$replaceRoot: {newRoot: {$arrayToObject: "$data"}}}
])
See how it works on the playground example
I have an array of objects call "extra" with different properties: some objects have "plus" and some haven't.
I want to create inside this "extra" array, 2 different arrays one called "cheap" with all the object that don't have the "plus" property and one called "exp" with only the objects with the "plus" property.
I think I can use the $reduce method in mongodb aggregate with $concatArrays and check with $cond if the property plus exists or not.
Something like that:
Data example:
{
extra: [
{
description: "laces",
type: "exterior",
plus: '200'
},
{
description: "sole",
type: "interior"
},
{
description: "logo",
type: "exterior"
},
{
description: "stud",
type: "exterior",
plus: '450'
}
],
}
{
$project: {
extra: {
$reduce: {
input: ['$extra'],
initialValue: {cheap: [], exp: []},
$cond: {
if: {$eq: ['$$this.plus', null]},
then: {
in: {
cheap: {
$concatArrays: ['$$value.cheap', '$$this'],
},
},
},
else: {
in: {
exp: {
$concatArrays: ['$$value.exp', '$$this'],
},
},
},
},
},
},
},
}
It doesn't work...I tried many ways or writing the $cond part without luck.
I can't figure it out.
Thank you all.
K.
Apart from some minor syntax issues you've had another problem is your understand of the $ne operator.
In this case you expect a missing value to be equal to null, this is not how Mongo works. so for a document:
{ name: "my name" }
The aggregation query:
{ $cond: { $eq: ["$missingField", null] } }
Will not give true as you expect as missing is not equal to null. I took the liberty to fix the syntax issues you've had, this working pipeline is the way to go:
db.collection.aggregate([
{
$project: {
extra: {
$reduce: {
input: "$extra",
initialValue: {
cheap: [],
exp: []
},
in: {
cheap: {
"$concatArrays": [
"$$value.cheap",
{
$cond: [
"$$this.plus",
[],
[
"$$this"
],
]
}
]
},
exp: {
"$concatArrays": [
"$$value.exp",
{
$cond: [
"$$this.plus",
[
"$$this"
],
[]
]
}
]
}
}
},
},
},
}
])
Mongo Playground
One thing to note is that $cond evaluates the plus field, meaning if the field does exist with a null value or a 0 value then it will consider this document matched for the cheap array. This is something to consider and change in case these are possible.
I have the following collections in MongoDB (mongoplayground) [characters,guilds]
I want to make a $lookup, that will add rank field to the resulting document, like that:
"members_t": [
{
"_id": ObjectId("5a934e000102030405000000"),
"level": 20,
"name": "test1",
"rank": 1
},
{
"_id": ObjectId("5a934e000102030405000001"),
"level": 40,
"name": "test2",
"rank": 2
}
]
old $lookup syntax can't help me with that, but the following query with the new syntax returns me an empty array in tested field (even without $addFields stage):
{
$lookup: {
from: "characters",
let: {
members_name: "$members.name",
rank: "$members.rank"
},
pipeline: [
{
$match: {
name: "$$members_name"
}
}
],
as: "tested"
}
}
So, is there any option to add an additional field after $lookup stage or not?
(Mongo -v 4.2.3, so the problem is not related with new syntax support)
You were almost close to solving it. Since the members are an array, you need to pass it through $lookup and then conditionally join it per each character with $reduce (You can do this with $filter as well, but then we need aditional stages).
Note: Explanation why we need to use $expr inside $lookup pipeline.
Try this one:
db.guilds.aggregate([
{
$lookup: {
from: "characters",
let: {
members: "$members"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$name",
"$$members.name"
]
}
}
},
{
$addFields: {
rank: {
$reduce: {
input: "$$members",
initialValue: null,
in: {
$cond: [
{
$eq: [
"$$this.name",
"$name"
]
},
"$$this.rank",
"$$value"
]
}
}
}
}
}
],
as: "members_t"
}
}
])
MongoPlayground
I am looking for a query for a $match stage in my aggregation which do almost the same, as in this question, but..
if field (named rank in my case) doesn't exists in document, add document to results
but if field, exists, apply $operator condition (in my case it's $max) to this field, and add all documents that suits this condition to the results.
MongoPlayground with example collection.
Result should be like this:
[
{
"method": 3,
"item": 1,
"rank": 3 //because it has field named rank, and suits condition {rank: $max}
},
{
"method": 4,
"item": 1 //we need this, because document doesn't have rank field at all
},
{
"method": 5,
"item": 1 //we need this, because document doesn't have rank field at all
}
]
Things, that I have tried already:
{
$match: {
$or: [
{item: id, rank: {$exists: true, $max: "$rank"}}, //id === 1
{item: id, rank: {$exists: false}} //id === 1
]
}
}
UPD: As for now, probably I don't limit with $match stage only, $project is also relevant after default match, so I could request every document during $match stage by id no matter, have the doc rank field or not, and then, during $project stage do a "separation" by rank $exists
Try this one:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$group: {
_id: "$item", //<- Change here your searching field
max: {
$max: "$rank" //<- Change here your field to apply $max
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$type: "$data.rank"
},
"missing"
]
},
{
$eq: [
"$data.rank",
"$max"
]
}
]
}
}
},
{
$replaceWith: "$data"
}
])
MongoPlayground
I have found an answer, separated from #Valijon's method, but it's also based on the logic above. My query is:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$project: {
method: 1,
item: 1,
rank: {
$ifNull: [
"$rank",
0
]
}
}
},
{
$group: {
_id: "$item",
data: {
$addToSet: "$$ROOT"
},
min_value: {
$min: "$rank"
},
max_value: {
$max: "$rank"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$or: [
{
$expr: {
$eq: [
"$data.rank",
"$max_value"
]
}
},
{
$expr: {
$eq: [
"$data.rank",
"$min_value"
]
}
},
]
}
}
])
My query is based on $project stage which gives the empty field value 0. It also could be -1, or any value that isn't used in collection. And then I separate results.
MongoPlayground
I am trying to aggregate on my User collection and $project out the fields I need. Then I want to set let variables in $lookup to be able to use the variable to find matching documents.
I want to do it this way, because I will have many $lookups by the variables.
However I am not able to get this working correctly. What am I doing wrong with the variables?
$project: {
_id: 1,
name: 1,
goal: 1
}
},
{
$lookup: {
from: "goals",
let: { user: "$_id" },
pipeline: [
{
$match: {
"user": "$$user"
}
},
{ $project: { _id: 0, leads: 0 } },
],
as: "goal"
}
},
{
$project: {
_id: 1,
name: 1,
goal: 1
}
},
You need to use $expr to use variable name inside the $match stage.
{ $match: { $expr: { $eq: ['$user', '$$user'] }}}