Запит на документи, де розмір масиву перевищує 1


664

У мене є збірка MongoDB з документами у такому форматі:

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

Зараз я можу отримати документи, які відповідають певному розміру масиву:

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

Це правильно повертає документи з двома елементами в nameмасиві. Однак я не можу виконати $gtкоманду повернути всі документи, у яких розмір nameполя перевищує 2:

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

Як я можу вибрати всі документи з nameмасивом розміром більше одного (бажано, не змінюючи поточну структуру даних)?


3
Новіші версії MongoDB мають оператор розміру $; Ви можете перевірити відповідь @ tobia
AlbertEngelB

4
Актуальне рішення: FooArray: {$ gt: {$ size: 'length'}} -> довжина може бути будь-яким числом
Sergi Nadal

Відповіді:


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.accomances.find ({$ where: "if (this.name && this.name.length> 1) {return this ;} "});
emson

ви можете вітати, так, ви можете використовувати будь-який JavaScript $where, він дуже гнучкий.
Андрій Оршич

8
@emson Я думаю, що було б швидше зробити щось на зразок {"name": {$ існує: 1}, $ where: "this.name.lenght> 1"} ... мінімізуючи частину у повільному запиті JavaScript. Я припускаю, що роботи і що $ існує, мали б перевагу.
nairbv

1
Я не мав уявлення, що ви можете вбудувати JavaScript у запит, json може бути громіздким. Багато з цих запитів одноразово вводяться вручну, тому оптимізація не потрібна. Я буду використовувати цей трюк часто +1
пферрел

3
Після додавання / видалення елементів з масиву нам потрібно оновити кількість "NamesArrayLength". Чи можна це зробити за допомогою одного запиту? Або для цього потрібно 2 запити, один для оновлення масиву та інший для оновлення рахунку?
WarLord

1327

Існує більш ефективний спосіб зробити це в 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}}}
);

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 ({"ім'я": "abc", "d.5": {$ існує: істина}, "d.6": {$ існує: істина}}) "nПовернено": 46525, "ExecutionTimeMillis ": 167289," totalKeysExamined ": 10990840," totalDocsExamined ": 10990840," inputStage ": {" етап ":" IXSCAN "," keyPattern ": {" ім'я ": 1," d ": 1}," indexName " : "name_1_d_1", "напрямок": "вперед", "indexBounds": {"ім'я": ["[\" abc \ ", \" abc \ "]"], "d": ["[MinKey, MaxKey ] "]}} Якщо ви бачите, що це сканується ціла таблиця.

Було б добре оновити відповідь, щоб порекомендувати інші альтернативи (наприклад 'name.1': {$exists: true}}, а також тому, що це жорстко закодовано для "1" і не масштабує довільну або параметричну мінімальну довжину масиву.
Dan Dascalescu

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" для транзиту документа і використовуєте його для фільтрації розміру імені


Це рішення є найбільш загальним, поряд з @ JohnnyHK, оскільки його можна використовувати для будь-якого розміру масиву.
Аруна

якщо я хочу використовувати "size_of_name" всередині проекції, то як я можу це зробити ?? Насправді я хочу використовувати $ slice всередині проекції, де його значення дорівнює $ slice: [0, "size_of_name" - пропустити] ??
Sudhanshu Gaur

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'} )

javascript виконується повільніше, ніж вбудовані оператори, які надає mongodb, але він дуже гнучкий. див.: stackoverflow.com/a/7811259/2893073 , тож остаточне рішення: stackoverflow.com/a/15224544/2893073
Едді,

26

Ви можете використовувати $ expr (оператор версії 3,6 монго), щоб використовувати функції агрегації у звичайному запиті.

Порівняти 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]}})
Sagar Veeram


22

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

Ви можете використовувати $ expr для того, щоб оцінити вираз усередині $ $, або знайти.

{ $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 використовує аргумент, що відповідає всім документам. Якщо б порожнє, я отримав би

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

Це по суті та сама відповідь, що і ця , надана на 2 роки раніше.
Дан Даскалеску

1

Я знаю його старе питання, але я намагаюся це знайти з $ gte та $ size у пошуку. Я думаю, що швидше знайти ().

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.