Encontre registros MongoDB em que o campo da matriz não esteja vazio

503

Todos os meus registros têm um campo chamado "fotos". Este campo é uma matriz de seqüências de caracteres.

Agora eu quero os 10 registros mais recentes em que essa matriz NÃO está vazia.

Eu pesquisei por aí, mas, estranhamente, não encontrei muito sobre isso. Eu li a opção $ where, mas queria saber o quão lento isso é para as funções nativas e se há uma solução melhor.

E mesmo assim, isso não funciona:

ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()

Retorna nada. Sair this.picturessem o bit de comprimento funciona, mas também retorna registros vazios, é claro.

skerit
fonte

Respostas:

829

Se você também possui documentos que não possuem a chave, pode usar:

ME.find({ pictures: { $exists: true, $not: {$size: 0} } })

O MongoDB não usa índices se o tamanho $ estiver envolvido, então aqui está uma solução melhor:

ME.find({ pictures: { $exists: true, $ne: [] } })

Desde o MongoDB 2.6, você pode comparar com o operador, $gtmas pode levar a resultados inesperados (você pode encontrar uma explicação detalhada nesta resposta ):

ME.find({ pictures: { $gt: [] } })
Chris
fonte
6
Para mim, essa é a abordagem correta, pois garante que a matriz exista e não esteja vazia.
LeandroCR
Como posso conseguir mesma funcionalidade usandomongoengine
Rohit Khatri
54
CUIDADO, ME.find({ pictures: { $gt: [] } })É PERIGOSO, mesmo nas versões mais recentes do MongoDB. Se você tiver um índice no seu campo de lista e esse índice for utilizado durante a consulta, você obterá resultados inesperados. Por exemplo: db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()retorna o número certo, enquanto db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()retorna 0.
precisa saber é o seguinte
1
Veja a minha resposta detalhada abaixo para saber por que isso pode não funcionar para você: stackoverflow.com/a/42601244/1579058
wojcikstefan
6
O comentário de @ wojcikstefan precisa ser votado para impedir que as pessoas usem a última sugestão que, de fato, sob certas circunstâncias, não retorna documentos correspondentes.
Thomas Jung
181

Depois de mais algumas pesquisas, especialmente nos documentos do mongodb e em partes intrigantes, esta foi a resposta:

ME.find({pictures: {$exists: true, $not: {$size: 0}}})
skerit
fonte
27
Isso não funciona. Não sei se isso funcionou anteriormente, mas isso também retornará objetos que não possuem a tecla 'pictures'.
Rdsoze
17
Inacreditável como essa resposta tem 63 votos positivos, quando na verdade o que @rdsoze disse é verdade - a consulta também retornará registros que não possuem o picturescampo.
Dan Dascalescu
5
Tenha cuidado, mongoDB não vai usar índices se $ tamanho é envolvido ligação . Seria melhor incluir {$ ne: []} e possivelmente {$ ne: null}.
Levente Dobson 12/02
17
@rdsoze a primeira linha da pergunta diz "Todos os meus registros têm um campo chamado" figuras ". Este campo é uma matriz" . Além disso, este é um cenário perfeitamente realista e comum. Essa resposta não está errada, ela funciona para a pergunta exatamente como está escrita, e criticá-la ou fazer voto negativo pelo fato de não resolver um problema diferente é bobagem.
Mark Amery
1
@Cec Toda a documentação diz que, se você usar $ size na consulta, ela não usará nenhum índice para fornecer resultados mais rápidos. Portanto, se você tem um índice nesse campo e deseja usá-lo, siga outras abordagens como {$ ne: []}, se isso funcionar para você, usará seu índice.
Levente Dobson
108

Isso também pode funcionar para você:

ME.find({'pictures.0': {$exists: true}});
tenbatsu
fonte
2
Agradável! Isso também permite verificar um tamanho mínimo. Você sabe se as matrizes são sempre indexadas sequencialmente? Haveria um caso em que pictures.2existe, mas pictures.1não existe ?
anushr
2
O $existsoperador é um booleano, não um deslocamento. @tenbatsu deve estar usando em truevez de 1.
ekillaby
2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? Sim, pode ser esse o caso.
O BNDR
@ TheBndr Isso só poderia acontecer se picturesfosse um sub-documento, não uma matriz. por exemplopictures: {'2': 123}
JohnnyHK
4
Isso é agradável e intuitivo, mas tenha cuidado se o desempenho for importante - ele fará uma verificação completa da coleção, mesmo se você tiver um índice pictures.
Wojcikstefan
35

Você se preocupa com duas coisas ao consultar - precisão e desempenho. Com isso em mente, testei algumas abordagens diferentes no MongoDB v3.0.14.

TL; DR db.doc.find({ nums: { $gt: -Infinity }})é o mais rápido e confiável (pelo menos na versão MongoDB que testei).

EDIT: Isso não funciona mais no MongoDB v3.6! Veja os comentários nesta postagem para uma solução em potencial.

Configuração

Inseri 1k documentos sem um campo de lista, 1k documentos com uma lista vazia e 5 documentos com uma lista não vazia.

for (var i = 0; i < 1000; i++) { db.doc.insert({}); }
for (var i = 0; i < 1000; i++) { db.doc.insert({ nums: [] }); }
for (var i = 0; i < 5; i++) { db.doc.insert({ nums: [1, 2, 3] }); }
db.doc.createIndex({ nums: 1 });

Reconheço que essa escala não é suficiente para levar o desempenho tão a sério quanto nos testes abaixo, mas é suficiente para apresentar a correção de várias consultas e o comportamento dos planos de consulta escolhidos.

Testes

db.doc.find({'nums': {'$exists': true}}) retorna resultados incorretos (pelo que estamos tentando realizar).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': {'$exists': true}}).count()
1005

-

db.doc.find({'nums.0': {'$exists': true}})retorna resultados corretos, mas também é lento usando uma verificação completa da coleção ( COLLSCANestágio de aviso na explicação).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).explain()
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.doc",
    "indexFilterSet": false,
    "parsedQuery": {
      "nums.0": {
        "$exists": true
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "nums.0": {
          "$exists": true
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": [ ]
  },
  "serverInfo": {
    "host": "MacBook-Pro",
    "port": 27017,
    "version": "3.0.14",
    "gitVersion": "08352afcca24bfc145240a0fac9d28b978ab77f3"
  },
  "ok": 1
}

-

db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}})retorna resultados incorretos. Isso ocorre devido a uma verificação de índice inválida, que não avança nenhum documento. Provavelmente será preciso, mas lento sem o índice.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 2,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$gt": {
              "$size": 0
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "({ $size: 0.0 }, [])"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}})retorna resultados corretos, mas o desempenho é ruim. Tecnicamente, ele faz uma varredura de índice, mas ainda avança todos os documentos e precisa filtrar por eles).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2016,
  "advanced": 5,
  "needTime": 2010,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$exists": true
          }
        },
        {
          "$not": {
            "nums": {
              "$size": 0
            }
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 2016,
    "advanced": 5,
    "needTime": 2010,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 2005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 2005,
      "executionTimeMillisEstimate": 0,
      "works": 2015,
      "advanced": 2005,
      "needTime": 10,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, MaxKey]"
        ]
      },
      "keysExamined": 2015,
      "dupsTested": 2015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $ne: [] }})retorna resultados corretos e é um pouco mais rápido, mas o desempenho ainda não é o ideal. Ele usa o IXSCAN, que apenas avança documentos com um campo de lista existente, mas depois filtra as listas vazias uma a uma.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 1018,
  "advanced": 5,
  "needTime": 1011,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "$not": {
            "nums": {
              "$eq": [ ]
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 1017,
    "advanced": 5,
    "needTime": 1011,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 1005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 1005,
      "executionTimeMillisEstimate": 0,
      "works": 1016,
      "advanced": 1005,
      "needTime": 11,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, undefined)",
          "(undefined, [])",
          "([], MaxKey]"
        ]
      },
      "keysExamined": 1016,
      "dupsTested": 1015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $gt: [] }})É PERIGOSO PORQUE DEPENDENDO DO ÍNDICE USADO PODE DAR RESULTADOS INESPERADOS. Isso ocorre devido a uma verificação de índice inválida que não avança nenhum documento.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
5

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 1,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "nums": {
        "$gt": [ ]
      }
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "([], BinData(0, ))"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums.0’: { $gt: -Infinity }}) retorna resultados corretos, mas apresenta desempenho ruim (usa uma verificação completa da coleção).

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "COLLSCAN",
  "filter": {
    "nums.0": {
      "$gt": -Infinity
    }
  },
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2007,
  "advanced": 5,
  "needTime": 2001,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "direction": "forward",
  "docsExamined": 2005
}

-

db.doc.find({'nums': { $gt: -Infinity }})surpreendentemente, isso funciona muito bem! Ele fornece os resultados certos e é rápido, avançando 5 documentos da fase de verificação do índice.

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "FETCH",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 16,
  "advanced": 5,
  "needTime": 10,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "docsExamined": 5,
  "alreadyHasObj": 0,
  "inputStage": {
    "stage": "IXSCAN",
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 15,
    "advanced": 5,
    "needTime": 10,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "keyPattern": {
      "nums": 1
    },
    "indexName": "nums_1",
    "isMultiKey": true,
    "direction": "forward",
    "indexBounds": {
      "nums": [
        "(-inf.0, inf.0]"
      ]
    },
    "keysExamined": 15,
    "dupsTested": 15,
    "dupsDropped": 10,
    "seenInvalidated": 0,
    "matchTested": 0
  }
}
wojcikstefan
fonte
Obrigado pela sua resposta muito detalhada @wojcikstefan. Infelizmente, sua solução sugerida não parece funcionar no meu caso. Eu tenho uma coleção do MongoDB 3.6.4 com documentos de 2m, a maioria deles com uma seen_eventsmatriz String, que também é indexada. Pesquisando com { $gt: -Infinity }, recebo imediatamente 0 documentos. Usando { $exists: true, $ne: [] }eu obtenho os 1,2 milhões de documentos mais prováveis, com muito tempo sendo desperdiçado no estágio FETCH: gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NCode
Parece que você está @Ncode direita - isso já não funciona em MongoDB v3.6 :( Eu brinquei com ele por alguns minutos e aqui está o que eu encontrei: 1. db.test_collection.find({"seen_events.0": {$exists: true}})é ruim porque ele usa uma varredura coleção 2.. db.test_collection.find({seen_events: {$exists: true, $ne: []}})É . ruim porque sua IXSCAN corresponde a todos os documentos e, em seguida, a filtragem é realizada na fase lenta FETCH 3. o mesmo vale para db.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}})4. Todos os outros consultas retornam resultados inválidos..
wojcikstefan
1
A @NCode encontrou uma solução! Se tiver certeza de que todos os não-vazia seen_eventscontêm cordas, você pode usar este: db.test_collection.find({seen_events: {$gt: ''}}).count(). Para confirmar se o desempenho está bom, confira db.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats. Você provavelmente pode impor que os eventos vistos são strings via validação de esquema: docs.mongodb.com/manual/core/schema-validation
wojcikstefan
Obrigado! Todos os valores existentes são cadeias de caracteres, então vou tentar isso. Há também um erro que discute esse problema no rastreador de
NCode
30

Começando com a versão 2.6, outra maneira de fazer isso é comparar o campo com uma matriz vazia:

ME.find({pictures: {$gt: []}})

Testando-o no shell:

> db.ME.insert([
{pictures: [1,2,3]},
{pictures: []},
{pictures: ['']},
{pictures: [0]},
{pictures: 1},
{foobar: 1}
])

> db.ME.find({pictures: {$gt: []}})
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a7"), "pictures": [ 1, 2, 3 ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a9"), "pictures": [ "" ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4aa"), "pictures": [ 0 ] }

Portanto, inclui adequadamente os documentos em que pictureshá pelo menos um elemento de matriz e exclui os documentos em que pictureshá uma matriz vazia, não uma matriz ou ausente.

JohnnyHK
fonte
7
CUIDADO, esta resposta pode causar problemas se você tentar usar índices. Fazendo db.ME.createIndex({ pictures: 1 })e depois db.ME.find({pictures: {$gt: []}})retornará zero resultados, pelo menos no MongoDB v3.0.14
wojcikstefan 4/17/17
@wojcikstefan Good catch. Precisa dar uma nova olhada nisso.
JohnnyHK
5

Você pode usar qualquer um dos seguintes para conseguir isso.
Ambos também cuidam de não retornar um resultado para objetos que não possuem a chave solicitada:

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})
Paul Imisi
fonte
4

Recupere todos e somente os documentos em que 'figuras' é uma matriz e não está vazia

ME.find({pictures: {$type: 'array', $ne: []}})

Se você estiver usando uma versão do MongoDb anterior à 3.2 , use em $type: 4vez de $type: 'array'. Observe que esta solução nem usa $ size , portanto, não há problemas com índices ("As consultas não podem usar índices para a parte $ size de uma consulta")

Outras soluções, incluindo estas (resposta aceita):

ME.find ({fotos: {$ existe: verdadeiro, $ não: {$ size: 0}}}); ME.find ({fotos: {$ existe: verdadeiro, $ ne: []}})

são errado , porque eles retornam documentos, mesmo que, por exemplo, 'retratos' é null, undefined, 0, etc.

SC1000
fonte
2

Use o $elemMatchoperador: de acordo com a documentação

O operador $ elemMatch corresponde a documentos que contêm um campo de matriz com pelo menos um elemento que corresponde a todos os critérios de consulta especificados.

$elemMatchesgarante que o valor seja uma matriz e que não esteja vazio. Portanto, a consulta seria algo como

ME.find({ pictures: { $elemMatch: {$exists: true }}})

PS Uma variante desse código é encontrada no curso M121 da MongoDB University.

Andres Moreno
fonte
0

Você também pode usar o método auxiliar Existe no operador Mongo.

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });
Comer no Joes
fonte
0
{ $where: "this.pictures.length > 1" }

use o $ where e passe o this.field_name.length que retorna o tamanho do campo da matriz e verifique-o comparando com o número. se qualquer matriz tiver algum valor além do tamanho da matriz, deve ser pelo menos 1. para que todo o campo da matriz tenha comprimento maior que um, significa que há alguns dados nessa matriz

Prabhat Yadav
fonte
-8
ME.find({pictures: {$exists: true}}) 

Simples assim, isso funcionou para mim.

Luis Fletes
fonte