带有 Mongoose 子文档的聚合查询
An aggregate query with subdocuments with Mongoose
我正在开发一个不使用基于 sql 的猫鼬的聊天项目。我陷入了查询部分。我在下面留下我当前使用的模型和查询。您可以在下面找到预期的结果。提前感谢您的帮助。
首先,总结一下题目。我们有房间和消息集合。每条消息包含一个 roomId。并且一些消息还包含一个名为 quoteMessage 的字段,该字段引用其自己的图表。查询房间的时候,想带上包含这个房间_id值的消息,同时填写消息的子文档,即quoteMessage字段。
房间架构:
const roomSchema = new Schema({
_id: {
type: String,
required: true,
// unique: true,
immutable: true,
uppercase: true
},
guidesId: {
type: [Number],
required: true
},
members: [{
member: {
type: String,
required: true,
refPath: 'members.memberRole'
},
memberRole: {
type: String,
required: true,
enum: ['staff', 'customer']
}
}],
success: {
type: Boolean,
default: false
},
staffLastSeen: {
type: String,
default: () => null
},
createdAt: {
type: String,
default: () => moment().format()
},
updatedAt: {
type: String,
default: () => moment().format()
}
}, { versionKey: false });
消息架构:
const messageSchema = new Schema({
roomId: {
type: String,
required: true
},
ownerId: {
type: String
},
message: {
type: String,
required: true
},
quoteMessage: { type: String, ref: 'message' },
messageType: {
type: String,
default: messageTypes.text
},
createdAt: {
type: String,
default: () => moment().format()
},
updatedAt: {
type: String,
default: () => moment().format()
}
}, { versionKey: false });
我正在使用的查询:
const Room = require('../../../models/Room');
const rooms = await Room.aggregate([
{
$sort: { createdAt: -1 }
},
{
$lookup: {
from: 'messages',
as: 'messages',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$id', '$roomId'] }
}
},
// messages filter last 30 data.
{ $sort: { createdAt: -1 } },
{ $limit: 30 }
]
}
},
{ $addFields: { lastMessage: { $first: '$messages.createdAt' } } },
{
$sort: { lastMessage: -1 }
}
])
const result = await Room.populate(rooms, {
path: 'members.member',
select: ['-createdAt', '-updatedAt', '-password', '-email']
})
我的结果:
[
{
"_id": "1_BKN1",
"guidesId": [
44,
45
],
"success": false,
"members": [
{
"_id": "606d77df4321821b8484e0a8",
"member": {
"role": "Customer",
"_id": "1_BKN1",
"meetingId": 1,
"bookingId": 1,
"bookingReferenceCode": "BKN1",
"name": "Barney",
"surname": "Simpsons"
},
"memberRole": "customer"
},
{
"_id": "606d77f74321821b8484e0ad",
"member": {
"role": "Staff",
"_id": "606195183815891ca821bd4a",
"name": "Amy",
"surname": "Dark"
},
"memberRole": "staff"
}
],
"staffLastSeen": "2021-04-07T15:22:46+03:00",
"createdAt": "2021-04-07T12:14:07+03:00",
"updatedAt": "2021-04-07T15:22:46+03:00",
"messages": [
{
"_id": "606ed01fbe800510641f6990",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hello",
"ownerId": "1_BKN1",
"quoteMessage": "606d992294c3aa28b4bad411",
"createdAt": "2021-04-08T12:42:55+03:00",
"updatedAt": "2021-04-08T12:42:55+03:00"
},
{
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
}
]
}
]
预期结果:
[
{
"_id": "1_BKN1",
"guidesId": [
44,
45
],
"success": false,
"members": [
{
"_id": "606d77df4321821b8484e0a8",
"member": {
"role": "Customer",
"_id": "1_BKN1",
"meetingId": 1,
"bookingId": 1,
"bookingReferenceCode": "BKN1",
"name": "Barney",
"surname": "Simpsons"
},
"memberRole": "customer"
},
{
"_id": "606d77f74321821b8484e0ad",
"member": {
"role": "Staff",
"_id": "606195183815891ca821bd4a",
"name": "Amy",
"surname": "Dark"
},
"memberRole": "staff"
}
],
"staffLastSeen": "2021-04-07T15:22:46+03:00",
"createdAt": "2021-04-07T12:14:07+03:00",
"updatedAt": "2021-04-07T15:22:46+03:00",
"messages": [
{
"_id": "606ed01fbe800510641f6990",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hello",
"ownerId": "1_BKN1",
"quoteMessage": {
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
},
"createdAt": "2021-04-08T12:42:55+03:00",
"updatedAt": "2021-04-08T12:42:55+03:00"
},
{
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
}
]
}
]
经过一些研究,我了解到 .populate 函数可以接受一个列表,并且其中的对象被赋予模型引用。我通过以下更新的查询达到了预期的结果。
更新查询:
const Room = require('../../../models/Room');
const rooms = await Room.aggregate([
{
$sort: { createdAt: -1 }
},
{
$lookup: {
from: 'messages',
as: 'messages',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$id', '$roomId'] }
}
},
// messages filter last 30 data.
{ $sort: { createdAt: -1 } },
{ $limit: 30 }
]
}
},
{ $addFields: { lastMessage: { $first: '$messages.createdAt' } } },
{
$sort: { lastMessage: -1 }
}
])
const result = await Room.populate(rooms, [
{
path: 'members.member',
select: ['-createdAt', '-updatedAt', '-password', '-email']
},
{
path: 'messages.quoteMessage',
model: 'message'
}
])
我正在开发一个不使用基于 sql 的猫鼬的聊天项目。我陷入了查询部分。我在下面留下我当前使用的模型和查询。您可以在下面找到预期的结果。提前感谢您的帮助。
首先,总结一下题目。我们有房间和消息集合。每条消息包含一个 roomId。并且一些消息还包含一个名为 quoteMessage 的字段,该字段引用其自己的图表。查询房间的时候,想带上包含这个房间_id值的消息,同时填写消息的子文档,即quoteMessage字段。
房间架构:
const roomSchema = new Schema({
_id: {
type: String,
required: true,
// unique: true,
immutable: true,
uppercase: true
},
guidesId: {
type: [Number],
required: true
},
members: [{
member: {
type: String,
required: true,
refPath: 'members.memberRole'
},
memberRole: {
type: String,
required: true,
enum: ['staff', 'customer']
}
}],
success: {
type: Boolean,
default: false
},
staffLastSeen: {
type: String,
default: () => null
},
createdAt: {
type: String,
default: () => moment().format()
},
updatedAt: {
type: String,
default: () => moment().format()
}
}, { versionKey: false });
消息架构:
const messageSchema = new Schema({
roomId: {
type: String,
required: true
},
ownerId: {
type: String
},
message: {
type: String,
required: true
},
quoteMessage: { type: String, ref: 'message' },
messageType: {
type: String,
default: messageTypes.text
},
createdAt: {
type: String,
default: () => moment().format()
},
updatedAt: {
type: String,
default: () => moment().format()
}
}, { versionKey: false });
我正在使用的查询:
const Room = require('../../../models/Room');
const rooms = await Room.aggregate([
{
$sort: { createdAt: -1 }
},
{
$lookup: {
from: 'messages',
as: 'messages',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$id', '$roomId'] }
}
},
// messages filter last 30 data.
{ $sort: { createdAt: -1 } },
{ $limit: 30 }
]
}
},
{ $addFields: { lastMessage: { $first: '$messages.createdAt' } } },
{
$sort: { lastMessage: -1 }
}
])
const result = await Room.populate(rooms, {
path: 'members.member',
select: ['-createdAt', '-updatedAt', '-password', '-email']
})
我的结果:
[
{
"_id": "1_BKN1",
"guidesId": [
44,
45
],
"success": false,
"members": [
{
"_id": "606d77df4321821b8484e0a8",
"member": {
"role": "Customer",
"_id": "1_BKN1",
"meetingId": 1,
"bookingId": 1,
"bookingReferenceCode": "BKN1",
"name": "Barney",
"surname": "Simpsons"
},
"memberRole": "customer"
},
{
"_id": "606d77f74321821b8484e0ad",
"member": {
"role": "Staff",
"_id": "606195183815891ca821bd4a",
"name": "Amy",
"surname": "Dark"
},
"memberRole": "staff"
}
],
"staffLastSeen": "2021-04-07T15:22:46+03:00",
"createdAt": "2021-04-07T12:14:07+03:00",
"updatedAt": "2021-04-07T15:22:46+03:00",
"messages": [
{
"_id": "606ed01fbe800510641f6990",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hello",
"ownerId": "1_BKN1",
"quoteMessage": "606d992294c3aa28b4bad411",
"createdAt": "2021-04-08T12:42:55+03:00",
"updatedAt": "2021-04-08T12:42:55+03:00"
},
{
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
}
]
}
]
预期结果:
[
{
"_id": "1_BKN1",
"guidesId": [
44,
45
],
"success": false,
"members": [
{
"_id": "606d77df4321821b8484e0a8",
"member": {
"role": "Customer",
"_id": "1_BKN1",
"meetingId": 1,
"bookingId": 1,
"bookingReferenceCode": "BKN1",
"name": "Barney",
"surname": "Simpsons"
},
"memberRole": "customer"
},
{
"_id": "606d77f74321821b8484e0ad",
"member": {
"role": "Staff",
"_id": "606195183815891ca821bd4a",
"name": "Amy",
"surname": "Dark"
},
"memberRole": "staff"
}
],
"staffLastSeen": "2021-04-07T15:22:46+03:00",
"createdAt": "2021-04-07T12:14:07+03:00",
"updatedAt": "2021-04-07T15:22:46+03:00",
"messages": [
{
"_id": "606ed01fbe800510641f6990",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hello",
"ownerId": "1_BKN1",
"quoteMessage": {
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
},
"createdAt": "2021-04-08T12:42:55+03:00",
"updatedAt": "2021-04-08T12:42:55+03:00"
},
{
"_id": "606d992294c3aa28b4bad411",
"messageType": "Text",
"roomId": "1_BKN1",
"message": "Hi",
"ownerId": "606195183815891ca821bd4a",
"createdAt": "2021-04-07T14:36:02+03:00",
"updatedAt": "2021-04-07T14:36:02+03:00"
}
]
}
]
经过一些研究,我了解到 .populate 函数可以接受一个列表,并且其中的对象被赋予模型引用。我通过以下更新的查询达到了预期的结果。
更新查询:
const Room = require('../../../models/Room');
const rooms = await Room.aggregate([
{
$sort: { createdAt: -1 }
},
{
$lookup: {
from: 'messages',
as: 'messages',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$id', '$roomId'] }
}
},
// messages filter last 30 data.
{ $sort: { createdAt: -1 } },
{ $limit: 30 }
]
}
},
{ $addFields: { lastMessage: { $first: '$messages.createdAt' } } },
{
$sort: { lastMessage: -1 }
}
])
const result = await Room.populate(rooms, [
{
path: 'members.member',
select: ['-createdAt', '-updatedAt', '-password', '-email']
},
{
path: 'messages.quoteMessage',
model: 'message'
}
])