Как напрямую запросить вложенное поле без указания родительского поля?

#mongodb #mongodb-query

#mongodb — монгодб #mongodb-запрос #mongodb

Вопрос:

У меня есть следующие 3 документа в mongodb:

документ 1:

 {
    "name": "device1",
    "camera": {
        "number": 3
    }
}
  

документ 2:

 {
    "name": "device2",
    "camera": {
        "number": 1
    }
}
  

документ 3:

 {
    "name": "device3",
    "wifi": {
        "number": 2
    }
}
  

Моя цель — найти все устройства, номер модулей которых > 1, возможно, модуль camera , wifi или другие.

Я знаю, что могу использовать next для получения устройства:

 > db.devices.find({"camera.number":{$gt:1}})
{ "_id" : ObjectId("5f436a3df716cb47d319245e"), "name" : "device1", "camera" : { "number" : 3 } }
> db.devices.find({"wifi.number":{$gt:1}})
{ "_id" : ObjectId("5f436a58f716cb47d3192460"), "name" : "device3", "wifi" : { "number" : 2 } }
  

Но, похоже, мне пришлось добавить что-то вроде «module.number» в запрос, вы знаете, может быть, у меня много модулей, тогда не так эффективно использовать camera.number , wifi.number amp; othermodule.number чтобы найти все подходящие устройства. Если возможно, у меня может быть что-нибудь волшебное, чтобы напрямую получить устройство, которое «имеет номер любого модуля> 1», я имею в виду, что нет необходимости указывать родительское поле?

Ответ №1:

Все просто, просто измените свою схему.

https://mongoplayground.net/p/npSvVzbnsyk

   {
    "name": "device1",
    "modules": [
      {
        "k": "camera",
        "v": 3
      }
    ]
  },
  {
    "name": "device2",
    "modules": [
      {
        "k": "wifi",
        "v": 3
      },
      {
        "k": "camera",
        "v": 2
      }
    ]
  },
  {
    "name": "device3",
    "modules": [
      {
        "k": "wifi",
        "v": 2
      }
    ]
  }

db.collection.find({"modules.v": 3})
  

Ответ №2:

Я не думаю, что это возможно с помощью find() метода, но вы можете попробовать метод aggregate(),

  • $addFields добавит device поле, которое преобразует $$ROOT объект в массив в k и v форматирует с помощью $objectToArray
  • $match вы можете сопоставить device.v.number с вашим номером
  • $project для скрытия device поля
 db.devices.aggregate([
  { $addFields: { device: { $objectToArray: "$$ROOT" } } },
  { $match: { "device.v.number": { $gt: 1 } } },
  { $project: { device: 0 } }
])
  

Игровая площадка


Другой возможный способ с aggregate() методом,

  • сопоставление с $expr
  • $ reduce будет вводить $$ROOT как $objectToArray , а внутри in проверяет условие, если число равно нулю, используя $ifNull, затем $add число в initialValue, иначе добавьте 0 .
 db.collection.aggregate([
  {
    $match: {
      $expr: {
        $gt: [
          {
            $reduce: {
              input: { $objectToArray: "$$ROOT" },
              initialValue: 0,
              in: { $add: ["$$value", { $ifNull: ["$$this.v.number", 0] }] }
            }
          },
          1 // add your search number
        ]
      }
    }
  }
])
  

Игровая площадка

Комментарии:

1. Новичок в mongodb, позвольте мне когда-нибудь разобраться, скоро вернется.

2. Хорошо, добавлены ссылочные ссылки по конвейеру.

3. { $addFields: { device: { $objectToArray: "$$ROOT" } } } Возникнет ли какая-либо проблема с эффективностью? Преобразовать документ в k-v и добавить device в качестве поля?

4. Я предполагаю, что это компромисс, чтобы использовать это по сравнению с использованием предопределенного k-v как и в другом упомянутом ответе, я попробую его производительность с моими собственными реальными наборами данных, спасибо за информацию.

5. $ addFields добавит новое поле, которое генерируется из корня всех полей текущего документа, так что, возможно, это займет немного больше памяти, я добавил другое решение с единственным совпадением, оно лучше, чем первый запрос, вы можете проверить разницу в эффективности обоих запросов, используя explain