Richiesta di documenti in cui la dimensione dell'array è maggiore di 1


664

Ho una collezione MongoDB con documenti nel seguente formato:

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

Al momento posso ottenere documenti che corrispondono a una dimensione dell'array specifica:

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

Ciò restituisce correttamente i documenti con 2 elementi namenell'array. Tuttavia, non posso eseguire un $gtcomando per restituire tutti i documenti in cui il namecampo ha una dimensione dell'array maggiore di 2:

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

Come posso selezionare tutti i documenti con un namearray di dimensioni maggiori di uno (preferibilmente senza dover modificare l'attuale struttura dei dati)?


3
Le versioni più recenti di MongoDB hanno l'operatore $ size; dovresti dare un'occhiata alla risposta di @ tobia
AlbertEngelB

4
Soluzione effettiva: FooArray: {$ gt: {$ size: 'length'}} -> la lunghezza può essere qualsiasi numero
Sergi Nadal,

Risposte:


489

Aggiornare:

Per le versioni mongodb 2.2+ un modo più efficiente per farlo, descritto da @JohnnyHK in un'altra risposta .


1.Usando $ dove

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

Ma...

Javascript viene eseguito più lentamente degli operatori nativi elencati in questa pagina, ma è molto flessibile. Vedere la pagina di elaborazione sul lato server per ulteriori informazioni.

2. Creare un campo aggiuntivoNamesArrayLength , aggiornarlo con la lunghezza dell'array di nomi e quindi utilizzare nelle query:

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

Sarà una soluzione migliore e funzionerà molto più velocemente (puoi creare un indice su di essa).


4
Fantastico, è stato perfetto grazie. Anche se in realtà ho alcuni documenti che non hanno un nome, quindi ho dovuto modificare la query per essere: db.accommodations.find ({$ where: "if (this.name && this.name.length> 1) {return this ;} "});
Emson,

sei il benvenuto, sì, puoi usare qualsiasi javascript in $where, è molto flessibile.
Andrew Orsich,

8
@emson Penso che sarebbe più veloce fare qualcosa come {"nome": {$ esiste: 1}, $ dove: "this.name.lenght> 1"} ... minimizzando la parte nella query javascript più lenta. Presumo che funzioni e che $ esiste avrebbe una precedenza più alta.
nairbv,

1
Non avevo idea che potresti inserire javascript nella query, Json può essere ingombrante. Molte di queste query vengono immesse una sola volta a mano, pertanto non è necessaria l'ottimizzazione.
Userò

3
Dopo aver aggiunto / rimosso elementi dall'array, è necessario aggiornare il conteggio di "NamesArrayLength". Questo può essere fatto in una singola query? Oppure richiede 2 query, una per l'aggiornamento dell'array e un'altra per l'aggiornamento del conteggio?
WarLord,

1329

Esiste un modo più efficiente per eseguire questa operazione in MongoDB 2.2+ ora che è possibile utilizzare indici di array numerici nelle chiavi dell'oggetto query.

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

È possibile supportare questa query con un indice che utilizza un'espressione di filtro parziale (richiede 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

16
Qualcuno potrebbe spiegare come indicizzarlo.
Ben

26
Sono davvero impressionato da quanto sia efficace e anche da come "fuori dagli schemi" stavi pensando di trovare questa soluzione. Funziona anche su 2.6.
earthmeLon

2
Funziona anche su 3.0. Grazie mille per averlo trovato.
pikanezi,

1
@Dims Nessuna differenza, davvero: {'Name Field.1': {$exists: true}}.
JohnnyHK,

9
@JoseRicardoBustosM. Ciò troverebbe i documenti in cui namecontiene almeno 1 elemento, ma l'OP stava cercando maggiore di 1.
JohnnyHK

128

Credo che questa sia la query più veloce che risponda alla tua domanda, perché non utilizza una $whereclausola interpretata :

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

Significa "tutti i documenti ad eccezione di quelli senza nome (array inesistente o vuoto) o con un solo nome".

Test:

> 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 non lo so. Questo è stato sicuramente meglio delle soluzioni Javascript, ma per i nuovi MongoDB dovresti probabilmente usare{'name.1': {$exists: true}}
Tobia

@Tobia il mio primo utilizzo era $ esiste solo ma in realtà usa l'intera scansione della tabella così molto lenta. db.test.find ({"name": "abc", "d.5": {$ esiste: vero}, "d.6": {$ esiste: vero}}) "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 lo vedi scansionato l'intera tabella.

Sarebbe bello aggiornare la risposta per raccomandare altre alternative (come 'name.1': {$exists: true}}, e anche perché questo è hardcoded per "1" e non si
adatta

1
Questo può essere veloce ma cade a pezzi se stai cercando elenchi> N, dove N non è piccolo.
Brandon Hill,

62

Puoi usare anche aggregato:

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

// aggiungi "size_of_name" al documento di transito e lo usi per filtrare la dimensione del nome


Questa soluzione è la più generale, insieme a quella di @ JohnnyHK poiché può essere utilizzata per qualsiasi dimensione di array.
arun

se voglio usare "size_of_name" all'interno della proiezione, allora come posso farlo ?? In realtà voglio usare $ slice all'interno della proiezione in cui il suo valore è uguale a $ slice: [0, "size_of_name" - skip] ??
Sudhanshu Gaur,

44

Prova a fare qualcosa del genere:

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

1 è il numero, se si desidera recuperare un record maggiore di 50, allora fare ArrayName.50 Grazie.


2
La stessa risposta è stata data tre anni prima .
Dan Dascalescu,

Vengo dal futuro e l'avrei apprezzato: questa soluzione funziona verificando se esiste un elemento in detta posizione. Pertanto, la raccolta deve essere maggiore | uguale a quel numero.
MarAvFe

possiamo inserire un numero dinamico come "ArrayName. <some_num>" all'interno della query?
Sahil Mahajan,

Sì, puoi usare qualsiasi numero. Se vuoi recuperare un record maggiore di N, passa n.
Aman Goel,


26

È possibile utilizzare $ expr (operatore versione 3.6 di mongo) per utilizzare le funzioni di aggregazione nelle query regolari.

Confronta query operatorsvs aggregation comparison operators.

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

Come passeresti invece di $nameun array che è un documento secondario, ad esempio in un record "persona" passport.stamps? Ho provato varie combinazioni di quotazioni ma ho capito "The argument to $size must be an array, but was of type: string/missing".
Dan Dascalescu,

3
@DanDascalescu Sembra che i timbri non siano presenti in tutti i documenti. È possibile utilizzare ifNull per produrre array vuoti quando i timbri non sono presenti. Qualcosa del generedb.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram,



13

Ho trovato questa soluzione per trovare elementi con un campo di matrice maggiore di una certa lunghezza

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

Il primo aggregato $ match utilizza un argomento vero per tutti i documenti. Se vuoto, otterrei

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

Questa è essenzialmente la stessa risposta di questa , fornita 2 anni prima.
Dan Dascalescu,

1

Conosco la sua vecchia domanda, ma ci sto provando con $ gte e $ size in cerca. Penso che find () sia più veloce.

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

Sebbene le risposte di cui sopra funzionino correttamente, quello che hai cercato di fare inizialmente era il modo corretto, tuttavia hai solo la sintassi al contrario (cambia "$ size" e "$ gt") ..

Corretta:

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

non corretto:

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

1
Non vedo perché così tanti voti negativi: questo funziona perfettamente per me!
Jake Stokes,

Non ho effettuato il downvote, ma non funziona (v4.2).
Evgeni Nabokov,

Funziona perfettamente, v 4.2.5
jperl
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.