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)?

emson
fuente
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).

Andrew Orsich
fuente
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}}}
);
JohnnyHK
fuente
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" ] }
>
Tobia
fuente
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

pensamiento_central
fuente
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.

Aman Goel
fuente
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
36

Nada de lo anterior funcionó para mí. Este lo hizo, así que lo estoy compartiendo:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
fuente
JavaScript se ejecuta más lentamente que los operadores nativos proporcionados por mongodb, pero es muy flexible. ver: stackoverflow.com/a/7811259/2893073 , por lo que la solución final es: stackoverflow.com/a/15224544/2893073
Eddy
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]}})
Sagar Veeram
fuente
¿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}}})
Yadvendar
fuente
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"
Barrard
fuente
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 } }})
Bhagvat Lande
fuente
-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}}})
Steffan Perry
fuente
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