Найти записи MongoDB, где поле массива не пустое

Во всех моих записях есть поле под названием "картинки". Это поле представляет собой массив строк.

Теперь мне нужны самые новые 10 записей, где этот массив НЕ пуст.

Я искал googled, но, как ни странно, я этого не нашел. Я прочитал параметр $where, но мне было интересно, как медленно это происходит с нативными функциями, и если есть лучшее решение.

И даже тогда это не работает:

ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()

Возвращает ничего. Выход из this.pictures без бит длины работает, но затем он также возвращает пустые записи, конечно.

Ответ 1

Если у вас также есть документы, у которых нет ключа, вы можете использовать:

ME.find({ pictures: { $exists: true, $not: {$size: 0} } })

MongoDB не использует индексы, если задействован $ size, так что вот лучшее решение:

ME.find({ pictures: { $exists: true, $ne: [] } })

Начиная с выпуска MongoDB 2.6, вы можете сравнить с оператором $gt но это может привести к неожиданным результатам (вы можете найти подробное объяснение в этом ответе):

ME.find({ pictures: { $gt: [] } })

Ответ 2

После более тщательного изучения, особенно в документах mongodb, и загадки вместе, это был ответ:

ME.find({pictures: {$exists: true, $not: {$size: 0}}})

Ответ 3

Это также может работать для вас:

ME.find({'pictures.0': {$exists: true}});

Ответ 4

Начиная с версии 2.6, другой способ сделать это - сравнить поле с пустым массивом:

ME.find({pictures: {$gt: []}})

Тестирование в оболочке:

> db.ME.insert([
{pictures: [1,2,3]},
{pictures: []},
{pictures: ['']},
{pictures: [0]},
{pictures: 1},
{foobar: 1}
])

> db.ME.find({pictures: {$gt: []}})
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a7"), "pictures": [ 1, 2, 3 ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a9"), "pictures": [ "" ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4aa"), "pictures": [ 0 ] }

Таким образом, он правильно включает документы, в которых pictures имеет хотя бы один элемент массива и исключает документы, где pictures - либо пустой массив, не массив, либо отсутствует.

Ответ 5

При запросе вы заботитесь о двух вещах - точности и производительности. Имея это в виду, я протестировал несколько разных подходов в MongoDB v3.0.14.

TL; DR db.doc.find({ nums: { $gt: -Infinity }}) - самый быстрый и надежный (по крайней мере, в тестируемой версии MongoDB).

РЕДАКТИРОВАТЬ: Это больше не работает в MongoDB v3.6! Смотрите комментарии под этим постом для потенциального решения.

Настроить

Я вставил 1k документов без поля списка, 1k документов с пустым списком и 5 документов с непустым списком.

for (var i = 0; i < 1000; i++) { db.doc.insert({}); }
for (var i = 0; i < 1000; i++) { db.doc.insert({ nums: [] }); }
for (var i = 0; i < 5; i++) { db.doc.insert({ nums: [1, 2, 3] }); }
db.doc.createIndex({ nums: 1 });

Я признаю, что этого недостаточно для того, чтобы серьезно относиться к производительности, как в тестах ниже, но этого достаточно, чтобы представить правильность различных запросов и поведение выбранных планов запросов.

тесты

db.doc.find({'nums': {'$exists': true}}) возвращает неверные результаты (для того, что мы пытаемся достичь).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': {'$exists': true}}).count()
1005

-

db.doc.find({'nums.0': {'$exists': true}}) возвращает правильные результаты, но также замедляет использование полного сканирования коллекции (обратите COLLSCAN этап COLLSCAN в объяснении).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).explain()
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.doc",
    "indexFilterSet": false,
    "parsedQuery": {
      "nums.0": {
        "$exists": true
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "nums.0": {
          "$exists": true
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": [ ]
  },
  "serverInfo": {
    "host": "MacBook-Pro",
    "port": 27017,
    "version": "3.0.14",
    "gitVersion": "08352afcca24bfc145240a0fac9d28b978ab77f3"
  },
  "ok": 1
}

-

db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}) возвращает неверные результаты. Это из-за недопустимого сканирования индекса, не выдвигающего никаких документов. Скорее всего, он будет точным, но медленным без индекса.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 2,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$gt": {
              "$size": 0
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "({ $size: 0.0 }, [])"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}) возвращает правильные результаты, но производительность плохая. Технически он выполняет сканирование индекса, но затем все равно перемещает все документы, а затем должен их отфильтровать).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2016,
  "advanced": 5,
  "needTime": 2010,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$exists": true
          }
        },
        {
          "$not": {
            "nums": {
              "$size": 0
            }
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 2016,
    "advanced": 5,
    "needTime": 2010,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 2005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 2005,
      "executionTimeMillisEstimate": 0,
      "works": 2015,
      "advanced": 2005,
      "needTime": 10,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, MaxKey]"
        ]
      },
      "keysExamined": 2015,
      "dupsTested": 2015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $ne: [] }}) возвращает правильные результаты и немного быстрее, но производительность все еще не идеальна. Он использует IXSCAN, который только продвигает документы с существующим полем списка, но затем должен отфильтровать пустые списки один за другим.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 1018,
  "advanced": 5,
  "needTime": 1011,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "$not": {
            "nums": {
              "$eq": [ ]
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 1017,
    "advanced": 5,
    "needTime": 1011,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 1005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 1005,
      "executionTimeMillisEstimate": 0,
      "works": 1016,
      "advanced": 1005,
      "needTime": 11,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, undefined)",
          "(undefined, [])",
          "([], MaxKey]"
        ]
      },
      "keysExamined": 1016,
      "dupsTested": 1015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $gt: [] }}) ОПАСНО, ПОТОМУ ЧТО В ЗАВИСИМОСТИ ОТ ИСПОЛЬЗОВАНИЯ ИНДЕКСА МОЖЕТ ДАТЬ НЕОЖИДАННЫЕ РЕЗУЛЬТАТЫ. Это из-за недопустимого сканирования индекса, который не опережает документы.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
5

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 1,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "nums": {
        "$gt": [ ]
      }
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "([], BinData(0, ))"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums.0: { $gt: -Infinity }}) возвращает правильные результаты, но имеет плохую производительность (использует полное сканирование коллекции).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "COLLSCAN",
  "filter": {
    "nums.0": {
      "$gt": -Infinity
    }
  },
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2007,
  "advanced": 5,
  "needTime": 2001,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "direction": "forward",
  "docsExamined": 2005
}

-

db.doc.find({'nums': { $gt: -Infinity }}) удивительно, это работает очень хорошо! Это дает правильные результаты и быстро, продвигая 5 документов от фазы сканирования индекса.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "FETCH",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 16,
  "advanced": 5,
  "needTime": 10,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "docsExamined": 5,
  "alreadyHasObj": 0,
  "inputStage": {
    "stage": "IXSCAN",
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 15,
    "advanced": 5,
    "needTime": 10,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "keyPattern": {
      "nums": 1
    },
    "indexName": "nums_1",
    "isMultiKey": true,
    "direction": "forward",
    "indexBounds": {
      "nums": [
        "(-inf.0, inf.0]"
      ]
    },
    "keysExamined": 15,
    "dupsTested": 15,
    "dupsDropped": 10,
    "seenInvalidated": 0,
    "matchTested": 0
  }
}

Ответ 6

Для достижения этой цели вы можете использовать любое из следующих действий.
Оба также заботятся о том, чтобы не возвращать результат для объектов, в которых нет запрошенного ключа:

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})

Ответ 7

Вы также можете использовать вспомогательный метод Exists над оператором Mongo $exists

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });

Ответ 8

{ $where: "this.pictures.length > 1" }

используйте $ where и передайте this.field_name.length, которое возвращает размер поля массива, и проверьте его, сравнив с числом. если какой-либо массив имеет какое-либо значение, чем размер массива должен быть не менее 1. Таким образом, все поля массива имеют длину больше единицы, это означает, что в этом массиве есть некоторые данные

Ответ 9

Получить все и только документы, где "pictures" - это массив, а не пустой

ME.find({pictures: {$type: 'array', $ne: []}})

Если используется версия MongoDb до 3.2, используйте $type: 4 вместо $type: 'array'. Обратите внимание, что в этом решении даже не используется $ size, поэтому с индексами проблем нет ("Запросы не могут использовать индексы для части $ size запроса")

Другие решения, в том числе следующие (принятый ответ):

ME.find({картинки: {$ exist: true, $ not: {$ size: 0}}}); ME.find({картинки: {$ exist: true, $ ne: []}})

неверны, потому что они возвращают документы, даже если, например, 'images' равно null, undefined, 0 и т.д.

Ответ 10

Используйте оператор $elemMatch: согласно документации

Оператор $ elemMatch сопоставляет документы, содержащие поле массива, по крайней мере с одним элементом, который соответствует всем указанным критериям запроса.

$elemMatches гарантирует, что значение является массивом и не является пустым. Так что запрос будет что-то вроде

ME.find({ pictures: { $elemMatch: {$exists: true }}})

PS Вариант этого кода можно найти в курсе MongoDB University M121.

Ответ 11

ME.find({pictures: {$exists: true}}) 

Просто так, это сработало для меня.