Напишите один запрос из нескольких запросов в MongoDB

У меня 2 коллекции users и files. Пользовательский документ выглядит следующим образом:

{
  "_id": "user1",
  "name": "John"
}

Документ file выглядит так:

{
  "_id": "fileID1",
  "url": "any_url",
  "userID": "user1"
}

У меня есть список идентификаторов пользователей, скажем: ["user1", "user2", "user5"], и я хочу выполнить один запрос, который принимает это list в качестве входных данных и возвращает 5 файлов каждого идентификатора пользователя.

На данный момент у меня есть наивная логика, которая не очень эффективна:

result_users = users_collection.find({}).limit(15)
  for user in result_users:
    user_id.append(user["_id"])
    result_files = files_collection.find({"userID": user["_id"]}).limit(5)
      for f in result_files:
        file_dict[user["_id"]].append(f)

Запрос, который я ищу, наивно будет выглядеть примерно так: result_files = files_collection.find({"userID": {"$in / $each": user_ids_list}).limit(5)

Результат, который я ищу:

{
  "user1": ["fileID1", "fileID2", "fileID8", "fileID3"],
  "user2": ["fileID4", "fileID5", "fileID6", "fileID9", "fileID7"],
  ...
}

Максимум 5 файлов на каждый идентификатор пользователя.

Я почти уверен, что это достижимо с помощью aggregation, но понятия не имею, как это правильно написать.

Примечание. Меня не волнует формат вывода, мне нужен только список файлов, которые правильно отсортированы по 5 файлов на каждый идентификатор пользователя.

Я могу сам сопоставить выходные данные, если уверен, что запрос возвращает правильный результат files.

Спасибо !

Обновлено: было бы здорово добавить сортировку («at», DESCENDING) в агрегацию коллекции файлов.

🤔 А знаете ли вы, что...
MongoDB поддерживает транзакции для обеспечения целостности данных в многопользовательской среде...


2
53
2

Ответы:

Это в JS, но я уверен, что в py вы могли бы сделать что-то вроде ниже:

const user_ids_list = ["user1", "user2", "user5"];

const result_files = await files_collection.aggregate([
  // Match files where userID is in the user_ids_list
  { 
    $match: { 
      userID: { $in: user_ids_list } 
    }
  },
  // Group by userID and collect up to 5 files per user
  { 
    $group: {
      _id: "$userID",
      files: { $push: "$_id" }
    }
  },
  // Limit to the first 5 files for each userID
  { 
    $project: { 
      files: { $slice: ["$files", 5] } 
    }
  }
]).toArray();

console.info(result_files);

Это должно дать вам результат, похожий на:

[
  { "_id": "user1", "files": ["fileID1", "fileID2", "fileID8", "fileID3", "fileID9"] },
  { "_id": "user2", "files": ["fileID4", "fileID5", "fileID6", "fileID7", "fileID10"] },
  ...
]

Решено

поскольку имена пользователей уже есть в файлах документации, вам не нужно запрашивать коллекцию пользователей.

  1. Сначала сузьте файлы, используя $match, в зависимости от наличия пользователя во входном массиве.
  2. Сгруппируйте по полю userID и введите идентификатор файла и временную метку (позже понадобится для сортировки).
  3. Теперь вызовите $sortArray для сгруппированного массива файлов (v). Теперь он отсортирован по убыванию (-1). На этом отсортированном вызове $slice и, наконец, сопоставьте разрезанный массив, чтобы получить массив только из идентификаторов. теперь это будет сгенерировано в формате [ { _id:"user1",v: [...]}, { _id:"user2",v: [...]}... ]
db.files.aggregate([
  {
    $match: { userID: { $in: ["user1", "user2", "user5"] } }
  },
  {
    $group: {
      _id: "$userID",
      v: { $push: { id: "$_id", at: "$at" } }
    }
  },
  {
    $project: {
      v: {
        $map: {
          input: { $slice: [{ $sortArray: { input: "$v", sortBy: { at: -1 } } }, 5] },
          in: "$$this.id"
        }
      }
    }
  }
])

детская площадка

если вы хотите преобразовать его в формат { "user1": [...], "user2": [...], ... }, вы можете добавить эти 2 этапа

  {
    $group: {
      _id: null,
      kv: { $push: { k: "$_id", v: "$v" } }
    }
  },
  {
    $replaceRoot: { newRoot: { $arrayToObject: "$kv" } }
  }

детская площадка