Trova i record MongoDB in cui il campo dell'array non è vuoto


503

Tutti i miei dischi hanno un campo chiamato "immagini". Questo campo è un array di stringhe.

Ora voglio i 10 record più recenti in cui questo array NON È vuoto.

Ho cercato su Google, ma stranamente non ho trovato molto su questo. Ho letto nell'opzione $ where, ma mi chiedevo quanto sia lento per le funzioni native e se esiste una soluzione migliore.

E anche allora, ciò non funziona:

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

Non restituisce nulla. Lasciare this.picturessenza il bit di lunghezza funziona, ma ovviamente restituisce anche record vuoti.

Risposte:


831

Se disponi anche di documenti che non dispongono della chiave, puoi utilizzare:

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

MongoDB non usa gli indici se è coinvolta $ size, quindi ecco una soluzione migliore:

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

Dalla versione 2.6 di MongoDB, puoi confrontare con l'operatore $gtma potresti portare a risultati imprevisti (puoi trovare una spiegazione dettagliata in questa risposta ):

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

6
Per me questo è l'approccio corretto, poiché assicura che l'array esista e non sia vuoto.
LeandroCR,

Come posso ottenere la stessa funzionalità usandomongoengine
Rohit Khatri il

54
ATTENZIONE, ME.find({ pictures: { $gt: [] } })È PERICOLOSO, anche nelle versioni MongoDB più recenti. Se hai un indice nel tuo campo elenco e quell'indice viene utilizzato durante la query, otterrai risultati imprevisti. Ad esempio: db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()restituisce il numero giusto, mentre db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()restituisce 0.
Wojcikstefan,

1
Vedere la mia risposta dettagliata qui sotto per imparare perché questo potrebbe non funzionare per voi: stackoverflow.com/a/42601244/1579058
wojcikstefan

6
Il commento di @ wojcikstefan deve essere votato per impedire alle persone di utilizzare l'ultimo suggerimento che, in determinate circostanze, in realtà non restituisce i documenti corrispondenti.
Thomas Jung,

181

Dopo un po 'più di ricerca, specialmente nei documenti mongodb e sconcertanti pezzi insieme, questa era la risposta:

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

27
Questo non funziona Non so se precedentemente funzionasse, ma questo restituirà anche oggetti che non hanno il tasto 'foto'.
rdsoze,

17
Incredibile come questa risposta abbia 63 voti, quando in realtà ciò che ha detto @rdsoze è vero - la query restituirà anche i record che non hanno il picturescampo.
Dan Dascalescu,

5
Fai attenzione, mongoDB non utilizzerà gli indici se il collegamento $ size è coinvolto . Sarebbe meglio includere {$ ne: []} e possibilmente {$ ne: null}.
Levente Dobson,

17
@rdsoze la prima riga della domanda afferma "Tutti i miei record hanno un campo chiamato" immagini ". Questo campo è un array" . Inoltre, questo è uno scenario perfettamente realistico e comune. Questa risposta non è sbagliata, funziona esattamente per la domanda come scritta, e criticarla o ridimensionarla per il fatto che non risolve un problema diverso è sciocca.
Mark Amery,

1
@Cec Tutta la documentazione dice che se si utilizza $ size nella query, non utilizzerà alcun indice per ottenere risultati più rapidi. Quindi, se hai un indice su quel campo e vuoi usarlo, segui altri approcci come {$ ne: []}, se funziona per te, userà il tuo indice.
Levente Dobson,

108

Questo potrebbe funzionare anche per te:

ME.find({'pictures.0': {$exists: true}});

2
Bello! Ciò consente anche di verificare la dimensione minima. Sai se gli array sono sempre indicizzati in sequenza? Ci sarebbe mai un caso in cui pictures.2esiste ma pictures.1non lo è?
Anushr

2
L' $existsoperatore è un valore booleano, non un offset. @tenbatsu dovrebbe usare trueinvece di 1.
ekillaby

2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? Sì, potrebbe succedere quel caso.
Il Bndr

@TheBndr Ciò potrebbe accadere solo se si picturestratta di un sottodocumento , non di un array. ad es.pictures: {'2': 123}
JohnnyHK,

4
È bello e intuitivo, ma fai attenzione se le prestazioni sono importanti: eseguirà una scansione completa della raccolta anche se hai un indice attivo pictures.
Wojcikstefan,

35

Ti interessano due cose durante le query: precisione e prestazioni. Con questo in mente, ho testato alcuni approcci diversi in MongoDB v3.0.14.

TL; DR db.doc.find({ nums: { $gt: -Infinity }})è il più veloce e affidabile (almeno nella versione MongoDB che ho testato).

EDIT: Questo non funziona più in MongoDB v3.6! Vedi i commenti sotto questo post per una potenziale soluzione.

Impostare

Ho inserito 1k documenti con un campo elenco, 1k documenti con un elenco vuoto e 5 documenti con un elenco non vuoto.

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

Riconosco che questa non è una scala sufficiente per prendere sul serio le prestazioni come nei test seguenti, ma è sufficiente per presentare la correttezza delle varie query e il comportamento dei piani di query scelti.

test

db.doc.find({'nums': {'$exists': true}}) restituisce risultati errati (per quello che stiamo cercando di ottenere).

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

-

db.doc.find({'nums.0': {'$exists': true}})restituisce risultati corretti, ma è anche lento utilizzando una scansione completa della raccolta (notare la COLLSCANfase nella spiegazione).

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 }}})restituisce risultati errati. Questo perché una scansione dell'indice non valida non ha fatto avanzare documenti. Probabilmente sarà preciso ma lento senza l'indice.

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 }}})restituisce risultati corretti, ma le prestazioni sono scadenti. Tecnicamente esegue una scansione dell'indice, ma poi fa ancora avanzare tutti i documenti e quindi deve filtrarli.

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: [] }})restituisce risultati corretti ed è leggermente più veloce, ma le prestazioni non sono ancora ideali. Utilizza IXSCAN che fa avanzare solo i documenti con un campo elenco esistente, ma deve quindi filtrare gli elenchi vuoti uno per uno.

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: [] }})È PERICOLOSO PERCHÉ DIPENDERE DALL'INDICE USATO POTREBBE DARE RISULTATI INASPETTATI. Questo a causa di una scansione dell'indice non valida che non fa avanzare documenti.

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 }}) restituisce risultati corretti, ma ha prestazioni scadenti (utilizza una scansione della raccolta completa).

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 }})sorprendentemente, funziona molto bene! Fornisce i risultati giusti ed è veloce, facendo avanzare 5 documenti dalla fase di scansione dell'indice.

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

Grazie per la tua risposta molto dettagliata @wojcikstefan. Sfortunatamente, la tua soluzione suggerita non sembra funzionare nel mio caso. Ho una collezione MongoDB 3.6.4 con documenti di 2m, la maggior parte dei quali ha un seen_eventsarray String, anch'esso indicizzato. Cercando con { $gt: -Infinity }, ottengo immediatamente 0 documenti. Utilizzando { $exists: true, $ne: [] }ottengo i documenti 1,2m più probabili, con un sacco di tempo sprecato nella fase FETCH: gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NC

Sembra che tu abbia ragione @Ncode - questo non funziona più in MongoDB v3.6 :( Ci ho provato per alcuni minuti ed ecco cosa ho trovato: 1. db.test_collection.find({"seen_events.0": {$exists: true}})è male perché usa una scansione della collezione. 2. db.test_collection.find({seen_events: {$exists: true, $ne: []}})è male perché IXSCAN corrisponde a tutti i documenti e quindi il filtro viene eseguito nella fase FETCH lenta 3. Lo stesso vale per db.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}})4. Tutte le altre query restituiscono risultati non validi
wojcikstefan

1
@NCode ha trovato una soluzione! Se si è certi che tutti i non-vuoto seen_eventscontengono stringhe, è possibile utilizzare questo: db.test_collection.find({seen_events: {$gt: ''}}).count(). Per confermare che funziona bene, controlla db.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats. Probabilmente puoi far valere che gli eventi visti sono stringhe tramite la convalida dello schema: docs.mongodb.com/manual/core/schema-validation
wojcikstefan

Grazie! Tutti i valori esistenti sono stringhe, quindi lo proverò. C'è anche un bug che discute di questo problema nel bugtracker MongoDB: jira.mongodb.org/browse/SERVER-26655
NC

30

A partire dalla versione 2.6, un altro modo per farlo è confrontare il campo con un array vuoto:

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

Provandolo nella 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 ] }

Quindi include correttamente i documenti in cui picturesè presente almeno un elemento dell'array ed esclude i documenti in cui picturesè presente un array vuoto, non un array o mancante.


7
ATTENZIONE questa risposta potrebbe darti problemi se provi a usare gli indici. Fare db.ME.createIndex({ pictures: 1 })e quindi db.ME.find({pictures: {$gt: []}})restituirà zero risultati, almeno in MongoDB v3.0.14
wojcikstefan

@wojcikstefan Buona cattura. Ho bisogno di dare un'occhiata a questo.
JohnnyHK

5

È possibile utilizzare uno dei seguenti per raggiungere questo obiettivo.
Entrambi si occupano anche di non restituire un risultato per gli oggetti in cui non è presente la chiave richiesta:

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

4

Recupera tutti e solo i documenti in cui "immagini" è un array e non è vuoto

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

Se si utilizza una versione MongoDb precedente alla 3.2 , utilizzare $type: 4invece di $type: 'array'. Si noti che questa soluzione non usa nemmeno $ size , quindi non ci sono problemi con gli indici ("Le query non possono usare gli indici per la porzione $ size di una query")

Altre soluzioni, comprese queste (risposta accettata):

ME.find ({pictures: {$ esiste: vero, $ non: {$ size: 0}}}); ME.find ({pictures: {$ esiste: vero, $ ne: []}})

sono sbagliate perché tornano i documenti anche se, per esempio, 'immagini' è null, undefined, 0, etc.


2

Utilizzare l' $elemMatchoperatore: secondo la documentazione

L'operatore $ elemMatch abbina i documenti che contengono un campo array con almeno un elemento che soddisfa tutti i criteri di query specificati.

$elemMatchessi assicura che il valore sia un array e che non sia vuoto. Quindi la query sarebbe qualcosa di simile

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

PS Una variante di questo codice si trova nel corso M121 dell'Università MongoDB.


0

È inoltre possibile utilizzare il metodo di supporto Esiste sull'operatore Mongo $ esiste

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });

0
{ $where: "this.pictures.length > 1" }

usa $ where e passa this.field_name.length che restituisce la dimensione del campo dell'array e controllalo confrontandolo con il numero. se un array ha un valore diverso dalla dimensione dell'array deve essere almeno 1. quindi tutto il campo dell'array ha una lunghezza maggiore di uno, significa che ha alcuni dati in quell'array


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

Semplice come quello, ha funzionato per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.