Mongodb aggregate by using input array - javascript

I am struggling to create an aggregate query for my MongoDB database.
here is my input array
recipients = [1,2,7]
here is my database collection
{
"chapter": 1,
"targets": [
{
type: 'user',
recipient: 1
}
]
},
{
"chapter": 1,
"targets": [
{
type: 'user',
recipient: 2
}
]
},
{
"chapter": 2,
"targets": [
{
type: 'user',
recipient: 3
}
]
},
{
"chapter": 3,
"targets": [
{
type: 'user',
recipient: 4
}
]
},
the desired output
should be [] because 7 doesn't exist in targets.recipient in the collection
here is what I've tried so far
db.collection.aggregate([
{
$match: {
'targets.recipient': { $in: recipients },
},
}
])
Any suggestions, thank you.

The way $in works is that it returns the document if there's any match between it's value and the array you're passing as a parameter. It looks like in your case you can use $in for initial filtering but then you want to return the result only if the result set contains all the values from the input array. In order to achieve it you can use $group to get all matching results and then apply $all:
db.collection.aggregate([
{
$match: { "targets.recipient": { $in: [1,2,7] } }
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$match: { "docs.targets.recipient": { $all: [1,2,7] } }
}
])
Mongo Playground

// only matched documents will be shown.
> db.targets.aggregate([ {$match:{"targets.recipient":{$in:[1,2,7]}}}, {$project:{chapter:1,targets:1}} ]).pretty();
{
"_id" : ObjectId("5f5de14c598d922a1e6eff4d"),
"chapter" : 1,
"targets" : [
{
"type" : "user",
"recipient" : 1
}
]
}
{
"_id" : ObjectId("5f5de14c598d922a1e6eff4e"),
"chapter" : 1,
"targets" : [
{
"type" : "user",
"recipient" : 2
}
]
}
>

Related

Get count of documents matching different conditions

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

mongoose nested array filter

I have a document form similar to this
{
"doc-id":2,
"interfaces": [
{
"interface-role": "ON",
"port-nb": 1
},
{
"interface-role": "OFF",
"port-nb": 2
},
{
"interface-role": "ON",
"port-nb": 3
},
{
"interface-role": "OFF",
"port-nb": 3
}
]
}
I want to query and get specific document interfaces and also have the ability to filter ON and OFF and that's what I did try so far
const doc = await this.doc
.findOne({
'doc-id': docId,
'interfaces["interface-role"]': interfaceRole, //ON or OFF
})
.select({ interfaces: 1, _id: 0 })
.exec();
so the result that I want to have is getting interfaces if there's no filter for interfaces-role and if there's one get the interfaces filtered
You can use a $or to do the conditional filtering with a $filter.
db.collection.aggregate([
{
$match: {
"doc-id": 2
}
},
{
"$addFields": {
"interfaces": {
"$filter": {
"input": "$interfaces",
"as": "i",
"cond": {
$or: [
{
$eq: [
null,
<interfaceRole>
]
},
{
$eq: [
"$$i.interface-role",
<interfaceRole>
]
}
]
}
}
}
}
}
])
Here is the Mongo playground when interfaceRole is not supplied.
Here is the Mongo playground when interfaceRole is supplied.

Query access variable outside query based on condition in NodeJS and MongoDB

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

MongoDB find all docs where field doesn't exists, plus if exists apply field operator ($max) condition

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

Get sum of values from unique docs in collection of duplicate docs in MongoDB

I am new to MongoDB and I am stuck in the below scenario.
I have a collection that contains duplicate docs.
I just want to get the sum of the property in each doc excluding the duplicate docs.
My Docs looks like this:
{"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":10,
"transactionId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"uid":"2019-11-20T17:39:17.405Z",
"createdAt":"2019-11-21T08:56:56.589+00:00",
"updatedAt":"2019-11-21T08:56:56.589+00:00","__v":0
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
Currently, I am doing this:
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"}}
},
])
It returns the sum of product_quantity all the matching docs (including the duplicate docs).
Current Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:110}
Expected Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:60}
I want to get the sum of only unique docs. How can I achieve it?
Thanks in advance!
You need to sum inside of the $group _id field, and then use the replaceRoot to achieve the the result you wanted.
MaterialsTrack.aggregate([
{
$match: {
$and: [
{
product_name: "Bottle"
},
{
account_name: "mrfsahas1234"
},
{
action_type: "purchase"
},
{
uid: "2019-11-20T17:39:17.405Z"
}
]
}
},
{
$group: {
_id: {
sellingId: "$sellingId",
PurchaseQuantity: {
$sum: "$product_quantity"
}
}
}
},
{
$replaceRoot: {
newRoot: {
_id: "$_id.sellingId",
PurchaseQuantity: "$_id.PurchaseQuantity"
}
}
}
]);
Sample Output:
[
{
"PurchaseQuantity": 50,
"_id": "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914"
}
]
Playground:
https://mongoplayground.net/p/MOneCRiSlO0
What about adding $addToSet to your aggregations pipeline
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"},"list" : {$addToSet : "$list"}}
},
])

Categories