iT邦幫忙

0

mongodb Aggregation查詢使用

請問各位大大
我現在想要使用mongo的aggregation
來實作avg這項功能
我原本的code是用find的方式

router.get('/test', function(req, res,next){
  data.findById(req.params.id,function(err, test){
      res.render('test', {
        test:test.score
      });

我現在想要改成aggregation 請問該如何是好呢
查了滿多aggregation使用方法都是直接使用沒有看到這種的

我還有試一種方法就是前端拿到資料後用JS來算
不過好像沒辦法...

dragonH iT邦超人 5 級 ‧ 2019-10-24 22:45:18 檢舉
你的 code 沒貼好
改好了
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

1
dragonH
iT邦超人 5 級 ‧ 2019-10-24 22:59:09

原始資料

/* 1 */
{
    "_id" : ObjectId("5db1b9fabfe18dffc8f7fc68"),
    "name" : "Jack",
    "quizs" : [ 
        10, 
        20, 
        30
    ]
}

/* 2 */
{
    "_id" : ObjectId("5db1b9fabfe18dffc8f7fc6a"),
    "name" : "Amy",
    "quizs" : [ 
        30, 
        40, 
        50
    ]
}

/* 3 */
{
    "_id" : ObjectId("5db1b9fabfe18dffc8f7fc6c"),
    "name" : "Ryan",
    "quizs" : [ 
        25, 
        35, 
        45
    ]
}

code

db.getCollection('ithelp').aggregate([
    {
        $addFields: {
            avg: {
                $avg: '$quizs'
            }
        }
    },
    {
        $match: {
            avg: {
                $gte: 30
            }
        }
    }
]);

result

/* 1 */
{
    "_id" : ObjectId("5db1b9fabfe18dffc8f7fc6a"),
    "name" : "Amy",
    "quizs" : [ 
        30, 
        40, 
        50
    ],
    "avg" : 40.0
}

/* 2 */
{
    "_id" : ObjectId("5db1b9fabfe18dffc8f7fc6c"),
    "name" : "Ryan",
    "quizs" : [ 
        25, 
        35, 
        45
    ],
    "avg" : 35.0
}

說明

透過 $avg 算出 quizs 的平均

並透過 $addFields 新增一個 avg column

再透過 $match 找尋符合條件的資料 ({avg: { $gte: 30 } })

所以你原本的 query 可以寫在 $match

我還有試一種方法就是前端拿到資料後用JS來算
不過好像沒辦法...

說 mongodb 沒辦法可能有些狀況還有可能

說 js 沒辦法應該不太可能

而且也應該是在後端算

看更多先前的回應...收起先前的回應...

不過這樣算完之後不是整個data都算嗎
所以我findById是要寫在 $match裡這樣嗎

dragonH iT邦超人 5 級 ‧ 2019-10-24 23:39:35 檢舉

shrine90459

不過這樣算完之後不是整個data都算嗎

是阿

aggregate 是會照順序執行的

所以你不想整個算

你可以調整裡面的順序

e.g.

db.getCollection('ithelp').aggregate([{ $match: { name: { $ne: 'Jack'}}}, { $addFields: { avg: { $avg: '$quizs'}}} ]);

上面的 code 會先找到 name 不等於 Jack

然後才算平均、新增一個 avg column

所以我findById是要寫在 $match裡這樣嗎

對的

dragonH
請問一下 我現在是先update資料然後把資料存進array裡
再把aggregate寫在update成功後執行那邊
不過好像都沒反應
這樣的寫法是不行的嗎

dragonH iT邦超人 5 級 ‧ 2019-10-27 19:50:01 檢舉

shrine90459

貼個 code 吧

dragonH抱歉最近忙考試

name:String,
address:String,
  comments:[{
    content:String,
    point:[Number],
  
  }], 這是database
  
router.post('/comment',  (req, res) => {
  const { postid, content,points } = req.body;
  const point=[points,point1,point2];
  const obj={content:content,point:point};
  Article.updateMany({ _id: postid }, { $push: { comments: obj }  },function(err,house){
    if(err){
      throw err;
    }else{

      Article.aggregate([{ $addFields: { avg: { $avg: '$point'}}} ]);

      res.redirect('/articles/house')

      console.log();

    }
  }); 

大概是這樣
現在point裡面可以存數值了我想把它做平均
但都沒反應

dragonH iT邦超人 5 級 ‧ 2019-10-31 19:35:45 檢舉

shrine90459

有任何錯誤嗎?

另外

沒看到你去用他 return 的值呀

dragonH
我是直接去資料庫看

內容
    comments:Array
      0:Object
       point:[4,5,6]
       _id:objectId("")
       content:""
      1:Object
       point:[1,2,3]
       _id:objectId("")
       content:""

但都沒新增FIELD

dragonH iT邦超人 5 級 ‧ 2019-10-31 20:49:27 檢舉

shrine90459

你是指沒看到 avg 這欄位嗎

這是當然的阿

他只是在你這次的查詢中新增一個 avg 欄位

並不是真的新增欄位在你的 database

要新增的話

一樣用 update

dragonH

      Article.aggregate([{ $addFields: { avg: { $avg: '$point'}}} ],(err,result)=>{
        Article.updateMany({_id: postid},{$push:{comments:result}});
        console.log(result);
      });

剛剛查一下才發現原來aggregate是查詢的語法
現在有點沒方向 我update應該是要把aggregate的結果update上去對吧
但我發現我這樣打的result會有每一筆資料

dragonH iT邦超人 5 級 ‧ 2019-10-31 22:17:31 檢舉

shrine90459

是的

不過你可以先看一下

是不是有必要存這平均值在 database

還是說這平均只有 query 時 會用到

只有 query 時 會用到

那就 query 時 用 aggregate 就好

不用去 update

如果你真要寫進 database 的話

其實也不用先用 aggregate 算平均

可以用一般的 query 把資料拿回來後

在後端算完平均在 update

然後你的寫法應該有點問題

這樣寫他應該會把整個 result push 到 comments裡吧

dragonH
因為我平均可能會做兩次
自從資料庫越來越多層後 思緒也就開始越來越亂了/images/emoticon/emoticon06.gif
就是把每個point都先平均 然後再平均一次
這樣有什麼比較簡單的方法嗎
還有就是 要怎麼單獨把aggregate後 想要的東西push到comments裡
剛剛有試過把 comments:avg這樣 不過他直接說undefined

dragonH iT邦超人 5 級 ‧ 2019-10-31 22:43:37 檢舉

shrine90459

aggregate 跟一般的 query 一樣

也是會回傳給你一個 data array

接下來就是一般的 array 的操作呀

可以用 Array.map 重新組一個含有平均值的 data array

再用迴圈根據每筆 data 的 objectID 來更新

map 參考

dragonH

const data =[{"post":{"point":[5],"_id":"5dbaa1d3125dcd4200c41133","content":"123"},"avg":5},{"post":{"point":[6,1,3],"_id":"5dbfdf105012092068f9b2af","content":"123"},"avg":3.3333333333333335}]
;
let sum = 0;
let itemsFound = 0;
const len = data.length;
let item = null;
for (let i = 0; i < len; i++) {
    item = data[i];
    if (item.found) {
        sum = item.avg + sum;
        itemsFound = itemsFound + 1;
    }
}
const avg = sum / itemsFound;
console.log("avg:", avg);

請問一下大大
我現在想做一個總平均的
不過我算出來都是NaN
請問是哪邊算錯了嗎
我查出來的object是


Object {
  avg: 5,
  post: Object {
    _id: "5dbaa1d3125dcd4200c41133",
    content: "123",
    point: [5]
  }
}

dragonH iT邦超人 5 級 ‧ 2019-11-04 19:30:31 檢舉

shrine90459

把值印出來看

console.log(sum, itemsFound)

會發現兩個都是 0

js 0/0 本來就會等於 NaN (Not a Number)

兩個都等於 0 的原因是因為

我不知道你的 item.found 是什麼

估計電腦也不知道

所以就沒執行 if () 裡面的 code

最後兩個都是預設值 0

另外如果是不同的問題

建議開另一個發問

不然別人很難爬文 /images/emoticon/emoticon13.gif

我要發表回答

立即登入回答