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
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 to delete the last 8 elements of an array saved in my mongodb database. With $pop I managed to delete the last, but not 8. How can I do that?
PostSchema.updateOne(
{ email: data.email },
{
$pop: {
gratitudeLog: 1,
},
},
(err, user) => {
if (err) {
res.sendStatus(404);
} else {
res.status(200).json(user);
}
}
);
I would appreciate some help.
Thanks
Use $slice to remove the last 8 elements. Remember to check whether the array size is greater than 8.
db.collection.update({
email: "123#gmail.com"
},
[
{
$set: {
gratitudeLog: {
"$cond": {
"if": {
$gt: [
{
$size: "$gratitudeLog"
},
8
]
},
"then": {
"$slice": [
"$gratitudeLog",
{
"$subtract": [
{
"$size": "$gratitudeLog"
},
8
]
}
]
},
"else": []
}
}
}
}
],
{
multi: true
})
Mongo Playground
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'm using $lookup, which may or may not return something. But if it does, i need to check the looked up value and check a specific field to see if it is null, and if it is, set it to something.
I tried this:
{
$lookup:
{
from:
"item_templates",
localField:
"based_on",
foreignField:
"_id",
as:
"template"
}
},
{
$addFields:
{
"template.image.url":
{
$ifNull:
[
"$template.image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
}
}
},
but it seems to have no effect on template.image.url (which is an array of objects), it actually returns an array instead, with one element as null.
You can use newer $lookup syntax to achieve the expected result
db.collection.aggregate([
{ "$lookup": {
"from": "item_templates",
"let": { "based_on": "$based_on" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$based_on"] }}},
{ "$addFields": {
"image.url": {
"$ifNull": [
"$image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
}
}}
],
"as": "template"
}}
])
Try with $project.
{
$unwind:{
path: "$template",
includeArrayIndex: "arrayIndex",
preserveNullAndEmptyArrays: true
}
},
{
$project:{
template.image.url: {
$ifNull: [
"$template.image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
},
}
}