collection.find on an item in a subarray - node.js

I have the following Object Structure:
[
{
name: "someThing"
,securities: [ {"id": "2241926"} ]
}
]
I want to be able to return all objects in the outer array, that has at least one child secuirty with an id that starts with a value. I have tried a few things and keep running up short. On the mongoo console, this query works:
db.caseSummary.find({"securities.id": /^224.*/i})
We are using ES6, so please apologies for the generator syntax:
const q = require("q");
const startsWith = function(term){
return new RegExp('^' + term + '.*', 'i')
}
const find = function*(collection, criteria){
const command = q.nbind(collection.find, collection),
myCriteria = criteria || {},
results = yield command(myCriteria),
toArray = q.nbind(results.toArray, results) ;
return yield toArray()
}
const searchBySecurity = function*(mongo, term) {
const collection = mongo.collection("caseSummary"),
criteria = {"securities.id": startsWith(term) };
return yield find(collection, criteria);
}
so searchBySecurity(this.mongo, '224') It returns an empty array, it should return the same results as the mongo console. I guess I am missing a pretty basic concept in translating my search criteria or invoking the find writing this in node from raw mongo console query.
Edit 1: to be clear I want to return all parent objects, which have subarrays that contain a value that starts with the term passed in...
Edit 2:
I changed the criteria to be:
criteria = { "securities": {
"$elemMatch": {
"id": "901774109" //common.startsWith(term)
}
}
};
Still the same results.
Edit 3:
Using nodejs - mongodb "version": "1.4.38"
Edit 4:
this ended up not being an issue

Related

Create array and randomize node.js array

I'm trying to write a cisco webex bot which get all people in the space(room) and randomly write only one name.
I have this code
framework.hears("daily host", function (bot) {
console.log("Choosing a daily host");
responded = true;
// Use the webex SDK to get the list of users in this space
bot.webex.memberships.list({roomId: bot.room.id})
.then((memberships) => {
for (const member of memberships.items) {
if (member.personId === bot.person.id) {
// Skip myself!
continue;
}
let names = (member.personDisplayName) ? member.personDisplayName : member.personEmail;
let arrays = names.split('\n');
var array = arrays[Math.floor(Math.random()*items.length)];
console.log(array)
bot.say(`Hello ${array}`);
}
})
.catch((e) => {
console.error(`Call to sdk.memberships.get() failed: ${e.messages}`);
bot.say('Hello everybody!');
});
});
But this doesn't work.
Also name after i use let arrays = names.split('\n'); separated by space and don't have comma.
I think because of what code doesn't work
Output of console log:
[ 'George Washington' ]
[ 'John' ]
[ 'William Howard Taft' ]
Main question now how to turn output to array?
That's because arrays[Math.floor(Math.random()*items.length)] only assigns an array with length 3. You need to randomise the index and push to array or use a sort function on the original array
var array = arrays.sort((a,b)=>{
return Math.floor(Math.random()*arrays.length);
});
if you are looking to get the output as per you question you can use reduce instead of sort.
var arrays = [ 'George Washington', 'John', 'William Howard Taft'];
var array = arrays.reduce((a,i)=>{
if(!a) a = [];
a.splice(Math.floor(Math.random()*arrays.length), 0, [i]);
return a;
},[]);
Here is how to get a single name from your data, and ensuring it is a string. There are only four names in the array, so run the snippet several times if you keep getting the same name.
// A list of names. Notice that Arraymond is an array; the other names are strings.
const names = [ 'George Washington', 'John', 'William Howard Taft', ['Arraymond'] ];
// Randomize the names
const randomNames = names.sort(() => Math.random() - 0.5);
// Get the first name. Make sure the name is a string (not an array)
const name = randomNames[0].toString();
console.log(name)
A tip: don't name your array "array" or "arrays" - it is not meaningful. Use good naming conventions and meaningful variable names that help others understand what the code is doing.

Make a mongoDB query using OR statement and array

I have web interface need to access an URL (shown below) and return only results with requested groups:
http://localhost:3000/device/getgroups/group1,group2,group3,group4
In MongoDB I have entries with the field 'loc_group'.
I want to return the results only where have the field "loc_group" = group1 OR group2 OR group3 OR group4.
My routes are like this:
router.get('/getgroups/:groups', async (req, res) => {
try {
var groups = "req.parms.groups";
#split groups
var arr = groups.split(",");
#iterate in the results on array (arr)
for (var i = 0; i < arr.length; i++) {
arr[i] = ++arr[i];
if (i = arr.lenght) {
var query = "{'loc_group': '" + arr[i] + "' }"
var totals = totals + query;
} else {
var query = "{'loc_group': '" + arr[i] + "' },"
var totals = totals + query;
}
}
# after this for, the totals must looks like
# "{'loc_group': 'group1' }"
# "{'loc_group': 'group2' }"
# "{'loc_group': 'group3' }"
# "{'loc_group': 'group4' }"
#
# then i try to use it on the mongodb query:
const logues = await device.find({ $or: [ totals ], })
res.json(logues)
} catch (err) {
res.status(500).json({ message: err.message })
}
})
but it's not working, what I get when enter the URL is:
{"message":"$or/$and/$nor entries need to be full objects"}
I am a beginner to nodejs and this is my first question here, if need some other information or something please let me know. Thanks.
There are several issues with your code. But the main one is basically ignoring the expectations of API you work with. See, MongoDB expects regular JavaScript objects passed in $or query. What you give it instead is a concatenation of stringified objects, each created by this expression:
var query = "{'loc_group': '" + arr[i] + "' }"
... passed into regular array. Apparently, MongoDB couldn't guess what you wanted, hence $or/$and/$nor entries need to be full objects error.
So, instead of doing JS work on your own, just use native objects to create a query. In fact, you don't even need $or here, as you're looking for the same property's values. Quoting the docs:
When using $or with that are equality checks for the
value of the same field, use the $in operator instead of the $or
operator.
So here's one (simplified) way to do it:
router.get('/getgroups/:groups', async (req, res) => {
const locGroups = req.params.groups.split(',');
const logues = await device.find({ loc_group: { $in: locGroups } });
res.json(logues);
}
What? Yes, that's essentially it. You might consider adding some validation and error handling to this code, and may be some logging, too, but it should be a middleware concern anyway, so the controller stays THAT simple. And that's the beauty - and the reason of existence - of the modern backend-oriented ecosystems: easy things should stay easy.

How to pull a range of objects from an array of objects and re-insert them at a new position in the array?

Desired Behaviour
Pull a range of objects from an array of objects and push them back to the array at a new index.
For example, pull objects from the array where their index is between 0 and 2, and push them back to the array at position 6.
For reference, in jQuery, the desired behaviour can be achieved with:
if (before_or_after === "before") {
$("li").eq(new_position).before($("li").slice(range_start, range_end + 1));
} else if (before_or_after === "after") {
$("li").eq(new_position).after($("li").slice(range_start, range_end + 1));
}
jsFiddle demonstration
Schema
{
"_id": ObjectId("*********"),
"title": "title text",
"description": "description text",
"statements": [
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
}]
}
What I've Tried
I am able to reposition a single object in an array of objects with the code below.
It uses pull to remove the object from the array and push to add it back to the array at a new position.
In order to do the same for a range of objects, I think I just need to modify the $pull and $push variables but:
I can't figure out how to use $slice in this context, either as a projection or an aggregation, in a $pull operation
Because I can't figure out the first bit, I don't know how to attempt the second bit - the $push operation
// define the topic_id to search for
var topic_id = request_body.topic_id;
// make it usable as a search query
var o_id = new ObjectID(topic_id);
// define the statement_id to search for
var statement_id = request_body.statement_id;
// define new position
var new_position = Number(request_body.new_position);
// define old position
var old_position = Number(request_body.old_position);
// define before or after (this will be relevant later)
// var before_or_after = request_body.before_or_after;
// define the filter
var filter = { _id: o_id };
// define the pull update - to remove the object from the array of objects
var pull_update = {
$pull: {
statements: { id: statement_id } // <----- how do i pull a range of objects here
}
};
// define the projection so that only the 'statements' array is returned
var options = { projection: { statements: 1 } };
try {
// perform the pull update
var topic = await collection.findOneAndUpdate(filter, pull_update, options);
// get the returned statement object so that it can be inserted at the desired index
var returned_statement = topic.value.statements[old_position];
// define the push update - to add the object back to the array at the desired position
var push_update = {
$push: {
statements: {
$each: [returned_statement],
$position: new_position
}
} // <----- how do i push the range of objects back into the array here
};
// perform the push update
var topic = await collection.findOneAndUpdate(filter, push_update);
}
Environments
##### local
$ mongod --version
db version v4.0.3
$ npm view mongodb version
3.5.9
$ node -v
v10.16.3
$ systeminfo
OS Name: Microsoft Windows 10 Home
OS Version: 10.0.18363 N/A Build 18363
##### production
$ mongod --version
db version v3.6.3
$ npm view mongodb version
3.5.9
$ node -v
v8.11.4
RedHat OpenShift Online, Linux
Edit
Gradually, figuring out parts of the problem, I think:
Using the example here, the following returns objects from array with index 0 - 2 (ie 3 objects):
db.topics.aggregate([
{ "$match": { "_id": ObjectId("********") } },
{ "$project": { "statements": { "$slice": ["$statements", 0, 3] }, _id: 0 } }
])
Not sure how to use that in a pull yet...
I also looked into using $in (even though i would prefer to just grab a range of objects than have to specify each object's id), but realised it does not preserve the order of the array values provided in the results returned:
Does MongoDB's $in clause guarantee order
Here is one solution to re-ordering results from $in in Node:
https://stackoverflow.com/a/34751295
Here an example with mongo 3.5
const mongo = require('mongodb')
;(async function (params) {
const client = await mongo.connect('mongodb://localhost:27017')
const coll = client.db('test').collection('test')
const from0to99 = Array(100).fill('0').map((_, i) => String(i))
const from5To28 = Array(24).fill('0').map((_, i) => String(i + 5))
const insert = { statements: from0to99.map(_ => ({ id: _ })) }
await coll.insertOne(insert)
const all100ElementsRead = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$pull: {
statements: {
id: { $in: from5To28 }
}
}
},
{ returnOriginal: true }
)
/**
* It shows the object with the desired _id BEFORE doing the $pull
* You can process all the old elements as you wish
*/
console.log(all100ElementsRead.value.statements)
// I use the object read from the database to push back
// since I know the $in condition, I must filter the array returned
const pushBack = all100ElementsRead.value.statements.filter(_ => from5To28.includes(_.id))
// push back the 5-28 range at position 72
const pushed = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$push: {
statements: {
$each: pushBack,
$position: 72 // 0-indexed
}
}
},
{ returnOriginal: false }
)
console.log(pushed.value.statements) // show all the 100 elements
client.close()
})()
This old issue helped
if you want "desired behavior" when mutating arrays ,
you add these to checklist:
array.length atleast==7 if you want to add ,splice at 6
creates a new array if u use concat
mutates orignal if used array.push or splice or a[a.length]='apple'
USE slice() to select between incex1 to index2.
or run a native for loop to select few elements of array or
apply a array.filter() finction.
once you select your elements which needed to be manupulated you mentioned you want to add it to end. so this is the method below.
about adding elements at end:
CONCAT EXAMPLE
const original = ['🦊']; //const does not mean its immutable just that it cant be reassigned
let newArray;
newArray = original.concat('🦄');
newArray = [...original, '🦄'];
// Result
newArray; // ['🦊', '🦄']
original; // ['🦊']
SPLICE EXAMPLE:
const zoo = ['🦊', '🐮'];
zoo.splice(
zoo.length, // We want add at the END of our array
0, // We do NOT want to remove any item
'🐧', '🐦', '🐤', // These are the items we want to add
);
console.log(zoo); // ['🦊', '🐮', '🐧', '🐦', '🐤']

How to search data in mongodb with dynamic fields using mongoose?

I've a node.js api in which user sends the required fields as an array to be fetched from the mongodb database. I need to find the data of that fields using Find query. I've written forEach statement to loop through that array and got the array elements. But when I try to get the results by inserting the array elements in the query, it doesn't giving the required results. Could any one please help me in resolving the issue by seeing the code below?
templateLevelGraphData: async function(tid,payload){
let err, templateData, respData = [], test, currentValue;
[err,templateData] = await to(Template.findById(tid));
var templateId = templateData.templateId;
payload.variables.forEach(async data=>{
console.log(data); //data has the array elements like variables=["humidity"]
[err, currentValue] = await to(mongoose.connection.db.collection(templateId).find({},{data:1}).sort({"entryDayTime":-1}).limit(1).toArray());
console.log(currentValue);
});
return "success";
}
The expected output is,
[ { humidity: 36 } ]
But I'm getting only _id like,
[ { _id: 5dce3a2df89ab63ee4d95495 } ]
I think data is not applying in the query. But I'm printing the data in the console where it's giving the correct results by displaying the array elements like, humidity. What I need to do to make it work?
When you are passing {data: 1} you are passing an array where is expecting name of column.
You have to create an object where the keys are going to be the elements of the array and set them to 1.
const projection = data.reduce((a,b) => (a[b]=1, a), {});
[...] .find({}, projection) [...]
Actually I got the solution.
for(let i=0;i<payload.variables.length;i++){
var test = '{"'+ payload.variables[i] +'":1,"_id":0}';
var query = JSON.parse(test);
[err, currentValue] = await to(mongoose.connection.db.collection(templateId).find({"deviceId":deviceId},query).sort({"entryDayTime":-1}).limit(1).toArray());
console.log(currentValue); //It's giving the solution
}

Mongoose get a random element except one

I have a collection of articles in mongodb. I choose an article that i want to render, and I want two other articles chosen randomly. I want to pick two articles in my collection that are not the same, and are not the article I have chosen before.
Been on this problem for hours, search for a solution but only found how to pick an element randomly, but not except one...
Here is what I have now :
article.find({}, function(err, articles{
var articleChosen = articles.filter(selectArticleUrl, articleUrl)[0];
article.find({})
.lean()
.distinct("_id")
.exec(function(err, arrayIds){
var articleChosenIndex = arrayIds.indexOf(articleChosen._id);
arrayIds.splice(articleChosenIndex, 1);
chooseRdmArticle(arrayIds, function(articleRdm1Id){
var articleRmd1 = articles.filter(selectArticleId, articleRdm1Id)[0];
var articleRdm1Index = arrayIds.indexOf(articleRdm1Id);
arrayIds.splice(articleRdm1Index, 1);
chooseRdmArticle(arrayIds, function(articleRdm2Id){
var articleRmd2 = articles.filter(selectArticleId, articleRdm2Id)[0];
// do stuff with articleChosen, articleRmd1 and articleRmd2
})
})
})
})
where the function which choose rdm article is :
function chooseRdmArticle(articles, callback){
var min = Math.ceil(0);
var max = Math.floor(articles.length);
var rdm = Math.floor(Math.random() * (max - min)) + min;
callback(articles[rdm])
}
and the function which select the article from its url is :
function selectArticleUrl(element){
return element.url == this
}
My idea was to work on the array containing all the ObjectId (arrayIds here), to choose two Ids randomly after removing the articleChosen id. But I understood that arrayIds.indexOf(articleRdm1Id); couldn't work because ObjectIds are not strings ... Is there a method to find the index of the Id I want? Or any better idea ?
Thanks a lot !
Run two queries where the first fetches the chosen document and the other uses the aggregation framework to run a pipeline with the $sample operator to return 2 random documents from the collection except the chosen one.
The following query uses Mongoose's built-in Promises to demonstrate this:
let chosenArticle = article.find({ "url": articleUrl }).exec();
let randomArticles = article.aggregate([
{ "$match": { "url": { "$ne": articleUrl } } },
{ "$sample": { "size": 2 } }
]).exec();
Promise.all([chosenArticle, randomArticles]).then(articles => {
console.log(articles);
});
There is the mongodb command $sample, which is gonna read documents in a random way.
Example from the documentation :
db.users.aggregate( [ { $sample: { size: 3 } } ] )
I had the same problem and this works for me
const suggestedArticles = await Article.find({
articleId: { $ne: req.params.articleId },
}).limit(2);

Resources