MongoDB: поиск по значению в объекте groups
Решение для поиска документов в MongoDB, где в объекте groups содержится конкретное значение UUID. Использование агрегационного конвейера и оператора $expr.
Как в MongoDB найти документы, где в объекте groups содержится конкретное значение (UUID)?
Коллекция user_logs содержит документы вида:
{
_id: ObjectId('6943d11b18090e12e90dba1e'),
type: 'user',
groups: {
'0': '1ed9c8d6-aab1-67aa-9f37-1d6076c03cf9',
'1': '1ed9c8d6-aab3-6988-afc8-1d6076c03cf9',
'2': '1ed9c8d6-aab4-605e-90c4-1d6076c03cf9'
},
ip: '105.105.105.105',
userId: '5a52d7c8-7690-4770-b482-0324a37951af',
login: 'login',
fio: 'ФИО',
createdAt: ISODate('2025-08-14T16:46:40.000Z')
}
Не работает запрос:
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" });
В Doctrine ODM поле groups определено как:
#[ODM\Field(type: 'hash', nullable: false)]
private array $groups = [];
Как правильно составить запрос find() или aggregation для поиска документов, содержащих заданное значение в объекте groups?
В MongoDB для поиска документов, где в объекте groups содержится конкретное значение (UUID), необходимо использовать агрегационный конвейер или оператор $expr, так как стандартный запрос find() не поддерживает поиск по значениям в полях-объектах с динамическими ключами. Основная проблема заключается в том, что MongoDB не предоставляет прямого оператора для поиска значений в хэш-полях, поэтому требуется преобразование объекта в массив для последующей фильтрации.
Содержание
- Проблема с базовым запросом
- Решение через агрегационный конвейер
- Решение через $expr
- Оптимизация производительности
- Альтернативные подходы
- Пример в Doctrine ODM
- Источники
- Заключение
Проблема с базовым запросом
Как указано в документации MongoDB, стандартный запрос db.collection.find({ field: value }) ищет точное совпадение всего поля. В вашем случае:
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" });
MongoDB ищет документы, где поле groups в точности равно указанному UUID, что невозможно, так как groups — это объект с множеством пар ключ-значение. Как объясняется в официальной документации MongoDB, такой подход не работает для поиска значений внутри вложенных структур.
Решение через агрегационный конвейер
Наиболее надежный способ — использовать агрегационный конвейер с операторами $objectToArray и $match:
db.user_logs.aggregate([
{
$addFields: {
groupsArray: { $objectToArray: "$groups" }
}
},
{
$match: {
"groupsArray.v": "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9"
}
}
])
Как это работает:
$objectToArrayпреобразует объектgroupsв массив вида:json[ { "k": "0", "v": "1ed9c8d6-aab1-67aa-9f37-1d6076c03cf9" }, { "k": "1", "v": "1ed9c8d6-aab3-6988-afc8-1d6076c03cf9" }, ... ]$matchфильтрует документы по значению поляvв этом массиве.
Этот метод подробно рассмотрен в ответах сообщества MongoDB и официальной документации.
Решение через $expr
Для более компактного решения можно использовать оператор $expr в find():
db.user_logs.find({
$expr: {
$in: [
"1ed9c8d6-aab4-605e-90c4-1d6076c03cf9",
{ $map: {
input: { $objectToArray: "$groups" },
as: "group",
in: "$$group.v"
}
}
]
}
})
Преимущества:
- Работает в рамках одного запроса
find() - Более читаемая логика с использованием
$in
Как показано в примерах из сообщества, этот подход эффективен для поиска значений в хэш-полях.
Оптимизация производительности
Для больших коллекций учитывайте:
- Индексация: Создайте индекс на поле
groups:javascriptdb.user_logs.createIndex({ "groups.v": 1 }) - Объем данных: Агрегационные запросы с
$objectToArrayтребуют больше ресурсов. Для часто используемых UUID рассмотрите нормализацию схемы. - Ограничение результатов: Добавьте
$limitв конвейер:javascript{ $limit: 100 }
Как рекомендовано в руководстве MongoDB, индексация полей вложенных объектов может значительно ускорить запросы.
Альтернативные подходы
1. Изменение структуры данных
Храните UUID в массиве вместо объекта:
{
"groups": [
"1ed9c8d6-aab1-67aa-9f37-1d6076c03cf9",
"1ed9c8d6-aab3-6988-afc8-1d6076c03cf9"
]
}
Тогда запрос будет простым:
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" })
2. Использование регулярных выражений
Для частичных совпадений (не рекомендуется для UUID):
db.user_logs.find({
groups: {
$regex: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9",
$options: "i"
}
})
Как объясняется в статье о поиске в MongoDB, регулярные выражения подходят для текстовых данных, но не для UUID из-за риска ложных срабатываний.
Пример в Doctrine ODM
Для Doctrine ODM с полем type: 'hash' используйте кастомный репозиторий с агрегацией:
class UserRepository extends ServiceDocumentRepository
{
public function findByGroupUuid(string $uuid): array
{
$qb = $this->createQueryBuilder();
return $qb
->hydrate(false)
->addAggregation([
'$addFields' => [
'groupsArray' => ['$objectToArray' => '$groups']
]
])
->addAggregation([
'$match' => [
'groupsArray.v' => $uuid
]
])
->getQuery()
->execute()
->toArray();
}
}
Источники
- MongoDB - Find document with array that contains a specific value
- MongoDB Docs - db.collection.find()
- Stack Overflow - Find documents that contains specific field
- MongoDB Docs - Query on Embedded Documents
- Stack Overflow - Mongodb find in hash by value
- Metanit - Операторы выборки в MongoDB
- MongoDB Community Hub - Querying with dynamic keys
- Stack Overflow - mongodb query $in on a Hash field
Заключение
Для поиска документов с конкретным значением в объекте groups в MongoDB используйте агрегационный конвейер с $objectToArray или оператор $expr в find(). Эти методы позволяют преобразовать хэш-объект в массив и выполнить фильтрацию по значениям. Для оптимизации производительности создавайте индексы и рассмотрите возможность нормализации структуры данных. В Doctrine ODM реализуйте аналогичный подход через кастомный репозиторий с использованием агрегаций MongoDB.