我正在将猫鼬(5.x.x)与填充函数一起使用引用填充数组(餐)。 在该数组中,我需要将价格(填充结果)和数量(基本模式的一部分)相乘。
我的填充结果如下:
{
"_id": "5bea354235711482876f8fa8",
"meals": [
{
"meal": {
"_id": "5be93c7074488c77b10fba00",
"name": "Chicken Nuggets",
"price": 3
},
"quantity": 12
},
{
"meal": {
"_id": "5be93c9274488c77b10fba01",
"name": "Beef Burger",
"price": 6
},
"quantity": 4
}
],
"__v": 0
}
目标是在此结果集中添加“总价”,但我找不到任何优雅的方法。 我想避免在查询之外处理数据。
感谢您的帮助,
答案 0 :(得分:2)
因此,有两种方法可以做到这一点。
您基本上想从其他集合中获取“相关”数据,并将其与现有数组项“合并”。由于$lookup
不能做到这一点,因此您实际上不能只是“瞄准”现有的数组,但是它可以编写另一个数组,然后可以将它们“合并”在一起:
let result1 = await Order.aggregate([
{ "$lookup": {
"from": Meal.collection.name,
"foreignField": "_id",
"localField": "meals.meal",
"as": "mealitems"
}},
{ "$project": {
"meals": {
"$map": {
"input": "$meals",
"in": {
"meal": {
"$arrayElemAt": [
"$mealitems",
{ "$indexOfArray": [ "$mealitems._id", "$$this.meal" ] }
]
},
"quantity": "$$this.quantity",
"totalPrice": {
"$multiply": [
{ "$arrayElemAt": [
"$mealitems.price",
{ "$indexOfArray": [ "$mealitems._id", "$$this.meal" ] }
]},
"$$this.quantity"
]
}
}
}
}
}},
{ "$addFields": {
"totalOrder": {
"$sum": "$meals.totalPrice"
}
}}
]);
这基本上是由于"mealitems"
的结果而产生另一个数组$lookup
,然后使用$map
来处理原始文档数组并 transpose 返回内容数组的每个项目都重新放入结构中。
您可以结合$arrayElemAt
和$indexOfArray
进行此操作,以在此处找到要转置的匹配项。
使用$multiply
的其他计算元素也有一些“算术”,甚至还有使用$addFields
的附加$sum
阶段来“加总”以给出总体“顺序”总计”。
您“可以”在$project
阶段做所有的数学运算(之所以使用它,是因为我们不希望"mealitems"
内容。但这涉及更多一点,您可能想使用$let
用于数组匹配,因此您不必重复太多代码。
如果您确实愿意,甚至可以使用$lookup
的“子管道”形式。代替使用$map
来更改返回文档的操作是在返回数组“ 之前内部”完成,在返回结果之前,通过将原始文档数组通过{ {1}}参数:
let
无论哪种形式,这基本上都是// Aggregate with $lookup - sub-pipeline
let result2 = await Order.aggregate([
{ "$lookup": {
"from": Meal.collection.name,
"let": { "meals": "$meals" },
"pipeline": [
{ "$match": {
"$expr": {
"$in": [ "$_id", "$$meals.meal" ]
}
}},
{ "$replaceRoot": {
"newRoot": {
"meal": "$$ROOT",
"quantity": {
"$arrayElemAt": [
"$$meals.quantity",
{ "$indexOfArray": [ "$$meals.meal", "$_id" ] }
]
},
"totalPrice": {
"$multiply": [
{ "$arrayElemAt": [
"$$meals.quantity",
{ "$indexOfArray": [ "$$meals.meal", "$_id" ] }
]},
"$price"
]
}
}
}}
],
"as": "meals"
}},
{ "$addFields": {
"totalOrder": {
"$sum": "$meals.totalPrice"
}
}}
]);
通过“合并”内容的幕后寓言,但是当然使用$lookup
聚合只是一个请求。
或者,您可以只在JavaScript中操纵结果结构。它已经存在,而您真正需要的只是populate()
,以便能够更改生成的对象:
lean()
它看起来很简单,基本上是同一件事,除了已经为您完成了“合并”,当然这是对服务器的两个请求,以便返回所有数据。
作为可复制的完整列表:
// Populate and manipulate
let result3 = await Order.find().populate('meals.meal').lean();
result3 = result3.map(r =>
({
...r,
meals: r.meals.map( m =>
({
...m,
totalPrice: m.meal.price * m.quantity
})
),
totalOrder: r.meals.reduce((o, m) =>
o + (m.meal.price * m.quantity), 0
)
})
);
返回的结果如下:
const { Schema } = mongoose = require('mongoose');
// Connection
const uri = 'mongodb://localhost:27017/menu';
const opts = { useNewUrlParser: true };
// Sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);
// Schema defs
const mealSchema = new Schema({
name: String,
price: Number
});
const orderSchema = new Schema({
meals: [
{
meal: { type: Schema.Types.ObjectId, ref: 'Meal' },
quantity: Number
}
]
});
const Meal = mongoose.model('Meal', mealSchema);
const Order = mongoose.model('Order', orderSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// main
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
);
// Set up data
let [Chicken, Beef] = await Meal.insertMany(
[
{ name: "Chicken Nuggets", price: 3 },
{ name: "Beef Burger", price: 6 }
]
);
let order = await Order.create({
meals: [
{ meal: Chicken, quantity: 12 },
{ meal: Beef, quantity: 4 }
]
});
// Aggregate with $lookup - traditional
let result1 = await Order.aggregate([
{ "$lookup": {
"from": Meal.collection.name,
"foreignField": "_id",
"localField": "meals.meal",
"as": "mealitems"
}},
{ "$project": {
"meals": {
"$map": {
"input": "$meals",
"in": {
"meal": {
"$arrayElemAt": [
"$mealitems",
{ "$indexOfArray": [ "$mealitems._id", "$$this.meal" ] }
]
},
"quantity": "$$this.quantity",
"totalPrice": {
"$multiply": [
{ "$arrayElemAt": [
"$mealitems.price",
{ "$indexOfArray": [ "$mealitems._id", "$$this.meal" ] }
]},
"$$this.quantity"
]
}
}
}
}
}},
{ "$addFields": {
"totalOrder": {
"$sum": "$meals.totalPrice"
}
}}
]);
log(result1);
// Aggregate with $lookup - sub-pipeline
let result2 = await Order.aggregate([
{ "$lookup": {
"from": Meal.collection.name,
"let": { "meals": "$meals" },
"pipeline": [
{ "$match": {
"$expr": {
"$in": [ "$_id", "$$meals.meal" ]
}
}},
{ "$replaceRoot": {
"newRoot": {
"meal": "$$ROOT",
"quantity": {
"$arrayElemAt": [
"$$meals.quantity",
{ "$indexOfArray": [ "$$meals.meal", "$_id" ] }
]
},
"totalPrice": {
"$multiply": [
{ "$arrayElemAt": [
"$$meals.quantity",
{ "$indexOfArray": [ "$$meals.meal", "$_id" ] }
]},
"$price"
]
}
}
}}
],
"as": "meals"
}},
{ "$addFields": {
"totalOrder": {
"$sum": "$meals.totalPrice"
}
}}
]);
log(result2);
// Populate and manipulate
let result3 = await Order.find().populate('meals.meal').lean();
result3 = result3.map(r =>
({
...r,
meals: r.meals.map( m =>
({
...m,
totalPrice: m.meal.price * m.quantity
})
),
totalOrder: r.meals.reduce((o, m) =>
o + (m.meal.price * m.quantity), 0
)
})
);
log(result3);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()