Как мне получить уникальные значения между двумя полями при запросе в MongoDB (mongoose)

Вопрос или проблема

В настоящее время я создаю папку входящих сообщений в приложении для обмена сообщениями. Для этого я хотел бы извлечь все уникальные идентификаторы пользователей (ObjectID), которые я сохранил в своей схеме сообщений, чтобы увидеть все уникальные разговоры, которые ведет пользователь. Проблема в том, что идентификаторы хранятся в двух отдельных полях: “to” и “from”. Есть ли способ получитьdistinct значения из обоих полей? (т.е. если userA есть как в поле to, так и в поле from, он будет извлечен только один раз)

В настоящее время я пытался использовать как $setUnion, так и $addToSet без успеха.

Моя схема сообщений выглядит следующим образом

const MessageSchema = new Schema(
    {
        message: { type: String, required: true, maxLength: 128 },
        image: { type: String },
        from: { type: Schema.Types.ObjectId, required: true, ref: "User" },
        to: [
            {
                type: Schema.Types.ObjectId,
                required: true,
                refPath: "docModel",
            },
        ],
        docModel: { type: String, required: true, enum: ["Group", "User"] },
    },
    { timestamps: true },
);

Вот несколько примеров документов:

[
    {
        "_id": "671175112a0bcfdb66f833fc",
        "message": "hi",
        "from": "6706c5576692b95bb39dabae",
        "to": [
            "6708368712a817cd73810b5e"
        ],
        "docModel": "User",
        "createdAt": "2024-10-17T20:35:29.164Z",
        "updatedAt": "2024-10-17T20:35:29.164Z",
        "__v": 0
    },
    {
        "_id": "6711755e2a0bcfdb66f83406",
        "message": "bye",
        "from": "671175392a0bcfdb66f83401",
        "to": [
            "6706c5576692b95bb39dabae"
        ],
        "docModel": "User",
        "createdAt": "2024-10-17T20:36:46.259Z",
        "updatedAt": "2024-10-17T20:36:46.259Z",
        "__v": 0
    },
    {
        "_id": "671181f7c2db995b7ef3f976",
        "message": "1",
        "from": "6706c5576692b95bb39dabae",
        "to": [
            "671175392a0bcfdb66f83401"
        ],
        "docModel": "User",
        "createdAt": "2024-10-17T21:30:31.032Z",
        "updatedAt": "2024-10-17T21:30:31.032Z",
        "__v": 0
    }
]

В приведенном выше примере результат, который я хочу получить, – это массив:

[6706c5576692b95bb39dabae, 671175392a0bcfdb66f83401, 6708368712a817cd73810b5e]

При этом это уникальные значения, которые появляются или в поле from, или в поле to (и если они появляются и в поле from, и в поле to, они добавляются в массив только один раз)

А вот мой код для запроса к MongoDB. Я пытаюсь запросить все документы, где идентификатор пользователя появляется либо в “to”, либо в “from”, что работает. Затем я пытаюсь получить уникальные значения двух полей вместе, что не работает.

Я планирую удалить идентификатор пользователя запрашивающего пользователя, как только я извлеку все уникальные идентификаторы, чтобы получить окончательный список уникальных разговоров.

    const uniqueUsers = await Message.aggregate([
        {
            $match: {
                $or: [
                    {
                        from: new mongoose.Types.ObjectId(`${req.user.id}`),
                    },
                    {
                        to: {
                            $in: [
                                new mongoose.Types.ObjectId(`${req.user.id}`),
                            ],
                        },
                    },
                ],
            },
        },
        {
            $group: {
                _id: null,
                unique: { $setUnion: ["$from", "$to"] },
            },
        },
    ]);

На этапе группировки вам нужно будет $addToSet добавить все идентификаторы из полей from и to в 2 массива соответственно. После этого выполните $setUnion для комбинирования 2 массивов вместе.

db.collection.aggregate([
  {
    "$match": {
      $expr: {
        $let: {
          vars: {
            userIdInput: "6706c5576692b95bb39dabae"
          },
          "in": {
            "$or": [
              {
                $eq: [
                  "$$userIdInput",
                  "$from"
                ]
              },
              {
                $in: [
                  "$$userIdInput",
                  "$to"
                ]
              }
            ]
          }
        }
      }
    }
  },
  {
    "$unwind": "$to"
  },
  {
    "$group": {
      "_id": null,
      "froms": {
        "$addToSet": "$from"
      },
      "tos": {
        "$addToSet": "$to"
      }
    }
  },
  {
    "$project": {
      allIds: {
        "$setUnion": [
          "$froms",
          "$tos"
        ]
      }
    }
  }
])

Mongo Playground

Ответ или решение

Чтобы получить уникальные значения из двух полей в MongoDB (Mongoose), когда вы работаете с схемой сообщений, вы должны использовать агрегацию. Запрос должен эффективно обрабатывать значки пользователей, хранящиеся в полях "from" и "to". Ваша цель — извлечь уникальные идентификаторы пользователей в формате ObjectId, присутствующие в обеих колонках.

Ваша схема сообщений выглядит следующим образом:

const MessageSchema = new Schema(
    {
        message: { type: String, required: true, maxLength: 128 },
        image: { type: String },
        from: { type: Schema.Types.ObjectId, required: true, ref: "User" },
        to: [
            {
                type: Schema.Types.ObjectId,
                required: true,
                refPath: "docModel",
            },
        ],
        docModel: { type: String, required: true, enum: ["Group", "User"] },
    },
    { timestamps: true },
);

Допустим, у вас есть следующие документы:

[
    {
        "_id": "671175112a0bcfdb66f833fc",
        "message": "hi",
        "from": "6706c5576692b95bb39dabae",
        "to": ["6708368712a817cd73810b5e"],
        "docModel": "User",
    },
    {
        "_id": "6711755e2a0bcfdb66f83406",
        "message": "bye",
        "from": "671175392a0bcfdb66f83401",
        "to": ["6706c5576692b95bb39dabae"],
        "docModel": "User",
    },
    {
        "_id": "671181f7c2db995b7ef3f976",
        "message": "1",
        "from": "6706c5576692b95bb39dabae",
        "to": ["671175392a0bcfdb66f83401"],
        "docModel": "User",
    }
]

Вы хотите собрать все уникальные идентификаторы, чтобы получить результат в виде:

["6706c5576692b95bb39dabae", "671175392a0bcfdb66f83401", "6708368712a817cd73810b5e"]

Решение через агрегацию

Вам нужно создать агрегированный запрос, чтобы сначала сопоставить сообщения, а затем объединить уникальные ObjectId из обоих полей. Вот как вы можете это сделать:

const uniqueUsers = await Message.aggregate([
    {
        $match: {
            $or: [
                { from: mongoose.Types.ObjectId(req.user.id) },
                { to: mongoose.Types.ObjectId(req.user.id) }
            ]
        }
    },
    {
        $unwind: "$to" // Разворачиваем массив to в отдельные документы
    },
    {
        $group: {
            _id: null,
            froms: { $addToSet: "$from" }, // Собираем уникальные ids из поля from
            tos: { $addToSet: "$to" } // Собираем уникальные ids из поля to
        }
    },
    {
        $project: {
            allIds: {
                $setUnion: ["$froms", "$tos"] // Объединяем уникальные идентификаторы в один массив
            }
        }
    }
]);

Пояснение этапов агрегации:

  1. $match: Начинаем с фильтрации сообщений, чтобы получить только те, которые связаны с текущим пользователем, находящимся в полях "from" или "to".

  2. $unwind: Разворачиваем массив "to", чтобы каждый элемент этого массива стал отдельным документом.

  3. $group: Группируем результаты, собирая уникальные значения из полей "from" и "to" в два разных массива: froms и tos.

  4. $project: Используем setUnion, чтобы объединить массивы froms и tos в один, содержащий уникальные идентификаторы пользователей.

Заключение

Таким образом, с помощью агрегации в MongoDB (Mongoose) вы можете эффективно получить все уникальные идентификаторы пользователей, участвующих в переписке, сохраняя их только один раз, даже если они встречаются в обоих полях. Этот метод оптимален для получения информации о пользователях и может быть использован для дальнейшей обработки данных, таких как визуализация переписки в вашем мессенджере.

Оцените материал
Добавить комментарий

Капча загружается...