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


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


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

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

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


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.


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


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

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

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"

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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.