Map reduce with mongoosejs - node.js

I'm trying to get a map reduce operation to work using Nodejs, mongoose and MongoDB.
I've got a fairly flat schema structure and I want to get a list of value/date pairs per 'named' object. There is clearly something wrong with the map reduce function but I can't see how to fix it to get the format I'm after.
The MongoDB document schema is as follows:
{ "name" : "object1", "value" : "123", "date" : "2013-01-02 01:00:00" }
{ "name" : "object1", "value" : "456", "date" : "2013-01-02 02:00:00" }
{ "name" : "object2", "value" : "123", "date" : "2013-01-02 02:00:00" }
The map reduce functions I'm using in my Mongoose Schema are as follows:
var o = {};
o.map = function () {
emit(
{ 'name': this.name },
{'data': [ { 'value': this.value, 'date': this.date} ] }
)
}
o.reduce = function (k, vals) {
var reduced = {'data': []};
for (var i in vals) {
reduced.data.push(vals[i]);
}
return reduced;
}
this.mapReduce(o, function(err, model) {
if(err) console.log(err);
model.find().exec(cb);
});
What I'd like to see is some JSON along the lines of
[
{
"name": "object1",
"data": [
{
"value": "123",
"date": "2013-01-02T01:00:00.123456"
},
{
"value": "456",
"date": "2013-01-02T02:00:00.123456"
},
]
},
{
"name": "object2",
"data": [
{
"value": "123",
"date": "2013-04-22T13:10:03.893018"
},
...
]
},
...
But I end up getting this nested mess! Can someone give me a pointer on what I've missed!!
[
{
"_id": {
"name": "object1"
},
"value": {
"data": [
{
"data": [
{
"data": [
{
"data": [
{
"value": "123",
"date": "2013-01-02T02:00:00.123456"
}
]
},
{
"data": [
{
"value": "465",
"date": "2013-01-02T01:00:00.123456"
}
]
},
... etc

try something like that: (not tested but the idea is here I think :))
var o = {};
o.map = function () {
emit(this.name, { 'value': this.value, 'date': this.date} )
}
o.reduce = function (k, vals) {
var reduced = {name: k, data : []};
for (var i in vals) {
reduced.data.push(vals[i]);
}
return reduced;
// or just return vals? it's already the array you want.
}

Related

MongoDB aggregation search in objects of array [nodejs, mongoose]

I'm getting from the client side an filter object like:
{
appId: "01",
items: [ '60522e84feecf7036fa11831', '60522c47feecf7036fa1182d' ],
//offset limit
}
my query is:
await someCollection.aggregate([
{ $match: query },
{
$group: {//some fields}
},
])
.sort({date: -1})
.skip(+req.query.offset)
.limit(+req.query.limit)
collection is:
[
{
"_id": 1,
"shop": 1,
"appId": "01",
"items": [
{
"itemId": "777"
},
{
"itemId": "666"
},
]
},
{
"_id": 2,
"shop": 2,
"appId": "01",
"items": [
{
"itemId": "666"
},
{
"itemId": "123"
},
]
},
{
"_id": 3,
"shop": 2,
"appId": "01",
"items": [
{
"itemId": "x"
},
]
}
]
on my Backend query generates dynamically:
const query = {
'$expr':{
'$and':[
{'$eq': ['$appId', req.user.appId.toString()]},
]
}
}
If coming query have a products array I need to search id's in the objects array.
for example: ['777', 'x'] as result need to have 2 items where "_id": 1 and "_id": 3
my code is:
if(req.query.products) {
typeof req.query.products === 'string' ? req.query.products = [req.query.products] : req.query.products
let bb = req.query.products.map(function(el) { return mongoose.Types.ObjectId(el) })
query['$expr']['$and'].push({
$or: [{
$eq: ['$items.itemId', bb]
}],
}
}
mongoplayground
so, I need to use $in operator with $match & $and dynamically, but I have no idea how
I would try it like this:
const query = { ['$or']: [] }
for (let k of Object.keys(req.user)) {
if (Array.isArray(req.user[k])) {
for (let i in req.user[k])
query['$or'].push({ [`${k}.itemId`]: mongoose.Types.ObjectId(i) });
} else {
query[k] = req.user[k].toString();
}
}
await someCollection.aggregate([
{ $match: query },
{
$group: {//some fields}
},
])

How to get Nested data in node js

I want to get items per category in node js. how to get it. Here is the example that i want
"status": true,
"total_results": 4,
"categories": [
{
"id": 2,
"name": "Category 1",
"items":[
{
"name" :"item1"
},
{
"name": "item2"
}
]
},
{
"id": 4,
"name": "Category 2",
"items":[
{
"name" :"item1"
},
{
"name": "item2"
}
]
}
]
}
Here is the code i write for getting above array result in nodejs
pool.query(`Select * FROM categories WHERE catalog_id = '${fields.catalog_id}'`, async (error, result, field) => {
if (error) { // if error
console.log(error);
return false;
}
var Catarr = result.map((item,index)=>item.id)
pool.query(`Select * FROM items WHERE category_id IN (${Catarr})`, async (error, menuItems, field) => {
if (error) { // if error
console.log(error);
return false;
}
var parsed_items = [];
await result.forEach((item,index)=>{
var items = menuItems.filter((p_item)=>p_item.category_id == item.id)
var obj = {
name: item.name,
id: item.id,
items
}
parsed_items.push(obj)
})
res.status(200).json({status:true,total_results:result.length,categories:parsed_items});
});
});
Sharing my suggestion as per my understanding; Please refer to below code snippet:
const data={ "status": true,
"total_results": 4,
"categories": [
{
"id": 2,
"name": "Category 1",
"items":[
{
"name" :"item1"
},
{
"name": "item2"
}
]
},
{
"id": 4,
"name": "Category 2",
"items":[
{
"name" :"item1"
},
{
"name": "item2"
}
]
}
]
};
const expectedResult=data.categories.reduce((iter, item)=>{
iter[item.name]=item.items? item.items.length : 0; // we can use item.id if required
return iter;
}, {});
alert(JSON.stringify(expectedResult));
console.log('Items per category - ',expectedResult);

MongoDB push into nested array

I have this scheme
{
"_id": {
"$oid": "5e187b1791c51b4b105fcff0"
},
"username": "test",
"email": "test#test.com",
"role": "admin",
"userScoreFantasy": {
"tournaments": [
{
"tournament_id": {
"$oid": "5e1892fb480f344830a3f160"
},
"predictions": [],
"score": null
},
{
"tournament_id": {
"$oid": "5e189f5f8d88292754e10b37"
},
"predictions": [],
"score": null
}
],
"totalScore": null
},
}
I want to do this :
Find user with a predefined user id
Pass all userScoreFantasy.tournaments array to find a specific tournament id
Push into the found tournament predictions array an object like this one :
{
score,
"match_id": foundMatch._id
}
So the tournaments array will become :
[
{
"tournament_id": {
"$oid": "5e1892fb480f344830a3f160"
},
"predictions": [
"score" : 200,
"match_id": "5e1892fb480f34faw21410"
],
"score": null
},
]
I tried to do this :
Users.update({
"_id": prediction.user_id,
"userScoreFantasy.tournaments": {
"$elemMatch": {
"tournament_id": foundMatch.tournament_id
}
}
}, {
"$push": {
"userScoreFantasy.tournaments.$.predictions": {
score,
"match_id": foundMatch._id
}
}
})
But the array is not updating.
EDIT :
Working call :
Users.updateOne({
"_id": ObjectID(prediction.user_id),
}, {
$addToSet: {
"userScoreFantasy.tournaments.$[element].predictions": {
score,
"match_id": foundMatch._id
}
}
}, {
arrayFilters: [{
"element.tournament_id": ObjectID(foundMatch.tournament_id)
}]
}
)
You should use the position indentifier to update your arrays, like so:
Users.updateOne(
{
"_id": prediction.user_id,
},
{
$addToSet: {
"userScoreFantasy.tournaments.$[element].predictions": {
score,
"match_id": foundMatch._id
}
}
},
{arrayFilters: [{"element.tournament_id": foundMatch.tournament_id}]}
);

Pulling elements from mongodb array

I have a question for which I've wasted more time than I should have and I don't seem to get what I'm doing wrong.
I have the below document in MongoDB:
{
"personal": {
...
},
"preferences": {
....
},
"_id": "5b2efdad564191054807c2b1",
"pets": [],
"conversations": [
{
"unread": 1,
"participants": [
{
"_id": "5b2efdcd564191054807c2b2",
"name": "Mighty Jules"
}
],
"messages": [
{
"sender": "self",
"timestamp": "2018-06-24T12:29:50.656Z",
"_id": "5b2f8ebede342a12a8dcc9d2",
"text": "..."
},
{
"sender": "self",
"timestamp": "2018-06-24T12:29:58.022Z",
"_id": "5b2f8ec6de342a12a8dcc9d8",
"text": "..."
},
{
"sender": "5b2efdcd564191054807c2b2",
"timestamp": "2018-06-24T12:30:27.562Z",
"_id": "5b2f8ee3de342a12a8dcc9e5",
"text": "..."
},
{
"sender": "self",
"timestamp": "2018-06-24T12:32:48.034Z",
"_id": "5b2f8f70d3a83e25bc1abbb2",
"text": "..."
},
{
"sender": "self",
"timestamp": "2018-06-24T12:36:20.027Z",
"_id": "5b2f9044d4137828283c5a60",
"text": "..."
},
{
"sender": "5b2efdcd564191054807c2b2",
"timestamp": "2018-06-24T12:37:39.965Z",
"_id": "5b2f90939b4b2a4af8cf50db",
"text": "..."
}
],
"last_message": "2018-06-24T12:37:39.965Z",
"_id": "5b2efdcd564191054807c2b2"
},
{
"unread": 1,
"participants": [
{
"_id": "5b300ff657957c1aa0ed0576",
"name": "Super Frank"
}
],
"messages": [
{
"sender": "5b300ff657957c1aa0ed0576",
"timestamp": "2018-06-24T21:42:49.392Z",
"_id": "5b30105957957c1aa0ed0583",
"text": "..."
}
],
"last_message": "2018-06-24T21:42:49.392Z",
"_id": "5b300ff657957c1aa0ed0576"
}
],
"created_date": "2018-06-24T02:10:53.314Z",
"lastLogin_date": "2018-06-24T02:10:53.314Z",
"lastUpdate_date": "2018-06-25T02:09:53.281Z",
"__v": 0
}
I am trying to delete just a couple of messages using mongoose:
const user = await User.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId("5b2efdad564191054807c2b1"), //Which is the one that doc
"conversations._id": mongoose.Types.ObjectId("5b2efdcd564191054807c2b2")
},
{
$pull: {
"conversations.$.messages": {
$in: [
{ _id: mongoose.Types.ObjectId("5b2f9044d4137828283c5a60") },
{ _id: mongoose.Types.ObjectId("5b2f90939b4b2a4af8cf50db") }
]
}
}
},
{
new: true,
projection: {
conversations: 1
}
}
);
In the response I get the same, nothing gets deleted, I get no errors.
First of all, _ids in the example document are strings, not ObjectId.
Secondly, the $pull syntax is wrong. Please read https://docs.mongodb.com/manual/reference/operator/update/pull/#remove-items-from-an-array-of-documents. It should be:
{
$pull: {
"conversations.$.messages": {
"_id": {
"$in": ["5b2f9044d4137828283c5a60", "5b2f90939b4b2a4af8cf50db"]
}
}
}
}
It will pull messages from the first matching conversation, if it is what you want. If you want to remove messages from all matching conversations, you need to use $[] instead: "conversations.$[].messages"
function exists(Arr, objId){
for(var i = 0; i < Arr.length; i++)
if(objId.equals(Arr[i]))
return true
return false
}
var user = await User.findOne(
{_id: mongoose.Types.ObjectId("5b2efdad564191054807c2b1")}
)
for(var j = 0; j<user.conversations.length; j++)
if(exists([conId],user.conversations[j]._id)
break
if(j<user.conversations.length)
for(var i =0; i < user.conversations[j].messages.length; i++)
if(exists(Arr, user.conversations.messages[i]._id))
delete user.conversations.messages[i]
user.save()
Edit: using only db operations
If you want to use $in then you will have to pass the whole sub array.
await User.update({_id : "5b2efdad564191054807c2b1", "conversations._id" : "5b2efdcd564191054807c2b2" }, { $pull: { "conversations.$.messages":{ $in : [{"sender" : "self", "timestamp" : "2018-06-24T12:32:48.034Z","_id" : "5b2f8f70d3a83e25bc1abbb2","text" : "..."}] } } }, {multi : true} )
something like that as $in compares the whole document to each element in the array.
In your case it does not find a match but still it succeeded in the process and hence no error.
if you want to do only by id you will have to loop through for each of your ids as follows
for(var i = 0; i < ids.length; i++)
await User.update({_id : "5b2efdad564191054807c2b1", "conversations._id" : "5b2efdcd564191054807c2b2" }, { $pull: { "conversations.$.messages": {_id : ids[i] } } }, {multi : true} )
or you know the _id field so use in to search for $id
await User.update({_id : "5b2efdad564191054807c2b1", "conversations._id" : "5b2efdcd564191054807c2b2" }, { $pull: { "conversations.$.messages":{ "_id" : { $in : ids } } } }, {multi : true} )

Add response to each item for loop in node.js and postgres

I have an array of schools like this:
{
"schools": [
{
"name": "S1",
"id": 1
},
{
"name": "S2",
"id": 2
},
{
"name": "S3",
"id": 3
}
]
}
and each school has schedule. To Get that I iterate the schools array in a promise and when I get the response I get an array like this
{
"schools": [
{
"name": "S1",
"id": 1
},
{
"name": "S2",
"id": 2
},
{
"name": "S3",
"id": 3
}
],
"schedules": [
[],
[
{
"id_schedule": 58,
"hour1": "13:00:00",
"hour2": "20:00:00",
"id_schools_schedule": 2
}
],
[
{
"id_schedule": 59,
"hour1": "06:30:00",
"hour2": "22:30:00",
"id_schools_schedule": 3
}
]
]
}
I want to know. how to asign the response of each item?
this is my code
for (var i =0; i < datosRes.schools.length; i++){
array_horarios.push(ObtSchedule(datosRes.schools, i))
}
Promise.all(array_horarios).then(response => {
datosRes.horarios = response;
eq.local = data;
}).catch(err => {
return res.json(200,{"datos":datosRes});
})
function ObtHorario(schools, i){
return new Promise(function(resolve, reject){
var id_school = schools[i].id;
Mod_Schedule.obtSchedule(id_school,function(error, data){
if(error || data.error){
errorDB = {"error_log": error, "error_data": data.error};
reject(errorDB)
}else{
resolve(data)
}
})
})
}
What I am doing wrong?
I get the response but only I want to add to each item of schools the schedules
Thanks in advance
First thing first:
You can send ONE response to ONE request. So your question to send multiple responses is invalid.
Here's what you can do, you can get the array of schools with their schedules.
If you are using MongoDB, here's what you can do:
Using Aggregate query:
db.schools.aggregate([
{
$match: {} // Your condition to match schools here
},
{
$lookup: {
from:"schedules",
localField: id,
foreignField: id_schools_schedule,
as: "schedulesData"
}
},
]);
Here, you will get data something like:
[
{
"name": "S1",
"id": 1,
"schedulesData": []
},
{
"name": "S2",
"id": 2,
"schedulesData": [{
"id_schedule": 58,
"hour1": "13:00:00",
"hour2": "20:00:00",
"id_schools_schedule": 2
}]
},
{
"name": "S3",
"id": 3,
"schedulesData": [
{
"id_schedule": 59,
"hour1": "06:30:00",
"hour2": "22:30:00",
"id_schools_schedule": 3
}
]
}
]

Resources