Вы хотите $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
равно , необязательно , используемое какспособ взять существующие свойства объекта для каждого элемента (price
и quantity
) и включить их в выходной объект.Альтернатива состоит в том, чтобы вручную указывать свойства в выходных объектах для каждого элемента, как показано.
Конечно, для всего документа то же самое, по сути, предоставляется, но просто возвращает одно значениеи передаем это оператору $sum
для "суммирования" результатов
Все это говорит о том, что нет ничего плохого в простой манипуляции с результатом post returnс сервера:
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 Documents и, таким образом, позволяет манипулировать структурой возвращаемых результатов.
Вот полный список обоих подходов:
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
}
]
}