Mongodb: Aggregation/Pipilines 根据嵌套文档连接两个模式的数据
Mongodb: Aggregation/Pipilines to join two schema's data based on nested documents
这是我的架构:
var mongoose = require('mongoose');
var EventSchema = new mongoose.Schema({
name: {type:String},
start_date: {type:Date},
duration: {type:String},
event_id:{type: mongoose.Schema.Types.ObjectId}
});
var Project = new mongoose.Schema({
name: {
type: String,
required: '{PATH} is required!'
},
user_id:{
type: mongoose.Schema.Types.ObjectId,
required:'{PATH} is required!'
},
client: {type: Object},
no_of_events: {type: String, required: '{PATH} is required!'},
start_date:{type:Date, required: '{PATH} is required!'},
end_date:{type:Date, required: '{PATH} is required!'},
budget:{type: String},
groom:{type:Object},
bride:{type:Object},
events:[EventSchema],
status:{type:Number,Default:1}, //0=Inactive, 1=Active, 2=Completed
created_at: { type: Date, default: Date.now },
updated_at: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Project', Project);
在创建项目时,我正在添加多个事件。因此在 'events' 中有对象数组。这是示例数据:
{
"_id": {
"$oid": "5b43582a716d9a4e96345f4a"
},
"bride": {
"city": "Hyderabad",
"phone": "09876543211",
"dob": "1993-05-06T18:30:00.000Z",
"name": "Shriya Bhupal"
},
"groom": {
"city": "Hyderabad",
"phone": "09876543211",
"dob": "1993-08-09T18:30:00.000Z",
"name": "Anindith Reddy"
},
"client": {
"name": "Apoorva Pagar"
},
"end_date": {
"$date": "2018-07-22T18:30:00.000Z"
},
"start_date": {
"$date": "2018-07-10T18:30:00.000Z"
},
"no_of_events": "4",
"user_id": {
"$oid": "5b126966bcc8072e526346ad"
},
"name": "Big Fat Wedding",
"updated_at": {
"$date": "2018-07-09T12:42:18.263Z"
},
"created_at": {
"$date": "2018-07-09T12:42:18.263Z"
},
"events": [
{
"name": "Engagement",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-11T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4b"
}
},
{
"name": "Mehndi",
"event_id": {
"$oid": "5b3b23b314cdec23c19c034d"
},
"start_date": {
"$date": "2018-07-12T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4c"
}
},
{
"name": "Sangeet",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-17T18:30:00.000Z"
},
"duration": "2 Days",
"_id": {
"$oid": "5b43582a716d9a4e96345f4d"
}
}
],
"__v": 0
}
这里的情况是,如果我从默认事件中添加事件,则有event_id,否则没有event_id。因此,如果我添加,默认事件将保存在另一个名为 'defaultevents' 的模式中。现在,如果 event_id 存在于数据中,我想在每个事件对象中添加 eventdetail。
{"events": [
{
// it should include event_detail:{ details about event from defaultevents}
"name": "Engagement",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-11T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4b"
}
},
{
// it should not include event_details as there is no event_id
"name": "Mehndi",
"start_date": {
"$date": "2018-07-12T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4c"
}
}
]
}
DefaultEvents 相同数据:
{
"_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"image": "https://dostbucket.s3.us-east-2.amazonaws.com/events/1530601666360mehndi.svg",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"name": "Sangeet",
"updated_at": {
"$date": "2018-07-03T07:18:01.166Z"
},
"created_at": {
"$date": "2018-07-03T07:18:01.166Z"
},
"__v": 0
}
mongod 版本:3.4.7 (MMAPv1)
您可以使用 $lookup
聚合来做到这一点
db.colletion.aggregate([
{ "$unwind": "$events" },
{ "$lookup": {
"from": DefaultEventSchema.collection.name,
"localField": "events.event_id",
"foreignField": "_id",
"as": "events.event_id"
}},
{ "$unwind": { "path": "$events.event_id", "preserveNullAndEmptyArrays": true } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" },
"bride": { "$first": "$bride" },
"groom": { "$first": "$groom" },
"client": { "$first": "$client" },
"end_date": { "$first": "$end_date" },
"no_of_events": { "$first": "$no_of_events" },
"name": { "$first": "$name" },
"created_at": { "$first": "$created_at" },
"updated_at": { "$first": "$updated_at" }
}}
])
Simerjit,因为 mongo 不提供 $(定位运算符)来搜索和更新对象数组(如果我们尝试只更新第一个对象 read more here),我们需要按以下方式进行:
var products = Product.find();
products.forEach(product => {
product.events.forEach(event => {
if ('event_id' in event) {
var product = product.events[event];
product.event.event_details = 'my details';
product.save(product);
}
});
});
您可能需要对代码进行一些调整,但我希望它能让您了解如何实现您的目标。
您可以在 3.6 中使用以下聚合。
ProjectModel.aggregate([
{"$unwind":"$events"},
{"$lookup":{
"from": "event", //name of the foreign collection not model or schema name
"localField": "events.event_id",
"foreignField": "_id",
"as": "events.eventdetail"
}},
{"$unwind":{"path":"$events.eventdetail", "preserveNullAndEmptyArrays":true }}, // Keep non matching events
{"$group":{
"_id": "$_id",
"events": { "$push": "$events" },
"data": { "$first": "$$ROOT" } // $$ROOT to keep the entire data
}},
{"$addFields":{"data.events":"$events", "events":0}}, // Replace the events with grouped events.
{"$replaceRoot":{"newRoot":"$data"}}
])
这是我的架构:
var mongoose = require('mongoose');
var EventSchema = new mongoose.Schema({
name: {type:String},
start_date: {type:Date},
duration: {type:String},
event_id:{type: mongoose.Schema.Types.ObjectId}
});
var Project = new mongoose.Schema({
name: {
type: String,
required: '{PATH} is required!'
},
user_id:{
type: mongoose.Schema.Types.ObjectId,
required:'{PATH} is required!'
},
client: {type: Object},
no_of_events: {type: String, required: '{PATH} is required!'},
start_date:{type:Date, required: '{PATH} is required!'},
end_date:{type:Date, required: '{PATH} is required!'},
budget:{type: String},
groom:{type:Object},
bride:{type:Object},
events:[EventSchema],
status:{type:Number,Default:1}, //0=Inactive, 1=Active, 2=Completed
created_at: { type: Date, default: Date.now },
updated_at: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Project', Project);
在创建项目时,我正在添加多个事件。因此在 'events' 中有对象数组。这是示例数据:
{
"_id": {
"$oid": "5b43582a716d9a4e96345f4a"
},
"bride": {
"city": "Hyderabad",
"phone": "09876543211",
"dob": "1993-05-06T18:30:00.000Z",
"name": "Shriya Bhupal"
},
"groom": {
"city": "Hyderabad",
"phone": "09876543211",
"dob": "1993-08-09T18:30:00.000Z",
"name": "Anindith Reddy"
},
"client": {
"name": "Apoorva Pagar"
},
"end_date": {
"$date": "2018-07-22T18:30:00.000Z"
},
"start_date": {
"$date": "2018-07-10T18:30:00.000Z"
},
"no_of_events": "4",
"user_id": {
"$oid": "5b126966bcc8072e526346ad"
},
"name": "Big Fat Wedding",
"updated_at": {
"$date": "2018-07-09T12:42:18.263Z"
},
"created_at": {
"$date": "2018-07-09T12:42:18.263Z"
},
"events": [
{
"name": "Engagement",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-11T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4b"
}
},
{
"name": "Mehndi",
"event_id": {
"$oid": "5b3b23b314cdec23c19c034d"
},
"start_date": {
"$date": "2018-07-12T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4c"
}
},
{
"name": "Sangeet",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-17T18:30:00.000Z"
},
"duration": "2 Days",
"_id": {
"$oid": "5b43582a716d9a4e96345f4d"
}
}
],
"__v": 0
}
这里的情况是,如果我从默认事件中添加事件,则有event_id,否则没有event_id。因此,如果我添加,默认事件将保存在另一个名为 'defaultevents' 的模式中。现在,如果 event_id 存在于数据中,我想在每个事件对象中添加 eventdetail。
{"events": [
{
// it should include event_detail:{ details about event from defaultevents}
"name": "Engagement",
"event_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"start_date": {
"$date": "2018-07-11T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4b"
}
},
{
// it should not include event_details as there is no event_id
"name": "Mehndi",
"start_date": {
"$date": "2018-07-12T18:30:00.000Z"
},
"duration": "1 Day",
"_id": {
"$oid": "5b43582a716d9a4e96345f4c"
}
}
]
}
DefaultEvents 相同数据:
{
"_id": {
"$oid": "5b3b232914cdec23c19c034c"
},
"image": "https://dostbucket.s3.us-east-2.amazonaws.com/events/1530601666360mehndi.svg",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"name": "Sangeet",
"updated_at": {
"$date": "2018-07-03T07:18:01.166Z"
},
"created_at": {
"$date": "2018-07-03T07:18:01.166Z"
},
"__v": 0
}
mongod 版本:3.4.7 (MMAPv1)
您可以使用 $lookup
聚合来做到这一点
db.colletion.aggregate([
{ "$unwind": "$events" },
{ "$lookup": {
"from": DefaultEventSchema.collection.name,
"localField": "events.event_id",
"foreignField": "_id",
"as": "events.event_id"
}},
{ "$unwind": { "path": "$events.event_id", "preserveNullAndEmptyArrays": true } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" },
"bride": { "$first": "$bride" },
"groom": { "$first": "$groom" },
"client": { "$first": "$client" },
"end_date": { "$first": "$end_date" },
"no_of_events": { "$first": "$no_of_events" },
"name": { "$first": "$name" },
"created_at": { "$first": "$created_at" },
"updated_at": { "$first": "$updated_at" }
}}
])
Simerjit,因为 mongo 不提供 $(定位运算符)来搜索和更新对象数组(如果我们尝试只更新第一个对象 read more here),我们需要按以下方式进行:
var products = Product.find();
products.forEach(product => {
product.events.forEach(event => {
if ('event_id' in event) {
var product = product.events[event];
product.event.event_details = 'my details';
product.save(product);
}
});
});
您可能需要对代码进行一些调整,但我希望它能让您了解如何实现您的目标。
您可以在 3.6 中使用以下聚合。
ProjectModel.aggregate([
{"$unwind":"$events"},
{"$lookup":{
"from": "event", //name of the foreign collection not model or schema name
"localField": "events.event_id",
"foreignField": "_id",
"as": "events.eventdetail"
}},
{"$unwind":{"path":"$events.eventdetail", "preserveNullAndEmptyArrays":true }}, // Keep non matching events
{"$group":{
"_id": "$_id",
"events": { "$push": "$events" },
"data": { "$first": "$$ROOT" } // $$ROOT to keep the entire data
}},
{"$addFields":{"data.events":"$events", "events":0}}, // Replace the events with grouped events.
{"$replaceRoot":{"newRoot":"$data"}}
])