mirror of
https://github.com/Steffo99/unimore-bda-1.git
synced 2024-11-22 06:14:19 +00:00
Interrogazioni di esempio sul dataset Amazon
This commit is contained in:
parent
d4bbe97a07
commit
7bac1efc37
5 changed files with 202 additions and 70 deletions
0
.gitmodules
vendored
0
.gitmodules
vendored
233
README.md
233
README.md
|
@ -13,21 +13,7 @@
|
||||||
|
|
||||||
## Premessa
|
## Premessa
|
||||||
|
|
||||||
L'attività è stata svolta su MongoDB 6.0.2, attraverso il progetto [Docker Compose](https://docs.docker.com/compose/) allegato.
|
L'attività è stata svolta su MongoDB 6.0.2, attraverso la macchina virtuale predisposta su [Azure Labs](https://labs.azure.com/virtualmachines), `LabBDA2`.
|
||||||
|
|
||||||
Per ricreare lo stesso ambiente di lavoro utilizzato, sarà necessario:
|
|
||||||
|
|
||||||
1. Inserire il file `metaexport.json` all'interno della cartella `seed`, non allegato per motivi di dimensioni.
|
|
||||||
|
|
||||||
2. Con un daemon Docker in esecuzione, e Docker Compose installato sulla macchina locale, "accendere" il progetto:
|
|
||||||
```console
|
|
||||||
# docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Con la [MongoDB Shell](https://www.mongodb.com/try/download/shell) installata sulla macchina locale, è possibile interfacciarsi al database con:
|
|
||||||
```console
|
|
||||||
$ mongosh --username=unimore --password=unimore --authenticationDatabase=admin mongodb://127.0.0.1:27017/amazon
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Introduzione
|
## Introduzione
|
||||||
|
@ -36,10 +22,14 @@ Sviluppando una applicazione o effettuando analisi su dati, è possibile aver bi
|
||||||
|
|
||||||
Per comodità, MongoDB include funzionalità basilari per la ricerca in linguaggio naturale, in modo che i suoi utilizzatori possano usufruirne senza dover configurare un motore di ricerca esterno come [ElasticSearch](https://www.elastic.co/).
|
Per comodità, MongoDB include funzionalità basilari per la ricerca in linguaggio naturale, in modo che i suoi utilizzatori possano usufruirne senza dover configurare un motore di ricerca esterno come [ElasticSearch](https://www.elastic.co/).
|
||||||
|
|
||||||
|
Sono disponibili funzionalità di ricerca più avanzate, ma sono limitate al piano managed del database, [MongoDB Atlas](https://www.mongodb.com/pricing): esse non vengono prese in considerazione da questa relazione.
|
||||||
|
|
||||||
|
|
||||||
### Il problema degli indici
|
### Il problema degli indici
|
||||||
|
|
||||||
Una richiesta molto comune che riguarda il testo naturale è trovare i documenti contenenti parole specifiche in uno o più campi: ad esempio, in una collezione di film, si potrebbe voler trovare quello con un determinato titolo.
|
Quando si effettua una ricerca in linguaggio naturale su una collezione di documenti, solitamente si desidera trovare uno o più documenti dal contenuto più simile possibile al contenuto della richiesta effettuata.
|
||||||
|
|
||||||
|
Ad esempio, in un database di film, si potrebbe voler trovare il film con uno specifico titolo, o con un termine specifico nella descrizione.
|
||||||
|
|
||||||
Non effettuando confronti diretti (`===`) tra i contenuti dei campi interrogati e i termini della query, non è possibile fare uso dei comuni indici usati per la ricerca binaria; è invece necessario un indice apposito, detto *[Text Index](https://www.mongodb.com/docs/manual/core/index-text/)*, in grado di processare correttamente le stringhe dei documenti nella collezione.
|
Non effettuando confronti diretti (`===`) tra i contenuti dei campi interrogati e i termini della query, non è possibile fare uso dei comuni indici usati per la ricerca binaria; è invece necessario un indice apposito, detto *[Text Index](https://www.mongodb.com/docs/manual/core/index-text/)*, in grado di processare correttamente le stringhe dei documenti nella collezione.
|
||||||
|
|
||||||
|
@ -131,12 +121,16 @@ db.EXAMPLE.createIndex(
|
||||||
|
|
||||||
Per operare efficacemente con il linguaggio naturale, è necessario effettuare alcune operazioni di preprocessing sulle stringhe in questione, trasformandole in insiemi di token.
|
Per operare efficacemente con il linguaggio naturale, è necessario effettuare alcune operazioni di preprocessing sulle stringhe in questione, trasformandole in insiemi di token.
|
||||||
|
|
||||||
> Ho scelto di considerare il contenuto e l'effetto di queste operazioni oltre lo scopo di questo testo, in quanto è stato ampiamente trattato in Gestione dell'Informazione alla triennale.
|
Queste operazioni, su MongoDB, sono:
|
||||||
|
1. tokenizzazione: le parole vengono separate, ripulite da punteggiatura e diacritici, e trasformate in _token_
|
||||||
|
2. eliminazione delle stopwords: i token più comuni e insignificanti ai fini della ricerca, come le congiunzioni, vengono eliminati
|
||||||
|
3. _opzionalmente_ stemming: i token vengono ridotti al loro prefisso, in modo che non ci sia disambiguazione tra singolari, plurali, maschili o femminili
|
||||||
|
|
||||||
|
Esse sono effettuate a runtime, nello specifico:
|
||||||
|
- quando si crea un nuovo Text Index, sono preprocessate le stringhe di tutti i documenti già esistenti;
|
||||||
|
- quando si inserisce un nuovo documento o se ne modifica uno già esistente, sono preprocessate le sue stringhe;
|
||||||
|
- quando si effettua un'interrogazione basata sul Text Index, sono preprocessati i termini di essa.
|
||||||
|
|
||||||
MongoDB effettua queste operazioni a runtime, nello specifico:
|
|
||||||
- quando si crea un nuovo Text Index, sono processate le stringhe di tutti i documenti già esistenti;
|
|
||||||
- quando si inserisce un nuovo documento o se ne modifica uno già esistente, sono processate le sue stringhe;
|
|
||||||
- quando si effettua un'interrogazione basata sul Text Index, sono processati i termini di essa.
|
|
||||||
|
|
||||||
##### Lingua
|
##### Lingua
|
||||||
|
|
||||||
|
@ -180,6 +174,7 @@ db.EXAMPLE.createIndex(
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Interrogazione sull'indice
|
### Interrogazione sull'indice
|
||||||
|
|
||||||
Una volta creato l'indice, è possibile usarlo per effettuare interrogazioni di linguaggio naturale sulla collezione utilizzando i metodi della collezione `.find()` e le chiavi speciali `$text` e `$search`:
|
Una volta creato l'indice, è possibile usarlo per effettuare interrogazioni di linguaggio naturale sulla collezione utilizzando i metodi della collezione `.find()` e le chiavi speciali `$text` e `$search`:
|
||||||
|
@ -193,6 +188,7 @@ db.EXAMPLE.find({
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Query speciali
|
#### Query speciali
|
||||||
|
|
||||||
Circondando di virgolette doppie `"` un termine della ricerca, è possibile richiedere la presenza esatta di uno dato termine o frase all'interno del documento:
|
Circondando di virgolette doppie `"` un termine della ricerca, è possibile richiedere la presenza esatta di uno dato termine o frase all'interno del documento:
|
||||||
|
@ -215,7 +211,7 @@ db.EXAMPLE.find({
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Prefissando un trattino `-` ad un termine è possibile filtrare i documenti che lo contengono:
|
Prefissando un trattino `-` ad un termine è possibile nascondere i documenti che lo contengono:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Cerca documenti che contengono le parole della stringa "La Mia Query" nei campi indicizzati, e che hanno SICURAMENTE la parola "Mia" da qualche parte
|
// Cerca documenti che contengono le parole della stringa "La Mia Query" nei campi indicizzati, e che hanno SICURAMENTE la parola "Mia" da qualche parte
|
||||||
|
@ -226,6 +222,7 @@ db.EXAMPLE.find({
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Query miste
|
#### Query miste
|
||||||
|
|
||||||
È possibile effettuare query `$text` assieme a query "normali", effettuando l'intersezione dei risultati:
|
È possibile effettuare query `$text` assieme a query "normali", effettuando l'intersezione dei risultati:
|
||||||
|
@ -260,13 +257,14 @@ db.EXAMPLE.find(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Interrogazioni di esempio sul dataset Amazon
|
## Interrogazioni di esempio sul dataset Amazon
|
||||||
|
|
||||||
### 0 - Creazione dell'indice sulla collezione Meta
|
### 0 - Creazione del Text Index sulla collezione `meta`
|
||||||
|
|
||||||
La collezione `meta` ha un solo campo in cui sono utilizzate stringhe con testo in linguaggio naturale: `description`.
|
La collezione `meta` ha un solo campo in cui sono utilizzate stringhe con testo in linguaggio naturale: `description`.
|
||||||
|
|
||||||
Pertanto, si crea un indice contenente solo quel campo specifico.
|
Pertanto, si è creato un indice contenente solo quel campo specifico.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
db.meta.createIndex(
|
db.meta.createIndex(
|
||||||
|
@ -280,10 +278,35 @@ db.meta.createIndex(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 0 - Creazione del Text Index sulla collezione `reviews`
|
||||||
|
|
||||||
|
La collezione `reviews` ha due campi di testo in linguaggio naturale: `summary`, il titolo della recensione, e `reviewText`, il contenuto completo della recensione.
|
||||||
|
|
||||||
|
Quindi, si è eliminato l'indice pre-esistente `summary_text`, e si è creato un indice contenente quei due campi, dando peso maggiore al campo `summary`, più rilevante.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
db.reviews.dropIndex("summary_text")
|
||||||
|
|
||||||
|
db.reviews.createIndex(
|
||||||
|
{
|
||||||
|
summary: "text",
|
||||||
|
reviewText: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reviews_text",
|
||||||
|
default_language: "english",
|
||||||
|
weights: {
|
||||||
|
summary: 2,
|
||||||
|
reviewText: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### 1 - Ricerca semplice
|
### 1 - Ricerca semplice
|
||||||
|
|
||||||
Si desidera cercare all'interno della collezione il videogioco _[Terraria](https://store.steampowered.com/app/105600/Terraria/)_.
|
Si desidera cercare all'interno della collezione tutti i dati relativi al videogioco _[Terraria](https://store.steampowered.com/app/105600/Terraria/)_.
|
||||||
|
|
||||||
Si effettua una query dove il termine "Terraria" viene cercato all'interno del Text Index.
|
Si effettua una query dove il termine "Terraria" viene cercato all'interno del Text Index.
|
||||||
|
|
||||||
|
@ -295,8 +318,7 @@ db.meta.findOne(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
La query ha successo e restituisce un documento con le caratteristiche richieste, ma il dataset pare essere sporco: immagine e prezzo del prodotto si riferiscono a una copia del videogioco _[Minecraft](https://www.minecraft.net/en-us)_.
|
||||||
La query ha avuto successo ed ha restituito il prodotto che aspettato:
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Risultato ottenuto</summary>
|
<summary>Risultato ottenuto</summary>
|
||||||
|
@ -338,14 +360,163 @@ La query ha avuto successo ed ha restituito il prodotto che aspettato:
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
### 2 -
|
### 2 - Ricerca mista
|
||||||
|
|
||||||
|
Si desidera trovare tutte le recensioni negative (2 stelle o inferiore) relative a qualche tipo di gioco (contenenti la parola "game" in qualche campo).
|
||||||
|
|
||||||
|
Si effettua la seguente query:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
db.reviews.find(
|
||||||
|
{
|
||||||
|
$text: {$search: "game"},
|
||||||
|
overall: {$lte: 2},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
La query ha successo, e restituisce 192139 documenti.
|
||||||
|
|
||||||
|
|
||||||
### 3 -
|
### 3 - Aggregazione con ricerca speciale
|
||||||
|
|
||||||
|
Si desidera trovare la posizione di ricerca media di prodotti riguardanti "Dungeons & Dragons" (e non con le parole Dungeons e Dragons nella descrizione).
|
||||||
|
|
||||||
|
Si effettua la seguente aggregazione:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
db.meta.aggregate([
|
||||||
|
// Trova i documenti che contengono "Dungeons & Dragons"
|
||||||
|
{$match: {
|
||||||
|
$text: {$search: `"Dungeons & Dragons"`}
|
||||||
|
}},
|
||||||
|
// Trasforma il sottodocumento salesRank in un array
|
||||||
|
{$project: {
|
||||||
|
salesRank: {$objectToArray: "$salesRank"},
|
||||||
|
}},
|
||||||
|
// Effettua l'unwind dell'array
|
||||||
|
{$unwind: {
|
||||||
|
path: "$salesRank",
|
||||||
|
}},
|
||||||
|
// Raggruppa per categoria calcolando la media
|
||||||
|
{$group: {
|
||||||
|
_id: "$salesRank.k",
|
||||||
|
avg: {$avg: "$salesRank.v"},
|
||||||
|
}}
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
L'aggregazione ha successo, e restituisce il seguente risultato:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
{ _id: 'Video Games', avg: 30079.060606060608 },
|
||||||
|
{ _id: 'Books', avg: 257210 },
|
||||||
|
{ _id: 'Software', avg: 26086 }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4 - Aggregazione con ricerca speciale e mista
|
||||||
|
|
||||||
|
Si desidera trovare la posizione di ricerca media, minima e massima dei videogiochi contenenti il termine "Dante".
|
||||||
|
|
||||||
|
Si effettua la seguente aggregazione:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
db.meta.aggregate([
|
||||||
|
// Trova i videogiochi che contengono il termine "Dante"
|
||||||
|
{$match: {
|
||||||
|
$text: {$search: "Dante"},
|
||||||
|
// Le categorie sono tuple di termini dal più generale al più specifico; per risolvere questa query è sufficiente che un termine qualsiasi sia "Video Games"
|
||||||
|
categories: {$elemMatch: {$elemMatch: {$eq: "Video Games"}}},
|
||||||
|
}},
|
||||||
|
// Trasforma il sottodocumento salesRank in un array
|
||||||
|
{$project: {
|
||||||
|
salesRank: {$objectToArray: "$salesRank"},
|
||||||
|
}},
|
||||||
|
// Effettua l'unwind dell'array salesRank
|
||||||
|
{$unwind: {
|
||||||
|
path: "$salesRank",
|
||||||
|
}},
|
||||||
|
// Raggruppa per categoria calcolando posizione minima, media e massima
|
||||||
|
{$group: {
|
||||||
|
_id: "$salesRank.k",
|
||||||
|
best: {$min: "$salesRank.v"},
|
||||||
|
avg: {$avg: "$salesRank.v"},
|
||||||
|
worst: {$max: "$salesRank.v"}
|
||||||
|
}}
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
L'aggregazione ha successo, e restituisce il seguente risultato:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
{
|
||||||
|
_id: 'Video Games',
|
||||||
|
best: 2112,
|
||||||
|
avg: 17816.904761904763,
|
||||||
|
worst: 74608
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### 4 -
|
### 5 - Aggregazione con ricerca e lookup
|
||||||
|
|
||||||
|
Si desidera trovare una bizzarra irregolarità statistica: trovare i dati dei 10 prodotti con le recensioni più alte, contando solo le recensioni in cui compare la parola "cringe".
|
||||||
|
|
||||||
### 5 -
|
Si effettua la seguente query:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
db.reviews.aggregate([
|
||||||
|
// Trova le recensioni che contengono il termine "cringe"
|
||||||
|
{$match:
|
||||||
|
{$text: {$search: "cringe"}},
|
||||||
|
},
|
||||||
|
// Raggruppa le recensioni per codice prodotto, conteggiandole e calcolandone la valutazione media
|
||||||
|
{$group: {
|
||||||
|
_id: "$asin",
|
||||||
|
avgRating: {$avg: "$overall"},
|
||||||
|
reviewCount: {$sum: 1},
|
||||||
|
}},
|
||||||
|
// Scarta i prodotti che non hanno almeno 3 recensioni "cringe": avrebbero facilmente degli outliers
|
||||||
|
{$match: {
|
||||||
|
reviewCount: {$gte: 3},
|
||||||
|
}},
|
||||||
|
// Effettua il lookup dei dati completi dei prodotti attraverso l'asin
|
||||||
|
{$lookup: {
|
||||||
|
from: "meta",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "asin",
|
||||||
|
as: "product",
|
||||||
|
}},
|
||||||
|
// Dato che nella collezione "meta" ci potrebbero essere più prodotti con lo stesso asin, effettua l'unwind dell'array dei prodotti trovati
|
||||||
|
{$unwind: {
|
||||||
|
path: "$product",
|
||||||
|
}},
|
||||||
|
// Mantieni solo i prodotti per cui è stato trovato un corrispondente nella tabella meta; ci sono tantissime non-corrispondenze
|
||||||
|
{$match: {
|
||||||
|
product: {$exists: true},
|
||||||
|
}},
|
||||||
|
// Ordina i prodotti per valutazione media decrescente
|
||||||
|
{$sort: {
|
||||||
|
avgRating: -1,
|
||||||
|
}},
|
||||||
|
// Mostra solo i 10 prodotti con valutazioni migliori
|
||||||
|
{$limit: 10},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
L'aggregazione ha successo, restituendo i seguenti prodotti come risultato:
|
||||||
|
|
||||||
|
1. `0061461369` (5.0* da 4 recensioni)
|
||||||
|
2. `0099496941` (5.0* da 4 recensioni)
|
||||||
|
3. `0062108026` (5.0* da 4 recensioni)
|
||||||
|
4. `0007423632` (5.0* da 3 recensioni)
|
||||||
|
5. `0060392991` (5.0* da 8 recensioni)
|
||||||
|
6. `0062115359` (5.0* da 3 recensioni)
|
||||||
|
7. `0060890096` (5.0* da 4 recensioni)
|
||||||
|
8. `0061713244` (5.0* da 3 recensioni)
|
||||||
|
9. `0007386648` (4.9* da 11 recensioni)
|
||||||
|
10. `0061448761` (4.8* da 4 recensioni)
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
version: "3.9"
|
|
||||||
|
|
||||||
|
|
||||||
services:
|
|
||||||
mongo:
|
|
||||||
image: "mongo:6.0.2-focal"
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:27017:27017"
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: "unimore"
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: "unimore"
|
|
||||||
MONGO_INITDB_DATABASE: "amazon"
|
|
||||||
|
|
||||||
mongoimport:
|
|
||||||
image: "mongo:6.0.2-focal"
|
|
||||||
volumes:
|
|
||||||
- "./seed:/seed"
|
|
||||||
entrypoint: >-
|
|
||||||
mongoimport
|
|
||||||
--host mongo
|
|
||||||
--db amazon
|
|
||||||
--authenticationDatabase admin
|
|
||||||
--username unimore
|
|
||||||
--password unimore
|
|
||||||
--collection meta
|
|
||||||
--type json
|
|
||||||
--file /seed/metaexport.json
|
|
||||||
depends_on:
|
|
||||||
mongo:
|
|
||||||
condition: service_started
|
|
4
seed/.gitignore
vendored
4
seed/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
# Data files should be provided externally
|
|
||||||
# I don't think I'm allowed to redistribute them
|
|
||||||
|
|
||||||
*.json
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Data files
|
|
||||||
|
|
||||||
This directory should contain an externally provided copy of:
|
|
||||||
|
|
||||||
- `metaexport.json` (SHA256: `917fc2a44d03a68747fbe23389db9b178401d6cd1a869b63436bfa0eee526139`)
|
|
Loading…
Reference in a new issue