【问题标题】:Simple $avg Query for NodejsNodejs 的简单 $avg 查询
【发布时间】:2017-01-03 03:37:10
【问题描述】:

这是我制作应用程序所需的所有代码。因此,我可以从 csv 文件导入数据/记录,也可以从 localhost:3000 的应用程序手动插入,然后将其插入数据库。从那里我将能够从这里查询。错误位于底部的 server.js 中。 bulk 和 avg 似乎都没有执行。

server.js,其中包括批量(1000 条数据及更多)和平均查询(空气温度)

var express = require('express');
var app = express();
var mongojs = require('mongojs');
var db = mongojs('meibanlist', ['meibanlist']);
var bodyParser = require('body-parser');

app.use(express.static(__dirname + '/public'));
app.use(bodyParser.json());

app.get('/meibanlist', function (req, res) {
  console.log('I received a GET request');

  db.meibanlist.find(function (err, docs) {
    console.log(docs);
    res.json(docs);
  });
});

app.post('/meibanlist', function (req, res) {
  console.log(req.body);
  db.meibanlist.insert(req.body, function(err, doc) {
    res.json(doc);
  });
});

app.delete('/meibanlist/:id', function (req, res) {
  var id = req.params.id;
  console.log(id);
  db.meibanlist.remove({_id: mongojs.ObjectId(id)}, function (err, doc) {
    res.json(doc);
  });
});

app.get('/meibanlist/:id', function (req, res) {
  var id = req.params.id;
  console.log(id);
  db.meibanlist.findOne({_id: mongojs.ObjectId(id)}, function (err, doc) {
    res.json(doc);
  });
});

app.put('/meibanlist/:id', function (req, res) {
  var id = req.params.id;
  console.log(req.body.machine_unit);
  db.meibanlist.findAndModify({
    query: {_id: mongojs.ObjectId(id)},
    update: {$set: {machine_unit: req.body.machine_unit, air_temperature: req.body.air_temperature, water_temperature: req.body.water_temperature, heat_temperature: req.body.heat_temperature, room_temperature: req.body.room_temperature, date: req.body.date, time: req.body.time}},
    new: true}, function (err, doc) {
      res.json(doc);
    }
  );
});

var cursor = db.meibanlist.find({"air_temperature": { "$exists": true, "$type": 2 }}),
    bulkUpdateOps = [];

cursor.forEach(function(doc){ 
    var newTemp = parseInt(doc.air_temperature, 10);
    bulkUpdateOps.push({ 
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": { "$set": { "air_temperature": newTemp } }
         }
    });

    if (bulkUpdateOps.length === 500) {
        db.meibanlist.bulkWrite(bulkUpdateOps);
        bulkUpdateOps = [];
    }
});         

if (bulkUpdateOps.length > 0) { db.meibanlist.bulkWrite(bulkUpdateOps); }

db.meibanlist.aggregate([
    {
        "$group": {
            "_id": null,
            "averageAirTemperature": { "$avg": "$air_temperature" } 
        }
    }
], function(err, results){
    if (err || !results) console.log ("record not found");
    else console.log(results[0]["averageAirTemperature"]);        
}); 

app.listen(3000);
console.log("Server running on port 3000");

index.html

<!DOCTYPE>
<html ng-app="myApp">
<head>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">

<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap-theme.min.css">

  <title>Meiban App</title>
</head>
<body>
  <div class="container" ng-controller="AppCtrl">
    <h1>Meiban App</h1>

    <table class="table">
      <thead>
        <tr>
          <th>Machine unit</th>         
          <th>Air Temperature</th>
          <th>Water Temperature</th>
          <th>Heat Temperature</th>       
          <th>Room Temperature</th>
          <th>Date</th>
          <th>Time</th>
          <th>Action</th>

          <th>&nbsp;</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><input class="form-control" ng-model="contact.machine_unit"></td>
          <td><input class="form-control" ng-model="contact.air_temperature"></td>
          <td><input class="form-control" ng-model="contact.water_temperature"></td>
          <td><input class="form-control" ng-model="contact.heat_temperature"></td>
          <td><input class="form-control" ng-model="contact.room_temperature"></td>
          <td><input class="form-control" ng-model="contact.date"></td>
          <td><input class="form-control" ng-model="contact.time"></td>
          <td><button class="btn btn-primary" ng-click="addCollection()">Add Collection</button></td>
          <td><button class="btn btn-info" ng-click="update()">Update</button>&nbsp;&nbsp;<button class="btn btn-info" ng-click="deselect()">Clear</button></td>
        </tr>
        <tr ng-repeat="contact in collection">
          <td>{{contact.machine_unit}}</td>
          <td>{{contact.air_temperature}}</td>
          <td>{{contact.water_temperature}}</td>
          <td>{{contact.heat_temperature}}</td>
          <td>{{contact.room_temperature}}</td>
          <td>{{contact.date}}</td>
          <td>{{contact.time}}</td>
          <td><button class="btn btn-danger" ng-click="remove(contact._id)">Remove</button></td>
          <td><button class="btn btn-warning" ng-click="edit(contact._id)">Edit</button></td>
        </tr>
      </tbody>
    </table>

  </div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.min.js"></script>
<script src="controller/controller.js"></script>
</body>
</html>

controller.js

var myApp = angular.module('myApp', []);
myApp.controller('AppCtrl', ['$scope', '$http', function($scope, $http) {
    console.log("Hello World from controller");


var refresh = function() {
  $http.get('/meibanlist').success(function(response) {
    console.log("I got the data I requested");
    $scope.meibanlist = response;
    $scope.contact = "";
  });
};

refresh();

$scope.addCollection = function() {
  console.log($scope.contact);
  $http.post('/meibanlist', $scope.contact).success(function(response) {
    console.log(response);
    refresh();
  });
};

$scope.remove = function(id) {
  console.log(id);
  $http.delete('/meibanlist/' + id).success(function(response) {
    refresh();
  });
};

$scope.edit = function(id) {
  console.log(id);
  $http.get('/meibanlist/' + id).success(function(response) {
    $scope.contact = response;
  });
};  

$scope.update = function() {
  console.log($scope.contact._id);
  $http.put('/meibanlist/' + $scope.contact._id, $scope.contact).success(function(response) {
    refresh();
  })
};

$scope.deselect = function() {
  $scope.contact = "";
}

}]);

【问题讨论】:

  • 您为什么要在一个结果 (Average) 中对两个单独的字段($length$breadth)进行平均?
  • 这只是一个示例。我有一个完全不同的领域。我只需要放一些东西来区分。实际上是价格和数量
  • 但关键是您正试图神奇地将这两个字段合二为一。你想要平均价格吗?数量的平均值?这两个值的乘积的平均值?或者完全是其他东西的平均值?
  • 我需要找到平均价格。
  • 那么我想你会想要像{"Average": {$avg: ["$price"]}}这样的东西。

标签: javascript node.js mongodb mongodb-query aggregation-framework


【解决方案1】:

正如文档指出的那样,$avg 运算符在两个管道步骤中工作,即 $project$group 阶段.我相信您需要在 $group 管道中使用 $avg 累加器,因为您想要所有价格的平均值。

$group 阶段使用时,$avg 返回将指定表达式应用于由_id 字段指出的共享同一组的一组文档。

_id 字段是必填的;但是,由于您要计算所有输入文档作为一个整体的累积平均值,您可以指定 _id 值为 null

db.database.aggregate([
    {
        "$group": {
            "_id": null,
            "Average": { "$avg": "$price" } 
        }
    }
], function(err, results){
    if (err !results) console.log ("record not found");
    else console.log(results[0]["Average"]);        
});

整理来自 cmets 的信息以及对您问题的后续更新,您遇到了一个障碍,因为您的值不是数字,因此 $avg 不起作用。

您需要使用 update() 方法将字符串值转换为数字值。这里的概念是loop through your collection with a cursor,对于光标内的每个文档,使用 $set 使用来自 parseInt() 的新值更新文档。

假设你的集合不是那么庞大,上面的直觉可以使用游标的 forEach() 方法来迭代它并更新集合中符合特定条件的每个文档。

以下 mongo shell 演示重点介绍了这种适用于小型数据集的方法:

mongo shell

db.meibanlist.find({"air_temperature": { "$exists": true, "$type": 2 }})
    .snapshot()
    .forEach(function(doc){ 
        var newTemp = parseInt(doc.air_temperature, 10);
        db.meibanlist.updateOne(
            { "_id": doc._id },
            { "$set": { "air_temperature": newTemp } }
        );
    });

现在为了提高性能,尤其是在处理大型集合时,请利用 Bulk() API 批量更新集合。

与上述操作相比,这非常有效,因为使用批量 API,您将分批将操作发送到服务器(例如,批量大小为 500),这会给您带来更好的效果 性能,因为您不会向服务器发送每个请求,而是每 500 个请求发送一次,从而使您的更新更加高效和快捷。

以下示例演示如何使用 MongoDB 版本 &gt;= 2.6&lt; 3.2 中提供的 Bulk() API。

mongo shell

var bulkUpdateOps = db.meibanlist.initializeUnOrderedBulkOp(),   
    counter = 0;

db.meibanlist.find({"air_temperature": { "$exists": true, "$type": 2 }})
    .snapshot()
    .forEach(function(doc){ 
        var newTemp = parseInt(doc.air_temperature, 10);
        bulkUpdateOps.find({ "_id": doc._id })
            .update({ "$set": { "air_temperature": newTemp } })

        counter++;  // increment counter for batch limit
        if (counter % 500 === 0) { 
            // execute the bulk update operation in batches of 1000
            bulkUpdateOps.execute(); 
            // Re-initialize the bulk update operations object
            bulkUpdateOps = db.meibanlist.initializeUnOrderedBulkOp();
        } 
})

// Clean up remaining operation in the queue
if (counter % 500 !== 0) { bulkUpdateOps.execute(); }

下一个示例适用于 MongoDB 版本 3.2 和更新的版本,该版本自 deprecated 开始使用 Bulk() API,并使用 bulkWrite() 提供了一组更新的 API。

它使用与上述相同的游标,但使用相同的 forEach() 游标方法创建具有批量操作的数组,以将每个批量写入文档推送到数组。因为写命令可以接受不超过 1000 个操作, 您需要将您的操作分组为最多 1000 个操作,并在循环达到 1000 次迭代时重新初始化数组。如果您的集合很大,上面的计数器变量可以有效地管理您的批量更新。它允许您批量更新操作并以 500 个批次将写入发送到服务器,这可以为您提供更好的性能,因为您不会将每个请求都发送到服务器,而是每 500 个请求发送一次。

对于批量操作,MongoDB 将默认的内部限制为每批 1000 次操作,因此选择 500 个文档是好的,因为您可以控制批量大小,而不是让 MongoDB 强加默认值,即对于较大的操作> 1000 个文档的大小。

var cursor = db.meibanlist.find({"air_temperature": { "$exists": true, "$type": 2 }}),
    bulkUpdateOps = [];

cursor.forEach(function(doc){ 
    var newTemp = parseInt(doc.air_temperature, 10);
    bulkUpdateOps.push({ 
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": { "$set": { "air_temperature": newTemp } }
         }
    });

    if (bulkUpdateOps.length === 500) {
        db.meibanlist.bulkWrite(bulkUpdateOps);
        bulkUpdateOps = [];
    }
});         

if (bulkUpdateOps.length > 0) { db.meibanlist.bulkWrite(bulkUpdateOps); }

更新集合后,您就可以愉快地在 aggregate() 函数中应用 $avg 运算符。以下将返回所有合并文档的平均 air_temperature

db.meibanlist.aggregate([
    {
        "$group": {
            "_id": null,
            "averageAirTemperature": { "$avg": "$air_temperature" } 
        }
    }
], function(err, results){
    if (err !results) console.log ("record not found");
    else console.log(results[0]["averageAirTemperature"]);        
}); 

更新

上面的代码在 mongo shell 中工作。对于 mongojs 版本,将操作封装在可以在需要时调用的函数中。例如,您可以将批量更新逻辑放在它自己的函数 bulkUpdate 中:

function bulkUpdate(collection, callback)
    var ops = [];

    collection.find({
        "air_temperature": { "$exists": true, "$type": 2 }
    }, function (err, docs) {
        docs.forEach(function(doc){ 
            ops.push({ 
                "updateOne": {
                    "filter": { "_id": doc._id },
                    "update": { 
                        "$set": { 
                            "air_temperature": parseInt(doc.air_temperature, 10),
                            "water_temperature": parseInt(doc.water_temperature, 10),
                            "heat_temperature": parseInt(doc.heat_temperature, 10),
                            "room_temperature": parseInt(doc.room_temperature, 10)
                        } 
                    }
                 }
            });

            if (ops.length === 500) {
                collection.bulkWrite(bulkUpdateOps, function(err, result){
                    if (err) callback(err);
                    callback(null, result);
                });
                ops = [];
            }
        });         

        if (ops.length > 0) { 
            collection.bulkWrite(ops, function(err, result){
                callback(null, result);
            }); 
        }
    });
}

以及获取平均温度的逻辑:

function getAverageTemps(collection, callback) {
    collection.aggregate([
        {
            "$group": {
                "_id": null,
                "averageAirTemperature": { "$avg": "$air_temperature" },
                "averageWaterTemperature": { "$avg": "$water_temperature" },
                "averageHeatTemperature": { "$avg": "$heat_temperature" },
                "averageRoomTemperature": { "$avg": "$room_temperature" }
            }
        }
    ], function(err, results){
        if (err || !results) callback(err);
        else callback(null, results);        
    }); 
}

然后您可以按如下方式调用它,例如,您可以在 API 中放置一个端点来检索平均温度:

app.get('/meibanlist/averagetemps', function (req, res) {
    getAverageTemps(db.meibanlist, function (err, temps) {
        if (err || !temps) console.log ("record not found");
        else res.json(temps);
    });
});

【讨论】:

  • 当我用我的代码运行你的代码时。输出显示:
  • 服务器在 3000 端口上运行
  • 0。他们刚刚显示 0。你能帮帮我吗?这是因为我没有在 $price 中插入任何数字,因为我做了
  • 我在数据库中有记录。但它仍然显示 0
  • 是的。数据库和集合具有相同的名称。还是可以同名?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-03
  • 2022-09-23
  • 2018-08-30
  • 1970-01-01
相关资源
最近更新 更多