用猫鼬查询计算交易项目总计和交易项目

时间:2019-09-28 03:18:22

标签: node.js mongodb mongoose aggregation-framework

在猫鼬中,我有一个交易集合。每个交易都有一个类似这样的项目列表:

var transactionItemSchema = new mongoose.Schema({
    productId: String,
    quantity: Number,
    price: Number
});

var transactionSchema = new mongoose.Schema({
    details: String,
    items: [transactionItemSchema ],
}, {
    timestamps: true
});

我需要通过乘以价格*数量并四舍五入两位小数来计算每个项目的总价值,但是我还需要通过对交易中所有项目的总和求和来获得交易总和。因此,例如,如果我在mongo中有此交易记录:

[{
  details: 'First Transaction',
  items: [{
      price: 5.2,
      quantity: 2
    }, {
      price: 4,
      quantity: 3
    }]
  }, {
  details: 'First Transaction',
  items: [{
      price: 0.333,
      quantity: 3
    }]
  }]

在进行交易时返回如下信息:

[{
  total: 22.40,
  details: 'First Transaction',
  items: [{
    price: 5.2,
    quantity: 2,
    total: 10.40
  }, {
    price: 4,
    quantity: 3,
    total: 12.00
  }]
}, {
  total: 1.00,
  details: 'Second Transaction',
  items: [{
    price: 0.333,
    quantity: 3,
    total: 1.00
  }]
}]

有没有办法通过猫鼬的一些聚集来实现这一目标?

1 个答案:

答案 0 :(得分:1)

您要在这里$map$multiply

假设模型是调用Transaction

Transaction.aggregate([
  { "$addFields": {
    "items": {
      "$map": {
        "input": "$items",
        "in": {
          "$mergeObjects": [
            "$$this",
            { "total": { "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2] } }
          ]
        }
      }
    }
  }}
])

或者没有$mergeObjects

Transaction.aggregate([
  { "$addFields": {
    "total": {
      "$sum": {
        "$map": {
          "input": "$items",
          "in": {
            "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2]
          }
        }
      }
    },
    "items": {
      "$map": {
        "input": "$items",
        "in": {
          "price": "$$this.price",
          "quantity": "$$this.quantity",
          "total": { "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2] }
        }
      }
    }
  }}
])

$map运算符实际上用于数组转换,其中您提供input数组和一个表达式,以应用于定义对象输出的每个数组元素对于每个元素。在这里,$multiply与两个参数一起应用于“相乘”以获得结果。

$mergeObjects可选,用作获取每个元素(pricequantity)的现有对象属性并将其包含在输出中的一种方法宾语。另一种方法是手动为每个元素在输出对象中指定属性,如图所示。

当然,对于 total总计,基本上会提供相同的内容,但是只是返回一个值并将其馈送到$sum运算符以“总计”结果

所有这些都说明,简单地处理服务器返回的结果 post 并没有错:

let results = await Transaction.find().lean();

// Then manipulate the items arrays

results = results.map(r =>
  ({
    ...r,
    total: r.items.reduce((o, i) =>
       o + parseFloat((i.price * i.quantity).toFixed(2)), 0),
    items: r.items.map(i =>
      ({ ...i, total: parseFloat((i.price * i.quantity).toFixed(2)) })
    )
  })
);

请仅在此处注意使用lean(),该方法将返回纯JavaScript对象而不是Mongoose文档,从而使您可以操纵返回结果的结构。


以下是这两种方法的完整清单:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/test';
const opts = { useNewUrlParser: true, useUnifiedTopology: true };

mongoose.Promise = global.Promise;

mongoose.set('debug', true);
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);

const transactionItemSchema = new Schema({
  productId: String,
  quantity: Number,
  price: Number
});

const transactionSchema = new Schema({
  details: String,
  items: [transactionItemSchema]
},{
  timestamps: true
});

const Transaction = mongoose.model('Transaction', transactionSchema);


const initialData = [
  {
    details: 'First Transaction',
    items: [
      { price: 5.2, quantity: 2 },
      { price: 4, quantity: 3 }
    ]
  },
  {
    details: 'Second Transaction',
    items: [
      { price: 0.333, quantity: 3 }
    ]
  }
];

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {
    const conn = await mongoose.connect(uri, opts);

    // Clean data
    await Promise.all(
      Object.values(conn.models).map(m => m.deleteMany())
    );

    await Transaction.insertMany(initialData);

    // Aggregate example

    let result1 = await Transaction.aggregate([
      { "$addFields": {
        "total": {
          "$sum": {
            "$map": {
              "input": "$items",
              "in": {
                "$round": [
                  { "$multiply": [ "$$this.price", "$$this.quantity" ] },
                  2
                ]
              }
            }
          }
        },
        "items": {
          "$map": {
            "input": "$items",
            "in": {
              "$mergeObjects": [
                "$$this",
                { "total": {
                  "$round": [
                    { "$multiply": [ "$$this.price", "$$this.quantity" ] },
                    2
                  ]
                }}
              ]
            }
          }
        }
      }}
    ]);

    log({ result1 });


    // Plain JavaScript example

    let result2 = await Transaction.find().lean();

    result2 = result2.map(r =>
      ({
        ...r,
        total: r.items.reduce((o, i) =>
           o + parseFloat((i.price * i.quantity).toFixed(2)), 0),
        items: r.items.map(i =>
          ({ ...i, total: parseFloat((i.price * i.quantity).toFixed(2)) })
        )
      })
    );

    log({ result2 });

  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})();

输出:

Mongoose: transactions.deleteMany({}, {})
Mongoose: transactions.insertMany([ { _id: 5d8f4dfcaf9f6a2f8ec28039, details: 'First Transaction', items: [ { _id: 5d8f4dfcaf9f6a2f8ec2803b, price: 5.2, quantity: 2 }, { _id: 5d8f4dfcaf9f6a2f8ec2803a, price: 4, quantity: 3 } ], __v: 0, createdAt: 2019-09-28T12:11:40.060Z, updatedAt: 2019-09-28T12:11:40.061Z }, { _id: 5d8f4dfcaf9f6a2f8ec2803c, details: 'Second Transaction', items: [ { _id: 5d8f4dfcaf9f6a2f8ec2803d, price: 0.333, quantity: 3 } ], __v: 0, createdAt: 2019-09-28T12:11:40.062Z, updatedAt: 2019-09-28T12:11:40.062Z } ], {})
Mongoose: transactions.aggregate([ { '$addFields': { total: { '$sum': { '$map': { input: '$items', in: { '$round': [ { '$multiply': [ '$$this.price', '$$this.quantity' ] }, 2 ] } } } }, items: { '$map': { input: '$items', in: { '$mergeObjects': [ '$$this', { total: { '$round': [ { '$multiply': [Array] }, 2 ] } } ] } } } } } ], {})
{
  "result1": [
    {
      "_id": "5d8f4dfcaf9f6a2f8ec28039",
      "details": "First Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803b",
          "price": 5.2,
          "quantity": 2,
          "total": 10.4
        },
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803a",
          "price": 4,
          "quantity": 3,
          "total": 12
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.060Z",
      "updatedAt": "2019-09-28T12:11:40.061Z",
      "total": 22.4
    },
    {
      "_id": "5d8f4dfcaf9f6a2f8ec2803c",
      "details": "Second Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803d",
          "price": 0.333,
          "quantity": 3,
          "total": 1
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.062Z",
      "updatedAt": "2019-09-28T12:11:40.062Z",
      "total": 1
    }
  ]
}
Mongoose: transactions.find({}, { projection: {} })
{
  "result2": [
    {
      "_id": "5d8f4dfcaf9f6a2f8ec28039",
      "details": "First Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803b",
          "price": 5.2,
          "quantity": 2,
          "total": 10.4
        },
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803a",
          "price": 4,
          "quantity": 3,
          "total": 12
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.060Z",
      "updatedAt": "2019-09-28T12:11:40.061Z",
      "total": 22.4
    },
    {
      "_id": "5d8f4dfcaf9f6a2f8ec2803c",
      "details": "Second Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803d",
          "price": 0.333,
          "quantity": 3,
          "total": 1
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.062Z",
      "updatedAt": "2019-09-28T12:11:40.062Z",
      "total": 1
    }
  ]
}