带有 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'
    }
  ])