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

503

Все мои записи имеют поле под названием «картинки». Это поле представляет собой массив строк.

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

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

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

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

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

skerit
источник

Ответы:

831

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

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

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

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

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

ME.find({ pictures: { $gt: [] } })
Крис'
источник
6
Для меня это правильный подход, поскольку он гарантирует, что массив существует и не является пустым.
LeandroCR
Как я могу достичь той же функциональности, используяmongoengine
Rohit Khatri
54
ОСТОРОЖНО, ME.find({ pictures: { $gt: [] } })ОПАСНО, даже в новых версиях MongoDB. Если у вас есть индекс в поле списка, и этот индекс используется во время запроса, вы получите неожиданные результаты. Например: db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()возвращает правильный номер, а db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()возвращает 0.
wojcikstefan
1
Смотрите мой подробный ответ ниже, чтобы узнать, почему это может не сработать для вас: stackoverflow.com/a/42601244/1579058
wojcikstefan
6
Комментарий @ wojcikstefan необходимо опровергнуть, чтобы люди не использовали последнее предложение, которое, действительно, при определенных обстоятельствах не возвращает соответствующие документы.
Томас Юнг,
181

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

ME.find({pictures: {$exists: true, $not: {$size: 0}}})
skerit
источник
27
Это не работает Я не знаю, работало ли это ранее, но это также вернет объекты, у которых нет ключа 'pictures'.
rdsoze
17
Невероятно , как этот ответ имеет 63 upvotes, когда на самом деле , что @rdsoze сказал верно - запрос будет также возвращать записи , которые не имеют picturesполя.
Дан Даскалеску
5
Будьте осторожны, mongoDB не будет использовать индексы, если используется ссылка $ size . Было бы лучше включить {$ ne: []} и, возможно, {$ ne: null}.
Левенте Добсон
17
@rdsoze самая первая строка вопроса гласит: «Все мои записи имеют поле с именем« картинки ». Это поле является массивом» . Более того, это вполне реалистичный и распространенный сценарий. Этот ответ не является неправильным, он работает на вопрос точно так, как написано, и критиковать или опускать его за то, что он не решает другую проблему, глупо.
Марк Амери
1
@Cec Вся документация гласит, что если вы используете $ size в запросе, он не будет использовать какой-либо индекс для получения более быстрых результатов. Поэтому, если у вас есть индекс в этом поле и вы хотите его использовать, придерживайтесь других подходов, таких как {$ ne: []}, если это работает для вас, это будет использовать ваш индекс.
Левенте Добсон
108

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

ME.find({'pictures.0': {$exists: true}});
tenbatsu
источник
2
Ницца! Это также позволяет проверить минимальный размер. Знаете ли вы, если массивы всегда индексируются последовательно? Был бы когда-нибудь случай, когда pictures.2существует, но pictures.1не существует ?
Anushr
2
$existsОператор является логическим, а не смещением. @tenbatsu следует использовать trueвместо 1.
ekillaby
2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? Да, это может случиться.
Bndr
@TheBndr Это может произойти, только если picturesэто вспомогательный документ, а не массив. напримерpictures: {'2': 123}
JohnnyHK
4
Это красиво и интуитивно понятно, но будьте осторожны, если важна производительность - он выполнит полное сканирование коллекции, даже если у вас есть индекс pictures.
wojcikstefan
35

При запросе вы заботитесь о двух вещах - точности и производительности. Имея это в виду, я протестировал несколько разных подходов в 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этап уведомления в объяснении).

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
  }
}
wojcikstefan
источник
Спасибо за ваш очень подробный ответ @wojcikstefan. К сожалению, предложенное вами решение не работает в моем случае. У меня есть коллекция MongoDB 3.6.4 с 2 млн документов, большинство из которых имеют seen_eventsмассив строк, который также индексируется. Выполняя поиск { $gt: -Infinity }, я сразу получаю 0 документов. Используя { $exists: true, $ne: [] }я получаю более вероятные документы в 1,2 млн., Причем на этапе FETCH тратится много времени: gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NCode
Кажется , что вы правильно @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. Все остальные запросы возвращают неверные результаты.
wojcikstefan
1
@NCode нашел решение! Если вы уверены , что все непустые seen_eventsсодержат строки, вы можете использовать это: db.test_collection.find({seen_events: {$gt: ''}}).count(). Чтобы подтвердить, что это хорошо работает, проверьте db.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats. Вы можете, вероятно, обеспечить соблюдение , что видел события строки с помощью проверки схемы: docs.mongodb.com/manual/core/schema-validation
wojcikstefan
Спасибо! Все существующие значения являются строками, поэтому я попробую это. Существует также ошибка, обсуждающая эту проблему в багтрекере MongoDB: jira.mongodb.org/browse/SERVER-26655
NCode
30

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

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

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

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})
Пол Имиси
источник
4

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

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

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

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

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

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

SC1000
источник
2

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

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

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

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

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

Андрес Морено
источник
0

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

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });
Ешь в Джо
источник
0
{ $where: "this.pictures.length > 1" }

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

Прабхат Ядав
источник
-8
ME.find({pictures: {$exists: true}}) 

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

Луис Флэтс
источник