Referring to ObjectIDs directly in MongoDB works, but not in Mongoose - javascript

The first code snippet achieves the desired effect:
//in mongodb console:
db.collection.update({"username":"name"}, {$pull : { "task" : { "_id" : ObjectId("55280205702c316b6d79c89c")}}})
Yet this second (seemingly equivalent) code snippet written in Mongoose does not:
//in Mongoose:
var taskID = "55280205702c316b6d79c88c"
var pull = { $pull : {}};
var idObj = {};
idObj["_id"] = taskID;
pull.$pull["task"] = idObj;
collectionModel.update({"username": "name"}, pull) //fails to work since taskID just shows up as a literal string 55280205702c316b6d79c88c instead of ObjectId("55280205702c316b6d79c88c")
Other things I've tried:
making var taskID equal to the string 'ObjectId(\"55280205702c316b6d79c88c\")'
setting var taskID equal to mongoose.Types.ObjectId("55280205702c316b6d79c88c")
How can I achieve the effect in the former code snippet in Mongoose?

Import ObjectId type and use it in your query :
var ObjectId = require('mongoose').Types.ObjectId;
idObj["_id"] = new ObjectId("55280205702c316b6d79c88c");

After you did the $pull operation in mongodb shell and tried the same update operation in Mongoose with the same ObjectId, it didn't work because the task array object element with "_id": ObjectId("55280205702c316b6d79c88c") was removed by the previous mongodb shell operation.
Try first running the same query from Mongoose but with a different and existing taskID which is cast to ObjectId. You could add an extension to the String object as follows:
String.prototype.toObjectId = function() {
var ObjectId = (require("mongoose").Types.ObjectId);
return new ObjectId(this.toString());
};
var taskID = "55280205702c316b6d79c88d".toObjectId(); // a different _id
collectionModel.update(
{ "username": "name" },
{ "$pull" : { "task": { "_id": taskID } } },
function (err, result) {
console.log(result);
}
);

Related

How to write a mongoDB query to match any element in an array of subdocuments against any element in a Typescipt Array?

I have a mongoDB database containing Events. Each event is defined by a number of fields, including an array of genres (which are subdocuments consisting of a {genre and subGenre}).
For example: an event could be of {genre: "music", subGenre: "jazz"}, and {genre: "music", subGenre: "blues"}. See event.js - the model. In this case, 2 "genre" sub documents are added to the "genres" array in the Event document in the database.
Back in my node application - see query.ts - I am trying to work out how to run a query that lets the user search for all events that match their genre preferences.
So:
the Event is defined by an array of genres (in the database), and
the user's preferences are defined by an array of genres (in the
application).
I am looking to define a mongoDB query that returns all Events where there is a match of any 1 {genre, subGenre} combination between the 2 arrays.
I looked into $in Query Selector in the mongoDB documentation and suspect it might need to be used... but programmatically, how do I write a query that expands to include all the values in the variable "searchGenres" in query.ts?
Thanks a lot in advance for your thoughts.
event.js: mongoDB Model for 'Events" defined using Mongoose - snippet:
let mongoose = require('mongoose');
let EventSchema = new mongoose.Schema({
genres: [
{
genre: String,
subGenre: String
}
]
)};
module.exports = mongoose.model('Event', EventSchema);
query.ts:
import mongoose = require('mongoose');
let Event = require ('../models/event');
class Genre {
genre: string;
subGenre: string;
constructor (gen: string, sub: string) {
this.genre = gen;
this.subGenre = sub;
}
}
async function runQuery()
{
let searchGenres : Array<Genre> = new Array<Genre>();
// populate searchGenres with some data here... e.g.
const searchGenre1 : Genre = new Genre ('music', 'jazz');
const searchGenre2 : Genre = new Genre ('music', 'rock');
searchGenres.push(searchGenre1);
searchGenres.push(searchGenre2);
// Query logic here:
// Return all events from the database, if the 'genres' array
// in the document matches any element in the searchGenres array
// defined above..
const events = await Event.find ({
'genres': {?help? }
});
}
```
After some self-education today, I have come up with a solution, which I am happy with - to append to end of query.ts:
type GenreQuerySelector = { genres: { $elemMatch: { 'genre': string; 'subGenre': string; }; }; };
let querySelectors : Array< GenreQuerySelector > = new Array< GenreQuerySelector >();
for (let genre of searchGenres) {
const genreQuery : GenreQuerySelector = {
genres: {
$elemMatch: {
'genre': genre.genre,
'subGenre': genre.subGenre
}
}
};
querySelectors.push(genreQuery);
};
const events = await Event.find ({ $or: querySelectors }).exec();

How to add a new key:value pair to existing sub object of a mongoDB document

I am trying to add a new key:value pair to an existing object of mongoDB document, but no steps are helping me
I tried $each, $push $addtoset but i understood those are for arrays then i tried $det but it is updating the existing key:value pair with new key:value pair
Here is my document
{
test:"abc",
test2:"cdf",
test3:{ 1:"one"}
}
if you observer test3 key in the above document i have already 1:"one" now i want to add new key value in the same object
Like
{
test:"abc",
test2:"cdf",
test3:{ 1:"one", 2:"two", 3:"three"}
}
is it possible in mongoDB?
Here is the mongo Query
let val = parseInt(DYNAMICVALUE)
var changfeemaildot = (req.session.email).replace(/\./g, '#')
var seld = {
_id: ObjectId(rx[0]._id)
};
var seldu = {
$set:{
emails: {
[changfeemaildot]: val
}
}
};
var collection =
connection.get().collection('problems');
collection.update(seld, seldu, function (err, rail) {
});
You can use $set. So your code can be something like this
db.collection.update({<your_condition>}, {$set: {"test3.2": "two", "test3.3": "three"}});
In your case, it will be something like this
var seldu = {$set: {["emails." + changfeemaildot]: val}}
You can use $set with findOneAndUpdate. So your code can be something like this
const { Types, connection } = require("mongoose");
const productList = await connection.collection('products').find({}).toArray()
productList.forEach(async function(myDoc) {
await connection.collection('products').
updateOne({ productId: Types.ObjectId(myDoc.productId) }, {
$set: {
productDisplayId: 'anything you want'
}
});
});

NodeJS MongoDB Dynamic Query

I have a NodeJS server running express with a get method at '/api/jobs/'. When called I get some querystring parameters from the url and they make a query against the MongoDB database to get jobs.
Example Url: '/api/jobs/?Groups=1,2&Statuses=3,4'
The MongoDB query i am trying to construct is this:
{ $or: [{"Group.Id" : 1}, {"Group.Id" : 2}], $or: [{"Status.Id": 3}, {"Status.Id": 4}]}
If I run this directly against the database I get the results I need but I can't think of way of constructing the query dynamically in JavaScript. Any attempt i've made gives me an object like this as the $or property can only exist once in a proper JSON object.
{$or : [{"Group.Id" : 1}, {"Group.Id" : 2}]}
Any ideas on how to do this either using JavaScript or thorugh the MongoDB Node API?
Firstly get the query properties, which will be strings:
const groupString = req.query.Groups; // === '1,2'
const statusString = req.query.Statuses; // === '3,4'
Split at the commas and parse as integers:
const groupNums = groupString.split(',').map(parseInt); // === [1, 2]
const statusNums = statusString.split(',').map(parseInt); // === [3, 4]
Then search for any of the group IDs. Your query should look like this:
const query = {"Group.Id": { $in: groupNums }, "Status.Id": { $in: statusNums } };
Do the same for the status IDs.
You can use this
app.get('/', function(req, res, next) {
var queryArry = [];
for (var i in req.query) {
var sampleObject = { value: [] };
sampleObject.name = i;
req.query[i].split(',').forEach(function(each) {
sampleObject.value.push(parseInt(each));
})
queryArry.push(sampleObject);
}
var mongoquery = {};
for (var i in queryArry) {
mongoquery[queryArry[i].name] = queryArry[i].value;
}
res.send(mongoquery);
});
But in this case, you have to send same name field of MongoDB and query parameter key. If you did this it is fully dynamic query builder.

How to replace string in all documents in Mongo

I need to replace a string in certain documents. I have googled this code, but it unfortunately does not change anything. I am not sure about the syntax on the line bellow:
pulpdb = db.getSisterDB("pulp_database");
var cursor = pulpdb.repos.find();
while (cursor.hasNext()) {
var x = cursor.next();
x['source']['url'].replace('aaa', 'bbb'); // is this correct?
db.foo.update({_id : x._id}, x);
}
I would like to add some debug prints to see what the value is, but I have no experience with MongoDB Shell. I just need to replace this:
{ "source": { "url": "http://aaa/xxx/yyy" } }
with
{ "source": { "url": "http://bbb/xxx/yyy" } }
It doesn't correct generally: if you have string http://aaa/xxx/aaa (yyy equals to aaa) you'll end up with http://bbb/xxx/bbb.
But if you ok with this, code will work.
To add debug info use print function:
var cursor = db.test.find();
while (cursor.hasNext()) {
var x = cursor.next();
print("Before: "+x['source']['url']);
x['source']['url'] = x['source']['url'].replace('aaa', 'bbb');
print("After: "+x['source']['url']);
db.test.update({_id : x._id}, x);
}
(And by the way, if you want to print out objects, there is also printjson function)
The best way to do this if you are on MongoDB 2.6 or newer is looping over the cursor object using the .forEach method and update each document usin "bulk" operations for maximum efficiency.
var bulk = db.collection.initializeOrderedBulkOp();
var count = 0;
db.collection.find().forEach(function(doc) {
print("Before: "+doc.source.url);
bulk.find({ '_id': doc._id }).update({
'$set': { 'source.url': doc.source.url.replace('aaa', 'bbb') }
})
count++;
if(count % 200 === 0) {
bulk.execute();
bulk = db.collection.initializeOrderedBulkOp();
}
// Clean up queues
if (count > 0)
bulk.execute();
From MongoDB 3.2 the Bulk() API and its associated methods are deprecated you will need to use the db.collection.bulkWrite() method.
You will need loop over the cursor, build your query dynamically and $push each operation to an array.
var operations = [];
db.collection.find().forEach(function(doc) {
print("Before: "+doc.source.url);
var operation = {
updateOne: {
filter: { '_id': doc._id },
update: {
'$set': { 'source.url': doc.source.url.replace('aaa', 'bbb') }
}
}
};
operations.push(operation);
})
operations.push({
ordered: true,
writeConcern: { w: "majority", wtimeout: 5000 }
})
db.collection.bulkWrite(operations);
Nowadays,
starting Mongo 4.2, db.collection.updateMany (alias of db.collection.update) can accept an aggregation pipeline, finally allowing the update of a field based on its own value.
starting Mongo 4.4, the new aggregation operator $replaceOne makes it very easy to replace part of a string.
// { "source" : { "url" : "http://aaa/xxx/yyy" } }
// { "source" : { "url" : "http://eee/xxx/yyy" } }
db.collection.updateMany(
{ "source.url": { $regex: /aaa/ } },
[{
$set: { "source.url": {
$replaceOne: { input: "$source.url", find: "aaa", replacement: "bbb" }
}}
}]
)
// { "source" : { "url" : "http://bbb/xxx/yyy" } }
// { "source" : { "url" : "http://eee/xxx/yyy" } }
The first part ({ "source.url": { $regex: /aaa/ } }) is the match query, filtering which documents to update (the ones containing "aaa")
The second part ($set: { "source.url": {...) is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline):
$set is a new aggregation operator (Mongo 4.2) which in this case replaces the value of a field.
The new value is computed with the new $replaceOne operator. Note how source.url is modified directly based on the its own value ($source.url).
Note that this is fully handled server side which won't allow you to perform the debug printing part of your question.
MongoDB can do string search/replace via mapreduce. Yes, you need to have a very special data structure for it -- you can't have anything in the top keys but you need to store everything under a subdocument under value. Like this:
{
"_id" : ObjectId("549dafb0a0d0ca4ed723e37f"),
"value" : {
"title" : "Top 'access denied' errors",
"parent" : "system.admin_reports",
"p" : "\u0001\u001a%"
}
}
Once you have this neatly set up you can do:
$map = new \MongoCode("function () {
this.value['p'] = this.value['p'].replace('$from', '$to');
emit(this._id, this.value);
}");
$collection = $this->mongoCollection();
// This won't be called.
$reduce = new \MongoCode("function () { }");
$collection_name = $collection->getName();
$collection->db->command([
'mapreduce' => $collection_name,
'map' => $map,
'reduce' => $reduce,
'out' => ['merge' => $collection_name],
'query' => $query,
'sort' => ['_id' => 1],
]);

MongoDB select where in array of _id?

is possible in mongo db to select collection's documents like in SQL :
SELECT * FROM collection WHERE _id IN (1,2,3,4);
or if i have a _id array i must select one by one and then recompose the array/object of results?
Easy :)
db.collection.find( { _id : { $in : [1,2,3,4] } } );
taken from: https://www.mongodb.com/docs/manual/reference/operator/query/in/#mongodb-query-op.-in
Because mongodb uses bson and for bson is important attribute types. and because _id is ObjectId you must use like this:
db.collection.find( { _id : { $in : [ObjectId('1'),ObjectId('2')] } } );
and in mongodb compass use like this:
{ "_id" : { $in : [ObjectId('1'),ObjectId('2')] } }
Note: objectId in string has 24 length.
You can try this
var ids = ["5883d387971bb840b7399130","5883d389971bb840b7399131","5883d38a971bb840b7399132"];
var oids = [];
ids.forEach(function(item){
oids.push(new ObjectId(item));
});
.find({ _id: {$in : oids}})
In this code list is the array of ids in user collection
var list = ["5883d387971bb840b7399130","5883d389971bb840b7399131","5883d38a971bb840b7399132"]
.find({ _id: {$in : list}})
if you want to find by user and also by another field like conditionally, you can easily do it like beneath with spread and ternary operator using aggregate and match
const p_id = patient_id;
let fetchingReports = await Reports.aggregate([
...(p_id
? [
{
$match: {
createdBy: mongoose.Types.ObjectId(id),
patient_id: p_id,
},
},
]
: [
{
$match: {
createdBy: mongoose.Types.ObjectId(id),
},
},

Categories