Following code is not updating book title, how can achieve my goal of updating book title?
user: {
_id: "123",
books: [{ title: "ABC", pages: 99 }],
}
await model.updateOne(
{
_id: userID,
"books._id": bookID,
},
{ book: { title: "" } }
);
From your scenario, you need arrayFilters.
db.collection.update({
_id: "123" //userID
},
{
$set: {
"books.$[book].title": ""
}
},
{
arrayFilters: [
{
"book._id": "1" //bookID
}
]
})
Sample Mongo Playground
References
How the arrayFilters Parameter Works in MongoDB
try this
await model.updateOne(
{
_id: userID,
"books._id": bookID,
},
{ title: "" }
);
Related
I'm relatively new to MongoDB/Mongoose and I've only performed simple queries. Now I'm having some trouble trying to filter my database in a slightly more complex way. I already did some research to tackle my previous issues, but now I can't move forward. Here's what happening:
This is my schema:
const userSchema = new mongoose.Schema({
email: String,
password: String,
movies: [
{
title: String,
movieId: Number,
view_count: Number,
rating: Number,
review: String,
},
],
lists: {
watched_movies: [
{
title: String,
director: String,
genres: [{ type: String }],
runtime: Number,
date: Date,
},
],
},
});
I want to make a GET request that matches simultaneously "lists.watched_movies": { _id: req.params.entryId } and also "movies.title": req.body.title for a given email, so that the outcome of the findOne query would be just those elements and not the whole document. What I'm trying to accomplish is something like that:
{
email: "some.email#gmail.com",
movies: [
{
title: "Mongoose Strikes Back",
movieId: 123,
view_count: 1,
rating: 3,
review: "Very confusing movie!"
}
],
lists: {
watched_movies: [
{
_id: 4321
title: "Mongoose Strikes Back",
director: "Mongo",
genres: ["Drama"],
runtime: 150,
date: "2021-11-22"
}
]
}
}
My first attempt to tackle it, however, wasn't successful. Here's what I tried:
router.route("/:entryId").get((req, res) => {
User.findOne(
{ email: "some.email#gmail.com" },
{
"lists.watched_movies": { $elemMatch: { _id: req.params.entryId } },
movies: { $elemMatch: { title: req.body.title } },
},
(err, entry) => {
if (!err) {
res.send(entry);
console.log(entry);
} else {
console.log(err);
}
}
);
});
It says that Cannot use $elemMatch projection on a nested field. I thought that maybe I can solve it by changing my schema, but I'd like to avoid it if possible.
For your scenario, you can use $filter to filter document(s) in nested array field.
db.collection.find({
email: "some.email#gmail.com"
},
{
"lists.watched_movies": {
"$filter": {
"input": "$lists.watched_movies",
"cond": {
"$eq": [
"$$this._id",
4321// req.params.entryId
]
}
}
},
movies: {
$elemMatch: {
title: "Mongoose Strikes Back"// req.body.title
}
}
})
Sample Mongo Playground
We have data stored in MongoDB by country code. Our document looks like the following,
[
{
title: '1',
US: {
data: { lastReportDate: '2021-09-09' } // will be fetched
},
GB: {
data: { lastReportDate: '2021-09-04' }
}
},
{
title: '2',
US: {
data: { lastReportDate: '2021-09-07' } // will NOT be fetched
}
},
{
title: '3',
US: {
data: null // will NOT be fetched
}
},
{
title: '4',
US: {
data: null
}
GB: {
data: { lastReportDate: '2021-09-08' } // will be fetched
},
NZ: {
data: { lastReportDate: '2021-09-04' }
}
},
{
title: '5',
GB: {
data: null
},
NZ: {
data: { lastReportDate: '2021-09-06' } // will be fetched
}
}
]
I want to fetch the titles which have the latest dates according to the countries.
For EX: in the above DB, we have the latest date for US as '2021-09-09', so I want to fetch all the titles which match this date in lastReportDate. For GB, the latest date is '2021-09-08' and for NZ, its '2021-09-06'.
We have around 180 countries in one document and I want to hit the DB minimum times. So can we build a query that can us latest dates for different countries and then query the Database according to that.
You can try below aggregation:
db.collection.aggregate([
{
$project: {
doc: {
$objectToArray:"$$ROOT"
},
title: "$title"
}
},
{
$unwind: "$doc"
},
{
$match: {
"doc.k": { $nin: [ "_id", "title" ] }
}
},
{
$group: {
_id: "$doc.k",
maxDate: { $max: "$doc.v.data.lastReportDate" },
titles: { $push: { date: "$doc.v.data.lastReportDate", title: "$title" } }
}
},
{
$project: {
_id: 0,
country: "$_id",
maxTitles: { $filter: { input: "$titles", cond: { $eq: [ "$$this.date", "$maxDate" ] } } }
}
}
])
The challenge here is that your countries are represented as keys of your document so you need to start with $obectToArray operator which in conjunction with $unwind will give you a list of countries with corresponding dates and titles.
Once you have them you can use $group to get $max date and then use $filter to get titles related to max date.
Mongo Playground
This is my mongo collection
const cl = [{
id: '601ad0d7afe0642f781f7794',
history: [{
_id: '601ad0d7afe0642f781f7795'
}, {
_id: '601ad0d7afe0642f781f7796'
}]
}];
I want to remove the item in history with id: '601ad0d7afe0642f781f7795'
This method is not removing the item.
cl.updateOne(
{ 'history._id': '601ad0d7afe0642f781f7795' },
{
$pull: {
history: {
_id: '601ad0d7afe0642f781f7795'
}
}
}
);
Have also tried but also isn't removing the item.
cl.updateOne(
{ 'history._id': '601ad0d7afe0642f781f7795' },
{
$pull: {
history: {
_id: {
'$in': ['601ad0d7afe0642f781f7795']
}
}
}
}
);
Can anyone advise what I'm missing?
I have an image schema that has a reference to a category schema and a nested array that contains an object with two fields (user, createdAt)
I am trying to query the schema by a category and add two custom fields to each image in my query.
Here is the solution with virtual fields:
totalLikes: Count of all nested attributes
schema.virtual("totalLikes").get(function() {
return this.likes.length;
});
canLike: Check if user with id "5c8f9e676ed4356b1de3eaa1" is included in the nested array. If user is included it should return false otherwise true
schema.virtual("canLike").get(function() {
return !this.likes.find(like => {
return like.user === "5c8f9e676ed4356b1de3eaa1";
});
});
In sql it would be a simple SUBQUERY but I can't get it working in Mongoose.
Schema:
import mongoose from "mongoose";
const model = new mongoose.Schema(
{
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category"
},
likes: [{
user: {
type: String,
required: true
},
createdAt: {
type: Date,
required: true
}
}]
})
here is a sample document:
[{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1de3eaa1",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
]
},
{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1dw223332",
createdAt:"2019-03-19T08:13:59.250+00:00"
},
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1d8498933",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
]
}]
Here is how it should look like:
[{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1de3eaa1",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
],
totalLikes: 1,
canLike: false
},
{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1dw223332",
createdAt:"2019-03-19T08:13:59.250+00:00"
},
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1d8498933",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
],
totalLikes: 2,
canLike: true
}]
Here is what I tried:
Resolver:
1) Tried in Mongoose call - Fails
const resources = await model.aggregate([
{ $match: {category: "5c90a0777952597cda9e9c8d"},
$addFields: {
totalLikes: {
$size: {
$filter: {
input: "$likes",
as: "el",
cond: "$$el.user"
}
}
}
},
$addFields: {
canLike: {
$match: {
'likes.user':"5c8f9e676ed4356b1de3eaa1"
}
}
}
}
])
2) Tried to change it after db call - works but not preferred solution
model.where({ competition: "5c90a0777952597cda9e9c8d" }).exec(function (err, records) {
resources = records.map(resource => {
resource.likes = resource.likes ? resource.likes: []
const included = resource.likes.find(like => {
return like.user === "5c8f9e676ed4356b1de3eaa1";
});
resource.set('totalLikes', resource.likes.length, {strict: false});
resource.set('canLike', !included, {strict: false});
return resource
});
})
Does anyone know how I can do it at runtime? THX
you can achieve it using aggregate
Model.aggregate()
.addFields({ // map likes so that it can result to array of ids
likesMap: {
$map: {
input: "$likes",
as: "like",
in: "$$like.user"
}
}
})
.addFields({ // check if the id is present in likesMap
canLike: {
$cond: [
{
$in: ["5c8f9e676ed4356b1de3eaa1", "$likesMap"]
},
true,
false
]
},
totalLikes: {
$size: "$likes"
}
})
.project({ // remove likesMap
likesMap: 0,
})
I have a collection named User, which contains the the fields firstName and secondName. But the data is in capital letters.
{
firstName: 'FIDO',
secondName: 'JOHN',
...
}
I wanted to know whether it is possible to make the field to camel case.
{
firstName: 'Fido',
secondName: 'John',
...
}
You can use a helper function to get your desired answer.
function titleCase(str) {
return str.toLowerCase().split(' ').map(function(word) {
return word.replace(word[0], word[0].toUpperCase());
}).join(' ');
}
db.User.find().forEach(function(doc){
db.User.update(
{ "_id": doc._id },
{ "$set": { "firstName": titleCase(doc.firstName) } }
);
});
Run an update operation with aggregate pipeline as follows:
const titleCase = key => ({
$concat: [
{ $toUpper: { $substrCP: [`$${key}`,0,1] } },
{ $toLower: {
$substrCP: [
`$${key}`,
1,
{ $subtract: [ { $strLenCP: `$${key}` }, 1 ] }
]
} }
]
});
db.User.updateMany(
{},
[
{ $set: {
firstName: titleCase('firstName'),
secondName: titleCase('secondName')
} }
]
)
Mongo Playground