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
Related
Suppose we have this array:
const array = [{ code:1, pw:'abc'}, { code:2, pw:'grt'}, { code:3, pw:'tpo'}, { code:4, pw:'xyz'}]
and we have these docs in our db from model called User:
[{ code:1, pw:'___'}, { code:2, pw:'___'}, { code:3, pw:'___'}, { code:4, pw:'___'}]
What's the most efficient way you'd suggest to update the pw fields from db with pws from the array at one shot (in Mongoose)? (we definitely want the codes from both arrays to match) Thank you.
A simple and efficient option will be to use a bulk:
const usersBulk = userModel.collection.initializeUnorderedBulkOp();
for (const user of array) {
usersBulk.find({code: user.code}).update({$set: {pw: user.pw}});
}
usersBulk.execute()
It can also be done in an update with pipeline query:
db.collection.updateMany(
{code: {$in: codes}},
[
{$set: {pw: {
$getField: {
field: "pw",
input: {
$first: {
$filter: {
input: array,
cond: {$eq: ["$$this.code", "$code"]}
}
}
}
}
}}}
]
)
See how it works on the playground example
But I think it might be less efficient than a bulk update.
You can do it like this:
db.collection.update({
"code": {
"$in": [
1,
2,
3,
4
]
}
},
[
{
"$set": {
"pw": {
"$cond": {
"if": {
"$eq": [
"$code",
1
]
},
"then": "abc",
"else": {
"$cond": {
"if": {
"$eq": [
"$code",
2
]
},
"then": "grt",
"else": {
"$cond": {
"if": {
"$eq": [
"$code",
3
]
},
"then": "tpo",
"else": "xyz"
}
}
}
}
}
}
}
}
],
{
multi: true
})
Working example
i'm trying to accomplish the following in mongoose:
Say i have the following collection
{
"_id": {
"$oid": "111"
},
"email": "xxx#mail.com",
"givenName": "xxx",
"familyName": "xxx",
"favoriteProducts": [{
"soldTo": "33040404",
"skus": ["W0541", "W2402"]
}, {
"soldTo": "1223",
"skus": ["12334"]
}]
}
i want to be able to add a sku to the favorite products array based on soldTo and _id.
When doing this there are two possible scenarios.
a. There is already an object in favoriteProducts with the given soldTo in which case the sku is simply added to the array.(for example add sku '12300' to soldTo '1223' for id '111')
b. There is no object with the given soldTo yet in which case this object need to be created with the given sku and soldTo. (for example add sku '123' to soldTo '321' for id '111')
so far i've done this but i feel like there is a way to do it in one query instead.
private async test() {
const soldTo = '1223';
const sku = '12300';
const id = '111';
const hasFavoriteForSoldTo = await userModel.exists({
_id: id,
'favoriteProducts.soldTo': soldTo,
});
if (!hasFavoriteForSoldTo) {
await userModel
.updateOne(
{
_id: id,
},
{ $addToSet: { favoriteProducts: { skus: [sku], soldTo } } },
)
.exec();
} else {
await userModel
.updateOne(
{
_id: id,
'favoriteProducts.soldTo': soldTo,
},
{ $addToSet: { 'favoriteProducts.$.skus': sku } }
)
.exec();
}
}
Use update-documents-with-aggregation-pipeline
Check out mongo play ground below. Not sure you want Output 1 or Output 2.
Output 1
db.collection.update({
_id: { "$oid": "111222333444555666777888" }
},
[
{
$set: {
favoriteProducts: {
$cond: {
if: { $in: [ "1223", "$favoriteProducts.soldTo" ] },
then: {
$map: {
input: "$favoriteProducts",
as: "f",
in: {
$cond: {
if: { $eq: [ "1223", "$$f.soldTo" ] },
then: { $mergeObjects: [ "$$f", { skus: [ "12300" ] } ] },
else: "$$f"
}
}
}
},
else: {
$concatArrays: [ "$favoriteProducts", [ { skus: [ "12300" ], soldTo: "1223" } ] ]
}
}
}
}
}
],
{
multi: true
})
mongoplayground
Output 2
db.collection.update({
_id: { "$oid": "111222333444555666777888" }
},
[
{
$set: {
favoriteProducts: {
$cond: {
if: { $in: [ "1223", "$favoriteProducts.soldTo" ] },
then: {
$map: {
input: "$favoriteProducts",
as: "f",
in: {
$cond: {
if: { $eq: [ "1223", "$$f.soldTo" ] },
then: {
$mergeObjects: [
"$$f",
{ skus: { $concatArrays: [ [ "12300" ], "$$f.skus" ] } }
]
},
else: "$$f"
}
}
}
},
else: {
$concatArrays: [ "$favoriteProducts", [ { skus: [ "12300" ], soldTo: "1223" } ] ]
}
}
}
}
}
],
{
multi: true
})
mongoplayground
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.
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 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"}}
},
])