Запрос документов, размер массива которых больше 1

664

У меня есть коллекция MongoDB с документами в следующем формате:

{
  "_id" : ObjectId("4e8ae86d08101908e1000001"),
  "name" : ["Name"],
  "zipcode" : ["2223"]
}
{
  "_id" : ObjectId("4e8ae86d08101908e1000002"),
  "name" : ["Another ", "Name"],
  "zipcode" : ["2224"]
}

В настоящее время я могу получить документы, которые соответствуют определенному размеру массива:

db.accommodations.find({ name : { $size : 2 }})

Это правильно возвращает документы с 2 элементами в nameмассиве. Тем не менее, я не могу выполнить $gtкоманду, чтобы вернуть все документы, где nameполе имеет размер массива больше 2:

db.accommodations.find({ name : { $size: { $gt : 1 } }})

Как я могу выбрать все документы с nameмассивом размером больше одного (желательно без необходимости изменять текущую структуру данных)?

emson
источник
3
Более новые версии MongoDB имеют оператор $ size; Вы должны проверить ответ @ Tobia
AlbertEngelB
4
Фактическое решение: FooArray: {$ gt: {$ size: 'length'}} -> длина может быть любым числом
Серджи Надаль

Ответы:

489

Обновить:

Для mongodb версий 2.2+ более эффективный способ сделать это описан @JohnnyHK в другом ответе .


1. Использование $ где

db.accommodations.find( { $where: "this.name.length > 1" } );

Но...

Javascript выполняется медленнее, чем нативные операторы, перечисленные на этой странице, но очень гибок. Смотрите страницу обработки на стороне сервера для получения дополнительной информации.

2. Создайте дополнительное поле NamesArrayLength, обновите его, указав длину массива имен, а затем используйте в запросах:

db.accommodations.find({"NamesArrayLength": {$gt: 1} });

Это будет лучшее решение и будет работать намного быстрее (вы можете создать индекс по нему).

Андрей Орсич
источник
4
Отлично, это было прекрасно, спасибо. Хотя у меня действительно есть некоторые документы, у которых нет имени, поэтому пришлось изменить запрос так: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {вернуть это ;} "});
Эмсон
пожалуйста, да, вы можете использовать любой JavaScript $where, он очень гибкий.
Андрей Орсич
8
@emson Я бы подумал, что было бы быстрее сделать что-то вроде {"name": {$ exist: 1}, $ where: "this.name.lenght> 1"} ... минимизировать часть в более медленном запросе javascript. Я предполагаю, что это работает и что $ существует будет иметь более высокий приоритет.
nairbv 13.12.12
1
Я понятия не имел, что вы можете встраивать javascript в запрос, json может быть громоздким. Многие из этих запросов вводятся только один раз вручную, поэтому оптимизация не требуется. Я буду часто использовать этот трюк +1
pferrel
3
После добавления / удаления элементов из массива нам нужно обновить счетчик «NamesArrayLength». Можно ли это сделать в одном запросе? Или это требует 2 запроса, один для обновления массива и другой для обновления счетчика?
WarLord
1329

Есть более эффективный способ сделать это в MongoDB 2.2+, теперь вы можете использовать числовые индексы массивов в ключах объекта запроса.

// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})

Вы можете поддержать этот запрос с помощью индекса, который использует частичное выражение фильтра (требуется 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);
JohnnyHK
источник
16
Может кто-нибудь объяснить, пожалуйста, как индексировать это.
Бен
26
Я действительно впечатлен тем, насколько это эффективно, а также как «из коробки» вы думали найти это решение. Это работает и на 2.6.
earthmeLon
2
Работает на 3.0 также. Большое вам спасибо за то, что нашли это.
pikanezi
1
Не @Dims никакой разницы, на самом деле: {'Name Field.1': {$exists: true}}.
JohnnyHK
9
@JoseRicardoBustosM. Было бы найти документы, где nameсодержится по крайней мере 1 элемент, но ОП искал больше, чем 1.
JohnnyHK
128

Я считаю, что это самый быстрый запрос, который отвечает на ваш вопрос, потому что он не использует интерпретированное $whereпредложение:

{$nor: [
    {name: {$exists: false}},
    {name: {$size: 0}},
    {name: {$size: 1}}
]}

Это означает, что «все документы, кроме тех, у которых нет имени (не существующего или пустого массива) или только с одним именем».

Тестовое задание:

> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>
Тобия
источник
9
@ Viren Я не знаю. Это было, конечно, лучше, чем решения Javascript, но для более новой MongoDB вы, вероятно, должны использовать{'name.1': {$exists: true}}
Tobia
@Tobia, мое первое использование было только $, но на самом деле сканирование всей таблицы было очень медленным. db.test.find ({"name": "abc", "d.5": {$ exist: true}, "d.6": {$ exist: true}}) "nReturned": 46525, "executeTimeMillis ": 167289," totalKeysExamined ": 10990840," totalDocsExamined ": 10990840," inputStage ": {" stage ":" IXSCAN "," keyPattern ": {" name ": 1," d ": 1}," indexName " : "name_1_d_1", "direction": "forward", "indexBounds": {"name": ["[\" abc \ ", \" abc \ "]"], "d": ["[MinKey, MaxKey ] "]}} Если вы видите, сканируется вся таблица.
Было бы неплохо обновить ответ, чтобы рекомендовать другие альтернативы (например 'name.1': {$exists: true}}, а также, потому что это жестко задано для «1» и не масштабируется до произвольной или параметрической минимальной длины массива).
Дан Даскалеску,
1
Это может быть быстро, но развалится, если вы ищете списки> N, где N не мало.
Брэндон Хилл
62

Вы также можете использовать агрегат:

db.accommodations.aggregate(
[
     {$project: {_id:1, name:1, zipcode:1, 
                 size_of_name: {$size: "$name"}
                }
     },
     {$match: {"size_of_name": {$gt: 1}}}
])

// вы добавляете «size_of_name» к транзитному документу и используете его для фильтрации размера имени

one_cent_thought
источник
Это решение является наиболее общим, наряду с @ JohnnyHK, поскольку оно может использоваться для любого размера массива.
Арун
если я хочу использовать «size_of_name» внутри проекции, то как я могу это сделать ?? На самом деле я хочу использовать $ slice внутри проекции, где его значение равно $ slice: [0, "size_of_name" - пропустить] ??
Судханшу Гаур
44

Попробуйте сделать что-то вроде этого:

db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})

1 - это число, если вы хотите получить запись больше 50, тогда сделайте ArrayName.50 Спасибо.

Аман Гоэль
источник
2
Тот же ответ был дан тремя годами ранее .
Дан Даскалеску
Я из будущего и оценил бы это: это решение работает, проверяя, существует ли элемент в указанной позиции. Следовательно, коллекция должна быть больше, чем эта цифра.
MarAvFe
Можем ли мы поместить в запросе динамическое число, например «ArrayName. <some_num>»?
Сахил Махаджан
Да, вы можете использовать любой номер. Если вы хотите получить запись больше чем N, тогда передайте n.
Аман Гоэль
36

Ничто из вышеперечисленного не помогло мне. Этот сделал так, я делюсь этим:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
источник
javascript выполняется медленнее, чем нативные операторы, предоставляемые mongodb, но он очень гибкий. см .: stackoverflow.com/a/7811259/2893073 , поэтому окончательное решение: stackoverflow.com/a/15224544/2893073
Eddy
26

Вы можете использовать $ expr (оператор версии 3.6 mongo), чтобы использовать функции агрегирования в обычном запросе.

Сравните query operatorsпротив aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})
Сагар Вирам
источник
Как бы вы передать вместо $nameмассива , который является поддокументом, например , в «лице» запись, passport.stamps? Я пробовал разные комбинации цитирования, но получаю "The argument to $size must be an array, but was of type: string/missing".
Дан Даскалеску
3
@DanDascalescu Похоже, штампы присутствуют не во всех документах. Вы можете использовать ifNull для вывода пустого массива, когда штампов нет. Нечто подобноеdb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Сагар Вирам
22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Yadvendar
источник
1
Это не подходит для других минимальных размеров (скажем, 10).
Дан Даскалеску
так же, как первый ответ
Arianpress
22

MongoDB 3.6 включает $ expr https://docs.mongodb.com/manual/reference/operator/query/expr/

Вы можете использовать $ expr для оценки выражения внутри $ match или поиска.

{ $match: {
           $expr: {$gt: [{$size: "$yourArrayField"}, 0]}
         }
}

или найти

collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});
Даниэль Тассоне
источник
1
Хотя это правильно, это дублирующий ответ. См stackoverflow.com/a/48410837/2424641 от @ user2683814
SteveB
13

Я нашел это решение, чтобы найти элементы с массивом поле больше определенной длины

db.allusers.aggregate([
  {$match:{username:{$exists:true}}},
  {$project: { count: { $size:"$locations.lat" }}},
  {$match:{count:{$gt:20}}}
])

Первый агрегат $ match использует аргумент true для всех документов. Если пусто, я бы получил

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"
Barrard
источник
По сути, это тот же ответ, что и этот , предоставленный двумя годами ранее.
Дан Даскалеску
1

Я знаю его старый вопрос, но я пытаюсь это сделать с помощью $ gte и $ size в find. Я думаю, что найти () быстрее.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})
Бхагват Ланде
источник
-5

Хотя вышеприведенные ответы все работают, то, что вы изначально пытались сделать, было правильным способом, однако у вас просто есть синтаксис в обратном направлении (переключите «$ size» и «$ gt») ..

Правильный:

db.collection.find({items: {$gt: {$size: 1}}})

Неправильно:

db.collection.find({items: {$size: {$gt: 1}}})
Штеффан Перри
источник
1
Я не понимаю, почему так много отрицательных голосов - это прекрасно работает для меня!
Джейк Стоукс
Я не понизил, но это не работает (v4.2).
Евгений Набоков
Работает отлично, v 4.2.5
jperl