I am trying to do a query between dates. In compass I can do the query without any problem using the native function ISODate(). But when trying in my code I can't import that function, and new Date() is not warking.
Documents as example:
let trxs = [{
_id:612e112f7a7eaa7a5c1fd0d3
created:2021-09-31T11:23:25.184+00:00
amount:19.98
user:"612e112f7a7eaa7a5c1fd0d1"
type:"deposit"
},
{
_id:612e112f7a7eaa7a5c1fd0d6
created:2021-09-31T11:23:25.184+00:00
amount:10
user:"612e112f7a7eaa7a5c1fd0d4"
type:"deposit"
}
]
Query
let trxs = await Transaction.aggregate([
{
$match: {
type: req.query.type,
$and: [
{
created:
{
$gt: new Date(new Date().setHours(0, 0, 0))
},
},
{
created:
{
$lt:new Date(new Date().setHours(23, 59, 59))
}
}
]
}
}, {
$group: {
_id: null,
amount: {
$sum: '$amount'
}
}
}
]);
//More info:
console.log(new Date(new Date().setHours(0, 0, 0))) // 2021-08-31T22:00:00.953Z
console.log(new Date(new Date().setHours(23, 59, 59))) // 2021-09-01T21:59:59.952Z
//Error
ReferenceError: amount is not defined
I tried to import ISODate function but I don't find the way to do it.
Try the moment.js library. There is no need for $and: []
{
$match: {
type: req.query.type,
created: {
$gt: moment().startOf('day').toDate(),
$lt: moment().endOf('day').toDate(),
}
}
}
What would be the better way to select count of users
Will the JavaScript filtering code work fine if the number of users increases?
Using multiple mongodb requests
const db = fastify.mongo.db;
const users_collection = await db.collection('users');
let users = {
registered: {
count: null,
typed_count: {
vk: null,
ok: null,
google: null,
oneclick: null,
},
},
};
users.registered.count = await users_collection.countDocuments();
users.registered.typed_count.vk = await users_collection.countDocuments({ 'social.vk': { $exists: true } });
users.registered.typed_count.ok = await users_collection.countDocuments({ 'social.ok': { $exists: true } });
users.registered.typed_count.google = await users_collection.countDocuments({ 'social.google': { $exists: true } });
users.registered.typed_count.oneclick = await users_collection.countDocuments({ social: { $exists: false } });
Using one mongodb request and javascript filtering
const db = fastify.mongo.db;
const users_collection = await db.collection('users');
let users = {
registered: {
count: null,
typed_count: {
vk: null,
ok: null,
google: null,
oneclick: null,
},
},
};
const data = await (await users_collection.find()).toArray();
users.registered.count = data.length;
users.registered.typed_count.vk = data.filter((obj) => obj.social && obj.social.vk).length;
users.registered.typed_count.ok = data.filter((obj) => obj.social && obj.social.ok).length;
users.registered.typed_count.google = data.filter((obj) => obj.social && obj.social.google).length;
users.registered.typed_count.oneclick = data.filter((obj) => !obj.social).length;
The First Method will take more time as too many network requests are involved
and
The Second Method will take too much of your server's memory (RAM) as all the documents will first be brought into the memory.
So we can reduce both time and memory by using MongoDB aggregation's $group pipeline, which will look something like this
db.collection.aggregate([
{
$group: {
_id: null,
vk: {
$sum: {
$cond: [{ $gt: ["$social.vk", null]}, 1, 0]
}
},
ok: {
$sum: {
$cond: [{ $gt: ["$social.ok", null]}, 1, 0]
}
},
google: {
$sum: {
$cond: [{ $gt: ["$social.google", null]}, 1, 0]
}
},
oneclick: {
$sum: {
$cond: [{ $lte: ["$social", null]}, 1, 0]
}
},
}
}
])
Working Example
I have encountered TypeError: collection.aggregate(...).cursor is not a function in loopback v3.8.0, loopback mongodb connector v1.18.1.
var pipeline = [{
$match: {
restaurantId: id
}
}, {
$project: {
'y': {
'$year': '$orderDateTime'
},
'm': {
'$month': '$orderDateTime'
},
'd': {
'$dayOfMonth': '$orderDateTime'
}
}
}, {
$group: {
'_id': {
'year': '$y',
'month': '$m',
'day': '$d'
},
'sum': {
'$sum': '$totalAmount'
}
}
}];
Model.getDataSource().connector.connect(function(err, db) {
var collection = db.collection('collection-name');
collection.aggregate(pipeline).cursor({ batchSize: 2500, async: true }).exec();
});
Instead of model name, I have also tried supplying collection name by following the below.
var sampleCollection = SampleModel.getDataSource().connector.collection(SampleModel.modelName);
var data = sampleCollection.aggregate(pipeline).cursor({ batchSize: 1000, async: true }).exec(function(err, cursor){
console.log(cursor);
});
var pipeline = [{
$match: {
restaurantId: ObjectID(id)
}
}, {
$project: {
'y': {
'$year': '$orderDateTime'
},
'm': {
'$month': '$orderDateTime'
},
'd': {
'$dayOfMonth': '$orderDateTime'
}
}
}, {
$group: {
_id: {
year: '$y',
month: '$m',
day: '$d'
},
totalRevenue: {
'$sum': '$totalBillAmount'
}
}
}];
Model.getDataSource().connector.connect(function(err, db) {
var collection = db.collection('model-name');
var cursor = collection.aggregate(pipeline, function(err, results) {
console.log(results);
});
});
I'm using Mongoose (MongoDB in node.js), and after reading this answer:
Replace value in array
I have another question:
Is it possible to do in the same sentence: push element into array or replace if this element is existing in the array?
Maybe something like this? (The example doesn't work)
Model.findByIdAndUpdate(id,
{
$pull: {"readers": {user: req.user.id}},
$push:{"readers":{user: req.user.id, someData: data}}
},{multi:true},callback)
Message error:
errmsg: 'exception: Cannot update \'readers\' and \'readers\' at the same time
Reference:
https://stackoverflow.com/a/15975515/4467741
Thank you!
Multiple operations on the same property path are simply not allowed in a single request, with the main reason being that the operations themselves have "no particular order" in the way the engine assigns them as the document is updated, and therefore there is a conflict that should be reported as an error.
So the basic abstraction on this is that you have "two" update operations to perform, being one to "replace" the element where it exists, and the other to "push" the new element where it does not exist.
The best way to implement this is using "Bulk" operations, which whilst still "technically" is "two" update operations, it is however just a "single" request and response, no matter which condition was met:
var bulk = Model.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": req.user.id }).updateOne({
"$set": { "readers.$.someData": data } }
});
bulk.find({ "_id": id, "readers.user": { "$ne": req.user.id } }).updateOne({
"$push": { "readers": { "user": req.user.id, "someData": data } }
});
bulk.execute(function(err,result) {
// deal with result here
});
If you really "need" the updated object in result, then this truly becomes a "possible" multiple request following the logic where the array element was not found:
Model.findOneAndUpdate(
{ "_id": id, "readers.user": req.user.id },
{ "$set": { "readers.$.someData": data } },
{ "new": true },
function(err,doc) {
if (err) // handle error;
if (!doc) {
Model.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": req.user.id } },
{ "$push": { "readers":{ "user": req.user.id, "someData": data } } },
{ "new": true },
function(err,doc) {
// or return here when the first did not match
}
);
} else {
// was updated on first try, respond
}
}
);
And again using you preferred method of not nesting callbacks with either something like async or nested promise results of some description, to avoid the basic indent creep that is inherrent to one action being dependant on the result of another.
Basically probably a lot more efficient to perform the updates in "Bulk" and then "fetch" the data afterwards if you really need it.
Complete Listing
var async = require('async'),
mongoose = require('mongoose')
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var userSchema = new Schema({
name: String
});
var dataSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
someData: String
},{ "_id": false });
var testSchema = new Schema({
name: String,
readers: [dataSchema]
});
var User = mongoose.model( 'User', userSchema ),
Test = mongoose.model( 'Test', testSchema );
var userId = null,
id = null;
async.series(
[
// Clean models
function(callback) {
async.each([User,Test],function(model,callback) {
model.remove({},callback);
},callback);
},
// Create a user
function(callback) {
User.create({ name: 'bill' },function(err,user) {
userId = user._id;
callback(err);
});
},
function(callback) {
Test.create({ name: 'Topic' },function(err,topic) {
id = topic._id;
console.log("initial state:");
console.log(topic);
callback(err);
});
},
// 1st insert array 2nd update match 1 modified
function(callback) {
var bulk = Test.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": userId }).updateOne({
"$set": { "readers.$.someData": 1 }
});
bulk.find({ "_id": id, "readers.user": { "$ne": userId }}).updateOne({
"$push": { "readers": { "user": userId, "someData": 1 } }
});
bulk.execute(function(err,result) {
if (err) callback(err);
console.log("update 1:");
console.log(JSON.stringify( result, undefined, 2));
Test.findById(id,function(err,doc) {
console.log(doc);
callback(err);
});
});
},
// 2nd replace array 1st update match 1 modified
function(callback) {
var bulk = Test.collection.initializeOrderedBulkOp();
bulk.find({ "_id": id, "readers.user": userId }).updateOne({
"$set": { "readers.$.someData": 2 }
});
bulk.find({ "_id": id, "readers.user": { "$ne": userId }}).updateOne({
"$push": { "readers": { "user": userId, "someData": 2 } }
});
bulk.execute(function(err,result) {
if (err) callback(err);
console.log("update 2:");
console.log(JSON.stringify( result, undefined, 2));
Test.findById(id,function(err,doc) {
console.log(doc);
callback(err);
});
});
},
// clear array
function(callback) {
Test.findByIdAndUpdate(id,
{ "$pull": { "readers": {} } },
{ "new": true },
function(err,doc) {
console.log('cleared:');
console.log(doc);
callback(err);
}
);
},
// cascade 1 inner condition called on no array match
function(callback) {
console.log('update 3:');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": userId },
{ "$set": { "readers.$.someData": 1 } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
if (!doc) {
console.log('went inner');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": userId } },
{ "$push": { "readers": { "user": userId, "someData": 1 } } },
{ "new": true },
function(err,doc) {
console.log(doc)
callback(err);
}
);
} else {
console.log(doc);
callback(err);
}
}
);
},
// cascade 2 outer condition met on array match
function(callback) {
console.log('update 3:');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": userId },
{ "$set": { "readers.$.someData": 2 } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
if (!doc) {
console.log('went inner');
Test.findOneAndUpdate(
{ "_id": id, "readers.user": { "$ne": userId } },
{ "$push": { "readers": { "user": userId, "someData": 2 } } },
{ "new": true },
function(err,doc) {
console.log(doc)
callback(err);
}
);
} else {
console.log(doc);
callback(err);
}
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Output:
initial state:
{ __v: 0,
name: 'Topic',
_id: 55f60adc1beeff6b0a175e98,
readers: [] }
update 1:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { user: 55f60adc1beeff6b0a175e97, someData: '1' } ] }
update 2:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { user: 55f60adc1beeff6b0a175e97, someData: '2' } ] }
cleared:
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [] }
update 3:
went inner
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { someData: '1', user: 55f60adc1beeff6b0a175e97 } ] }
update 3:
{ _id: 55f60adc1beeff6b0a175e98,
name: 'Topic',
__v: 0,
readers: [ { someData: '2', user: 55f60adc1beeff6b0a175e97 } ] }
I find many great answers here on SO like this answer an this. But i can not get it to work...
I tried ObjectId("55cf816559d2fc8d0e6c14a8") in the query where the id is.
This query works when robotmongo run it:
db.getCollection('events').update(
{ "_id": ObjectId("55cf816559d2fc8d0e6c14a8") },
{ "$pull": { "workers" : { "_id": ObjectId("55cf89ac7cba1d0a10ca86c7")}}},
false,
true
)
Side note, what is the false,true for?
Here is my current code
event.update(
{'_id': "55cf816559d2fc8d0e6c14a8"},
{ "$pull": { "workers" : {_id: "55cf89ac7cba1d0a10ca86c7"}}},
function(err, result) {
console.log(err);
console.log(result);
}
);
I do not get any errors and the result is equal to 1.
Works for me. You must be doing something differently and incorrectly:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var workerSchema = new Schema({
name: String
});
var eventSchema = new Schema({
name: String,
workers: [workerSchema]
});
var Event = mongoose.model( 'Event', eventSchema );
async.series(
[
function(callback) {
Event.remove({},function(err) {
callback(err);
});
},
function(callback) {
var event = new Event({
_id: "55cf816559d2fc8d0e6c14a8",
name: "Great thing"
});
event.workers.push({
_id: "55cf89ac7cba1d0a10ca86c7",
name: "Worker1"
});
event.save(function(err,event) {
console.log(event);
callback(err);
});
},
function(callback) {
Event.findOneAndUpdate(
{ "_id": "55cf816559d2fc8d0e6c14a8" },
{ "$pull": { "workers": { "_id": "55cf89ac7cba1d0a10ca86c7" } } },
{ "new": true },
function(err,event) {
console.log(event)
callback(err);
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
With the expected output:
{ __v: 0,
_id: 55cf816559d2fc8d0e6c14a8,
name: 'Great thing',
workers: [ { _id: 55cf89ac7cba1d0a10ca86c7, name: 'Worker1' } ] }
{ _id: 55cf816559d2fc8d0e6c14a8,
name: 'Great thing',
__v: 0,
workers: [] }