Базы данных

MongoDB: поиск по значению в объекте groups

Решение для поиска документов в MongoDB, где в объекте groups содержится конкретное значение UUID. Использование агрегационного конвейера и оператора $expr.

Как в MongoDB найти документы, где в объекте groups содержится конкретное значение (UUID)?

Коллекция user_logs содержит документы вида:

json
{
    _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')
}

Не работает запрос:

javascript
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" });

В Doctrine ODM поле groups определено как:

php
#[ODM\Field(type: 'hash', nullable: false)]
private array $groups = [];

Как правильно составить запрос find() или aggregation для поиска документов, содержащих заданное значение в объекте groups?

В MongoDB для поиска документов, где в объекте groups содержится конкретное значение (UUID), необходимо использовать агрегационный конвейер или оператор $expr, так как стандартный запрос find() не поддерживает поиск по значениям в полях-объектах с динамическими ключами. Основная проблема заключается в том, что MongoDB не предоставляет прямого оператора для поиска значений в хэш-полях, поэтому требуется преобразование объекта в массив для последующей фильтрации.

Содержание

Проблема с базовым запросом

Как указано в документации MongoDB, стандартный запрос db.collection.find({ field: value }) ищет точное совпадение всего поля. В вашем случае:

javascript
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" });

MongoDB ищет документы, где поле groups в точности равно указанному UUID, что невозможно, так как groups — это объект с множеством пар ключ-значение. Как объясняется в официальной документации MongoDB, такой подход не работает для поиска значений внутри вложенных структур.

Решение через агрегационный конвейер

Наиболее надежный способ — использовать агрегационный конвейер с операторами $objectToArray и $match:

javascript
db.user_logs.aggregate([
  {
    $addFields: {
      groupsArray: { $objectToArray: "$groups" }
    }
  },
  {
    $match: {
      "groupsArray.v": "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9"
    }
  }
])

Как это работает:

  1. $objectToArray преобразует объект groups в массив вида:
    json
    [
      { "k": "0", "v": "1ed9c8d6-aab1-67aa-9f37-1d6076c03cf9" },
      { "k": "1", "v": "1ed9c8d6-aab3-6988-afc8-1d6076c03cf9" },
      ...
    ]
    
  2. $match фильтрует документы по значению поля v в этом массиве.

Этот метод подробно рассмотрен в ответах сообщества MongoDB и официальной документации.

Решение через $expr

Для более компактного решения можно использовать оператор $expr в find():

javascript
db.user_logs.find({
  $expr: {
    $in: [
      "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9",
      { $map: {
          input: { $objectToArray: "$groups" },
          as: "group",
          in: "$$group.v"
        }
      }
    ]
  }
})

Преимущества:

  • Работает в рамках одного запроса find()
  • Более читаемая логика с использованием $in

Как показано в примерах из сообщества, этот подход эффективен для поиска значений в хэш-полях.

Оптимизация производительности

Для больших коллекций учитывайте:

  1. Индексация: Создайте индекс на поле groups:
    javascript
    db.user_logs.createIndex({ "groups.v": 1 })
    
  2. Объем данных: Агрегационные запросы с $objectToArray требуют больше ресурсов. Для часто используемых UUID рассмотрите нормализацию схемы.
  3. Ограничение результатов: Добавьте $limit в конвейер:
    javascript
    { $limit: 100 }
    

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

Альтернативные подходы

1. Изменение структуры данных

Храните UUID в массиве вместо объекта:

json
{
  "groups": [
    "1ed9c8d6-aab1-67aa-9f37-1d6076c03cf9",
    "1ed9c8d6-aab3-6988-afc8-1d6076c03cf9"
  ]
}

Тогда запрос будет простым:

javascript
db.user_logs.find({ groups: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9" })

2. Использование регулярных выражений

Для частичных совпадений (не рекомендуется для UUID):

javascript
db.user_logs.find({ 
  groups: { 
    $regex: "1ed9c8d6-aab4-605e-90c4-1d6076c03cf9",
    $options: "i"
  } 
})

Как объясняется в статье о поиске в MongoDB, регулярные выражения подходят для текстовых данных, но не для UUID из-за риска ложных срабатываний.

Пример в Doctrine ODM

Для Doctrine ODM с полем type: 'hash' используйте кастомный репозиторий с агрегацией:

php
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();
    }
}

Источники

  1. MongoDB - Find document with array that contains a specific value
  2. MongoDB Docs - db.collection.find()
  3. Stack Overflow - Find documents that contains specific field
  4. MongoDB Docs - Query on Embedded Documents
  5. Stack Overflow - Mongodb find in hash by value
  6. Metanit - Операторы выборки в MongoDB
  7. MongoDB Community Hub - Querying with dynamic keys
  8. Stack Overflow - mongodb query $in on a Hash field

Заключение

Для поиска документов с конкретным значением в объекте groups в MongoDB используйте агрегационный конвейер с $objectToArray или оператор $expr в find(). Эти методы позволяют преобразовать хэш-объект в массив и выполнить фильтрацию по значениям. Для оптимизации производительности создавайте индексы и рассмотрите возможность нормализации структуры данных. В Doctrine ODM реализуйте аналогичный подход через кастомный репозиторий с использованием агрегаций MongoDB.

Авторы
Проверено модерацией
Модерация