I have a document that looks like this:
{
"id": "12345",
"channels": [
{
"id": "67890",
"count": 1
}
]
}
What I want to do is increment a given channel count by one. So a user sends a message and I look up the user, find the proper channel by "id" in the "channels" array, and increment "count" by one.
I tried the following query:
UserModel.findOneAndUpdate({id: this.id},
{'channels.id': channel.id},
{$set: {$inc: {'channels.$.count': 1}}}
It didn't fail, surprisingly. But it also didn't increment the count.
Two fixes needed: query has to be a single object and $inc is a separate operator so you don't need $set:
UserModel.findOneAndUpdate({id: this.id, 'channels.id': channel.id},
{ $inc: {'channels.$.count': 1}})
Related
I have this schema:
...
recommended:{
type:[{
movieId:String,
recommendedBy:[String]
}],
default:[],
},
name: {
type: String,
}
from the request I get movieId and id.
I want to add to recommended array a new object if there is no other item with the same movieId there already, and add theid to the recommendedBy nested array in both cases.
so far I have this:
const user = User.findByIdAndUpdate({_id:'123'},
{$AddToSet:{'recommended.$[movie].recommendedBy':id}},
{arrayFilters:[{'movie.movieId':movieId}]})
this will only set the recommendedBy of an already existing item with the same movieId,
but will not push a new object with the new movieId property.
(so it's basically $AddToSet for both arrays)
How could I achieve that? thanks!
Maybe you need something simple like this that is doing upsert or update for the recommended array element:
db.collection.update({},
[
{
$set: {
recommended: {
"$reduce": {
"input": "$recommended",
"initialValue": // assign your edit data as init value
[
{
"movieId": "1",
"recommendedBy": "who recommended"
}
],
"in": {
"$cond": {
"if": {
$in: [
"$$this.movieId",
"$$value.movieId"
]
},
"then": "$$value",
"else": {
"$concatArrays": [
"$$value",
[
"$$this"
]
]
}
}
}
}
}
}
}
])
explained:
No filter but you can add anything in the initial document filter to search only limited set of documents.
In the update stage you use $reduce to check input array elements that you need to upsert or update the value in the affected array element.
( Confirmed to work in mongod 4.4 )
playground
I couldn't understand #R2D2's answer so I came up with this workaround which does unexpectedly well for me, since I also need to be able to remove the movieId and all the recommendedBy with it.
I changed my schema to this:
recommanded:{
type: Map,
of:[String],
default:{}
},
I'm removing/ adding with:
User.findByIdAndUpdate({_id:"123",{$addToSet:{[`recommended.${movieId}`]:id}})
And
User.findByIdAndUpdate({_id:"123",{$unset:{[`recommended.${movieId}`]:""}})
{
"_id" : ObjectId("5d3acf79ea99ef80dca9bcca"),
"memberId" : "123",
"generatedId" : "00000d2f-9922-457a-be23-731f5fefeb14",
"memberType" : "premium"
},
{
"_id" : ObjectId("5e01554cea99eff7f98d7eed"),
"memberId" : "123",
"generatedId" : "34jkd2092sdlk02kl23kl2309k2309kr",
"memberType" : "premium"
}
I have 1 million docs like this format and how can i remove duplicated docs based on "memberId".
I need to be remove the duplicated docs where the "generatedId" value do not contain "-". In this example it should be deleted the bottom doc since it does not contains "-" in the "generatedId" value.
Can someone share any idea how to do this.
Well, there can be a strategy, but still, it depends on your data a lot.
Let's say you take your docs. Group them by their Id's for counting (duplicates), and then from the duplicates separate out all those entries where generatedId does not contain hyphens "-". When you get these docs which are duplicates and also does not contain - in their generatedId, you can delete them.
const result = await Collection.aggregate([
{
$project: {
_id: 1, // keep the _id field where it is anyway
doc: "$$ROOT", // store the entire document in the "doc" field
},
},
{
$group: {
_id: "$doc.memberId", // group by the documents by memeberId
count: { $sum: 1 }, // count the number of documents in this group
generatedId: { $first: "$doc.generatedId" }, // for keeping these values to be passed to other stages
memberType: { $first: "$doc.memberType" }, // for keeping these values to be passed to other stages
},
},
{
$match: {
count: { $gt: 1 }, // only show what's duplicated because it'll have count greater than 1
// It'll match all those documents not having - in them
generatedId: { $regex: /^((?!-).)*$/g } / g,
},
},
]);
Now in the result, you'll have docs which were memberId duplicates and does not have - in their generatedId. You can query them for deletion.
Warning:
Depending on your data it's possible certain duplicated memberId does not have '-' at all in their generatedIds, so you might delete all docs.
Always take backup before performing operations that might behave uncertain way.
db.collection.aggregate([
{
// first match all records with having - in generatedId
"$match" : { "generatedId" : { "$regex": "[-]"} } },
// then group them
{
"$group": {
"_id": "$memberId",
}}
])
Let's say I have three person documents in a MongoDB, inserted in a random order.
{
"firstName": "Hulda",
"lastName": "Lamb",
},
{
"firstName": "Austin",
"lastName": "Todd",
},
{
"firstName": "John",
"lastName": "Doe",
}
My goal is to obtain, let's say, the next person after Austin when the list is in alphabetical order. So I would like to get the person with firstName = Hulda.
We can assume that I know Austin's _id.
My first attempt was to rely on the fact that _id is incremental, but it won't work because the persons can be added in any order in the database. Hulda's _id field has a value less than Austin's. I cannot do something like {_id: {$gt: <Austin's _id here>}};
And I also need to limit the number of returned elements, so N is a dynamic value.
Here is the code I have now, but as I mentioned, the ID trick is not working.
let cursor: any = this.db.collection(collectionName).find({_id: {$gt:
cursor = cursor.sort({firstName: 1});
cursor = cursor.limit(limit);
return cursor.toArray();
Some clarifications:
startId is a valid, existing _id of an object
limit is a variable holding an positive integer value
sorting and limit works as expected, just the selection of the next elements is wrong, so the {_id: {$gt: startId}}; messes up the selection.
Every MongoDB's Aggregation Framework operation's context is restricted to a single document. There's no mechanism like window functions in SQL. Your only way is to use $group to get an array which contains all your documents and then get Austin's index to be able to apply $slice:
db.collection.aggregate([
{
$sort: { firstName: 1 }
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
nextNPeople: {
$slice: [ "$docs", { $add: [ { $indexOfArray: [ "$docs.firstName", "Austin" ] }, 1 ] }, 1 ]
}
}
},
{ $unwind: "$nextNPeople" },
{
$replaceRoot: {
newRoot: "$nextNPeople"
}
}
])
Mongo Playground
Depending on your data size / MongoDB performance, above solution may or may not be acceptable - it's up to you to decide if you want to deploy such code on production since $group operation can be pretty heavy.
I am trying to query a data collection to return just one object from an array of objects using elemMatch. I have this data:
[
{
"_id": "5ba10e24e1e9f4062801ddeb",
"user": {
"_id": "5b9b9097650c3414ac96bacc",
"firstName": "blah",
"lastName": "blah blah",
"email": "blah#gmail.com"
},
"appointments": [
{
"date": "2018-09-18T14:39:36.949Z",
"_id": "5ba10e28e1e9f4062801dded",
"treatment": "LVL",
"cost": 30
},
{
"date": "2018-09-18T14:39:32.314Z",
"_id": "5ba10e24e1e9f4062801ddec",
"treatment": "LVL",
"cost": 30
}
],
"__v": 1
}
]
I need to query the DB and pull out just one appointment depending on the id passed to params. I am using the code below based on the docs here.
router.get(
"/booked/:app_id",
passport.authenticate("jwt", { session: false }),
(req, res) => {
Appointment.find()
.elemMatch("appointments", { id: req.params.app_id })
.then(app => res.json(app));
}
);
This is returning an empty array though when I test with postman. This is the first time I have used node.js and mongoDB so any help would be appreciated. Thanks.
You are using elemmatch as query operator, but you need to use it as projection operator
Change your function to this :
Appointment.find(
{"appointments": {$elemMatch: {_id: req.params.app_id }}},
{"appointments": {$elemMatch: {_id: req.params.app_id }}}
)
Why twice??
First object in find query param is $elemmatch query operator. It will return all documents, that have at least one entry matching _id in there appointments array. (but will return whole documents)
Second object (the same) is elemmatch projection oprator, which 'filters' appointments array and returns only the first matching element in array.
PS : cannot test this command. Try on your side
[{
"date": "18/12/2010",
"babies": [{
"id":1,
"name": "James",
"age": 8,
}, {
"id":2,
"name": "John",
"age": 4,
}]
}]
I want to set the age of John to 10 but failed. I have to do multi condition to be more specified.
Babies.update({"date":date, 'babies.id': 1}, {'$set': {age:10}, function(err, response){
res.json(response);
})
The first condition is date and the second condition is the array of object of babies, which in this case it's the id. Above query has no error and no effect, where did I do wrong?
I debug with doing this query
Babies.find({'babies.id': 1}, function(err, response){
res.json(response);
})
and it couldn't find the correct target, maybe that's the problem
Use {'$set': {'babies.$.age':10}} instead of {'$set': {age:10}}.
Babies.update({"date":date, 'babies.id': 1},
{'$set': {
'babies.$.age':10
}
},
function(err, response){
res.json(response);
})
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array.
Refer to MongoDB Positional Operator for more information.
Instead of passing only object of field value {age:10} in $set flag, Pass the value in format of Array.index.field. So it would become like this -
{ $set: { 'babies.$.age': 10 } }