Come già detto nelle precedenti lezioni, per la ricerca di dati in MongoDB si usa il metodo find
. Tutti i driver hanno un metodo che corrisponde a questo; il codice degli esempi che vedremo usa la sintassi BSON, che può quindi essere facilmente convertita nella sintassi usata dai vari driver. Alcuni di essi hanno anche altri metodi di estrazione dei dati, peculiari per i linguaggi usati (ad esempio LINQ in C#), ma in realtà funzionano come wrapper di find
.
Il metodo find
accetta in ingresso due parametri, entrambi opzionali: i criteri di filtraggio e le specifiche di proiezione. Tramite il primo si stabilisce quali documenti cercare, con il secondo invece quali campi dei documenti trovati devono o non devono essere restituiti. Per essere precisi, find non restituisce un elenco di documenti ma un cursore, ossia un puntatore ad un elenco che deve essere iterato per ottenere i documenti veri e propri. La console di mongo si occupa per noi di iterare il cursore e mostrare a video i risultati. Usando i driver, invece, è responsabilità nostra iterare il cursore nel modo specifico per il driver ed il linguaggio usato.
Non specificare i criteri di filtraggio comporta la restituzione di tutti i documenti (nessun filtraggio), mentre non specificando la proiezione si ottengono i documenti interi. Come abbiamo già visto, infatti, per ottenere tutti i documenti (con tutti i relativi campi) di una collezione, possiamo invocare la funzione senza parametri:
> db.books.find()
Per prima cosa vediamo come effettuare dei filtraggi.
Filtraggio per uguaglianza
Il filtraggio più semplice è quello per uguaglianza: cerchiamo documenti che abbiano esattamente un dato valore in un certo campo. Ad esempio:
> db.books.find({ isbn: '1783287756' })
Questa query restituisce il libro avente l’ISBN indicato. Per la precisione, il valore di ritorno è un iteratore contenente un solo libro con il codice ISBN indicato.
> db.books.find({ category: 'Programming' })
Quest’altra query, invece, restituisce un iteratore con tutti i libri indicati nella categoria Programming. Se il campo category
è un array, verrà cercata una corrispondenza all’interno dell’array. Ad esempio, il seguente documento verrà restituito tra i risultati:
{
isbn: "0321826620",
authors: [
{ name: "Pramod J.", surname: "Sadalage" } ,
{ name: "Martin", surname: "Fowler" }
],
title: "NoSQL Distilled",
category: [ "Programming", "Databases" ],
pages: 192,
published: new Date(2012, 8, 18)
}
La ricerca può essere fatta anche all’interno dei documenti incorporati. Ad esempio il documento precedente verrà restituito anche dalla seguente query:
> db.books.find({ 'authors.surname': 'Fowler' })
Infatti, verrà cercato nel campo authors
un documento con il campo surname
esattamente uguale a Fowler. Attenzione: la ricerca per stringhe è case sensitive, e considera anche i gli spazi (esattamente come nell’uguaglianza in Oracle).
Ricerca per confronto
Un altro tipo di filtraggio applicabile utilizza gli operatori forniti da MongoDB per la ricerca. Sul sito ufficiale si può accedere alla guida di riferimento di tutti gli operatori. La tabella seguente mostra gli operatori di confronto:
Operatore | Descrizione |
---|---|
$gt |
Maggiore di |
$gte |
Maggiore o uguale a |
$lt |
Minore di |
$lte |
Minore o uguale a |
$ne |
Diverso da |
$in |
Uguale ad un valore tra quelli inseriti in un array |
$nin |
Diverso da ogni valore tra quelli inseriti in un array |
La sintassi che utilizza gli operatori è lievemente diversa: bisogna specificare l’operazione di filtraggio su un campo, in forma di documento incorporato nel filtro:
> db.books.find( { pages: { $lte: 200 } } )
Questa interrogazione restituisce tutti i libri con meno di 200 pagine. La query che segue considera i libri che non hanno alcuna categoria tra quelle elencate:
> db.books.find( { category: { $nin: ["Java", "Databases", "Programming"] } } )
Combinare più filtri
I filtri si possono combinare usando gli operatori logici: $and
, $or
, $not
e $nor
.
Il $not
, essendo un operatore unario per definizione, accetta un solo parametro e si inserisce sempre dopo il nome del campo:
> db.books.find( { pages: { $not: { $gte: 200 } } } )
Gli altri operatori, invece, possono combinare filtri su vari campi:
> db.books.find( { $or: [ { pages: { $lte: 200 } } , { published: { $gt: new Date(2014, 0, 1) } } ] })
La precedente query considera solo i libri pubblicati dal 2014 e quelli con meno di 200 pagine. L’operatore $and
è implicito quando si specificano più operatori o più condizioni sullo stesso campo. Ad esempio la seguente query è implicitamente un $and
tra due filtri:
> db.books.find( { pages: { $lte: 200 }, published: { $gt: new Date(2014, 0, 1) } })
Ovviamente, gli operatori possono essere combinati:
> db.books.find( {
$or: [
{ pages: { $lte: 200 } },
{ $and: [
{ price: { $gt: new Date(2013, 0, 1) } },
{ price: { $lt: new Date(2014, 0, 1) } }
]
}
]
})
Filtri sullo schema dei dati
Tutti i filtri che abbiamo visto finora si basano sul valore dei campi. Se un documento non ha il campo indicato nel filtro, esso non verrà preso in considerazione e quindi non verrà restituito come risultato di find
.
Si possono filtrare i documenti richiedendo che esista espressamente un certo campo, oppure che esso non esista. La query seguente restituisce solo i libri per i quali il campo published
non è specificato:
> db.books.find( { published: { $exists: false } } )
Filtri basati su testo
Un modo di filtrare documenti basandosi su testo è usare le regular expression. Per esempio, la seguente query cerca la parola nosql nel campo title
dei documenti:
> db.books.find( { title: { $regex: /nosql/i } } )
La i
dopo il pattern di ricerca indica che non interessa la distinzione tra maiuscole e minuscole (case insensive).
Il limite di questo tipo di ricerca è che l’utilizzo delle regular expression è computazionalmente oneroso, anche perchè deve essere applicato a tutti i documenti della collezione. Un metodo più efficiente consiste nell’usare un indice testuale, come vedremo nella prossima lezione.
Filtri su array
Come abbiamo già visto, una normale ricerca su un campo array assume il significato di “almeno un elemento che soddisfa la condizione indicata”. Questo significa che, se ci sono due o più condizioni su un campo, è sufficiente che ognuna sia soddisfatta da almeno un elemento. Ad esempio, se abbiamo un array come il seguente:
[ new Date(2012, 3, 3), new Date(2015, 4, 4) ]
tale array soddisferà questa condizione:
> db.books.find( { published: { $gt: new Date(2013, 0, 1), $lt: new Date(2014,0,1) } } )
Infatti, una data soddisfa la prima condizione e l’altra la seconda. MongoDB introduce l’operatore $elemMatch
per indicare che deve esistere un elemento che deve soddisfare entrambe le condizioni:
> db.books.find( { published: { $elemMatch: { $gt: new Date(2013, 0, 1), $lt: new Date(2014,0,1) } } } )
Sostituendo l’operatore $all
a $elemMatch
, invece, si richiede che tutti gli elementi dell’array soddisfino le condizioni indicate.
Proiezione
Spesso i documenti memorizzati in MongoDB sono molto più grandi di quelli che vediamo nei nostri esempi, e spesso contengono molti array e documenti incorporati. Richiedere interi documenti per mezzo di find
significa far viaggiare molti dati (spesso non necessari) sulla rete. Per questo è possibile effettuare una proiezione dei documenti, ossia selezionare solo alcuni campi:
> db.books.find( { title: { $regex: /nosql/i } }, { title: 1, authors: 1 } )
Con questa query, si richiedono soltanto i campi title
ed authors
, oltre al campo _id
che viene restituito per default. Si può anche indicare di riportare tutti i campi tranne alcuni, utilizzando 0 come valore al filtro. Inserendo _id: 0
, in particolare, si richiede di non restituire l’_id
del documento.
L’operatore $slice
consente di ottenere solo un numero ristretto di elementi in un array. La seguente query, ad esempio, restituisce tutti i campi tranne un eventuale campo content
(ad esempio perché potrebbe essere molto grande) e mostra soltanto il primo degli autori del libro.
> db.books.find( { title: { $regex: /nosql/i } }, { content: 0, authors: { $slice: 1 } } )
Primo di procedere, è bene sottolineare che la proiezione non va confusa con la riduzione: quest’ultima, come vedremo nel seguito di questa guida, permette di aggregare vari documenti per effettuare medie, somme ed altre operazioni, mentre la proiezione riguarda un singolo documento.