Consulta para documentos em que o tamanho da matriz é maior que 1

664

Eu tenho uma coleção do MongoDB com documentos no seguinte formato:

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

Atualmente, posso obter documentos que correspondam a um tamanho de matriz específico:

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

Isso retorna corretamente os documentos com 2 elementos na namematriz. No entanto, não posso executar um $gtcomando para retornar todos os documentos em que o namecampo tem um tamanho de matriz maior que 2:

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

Como posso selecionar todos os documentos com uma namematriz de tamanho maior que um (de preferência sem precisar modificar a estrutura de dados atual)?

emson
fonte
3
As versões mais recentes do MongoDB têm o operador $ size; você deve verificar @ resposta de Tobia
AlbertEngelB
4
Solução real: FooArray: {$ gt: {$ size: 'length'}} -> comprimento pode ser qualquer número #
Sergi Nadal

Respostas:

489

Atualizar:

Nas versões mongodb 2.2 ou superior, a maneira mais eficiente de fazer isso é descrita por @JohnnyHK em outra resposta .


1.Usando $ where

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

Mas...

O Javascript é executado mais lentamente do que os operadores nativos listados nesta página, mas é muito flexível. Consulte a página de processamento no servidor para obter mais informações.

2.Crie um campo extraNamesArrayLength , atualize-o com o comprimento da matriz de nomes e use em consultas:

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

Será uma solução melhor e funcionará muito mais rápido (você pode criar um índice nele).

Andrew Orsich
fonte
4
Ótimo, foi perfeito, obrigado. Embora eu tenha alguns documentos que não têm nome, tive que modificar a consulta para: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {retorne isso ;} "});
emson
de nada, sim, você pode usar qualquer javascript $where, é muito flexível.
Andrew Orsich 18/10/11
8
@emson Eu acho que seria mais rápido fazer algo como {"name": {$ existe: 1}, $ where: "this.name.lenght> 1"} ... minimizando a parte da consulta javascript mais lenta. Presumo que funcione e que o $ existe tenha maior precedência.
nairbv
1
Eu não tinha idéia de que você poderia incorporar javascript na consulta, json pode ser complicado. Muitas dessas consultas são inseridas apenas uma vez manualmente, portanto, a otimização não é necessária. Vou usar este truque muitas vezes +1
pferrel
3
Após adicionar / remover elementos da matriz, precisamos atualizar a contagem de "NamesArrayLength". Isso pode ser feito em uma única consulta? Ou requer 2 consultas, uma para atualizar a matriz e outra para atualizar a contagem?
WarLord
1329

Há uma maneira mais eficiente de fazer isso no MongoDB 2.2+ agora que você pode usar índices de array numéricos em chaves de objetos de consulta.

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

Você pode oferecer suporte a esta consulta com um índice que use uma expressão de filtro parcial (requer 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);
JohnnyHK
fonte
16
Alguém poderia explicar como indexar isso.
Ben
26
Estou realmente impressionado com o quão eficaz isso é e também com o modo 'pronto para uso' que você estava pensando em encontrar esta solução. Isso funciona no 2.6 também.
precisa saber é o seguinte
2
Funciona em 3.0 também. Muito obrigado por encontrar isso.
Pikanezi
1
@Dims Nenhuma diferença, realmente: {'Name Field.1': {$exists: true}}.
21716 JohnnyHK
9
@JoseRicardoBustosM. Que iria encontrar os documentos que namecontém pelo menos um elemento, mas o OP estava procurando por maior que 1.
JohnnyHK
128

Acredito que esta é a consulta mais rápida que responde à sua pergunta, porque não usa uma $wherecláusula interpretada :

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

Significa "todos os documentos, exceto aqueles sem nome (matriz inexistente ou vazia) ou com apenas um nome".

Teste:

> 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
fonte
9
@viren eu não sei. Este foi certamente melhor do que as soluções de Javascript, mas por mais recente MongoDB você provavelmente deve usar{'name.1': {$exists: true}}
Tobia
@ Tobia, meu primeiro uso foi apenas $, mas ele realmente usa a varredura de tabelas inteiras de forma muito lenta. db.test.find ({"nome": "abc", "d.5": {$ existe: verdadeiro}, "d.6": {$ existe: verdadeiro}}) "nReturned": 46525, "ExecutionTimeMillis ": 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 ] "]}} Se você vê a tabela inteira digitalizada.
Seria bom para atualizar a resposta para recomendar outras alternativas (como 'name.1': {$exists: true}}, e também porque este é codificado para "1" e não escala a um arbitrária ou comprimento de matriz mínimo paramétrico.
Dan Dascalescu
1
Isso pode ser rápido, mas desmorona se você estiver procurando por listas> N, onde N não é pequeno.
Brandon Hill
62

Você também pode usar agregado:

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

// você adiciona "size_of_name" ao documento de trânsito e o usa para filtrar o tamanho do nome

one_cent_thought
fonte
Essa solução é a mais geral, junto com o @ JohnnyHK, pois pode ser usada para qualquer tamanho de matriz.
arun
se eu quiser usar "size_of_name" dentro da projeção, como posso fazer isso? Na verdade, eu quero usar $ slice dentro da projeção, onde seu valor é igual a $ slice: [0, "size_of_name" - pular] ??
Sudhanshu Gaur
44

Tente fazer algo assim:

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

1 é número, se você deseja buscar um registro maior que 50, faça ArrayName.50 Obrigado.

Aman Goel
fonte
2
A mesma resposta foi dada três anos antes .
Dan Dascalescu 30/06/19
Eu sou do futuro e teria apreciado isso: Esta solução funciona verificando se existe um elemento na referida posição. Portanto, a coleção deve ser maior | igual a esse número.
MarAvFe 9/07/19
podemos colocar um número dinâmico como "ArrayName. <some_num>" dentro da consulta?
Sahil Mahajan
Sim, você pode usar qualquer número. Se você deseja buscar um registro maior que N, passe n.
Aman Goel
36

Nenhuma das opções acima funcionou para mim. Este fez, então eu estou compartilhando:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
lesolorzanov
fonte
O javascript executa mais lentamente do que os operadores nativos fornecidos pelo mongodb, mas é muito flexível. consulte: stackoverflow.com/a/7811259/2893073 , Portanto, a solução final é: stackoverflow.com/a/15224544/2893073
Eddy
26

Você pode usar $ expr (operador da versão 3.6 mongo) para usar funções de agregação em consultas regulares.

Compare query operatorsvs aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})
Sagar Veeram
fonte
Como você passaria em vez de $nameuma matriz que é um subdocumento, por exemplo, em um registro de "pessoa" passport.stamps? Eu tentei várias combinações de citações, mas entendi "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu
3
@DanDascalescu Parece que os carimbos não estão presentes em todos os documentos. Você pode usar ifNull para gerar uma matriz vazia quando os carimbos não estiverem presentes. Algo comodb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram
22
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Yadvendar
fonte
1
Isso não se adapta bem a outros tamanhos mínimos (por exemplo, 10).
Dan Dascalescu
mesmo que a primeira resposta
arianpress
13

Encontrei esta solução, para encontrar itens com um campo de matriz maior que o comprimento certo

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

O primeiro agregado $ match usa um argumento verdadeiro para todos os documentos. Se em branco, eu ficaria

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"
Barrard
fonte
Esta é essencialmente a mesma resposta que essa , fornecida dois anos antes.
Dan Dascalescu
1

Eu sei que é uma pergunta antiga, mas estou tentando isso com $ gte e $ size em busca. Eu acho que encontrar () é mais rápido.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})
Bhagvat Lande
fonte
-5

Embora as respostas acima funcionem, o que você originalmente tentou fazer era a maneira correta, mas você só tem a sintaxe para trás (alterne "$ size" e "$ gt") ..

Corrigir:

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

Incorreta:

db.collection.find({items: {$size: {$gt: 1}}})
Steffan Perry
fonte
1
Não vejo por que tantos votos negativos - isso funciona perfeitamente para mim!
Jake Stokes
Não reduzi o voto, mas não funciona (v4.2).
Evgeni Nabokov
Funciona perfeitamente bem, v 4.2.5
jperl