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

285

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

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

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

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

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

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

Теги:
mongoose

8 ответов

419
Лучший ответ

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

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

MongoDB не использует индексы, если используется $size, поэтому лучшее решение:

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

[edit] Кроме того, начиная с версии MongoDB 2.6, вы можете сравнить с оператором $gt:

ME.find({ pictures: { $gt: [] } })
  • 6
    Для меня это правильный подход, поскольку он гарантирует, что массив существует и не является пустым.
  • 0
    Как я могу достичь той же функциональности, используя mongoengine
Показать ещё 5 комментариев
145

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

ME.find({pictures: {$not: {$size: 0}}})
  • 14
    Вы должны пометить этот ответ как принятый.
  • 25
    Это не работает Я не знаю, работало ли это ранее, но это также вернет объекты, у которых нет ключа 'pictures'.
Показать ещё 6 комментариев
80

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

ME.find({'pictures.0': {$exists: true}});
  • 1
    Ницца! Это также позволяет проверить минимальный размер. Знаете ли вы, если массивы всегда индексируются последовательно? Был бы когда-нибудь случай, когда pictures.2 существует, но pictures.1 нет?
  • 2
    Оператор $exists является логическим, а не смещением. @tenbatsu должен использовать true вместо 1 .
Показать ещё 3 комментария
26

Начиная с версии 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 - либо пустой массив, не массив, либо отсутствует.

  • 6
    ВНИМАТЕЛЬНО этот ответ может вызвать проблемы, если вы попытаетесь использовать индексы. Выполнение db.ME.createIndex({ pictures: 1 }) а затем db.ME.find({pictures: {$gt: []}}) вернет ноль результатов, по крайней мере, в MongoDB v3.0.14
  • 0
    @wojcikstefan Хороший улов. Нужно по-новому взглянуть на это.
15

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

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

Настройка

Я вставил 1k docs без поля списка, 1k docs с пустым списком и 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 в объяснении).

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
  }
}
  • 0
    Спасибо за ваш очень подробный ответ @wojcikstefan. К сожалению, предложенное вами решение не работает в моем случае. У меня есть коллекция MongoDB 3.6.4 с 2 млн. Документов, большинство из которых имеют seen_events String seen_events , который также индексируется. { $gt: -Infinity } поиск по { $gt: -Infinity } , я сразу получаю 0 документов. Используя { $exists: true, $ne: [] } я получаю более вероятные 1,2 млн. Документов, при этом на этапе FETCH тратится много времени: gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
  • 0
    Кажется, вы правы @Ncode - это больше не работает в MongoDB v3.6 :( Я поиграл с этим несколько минут, и вот что я нашел: 1. db.test_collection.find({"seen_events.0": {$exists: true}}) плохо, потому что использует сканирование коллекции. 2. db.test_collection.find({seen_events: {$exists: true, $ne: []}}) плохо, потому что его IXSCAN соответствует всем документы, а затем фильтрация выполняется в фазе медленного FETCH. 3. То же самое относится и к db.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}}) . 4. Все другие запросы возвращают неверные результаты.
Показать ещё 2 комментария
4

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню