Can not fetching the exact value with mongoose - node.js

I want to fetch data from mongodb using mongoose and send it as a response, but I don't get the exact answer, what's my mistake?
My codes are as below:
Firstly My model file:
* I'm inserting data in bulk with create()
const express = require('express');
const mongoose= require('mongoose');
const Schema = mongoose.Schema;
const ourDataSchema = new Schema ({
rank : Number,
totalPoints : Number
});
const rankTotalpoint = mongoose.model("rankTotalpoint", ourDataSchema);
const ourData = [
{rank : 1, totalPoints : 2000},
{rank : 2, totalPoints : 1980},
{rank: 3, totalPoints : 1940},
{rank: 4, totalPoints : 1890},
{rank : 5, totalPoints : 1830},
{rank : 6, totalPoints : 1765},
{rank : 7, totalPoints : 1600},
{rank : 8, totalPoints : 1565},
{rank : 9, totalPoints : 1465},
{rank : 10, totalPoints : 1450}
];
rankTotalpoint.create(ourData, function (error, data) {
if (error) {
console.log(error)
}
else {
console.log('saved!');
}
});
exports.result = function (param) {
const finalresult = rankTotalpoint.aggregate([
{
$project: {
diff: {
$abs: {
$subtract: [
param, // <<<----------------------- THIS IS THE USER SUPPLIED VALUE
"$totalPoints"
]
}
},
doc: "$$ROOT"
}
},
{
$sort: {
diff: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
rank: "$doc.rank"
}
}
])
return finalresult;
};
And my controller file codes where I imported my above(result) function to it :
const express = require('express');
const model = require('../model/logic');
exports.index = (req, res, next) => {
res.status(200).json({message : 'INSERT INPUTS HERE'});
};
exports.getUserData = (req, res, next) => {
const literature = req.body.literature * 4;
const arabic = req.body.arabic * 2;
const religion = req.body.religion * 3;
const english = req.body.english * 2;
const math = req.body.math * 4;
const physics = req.body.physics * 3;
const chemistry = req.body.chemistry *2;
//user supplied value
const TOTALPOINT = literature + arabic + religion + english + math + physics + chemistry;
let result = model.result(TOTALPOINT);
res.status(200).json(result);
};
And finally that's the response I get with postman :
{
"_pipeline": [
{
"$project": {
"diff": {
"$abs": {
"$subtract": [
0,
"$totalPoints"
]
}
},
"doc": "$$ROOT"
}
},
{
"$sort": {
"diff": 1
}
},
{
"$limit": 1
},
{
"$project": {
"_id": 0,
"rank": "$doc.rank"
}
}
],
"options": {}
}
What I want to get?
I want to get a rank based on the user input(TOTALPOINT) that I'm getting, So instead of sending the above response, I just want to send back the rank to the user.
If the user value matches a totalpoints, send it's rank as a response and if the exact value doesn't exist, find the closest totalPoints and send the rank as response.
Like this:
[
{
"rank": 5
}
]
Thank you

Your issue is because Mongoose is promise/async based. You are not awaiting anything, so your code returns a variable that has not been set yet by your query..
I was testing using 2 files: myMongoose.js and index.js..
// myMongoose.js
// ** CODE THAT SAVES DATA TO DATABASE HAS BEEN REMOVED FOR BREVITY **
require('dotenv').config();
const mongoose = require('mongoose');
const RankTotalpointSchema = new mongoose.Schema({
rank: Number,
totalPoints: Number
});
mongoose.set('useCreateIndex', true);
const mongoConnection = mongoose.createConnection(process.env.MONGO_DB_STRING, {
useUnifiedTopology: true,
useNewUrlParser: true,
useFindAndModify: false,
});
const RankTotalpoint = mongoConnection.model("RankTotalpoint", RankTotalpointSchema, 'Testing');
/**
* ~~~~~~ **** THIS HAS TO BE AN ASYNC FUNCTION **** ~~~~~~
*/
exports.result = async function (param) {
const finalresult = await RankTotalpoint.aggregate([{
$project: {
diff: {
$abs: {
$subtract: [
param, // <<<----------------------- THIS IS THE USER SUPPLIED VALUE
"$totalPoints"
]
}
},
doc: "$$ROOT"
}
},
{
$sort: {
diff: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
rank: "$doc.rank"
}
}
])
return finalresult;
};
...and then in index.js:
// index.js
const { result } = require('./myMongoose');
// Use it like this:
async function init() {
try {
const d = await result(1800);
console.log(d);
} catch (err) {
console.error(err);
}
}
init(); // -> [ { rank: 5 } ]
// --------------------------------------------------------------------
// ...or like this:
(async () => {
try {
const d = await result(1800);
console.log(d); // -> [ { rank: 5 } ]
} catch (err) {
console.error(err);
}
})()
// --------------------------------------------------------------------
// ...or like this:
result(1800)
.then(d => console.log(d)) // -> [ { rank: 5 } ]
.catch(err => console.error(err))

Related

how to update an object of an element in array in mongodb?

This is the structure i have, i want to update the nested array element if an object key matches for example - i want to match grnno :"10431000" and update the other keys of that object like vehicle_no,invoice_no etc.
{
"_id" : ObjectId("5f128b8aeb27bb63057e3887"),
"requirements" : [
{
"grns" : [
{
"invoice_no" : "123",
"vehicle_no" : "345",
"req_id" : "5f128c6deb27bb63057e388a",
"grnno" : "10431000"
},
{
"invoice_no" : "abc",
"vehicle_no" : "def",
"req_id" : "5f128c6deb27bb63057e388a",
"grnno" : "10431001"
}
]
}
]
}
I have tried this code
db.po_grn.update({
"requirements.grns.grnno":"10431001"
}, {
$set: {
"requirements.$.grns": {"invoice_no":"test",vehicle_no:"5455"}
}
})
But this is changing the structure i have like this
"requirements" : [
{
"grns" : {
"invoice_no" : "test",
"vehicle_no":"5455"
},
"req_id" : ObjectId("5f128b8aeb27bb63057e3886")
}
],
grns key should be array, and update should be of the particular object which matches the key "grnno". Please help me out. Thanks.
==Edit==
var grnno = req.body.grnno;
db.po_grn.find({
"requirements.grns.grnno":grnno
}).toArray(function(err, po_grn) {
console.log("po_grn",po_grn);
if (po_grn.length > 0) {
console.log("data.grn.grnno ", grnno);
var query = {
requirements: {
$elemMatch: {
"grns.grnno": grnno
}
}
};
var update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': data.invoice_no,
'requirements.$[].grns.$[inner].vehicle_no': data.vehicle_no,
}
};
var options = {
arrayFilters: [
{ "inner.grnno" : grnno }
]
};
db.po_grn.update(query, update, options
, function(er, grn) {
console.log("grn",grn,"er",er)
res.send({
status: 1,
message: "Grn updated successfully"
});
}
);
} else {
res.send({
status: 0,
message: "Grn not found "
});
}
})
Use a combination of $[] positional-all operator with array filters to update your inner nested document.
var query = {
requirements: {
$elemMatch: {
"grns.grnno": "10431001"
}
}
};
var update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': "test",
'requirements.$[].grns.$[inner].vehicle_no': "5455",
}
};
var options = {
arrayFilters: [
{ "inner.grnno" : "10431001" }
]
};
db.collection.update(query, update, options);
Update -
NodeJS native MongoDb driver code attached, which is working fine
const { MongoClient } = require('mongodb');
const url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) {
throw err;
}
const dbo = db.db("test");
(async() => {
const query = {
requirements: {
$elemMatch: {
"grns.grnno": "10431001"
}
}
};
const update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': "test",
'requirements.$[].grns.$[inner].vehicle_no': "5455",
}
};
const options = {
arrayFilters: [
{ "inner.grnno" : "10431001" }
],
multi: true
};
try {
const updateResult = await dbo.collection("collection").update(query, update, options);
} catch (err) {
console.error(err);
}
db.close();
})();
});

fetching data from mongodb and find closest value

My code look like this:
const express = require('express');
const mongoose= require('mongoose');
const Schema = mongoose.Schema;
const ourDataSchema = new Schema ({
rank : Number,
totalPoints : Number
});
const rankTotalpoint = mongoose.model("rankTotalpoint", ourDataSchema);
const ourData = [
{rank :1, totalPoints : 2000},
{rank :2, totalPoints : 1980},
{rank :3, totalPoints : 1940},
{rank :4, totalPoints : 1890},
{rank :5, totalPoints : 1830},
{rank :6, totalPoints : 1765}
];
rankTotalpoint.create(ourData, function (error) {
console.log('saved!');
if (error) {
console.log(error)
}
});
...
I have 2 questions:
How can I fetch a rank by it's totalPoint?
I tried find() but it didn't work, I guess I'm using it in a wrong way.
I'm getting an input(Number) from user, I want to match the number with the totalPoint(if exists) that we fetched from our database and return it's rank, and if the exact number didn't exist, I want to match it with the closest totalPoint in our database and return the rank as a response.
Please at least answer my first question!
Highly appreciate your answers guys,
I'm STUCK!
These are my controller file codes where I'm getting the user input and passed it to the code that you send me which I put them in my model files.
const express = require('express');
const model = require('../model/logic');
exports.index = (req, res, next) => {
res.status(200).json({message : 'INSERT INPUTS HERE'});
};
exports.getUserData = (req, res, next) => {
const literature = req.body.literature * 4;
const arabic = req.body.arabic * 2;
const religion = req.body.religion * 3;
const english = req.body.english * 2;
const math = req.body.math * 4;
const physics = req.body.physics * 3;
const chemistry = req.body.chemistry *2;
const TOTALPOINT = literature + arabic + religion + english + math + physics + chemistry;
let result = model.result(TOTALPOINT);
res.status(200).json(result);
};
And this is my model/logic file which I imported them into controller above:
const express = require('express');
const mongoose= require('mongoose');
const Schema = mongoose.Schema;
const ourDataSchema = new Schema ({
rank : Number,
totalPoints : Number
});
const rankTotalpoint = mongoose.model("rankTotalpointData", ourDataSchema);
const ourData = [
{rank : 1, totalPoints : 2000},
{rank : 2, totalPoints : 1980},
{rank: 3, totalPoints : 1940},
{rank:4, totalPoints : 1890},
{rank :5, totalPoints : 1830},
{rank : 6, totalPoints : 1765},
{rank : 7, totalPoints : 1600}
];
rankTotalpoint.create(ourData, function (error, data) {
if (error) {
console.log(error)
}
else {
console.log('saved!');
}
});
exports.result = function (param) {
const finalResult = rankTotalpoint.aggregate([
{
$project: {
diff: {
$abs: {
$subtract: [
param, // <<<----------------------- THIS IS THE USER SUPPLIED VALUE
"$totalPoints"
]
}
},
doc: "$$ROOT"
}
},
{
$sort: {
diff: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
rank: "$doc.rank"
}
}
])
return finalResult;
}
When I'm testing my app with postman I get this response in there :
{
"_pipeline": [
{
"$project": {
"diff": {
"$abs": {
"$subtract": [
16,
"$totalPoints"
]
}
},
"doc": "$$ROOT"
}
},
{
"$sort": {
"diff": 1
}
},
{
"$limit": 1
},
{
"$project": {
"_id": 0,
"rank": "$doc.rank"
}
}
],
"options": {}
}
Question #1:
To find a rank by its totalPoint, you can do:
You can check out a live demo here
db.collection.find({
totalPoints: 2000 // <<<------------------ The exact value you want to find
},
{
_id: 0,
rank: 1
})
Question #2:
To find closest rank, by a user supplied totalPoints value, you should be able to use the following query...
You can check out a live demo here
db.collection.aggregate([
{
$project: {
diff: {
$abs: {
$subtract: [
1800, // <<<----------------------- THIS IS THE USER SUPPLIED VALUE
"$totalPoints"
]
}
},
doc: "$$ROOT"
}
},
{
$sort: {
diff: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
rank: "$doc.rank"
}
}
])
UPDATE/FINAL ANSWER:
Your issue is because Mongoose is promise/async based. You are not awaiting anything, so your code returns a variable that has not been set yet by your query..
I was testing using 2 files: myMongoose.js and index.js..
// myMongoose.js
// ** CODE THAT SAVES DATA TO DATABASE HAS BEEN REMOVED FOR BREVITY **
require('dotenv').config();
const mongoose = require('mongoose');
const RankTotalpointSchema = new mongoose.Schema({
rank: Number,
totalPoints: Number
});
mongoose.set('useCreateIndex', true);
const mongoConnection = mongoose.createConnection(process.env.MONGO_DB_STRING, {
useUnifiedTopology: true,
useNewUrlParser: true,
useFindAndModify: false,
});
const RankTotalpoint = mongoConnection.model("RankTotalpoint", RankTotalpointSchema, 'Testing');
/**
* ~~~~~~ **** THIS HAS TO BE AN ASYNC FUNCTION **** ~~~~~~
*/
exports.result = async function (param) {
const finalresult = await RankTotalpoint.aggregate([{
$project: {
diff: {
$abs: {
$subtract: [
param, // <<<----------------------- THIS IS THE USER SUPPLIED VALUE
"$totalPoints"
]
}
},
doc: "$$ROOT"
}
},
{
$sort: {
diff: 1
}
},
{
$limit: 1
},
{
$project: {
_id: 0,
rank: "$doc.rank"
}
}
])
return finalresult;
};
...and then in index.js:
// index.js
const { result } = require('./myMongoose');
// Use it like this:
async function init() {
try {
const d = await result(1800);
console.log(d);
} catch (err) {
console.error(err);
}
}
init(); // -> [ { rank: 5 } ]
// --------------------------------------------------------------------
// ...or like this:
(async () => {
try {
const d = await result(1800);
console.log(d); // -> [ { rank: 5 } ]
} catch (err) {
console.error(err);
}
})()
// --------------------------------------------------------------------
// ...or like this:
result(1800)
.then(d => console.log(d)) // -> [ { rank: 5 } ]
.catch(err => console.error(err))

Set Incremental Values from Array of Items

How to update the multiple documents in MongoDB and set the value of the element in an increasing order?
I have got the document as follows
{
"_id" : ObjectId("5b162a31dfaf342dc44c920d")
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920f")
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920c")
}
How can I update the whole documents with a single query so that I can have a new element called "order" in every single field in an increasing order as below
{
"_id" : ObjectId("5b162a31dfaf342dc44c920d"),
"order": 1
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920f"),
"order": 2
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920c"),
"order": 3
}
Currently I am using the following way to solve the problem
for(let i = 0; i <= req.body.id.length;i++) {
const queryOpts = {
_id: ObjectId(req.body.id[i])
};
const updateOpts = {
$set: {
'order': i + 1
}
};
const dataRes = await req.db.collection('GalleryImage').updateOne(queryOpts, updateOpts);
if(i === req.body.id.length-1) {
return commonHelper.sendResponseMessage(res, dataRes, {
_id: req.body.id
}, moduleConfig.message.updateGalleryOrder);
}
If there any better way than this so that it would not be the expensive operation if there are large number of documents ?
Use bulkWrite() with Array.map() to construct the statement:
try {
let response = await req.db.collection('GalleryImage').bulkWrite(
req.body.id.map((_id,order) =>
({ updateOne: {
filter: { _id: ObjectId(_id) },
update: {
$set: { order: order+1 }
}
}})
)
);
} catch(e) {
// deal with any errors
}
Array.map() has the "index" of the array element being processed within it's second function argument. So simply use that to get the order and set that on all statements.
Rather than writing/responding with the database n times, this only needs happen "once".
There is no other way to get a "sequence" other than introducing it yourself, but at least we can do it with "one" write this way instead of several. Note also to "trap your possible errors" when using async/await syntax.
Example listing
const { MongoClient, ObjectID: ObjectId } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const data = [
"5b162a31dfaf342dc44c920d",
"5b162a31dfaf342dc44c920f",
"5b162a31dfaf342dc44c920c"
];
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const client = await MongoClient.connect(uri);
let db = client.db('test');
// Set up
await db.collection('gallery').removeMany({});
await db.collection('gallery').insertMany(
data.map(_id => ({ _id: ObjectId(_id) }))
);
// Update with indexes
let response = await db.collection('gallery').bulkWrite(
data.map((_id,idx) =>
({
updateOne: {
filter: { _id: ObjectId(_id) },
update: { $set: { order: idx+1 } }
}
})
)
);
log({ response });
let items = await db.collection('gallery').find().toArray();
log({ items });
client.close();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
And the output
{
"response": {
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 3,
"nModified": 3,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6563535160225038345",
"t": 18
}
}
}
{
"items": [
{
"_id": "5b162a31dfaf342dc44c920d",
"order": 1
},
{
"_id": "5b162a31dfaf342dc44c920f",
"order": 2
},
{
"_id": "5b162a31dfaf342dc44c920c",
"order": 3
}
]
}
Clearly shows nMatched: 3 and nModified: 3 just as is expected.

Update the same property of every document of a mongoDb collection with different values

I have a collection in mongoDb which looks like this
{
"slno" : NumberInt(1),
"name" : "Item 1"
}
{
"slno" : NumberInt(2),
"name" : "Item 2"
}
{
"slno" : NumberInt(3),
"name" : "Item 3"
}
I am receiving a request from angularJs frontend to update this collection to
{
"slno" : NumberInt(1),
"name" : "Item 3"
}
{
"slno" : NumberInt(2),
"name" : "Item 1"
}
{
"slno" : NumberInt(3),
"name" : "Item 2"
}
I am using Mongoose 5.0 ORM with Node 6.11 and express 4.15. Please help me find the best way to achieve this.
You basically want bulkWrite(), which can take the input array of objects and use it to make a "batch" of requests to update the matched documents.
Presuming the array of documents is being sent in req.body.updates, then you would have something like
const Model = require('../models/model');
router.post('/update', (req,res) => {
Model.bulkWrite(
req.body.updates.map(({ slno, name }) =>
({
updateOne: {
filter: { slno },
update: { $set: { name } }
}
})
)
})
.then(result => {
// maybe do something with the WriteResult
res.send("ok"); // or whatever response
})
.catch(e => {
// do something with any error
})
})
This sends a request given the input as:
bulkWrite([
{ updateOne: { filter: { slno: 1 }, update: { '$set': { name: 'Item 3' } } } },
{ updateOne: { filter: { slno: 2 }, update: { '$set': { name: 'Item 1' } } } },
{ updateOne: { filter: { slno: 3 }, update: { '$set': { name: 'Item 2' } } } } ]
)
Which efficiently performs all updates in a single request to the server with a single response.
Also see the core MongoDB documentation on bulkWrite(). That's the documentation for the mongo shell method, but all the options and syntax are exactly the same in most drivers and especially within all JavaScript based drivers.
As a full working demonstration of the method in use with mongoose:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const testSchema = new Schema({
slno: Number,
name: String
});
const Test = mongoose.model('Test', testSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
const data = [1,2,3].map(n => ({ slno: n, name: `Item ${n}` }));
const request = [[1,3],[2,1],[3,2]]
.map(([slno, n]) => ({ slno, name: `Item ${n}` }));
mongoose.connect(uri)
.then(conn =>
Promise.all(Object.keys(conn.models).map( k => conn.models[k].remove()))
)
.then(() => Test.insertMany(data))
.then(() => Test.bulkWrite(
request.map(({ slno, name }) =>
({ updateOne: { filter: { slno }, update: { $set: { name } } } })
)
))
.then(result => log(result))
.then(() => Test.find())
.then(data => log(data))
.catch(e => console.error(e))
.then(() => mongoose.disconnect());
Or for more modern environments with async/await:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const testSchema = new Schema({
slno: Number,
name: String
});
const Test = mongoose.model('Test', testSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
const data = [1,2,3].map(n => ({ slno: n, name: `Item ${n}` }));
const request = [[1,3],[2,1],[3,2]]
.map(([slno,n]) => ({ slno, name: `Item ${n}` }));
(async function() {
try {
const conn = await mongoose.connect(uri)
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await Test.insertMany(data);
let result = await Test.bulkWrite(
request.map(({ slno, name }) =>
({ updateOne: { filter: { slno }, update: { $set: { name } } } })
)
);
log(result);
let current = await Test.find();
log(current);
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Which loads the initial data and then updates, showing the response object ( serialized ) and the resulting items in the collection after the update is processed:
Mongoose: tests.remove({}, {})
Mongoose: tests.insertMany([ { _id: 5b1b89348f3c9e1cdb500699, slno: 1, name: 'Item 1', __v: 0 }, { _id: 5b1b89348f3c9e1cdb50069a, slno: 2, name: 'Item 2', __v: 0 }, { _id: 5b1b89348f3c9e1cdb50069b, slno: 3, name: 'Item 3', __v: 0 } ], {})
Mongoose: tests.bulkWrite([ { updateOne: { filter: { slno: 1 }, update: { '$set': { name: 'Item 3' } } } }, { updateOne: { filter: { slno: 2 }, update: { '$set': { name: 'Item 1' } } } }, { updateOne: { filter: { slno: 3 }, update: { '$set': { name: 'Item 2' } } } } ], {})
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 3,
"nModified": 3,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6564991738253934601",
"t": 20
}
}
Mongoose: tests.find({}, { fields: {} })
[
{
"_id": "5b1b89348f3c9e1cdb500699",
"slno": 1,
"name": "Item 3",
"__v": 0
},
{
"_id": "5b1b89348f3c9e1cdb50069a",
"slno": 2,
"name": "Item 1",
"__v": 0
},
{
"_id": "5b1b89348f3c9e1cdb50069b",
"slno": 3,
"name": "Item 2",
"__v": 0
}
]
That's using syntax which is compatible with NodeJS v6.x
A small change in Neil Lunn's answer did the job.
const Model = require('../models/model');
router.post('/update', (req,res) => {
var tempArray=[];
req.body.updates.map(({slno,name}) => {
tempArray.push({
updateOne: {
filter: {slno},
update: {$set: {name}}
}
});
});
Model.bulkWrite(tempArray).then((result) => {
//Send resposne
}).catch((err) => {
// Handle error
});
Thanks to Neil Lunn.

Mongo + check multiple fields existing

I am working mongo with nodejs.
I have array list:
var checkFields = ["field1","field2","field3"];
I try to get the count of records having the array list fields and user field is equal to admin.
Sample data:
[
{
"checkFields": {
"field1": "00124b3a5c31",
"user": "admin"
}
},
{
"checkFields": {
"field2": "00124b3a5c31",
"user": "admin"
}
},
{
"checkFields": {
"field1": "00124b3a5c31",
"user": "regular"
}
}
]
Query:
db.collection_name.find(
{"checkFields.user" : "admin"}
{ "checkFields.field1": { $exists: true} }
)
Expected Result:
Result is to get rows of count of matching the field in array list(checkFields).
Building up an $or array for the list of field existence checks is the right approach, but assuming you're on a current node.js build you can simplify the query creation to:
var checkFieldsLists = checkFields.map(field => ({
['checkFields.' + field]: {$exists: true}
}));
var query = {
$or: checkFieldsLists,
'checkFields.user': 'admin'
}
This removes the superfluous $or for the "user is admin" check which lets you also remove the outer $and, so that the generated query is:
{ '$or':
[ { 'checkFields.field1': { '$exists': true } },
{ 'checkFields.field2': { '$exists': true } },
{ 'checkFields.field3': { '$exists': true } } ],
'checkFields.user': 'admin' }
I tried the following code. Its working but don't know its good solution and perfomance. Please anyone have better answer means please post it.
var checkFields = ["field1", "field2", "field3"];
var checkFieldsLists = [];
for ( i = 0; i < checkFields.length; i++) {
var jsObj = {};
jsObj['checkFields.' + checkFields[i]] = {};
jsObj['checkFields.' + checkFields[i]].$exists = true;
checkFieldsLists.push(jsObj);
}
var query = {
"$and" : [{
"$or" : checkFieldsLists
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
};
console.log(JSON.stringify(query));
//console log will return
/*
{"$and":[{
"$or" : [{
"checkFields.field1" : {
"$exists" : true
}
}, {
"checkFields.field2" : {
"$exists" : true
}
}, {
"checkFields.field3" : {
"$exists" : true
}
}]
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
}
*/
collection.find(query);
Here is the solution using aggregate query.
var Db = require('mongodb').Db, Server = require('mongodb').Server, assert = require('assert');
var db = new Db('localhost', new Server('localhost', 27017));
var checkFields = ["field1", "field2", "field3"];
var checkFieldsLists = [];
for (var i = 0; i < checkFields.length; i++) {
var jsObj = {};
jsObj['checkFields.' + checkFields[i]] = {};
jsObj['checkFields.' + checkFields[i]].$exists = true;
checkFieldsLists.push(jsObj);
}
var query = {
"$and" : [{
"$or" : checkFieldsLists
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
};
var matchQuery = {
"$match" : {
"checkFields.user" : "admin",
"$or" : checkFieldsLists
}
};
var groupQuery = {
$group : {
_id : null,
count : {
$sum : 1
}
}
};
var aggregateCheckFields = function(db, callback) {
console.log("Match query is ====>" + JSON.stringify(matchQuery));
console.log("Group query is ====>" + JSON.stringify(matchQuery));
db.collection('checkfields').aggregate([ matchQuery, groupQuery ]).toArray(
function(err, result) {
assert.equal(err, null);
console.log("Result is ===>" + JSON.stringify(result));
if (result.length > 0) {
console.log("Count is ===>" + result[0].count);
}
callback(result);
});
};
db.open(function(err, db) {
aggregateCheckFields(db, function() {
db.close();
});
});
Output:-
Result is ===>[{"_id":null,"count":3}]
Count is ===>3

Resources