问题
I'm working on MongoDB 2.6.9 and NodeJs 0.10.37 and I have a collection vols
which means flights.
> db.vols.findOne()
{
"_id" : ObjectId("5717a5d4578f3f2556f300f2"),
"Orig" : "AGP",
"Dest" : "OTP",
"Flight" : 126,
"Routing" : "AGP-OTP",
"Stops" : 0,
"Seats" : 169,
"Ops_Week" : 3,
"Eff_Date" : "2016-04-14",
"Mkt_Al" : "0B",
"Dep_Time" : 1110,
"Thru_Point" : "",
"Arr_Time" : 1600,
"Block_Mins" : 230
}
Each document refers to one flight done by an Airline Company and it gives details, for instance, the previous document refers to a flight done directly ( Stops : 0 ). But the next one, the flight was with stop.
db.vols.findOne({Stops:1})
{
"_id" : ObjectId("5717a5d4578f3f2556f301c5"),
"Orig" : "CEK",
"Dest" : "IKT",
"Flight" : 7756,
"Routing" : "KZN-CEK-OVB-IKT",
"Stops" : 1,
"Seats" : 70,
"Ops_Week" : 2,
"Eff_Date" : "2016-04-11",
"Mkt_Al" : "2G",
"Dep_Time" : 1655,
"Thru_Point" : "OVB",
"Arr_Time" : 140,
"Block_Mins" : 345
}
Important:
Each Airline
has a score
in every route ( Origin
- Destination
)
How to calculate the score ?
So, I need to do these calculations and insert a new Field " QSI " into my collection vols
.
Important :
Average elapsed time in c4
means this :
For example we have a flight with stop let's say : a flight from A to C by B, the whole flight makes for example 60 min, but from A to B makes 20 min and from B to C makes 20 min, this average should return 40 min.
I tried this Solution, but for c4
things don't look like smething work :
var mongoose = require('mongoose'),
express = require('express'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/ramtest');
var volsSchema = new Schema({}, { strict : false, collection : 'vols' });
var MyModel = mongoose.model("MyModel", volsSchema);
mongoose.set('debug', true);
mongoose.connection.on("open", function(err) {
if (err) throw err;
var bulkUpdateOps = MyModel.collection.initializeUnorderedBulkOp(),
counter = 0;
MyModel.find({}).lean().exec(function(err, docs) {
if (err) throw err;
docs.forEach(function(doc) {
// computations
var c1, c2, c3, c4, qsi, first_leg, second_leg, total_flight;
c1 = 0.3728 + (0.00454 * doc.Seats);
c2 = (doc.Stops == 1) ? 0.03 : 1;
c3 = doc.Ops_Week;
if (doc.Stops == 1) {
var Mkt_Air = doc.Mkt_Al,
Origin = doc.Orig,
Destination = doc.Dest,
Thru_Point = doc.Thru_Point,
Effective_Date = doc.Eff_Date,
Block_Mins = doc.Block_Mins;
MyModel.find({ Mkt_Al : Mkt_Air }, { Orig : Origin }, { Dest : Thru_Point }, { Eff_Date : Effective_Date }).lean().exec(function(err, docs) {
docs.forEach(function(doc) {
var first_leg = doc.Block_Mins;
MyModel.find({ Mkt_Al : Mkt_Air }, { Orig : Thru_Point }, { Dest : Destination }, { Eff_Date : Effective_Date }).lean().exec(function(err, docs) {
docs.forEach(function(doc) {
var second_leg = doc.Block_Mins, total_flight = second_leg + first_leg;
c4 = Math.pow((Block_Mins / total_flight), -0.675);
qsi = c1 * c2 * c3 * c4;
}); // the end of docs.forEach(function (doc){
}); // the end of MyModel.find..
}); // the end of docs.forEach(function (doc){
}); // the end of MyModel.find..
} // end if
else {
c4 = 1;
}
qsi = c1 * c2 * c3 * c4;
counter++;
bulkUpdateOps.find({ "_id" : doc._id }).updateOne({
"$set" : { "Qsi" : qsi }
});
if (counter % 500 == 0) {
bulkUpdateOps.execute(function(err, result) {
if (err) throw err;
bulkUpdateOps = MyModel.collection.initializeUnorderedBulkOp();
console.log(result);
console.log(doc);
});
}
});
if (counter % 500 != 0) {
bulkUpdateOps.execute(function(err, result) {
if (err) throw err;
console.log(result);
});
}
});
var app = express();
app.listen(3000, function() {
console.log('Ready to calculate and insert the QSI');
});
});
The problem:
I think that the problem is with MyModel.find
like if i lose data inside this instruction ..., my score
is calculated cleanly when Stops = 0
, but if Stops = 1
, My score takes the value Nan
, and I have an error after some iterations like that callback(null, docs)
please who can help ??
How can I achieve the above ?
回答1:
There are several issues with your implementation. Firstly, you are using the find() method incorrectly as you are specifying too many arguments for the query:
MyModel.find(
{ Mkt_Al : Mkt_Air },
{ Orig : Origin },
{ Dest : Thru_Point },
{ Eff_Date : Effective_Date }
).lean().exec(function(err, docs) { .. }
should be
MyModel.find({
Mkt_Al: Mkt_Air,
Orig: Origin,
Dest: Thru_Point,
Eff_Date: Effective_Date
}).lean().exec(function(err, docs) { ... }
Again, you shouldn't be using the find() method in this instance because you only need a single document that matches the query to use in your computations. Taking the complex algorithm from your previous closed question:
Now I want to calculate a score
c4
and insert it into my collection :To do that I should calculate a value
c4
just like this :1) First I verify for each document if
( Field2 == 1 )
if it's true I continue else it's simplec4
takes value 1.2) Then I should make a loop "for" and see which document verify these conditions :
doc.Field1 == this.Field1 && doc.Field6 == this.Field6 && doc.Field7 == this.Field8
3) Then I take
doc.Field4
wich will be added to another document'sField4
4) I continue and I make another loop and look for another document wich verify these conditions :
it should have the same
Field1
just like the previous document and itsField6
equal to the previous documentField7
and itsField8
the same as Field8 in the first document5) Then I take
doc.Field4
and add it to the previousdoc.Field4
Using MyModel.findOne()
should suffice for tasks 3, 4 and 5 above. However, because of the asynchronous nature of the calls, you would need to nest the queries but fortunately the depth of the nested calls is not greater than 3 otherwise you will find yourself with a one-way ticket to Callback Hell. To avoid these common pitfalls, better to use Promises (since the native mongoose queries by default can return a Promise) or use the node-async package which includes a number of functions for dealing with situations like this.
If using the async library, it efficiently allows you to run run multiple asynchronous tasks (like the MyModel.findOne()
calls) that depend on each other and when they all finish do something else. In the above, you could use the async.series() method.
The following example demonstrates the above concept where you can calculate the Qsi
from the following sample documents in the test db.
Populate test db's vol collection:
db.vols.insert([
{
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 169,
"Block_Mins" : 230,
"Ops_Week" : 3,
"Orig" : "AGP",
"Dest" : "OTP",
"Thru_Point" : "",
},
{
"Mkt_Al" : "2G",
"Stops" : 1,
"Seats" : 260,
"Block_Mins" : 260,
"Ops_Week" : 2,
"Orig" : "CEK",
"Dest" : "IKT",
"Thru_Point" : "OVB",
},
{
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 140,
"Block_Mins" : 60,
"Ops_Week" : 2,
"Orig" : "BEK",
"Dest" : "OTP",
"Thru_Point" : "",
},
{
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 160,
"Block_Mins" : 90,
"Ops_Week" : 3,
"Orig" : "CEK",
"Dest" : "OVB",
"Thru_Point" : "",
},
{
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 60,
"Block_Mins" : 50,
"Ops_Week" : 3,
"Orig" : "OVB",
"Dest" : "IKT",
"Thru_Point" : "",
}
])
Node.js app:
var mongoose = require('mongoose'),
express = require('express'),
async = require('async'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var volSchema = new Schema({},{ strict: false, collection: 'vols' }),
Vol = mongoose.model("Vol", volSchema);
mongoose.set('debug', false);
mongoose.connection.on("open", function (err) {
if (err) throw err;
var bulkUpdateOps = Vol.collection.initializeUnorderedBulkOp(),
counter = 0;
Vol.find({}).lean().exec(function (err, docs) {
if (err) throw err;
var locals = {};
docs.forEach(function(doc) {
locals.c1 = 0.3728 + (0.00454 * doc.Seats);
locals.c3 = doc.Ops_Week;
if (doc.Stops == 1) {
async.series([
// Load doc with first leg first
function(callback) {
Vol.findOne({
Mkt_Al: doc.Mkt_Al,
Orig: doc.Orig,
Dest: doc.Dest
}).lean().exec(function (err, flight) {
if (err) return callback(err);
locals.first_leg = flight.Block_Mins;
callback();
});
},
// Load second leg doc
// (won't be called before task 1's "task callback"
// has been called)
function(callback) {
Vol.findOne({
Mkt_Al: doc.Mkt_Al,
Orig: doc.Thru_Point,
Dest: doc.Dest
}).lean().exec(function (err, flight) {
if (err) return callback(err);
locals.second_leg = flight.Block_Mins;
callback();
});
}
], function(err) { // This function gets called after the
// two tasks have called their "task callbacks"
if (err) throw err;
// Here locals will be populated with `first_leg`
// and `second_leg`
// Just like in the previous example
var total_flight = locals.second_leg + locals.first_leg;
locals.c2 = 0.03;
locals.c4 = Math.pow((doc.Block_Mins / total_flight), -0.675);
});
} else {
locals.c2 = 1;
locals.c4 = 1;
}
counter++;
console.log(locals);
bulkUpdateOps.find({ "_id" : doc._id }).updateOne({
"$set": {
"Qsi": (locals.c1 * locals.c2 * locals.c3 * locals.c4)
}
});
if (counter % 500 == 0) {
bulkUpdateOps.execute(function(err, result) {
if (err) throw err;
bulkUpdateOps = Vol.collection.initializeUnorderedBulkOp();
});
}
});
if (counter % 500 != 0) {
bulkUpdateOps.execute(function(err, result) {
if (err) throw err;
console.log(result.nModified);
});
}
});
});
Sample Output:
db.vols.find()
/* 1 */
{
"_id" : ObjectId("5767e7549ebce6d574702221"),
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 169,
"Block_Mins" : 230,
"Ops_Week" : 3,
"Orig" : "AGP",
"Dest" : "OTP",
"Thru_Point" : "",
"Qsi" : 3.42018
}
/* 2 */
{
"_id" : ObjectId("5767e7549ebce6d574702222"),
"Mkt_Al" : "2G",
"Stops" : 1,
"Seats" : 260,
"Block_Mins" : 260,
"Ops_Week" : 2,
"Orig" : "CEK",
"Dest" : "IKT",
"Thru_Point" : "OVB",
"Qsi" : 3.1064
}
/* 3 */
{
"_id" : ObjectId("5767e7549ebce6d574702223"),
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 140,
"Block_Mins" : 60,
"Ops_Week" : 2,
"Orig" : "BEK",
"Dest" : "OTP",
"Thru_Point" : "",
"Qsi" : 2.0168
}
/* 4 */
{
"_id" : ObjectId("5767e7549ebce6d574702224"),
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 160,
"Block_Mins" : 90,
"Ops_Week" : 3,
"Orig" : "CEK",
"Dest" : "OVB",
"Thru_Point" : "",
"Qsi" : 3.2976
}
/* 5 */
{
"_id" : ObjectId("5767e7549ebce6d574702225"),
"Mkt_Al" : "2G",
"Stops" : 0,
"Seats" : 60,
"Block_Mins" : 50,
"Ops_Week" : 3,
"Orig" : "OVB",
"Dest" : "IKT",
"Thru_Point" : "",
"Qsi" : 1.9356
}
回答2:
The problem is that in the Stops == 1
case, you are making an async call which will not set the value of c4
before you use it to calculate qsi
. The relevant block is here:
if (doc.Stops == 1) {
// do some stuff
MyModel.find({/* some query */}).lean().exec(function(err, docs) {
// SITE A: this function will not be called for a few milliseconds
// do some more stuff
c4 = Math.pow((Block_Mins / total_flight), -0.675);
qsi = c1 * c2 * c3 * c4;
}); // the end of MyModel.find..
} // end if
else {
c4 = 1;
}
qsi = c1 * c2 * c3 * c4; // SITE B: this will be called before SITE A, and qsi will be NaN since c4 is not initialized
bulkUpdateOps.find({ "_id" : doc._id }).updateOne({
"$set" : { "Qsi" : qsi } // qsi here will be from SITE B, and SITE A still hasn't been reached yet
});
A rough fix would be to do something like
function setQsi(qsi) {
bulkUpdateOps.find({ "_id" : doc._id }).updateOne({
"$set" : { "Qsi" : qsi }
});
}
if (doc.Stops == 1) {
// do some stuff
MyModel.find({/* some query */}).lean().exec(function(err, docs) {
// do some more stuff
c4 = Math.pow((Block_Mins / total_flight), -0.675);
setQsi(c1 * c2 * c3 * c4);
}); // the end of MyModel.find..
} // end if
else {
c4 = 1;
setQsi(c1 * c2 * c3 * c4);
}
For more complicated async patterns, you should look into using Promises.
来源:https://stackoverflow.com/questions/37791777/calculate-a-score-from-an-existing-fields-with-conditions