Consulta de documentos donde el tamaño de la matriz es mayor que 1


664

Tengo una colección MongoDB con documentos en el siguiente formato:

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

Actualmente puedo obtener documentos que coinciden con un tamaño de matriz específico:

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

Esto devuelve correctamente los documentos con 2 elementos en la namematriz. Sin embargo, no puedo hacer un $gtcomando para devolver todos los documentos donde el namecampo tiene un tamaño de matriz mayor que 2:

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

¿Cómo puedo seleccionar todos los documentos con una namematriz de un tamaño mayor que uno (preferiblemente sin tener que modificar la estructura de datos actual)?


3
Las versiones más nuevas de MongoDB tienen el operador $ size; deberías consultar la respuesta de @ tobia
AlbertEngelB

44
Solución real: FooArray: {$ gt: {$ size: 'length'}} -> la longitud puede ser cualquier número
Sergi Nadal

Respuestas:


489

Actualizar:

Para las versiones mongodb 2.2+ forma más eficiente de hacer esto descrito por @JohnnyHK en otra respuesta .


1.Utilizando $ where

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

Pero...

Javascript se ejecuta más lentamente que los operadores nativos enumerados en esta página, pero es muy flexible. Consulte la página de procesamiento del lado del servidor para obtener más información.

2.Cree un campo adicionalNamesArrayLength , actualícelo con la longitud de la matriz de nombres y luego utilícelo en consultas:

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

Será una mejor solución y funcionará mucho más rápido (puede crear un índice en él).


44
Genial, eso fue perfecto, gracias. Aunque en realidad tengo algunos documentos que no tienen un nombre, tuve que modificar la consulta para que sea: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {devolver esto ;} "});
Emson

de nada, sí, puedes usar cualquier javascript $where, es muy flexible.
Andrew Orsich

8
@emson Creo que sería más rápido hacer algo como {"nombre": {$ existe: 1}, $ where: "this.name.lenght> 1"} ... minimizando la parte en la consulta de JavaScript más lenta. Supongo que funciona y que el $ existe tendría mayor prioridad.
nairbv

1
No tenía idea de que podría incrustar JavaScript en la consulta, json puede ser engorroso. Muchas de estas consultas solo se ingresan una vez a mano, por lo que no se requiere optimización.
Usaré

3
Después de agregar / eliminar elementos de la matriz, necesitamos actualizar el recuento de "NamesArrayLength". ¿Se puede hacer esto en una sola consulta? ¿O requiere 2 consultas, una para actualizar la matriz y otra para actualizar el recuento?
WarLord

1329

Hay una manera más eficiente de hacer esto en MongoDB 2.2+ ahora que puede usar índices de matriz numérica en las claves de objeto de consulta.

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

Puede admitir esta consulta con un índice que use una expresión de filtro parcial (requiere 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

16
¿Podría alguien explicarme cómo indexar esto?
Ben

26
Estoy realmente impresionado con lo efectivo que es esto y también cuán 'fuera de la caja' estabas pensando en encontrar esta solución. Esto funciona en 2.6, también.
earthmeLon

2
Funciona en 3.0 también. Muchas gracias por encontrar esto.
pikanezi

1
@Dims No hay diferencia, en realidad: {'Name Field.1': {$exists: true}}.
JohnnyHK

99
@JoseRicardoBustosM. Eso encontraría los documentos donde namecontiene al menos 1 elemento, pero el OP estaba buscando más de 1.
JohnnyHK

128

Creo que esta es la consulta más rápida que responde a su pregunta, porque no utiliza una $wherecláusula interpretada :

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

Significa "todos los documentos excepto aquellos sin un nombre (ya sea una matriz inexistente o vacía) o con un solo nombre".

Prueba:

> 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" ] }
>

99
@viren no lo sé. Esto fue ciertamente mejor que las soluciones de Javascript, pero para MongoDB más nuevo, probablemente debería usar{'name.1': {$exists: true}}
Tobia

@Tobia mi primer uso fue $ solo existe, pero en realidad usa el escaneo de toda la tabla muy lento. 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 ] "]}} Si lo ve escaneó toda la tabla.

Sería bueno actualizar la respuesta para recomendar otras alternativas (como 'name.1': {$exists: true}}, y también porque está codificada para "1" y no escala a una longitud mínima de matriz arbitraria o paramétrica.
Dan Dascalescu

1
Esto puede ser rápido, pero se desmorona si está buscando listas> N, donde N no es pequeño.
Brandon Hill

62

También puedes usar agregado:

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

// agrega "size_of_name" al documento de tránsito y lo usa para filtrar el tamaño del nombre


Esta solución es la más general, junto con @ JohnnyHK, ya que se puede usar para cualquier tamaño de matriz.
Arun

si quiero usar "size_of_name" dentro de la proyección, ¿cómo puedo hacer eso? En realidad, quiero usar $ slice dentro de la proyección donde su valor es igual a $ slice: [0, "size_of_name" - skip] ??
Sudhanshu Gaur

44

Intenta hacer algo como esto:

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

1 es el número, si desea obtener un registro superior a 50, haga ArrayName.50 Gracias.


2
La misma respuesta se dio tres años antes .
Dan Dascalescu

Soy del futuro y lo habría apreciado: esta solución funciona comprobando si existe un elemento en dicha posición. Por lo tanto, la colección debe ser mayor | igual que ese número.
MarAvFe

¿podemos poner algún número dinámico como "ArrayName. <some_num>" dentro de la consulta?
Sahil Mahajan

Sí, puedes usar cualquier número. Si desea obtener un registro mayor que N, pase n.
Aman Goel


26

Puede usar $ expr (operador de versión 3.6 mongo) para usar funciones de agregación en consultas regulares.

Comparar query operatorsvs aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

¿Cómo pasaría en lugar de $nameuna matriz que es un subdocumento, por ejemplo, en un registro de "persona" passport.stamps? Intenté varias combinaciones de citas pero me sale "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu

3
@DanDascalescu Parece que los sellos no están presentes en todos los documentos. Puede usar ifNull para generar una matriz vacía cuando los sellos no están presentes. Algo así comodb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram

22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})

1
Esto no se adapta bien a otros tamaños mínimos (por ejemplo, 10).
Dan Dascalescu

igual que la primera respuesta
arianpress


13

Encontré esta solución, para encontrar elementos con un campo de matriz mayor que cierta longitud

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

El primer agregado de $ match usa un argumento que es verdadero para todos los documentos. Si estuviera en blanco, obtendría

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

Esta es esencialmente la misma respuesta que esta , proporcionada 2 años antes.
Dan Dascalescu

1

Sé su vieja pregunta, pero estoy intentando esto con $ gte y $ size en find. Creo que find () es más rápido.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

Aunque lo anterior responde a todo el trabajo, lo que trató originalmente de hacer fue la forma correcta, sin embargo, solo tiene la sintaxis hacia atrás (cambie "$ size" y "$ gt").

Correcto:

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

Incorrecto:

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

1
No veo por qué tantos votos negativos, ¡esto funciona perfectamente para mí!
Jake Stokes el

No voté en contra, pero no funciona (v4.2).
Evgeni Nabokov

Funciona perfectamente bien, v 4.2.5
jperl
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.