У меня 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 поддерживает транзакции для обеспечения целостности данных в многопользовательской среде...
Это в 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"] },
...
]
поскольку имена пользователей уже есть в файлах документации, вам не нужно запрашивать коллекцию пользователей.
$match
, в зависимости от наличия пользователя во входном массиве.userID
и введите идентификатор файла и временную метку (позже понадобится для сортировки).$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" } }
}