diff --git a/_config.yml b/_config.yml
index 76cca2a4..98282fa5 100644
--- a/_config.yml
+++ b/_config.yml
@@ -50,7 +50,7 @@ socials_in_search: false
bib_search: false
# Dimensions
-max_width: 930px
+max_width: 1000px
# TODO: add layout settings (single page vs. multi-page)
@@ -213,6 +213,7 @@ markdown: kramdown
highlighter: rouge
kramdown:
input: GFM
+ toc_levels: 1..6 # set the levels of the table of contents (1..6)
syntax_highlighter_opts:
css_class: "highlight"
span:
diff --git a/_formations/nosql/cassandra.md b/_formations/nosql/1_cassandra.md
similarity index 57%
rename from _formations/nosql/cassandra.md
rename to _formations/nosql/1_cassandra.md
index 9c2141d1..208e7309 100644
--- a/_formations/nosql/cassandra.md
+++ b/_formations/nosql/1_cassandra.md
@@ -1,12 +1,17 @@
---
layout: page
-title: Cassandra
-description: TP
-importance: 4
+title: TP1
+description: Cassandra
+importance: 1
category: BUT 3 - NoSQL
related_publications: false
permalink: /nosql/cassandra/
+type: formation
visible: true
---
-# TP NoSQL
+# TP1
+
+## Cours
+
+## TP
diff --git a/_formations/nosql/introduction.md b/_formations/nosql/2_neo4j.md
similarity index 52%
rename from _formations/nosql/introduction.md
rename to _formations/nosql/2_neo4j.md
index 19e39eb4..5027b1ae 100644
--- a/_formations/nosql/introduction.md
+++ b/_formations/nosql/2_neo4j.md
@@ -1,12 +1,17 @@
---
layout: page
-title: Introduction
-description: Cours
+title: TP2
+description: Neo4j
importance: 1
category: BUT 3 - NoSQL
related_publications: false
-permalink: /nosql/introduction/
+permalink: /nosql/neo4j/
+type: formation
visible: true
---
-# Cours NoSQL
+# TP2
+
+## Cours
+
+## TP
diff --git a/_formations/nosql/3_mongodb.md b/_formations/nosql/3_mongodb.md
new file mode 100644
index 00000000..895e4023
--- /dev/null
+++ b/_formations/nosql/3_mongodb.md
@@ -0,0 +1,136 @@
+---
+layout: page
+title: TP3
+description: Introduction à MongoDB
+importance: 3
+category: BUT 3 - NoSQL
+related_publications: false
+permalink: /nosql/mongodb/introduction/
+type: formation
+visible: true
+---
+
+## Configuration de la base de données
+
+Pour MongoDB, nous allons utiliser le service [MongoDB Atlas](https://www.mongodb.com/cloud/atlas), qui est un service de base de données cloud géré par MongoDB. Il permet de créer des clusters MongoDB facilement et rapidement, sans avoir à gérer l'infrastructure sous-jacente.
+
+### Création du compte Atlas
+
+Pour cela, nous allons créer un [compte MongoDB Atlas](https://account.mongodb.com/account/register?signedOut=true) afin de pouvoir héberger notre base de données MongoDB pour le TP. Il suffit de remplir le formulaire d'inscription avec votre adresse e-mail et un mot de passe. Vous pouvez également vous inscrire avec votre compte Google ou GitHub.
+
+### Création d'un projet Atlas
+
+> :exclamation: Notations
+>
+> Dans la suite du TP nous allons utiliser un certain nombre de noms de projet, d'utilisateur et de cluster. **Utilisez les mêmes noms que ceux indiqués** pour éviter toute confusion et faciliter le déroulement du TP.
+>
+{: .block-danger }
+
+Une fois que vous avez créé votre compte vous allez pouvoir créer un projet, nommé `but-sd` ici. Ensuite, une page demandera d'ajouter des membres, il n'y a rien à faire. Confirmez simplement la création du projet en cliquant sur **Create project**.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/creation-project.png" title="Creation project" class="img-fluid rounded z-depth-1" %}
+
+
+### Création d'un utilisateur Atlas
+
+Une fois ceci fait on aura besoin de créer un utilisateur, afin de pouvoir requêter la base de données. Pour cela, dans le menu à gauche, cliquez sur **Database access**.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/create-user.png" title="Create user" class="img-fluid rounded z-depth-1" %}
+
+
+Une fois sur la page des **Database access** cliquez sur **Add new database user** afin d'ajouter un utilisateur de la base de données. Cela ouvrira un nouvel onglet comme ci-dessous. Il faudra ainsi définir son nom, son mot de passe et son rôle. Dans notre cas, nous appelerons notre utilisateur `user_mongo` et nous générerons le mot de passe aléatoirement en cliquant sur **Autogenerate Secure Password**. Enfin, nous lui assignerons le rôle d'administrateur Atlas.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/creation-user.png" title="Creation user" class="img-fluid rounded z-depth-1" %}
+
+
+> #### :warning: Mot de passe
+>
+> Pensez à bien enregistrer le mot de passe dans un endroit sécurisé de votre ordinateur et ne pas le mettre sur un repo public GitHub.
+>
+{: .block-warning }
+
+Une fois ceci fait vous aurez la vue suivante :
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/user-created.png" title="User created" class="img-fluid rounded z-depth-1" %}
+
+
+### Création d'un cluster Atlas
+
+Maintenant nous pouvons créer un cluster qui hébergera notre base de données.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/create-cluster.png" title="Create cluster" class="img-fluid rounded z-depth-1" %}
+
+
+Nous prenons l'instance `M0` qui est gratuite et donnons un nom à cette dernière, ici `cluster-but-sd`. Il n'y a pas besoin de changer les autres paramètres.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/creation-cluster.png" title="Creation cluster" class="img-fluid rounded z-depth-1" %}
+
+
+### Connexion au cluster Atlas
+
+Enfin, la dernière étape consiste à choisir le connecteur à la base de données. Dans notre cas, nous utiliserons l'API Python donc nous sélectionnons **Drivers**.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/connection-method.png" title="Connection method" class="img-fluid rounded z-depth-1" %}
+
+
+Sur la page suivante nous pouvons choisir le type de Driver, Python dans notre cas.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/mongodb-driver.png" title="MongoDB driver" class="img-fluid rounded z-depth-1" %}
+
+
+> #### :information_source: Connection string
+>
+> Pensez à copier la _connection string_, cela nous servira ensuite à nous connecter en Python à la base de données que nous venons de créer.
+>
+{: .block-example }
+
+## Interrogation des données MongoDB en Python
+
+Pour requêter les données dans notre base de données nous allons utiliser le package [`pymongo`](https://docs.mongodb.com/drivers/pymongo/), que l'on peut installer via :
+
+```bash
+pip install pymongo[srv]
+```
+
+## Connexion à Compass
+
+Maintenant nous allons pouvoir utiliser le cluster créé sur Atlas. Commençons par se connecter à notre instance.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/compass-connection.png" title="Compass connection" class="img-fluid rounded z-depth-1" %}
+
+
+La connexion va se faire via la _connection string_ qui peut être trouvée dans la configuration du cluster Atlas.
+
+> #### :warning: Mot de passe
+>
+> N'oubliez pas de remplacer le mot de passe sur l'image ci-dessous par celui que vous avez enregistré plus haut.
+>
+{: .block-warning }
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/compass-add-connection.png" title="Compass add connection" class="img-fluid rounded z-depth-1" %}
+
+
+Une fois ceci fait, nous allons créer une base de données appelée `tp` qui contiendra une collection nommée `restaurants`.
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/create-database.png" title="Create database" class="img-fluid rounded z-depth-1" %}
+
+
+## Import des données
+
+Ensuite, il suffit d'importer le fichier JSON qui se situe [ici](https://github.com/alannadevgen/formation-nosql/blob/main/TP/TP1/restaurants.json).
+
+
+ {% include figure.liquid path="assets/img/nosql/mongodb/import-data.png" title="Import data" class="img-fluid rounded z-depth-1" %}
+
+
+Et voilà, vous êtes prêts à requêter les données ! :tada:
diff --git a/_formations/nosql/4_mongodb.md b/_formations/nosql/4_mongodb.md
new file mode 100644
index 00000000..f3dc840a
--- /dev/null
+++ b/_formations/nosql/4_mongodb.md
@@ -0,0 +1,13 @@
+---
+layout: page
+title: TP4
+description: Agrégation avec MongoDB
+importance: 4
+category: BUT 3 - NoSQL
+related_publications: false
+permalink: /nosql/mongodb/aggregation/
+type: formation
+visible: true
+---
+
+# TP4
\ No newline at end of file
diff --git a/_formations/nosql/5_examen.md b/_formations/nosql/5_examen.md
new file mode 100644
index 00000000..a791d15e
--- /dev/null
+++ b/_formations/nosql/5_examen.md
@@ -0,0 +1,13 @@
+---
+layout: page
+title: TP5
+description: TP noté
+importance: 4
+category: BUT 3 - NoSQL
+related_publications: false
+permalink: /nosql/exam/
+type: formation
+visible: true
+---
+
+# TP5
\ No newline at end of file
diff --git a/_formations/nosql/installation-mongodb.md b/_formations/nosql/installation-mongodb.md
deleted file mode 100644
index 5dbea607..00000000
--- a/_formations/nosql/installation-mongodb.md
+++ /dev/null
@@ -1,101 +0,0 @@
----
-layout: page
-title: Installation
-description: Installation de MongoDB
-importance: 3
-category: BUT 3 - NoSQL
-related_publications: false
-permalink: /nosql/mongodb/
-visible: true
----
-
-## Configuration de la base de données
-
-### Création du compte Atlas
-
-Nous allons créer un [compte Atlas](https://account.mongodb.com/account/register?signedOut=true) afin de pouvoir héberger une base de données MongoDB.
-
-### Création d'un projet Atlas
-
-Une fois que vous avez créé votre compte vous allez pouvoir créer un projet, nommé `but-sd` ici. Ensuite, une page demandera d'ajouter des membres, il n'y a rien à faire. Confirmez simplement la création du projet en cliquant sur **Create project**.
-
-
- {% include figure.liquid path="assets/img/mongodb/creation-project.png" title="example image" class="img-fluid rounded z-depth-1" %}
-
-
-### Création d'un utilisateur Atlas
-
-Une fois ceci fait on aura besoin de créer un utilisateur, afin de pouvoir requêter la base de données. Pour cela, dans le menu à gauche, cliquez sur **Database access**.
-
-
- {% include figure.liquid path="assets/img/mongodb/create-user.png" title="example image" class="img-fluid rounded z-depth-1" %}
-
-
-Une fois sur la page des **Database access** cliquez sur **Add new database user** afin d'ajouter un utilisateur de la base de données. Cela ouvrira un nouvel onglet comme ci-dessous. Il faudra ainsi définir son nom, son mot de passe et son rôle. Dans notre cas, nous appelerons notre utilisateur `user_mongo` et nous générerons le mot de passe aléatoirement en cliquant sur **Autogenerate Secure Password**. Enfin, nous lui assignerons le rôle d'administrateur Atlas.
-
-
- {% include figure.liquid path="assets/img/mongodb/creation-user.png" title="example image" class="img-fluid rounded z-depth-1" %}
-
-
-> [!CAUTION]
-> Pensez à bien enregistrer le mot de passe dans un endroit sécurisé de votre ordinateur et ne pas le mettre sur un repo public GitHub.
-
-Une fois ceci fait vous aurez la vue suivante :
-
-
-
-### Création d'un cluster Atlas
-
-Maintenant nous pouvons créer un cluster qui hébergera notre base de données.
-
-
-
-Nous prenons l'instance M0 qui est gratuite et donnons un nom à cette dernière, ici `cluster-but-sd`. Il n'y a pas besoin de changer les autres paramètres.
-
-
-
-### Connexion au cluster Atlas
-
-Enfin, la dernière étape consiste à choisir le connecteur à la base de données. Dans notre cas, nous utiliserons l'API Python donc nous sélectionnons **Drivers**.
-
-
-
-Sur la page suivante nous pouvons choisir le type de Driver, Python dans notre cas.
-
-
-
-> [!NOTE]
-> Pensez à copier la _connection string_, cela nous servira ensuite à nous connecter en Python à la base de données que nous venons de créer.
-
-## Interrogation des données MongoDB en Python
-
-Pour requêter les données dans notre base de données nous allons utiliser le package [`pymongo`](https://docs.mongodb.com/drivers/pymongo/), que l'on peut installer via :
-
-```bash
-$ pip install pymongo[srv]
-```
-
-## Connexion à Compass
-
-Maintenant nous allons pouvoir utiliser le cluster créé sur Atlas. Commençons par se connecter à notre instance.
-
-
-
-La connexion va se faire via la _connection string_ qui peut être trouvée dans la configuration du cluster Atlas.
-
-> [!CAUTION]
-> N'oubliez pas de remplacer le mot de passe sur l'image ci-dessous par celui que vous avez enregistré plus haut.
-
-
-
-Une fois ceci fait, nous allons créer une base de données appelée `tp` qui contiendra une collection nommée `restaurants`.
-
-
-
-## Import des données
-
-Ensuite, il suffit d'importer le fichier JSON qui se situe [ici](https://github.com/alannadevgen/formation-nosql/blob/main/TP/TP1/restaurants.json).
-
-
-
-Et voilà, vous êtes prêts à requêter les données ! :tada:
diff --git a/_formations/sae/3_queries.md b/_formations/sae/3_queries.md
index 1f4eb74e..da1a90f0 100644
--- a/_formations/sae/3_queries.md
+++ b/_formations/sae/3_queries.md
@@ -1,278 +1,30 @@
---
layout: page
-title: Migration
-description: Migration de la base de données
-importance: 4
+title: Requêtes SQL
+description: Requêtage de la base de données
+importance: 3
category: BUT 3 - SAÉ NoSQL
related_publications: false
-permalink: /sae/migration/
+permalink: /sae/queries/
visible: true
type: formation
---
-# Séance 3
+### Requêtes à programmer en SQL
-La première séance a permis de se familiariser avec la base de données *ClassicModel*. Durant la deuxième séance nous avons sélectionné la base de données NoSQL ainsi que son architecture cible.
+Voici les requêtes qui vont nous servir de test pour la réussite (ou non) de la migration. Pour chaque requête, faites un choix d’ordonnencement du résultat, s’il n’est pas précisé ou naturel. Cela permettra de mieux comparer les résultats après migration.
-1. Création de requêtes SQL sur la BD initiale ;
-2. Réflexion sur le format des données à obtenir et l’algorithme à réaliser ;
-3. Écriture du script Python permettant le passage de SQLite à NoSQL ;
-4. Création des requêtes initiales au nouveau format NoSQL pour s’assurer que la migration s’est bien passée.
+1. Lister les clients n’ayant jamais effecuté une commande ;
+2. Pour chaque employé, le nombre de clients, le nombre de commandes et le montant total de celles-ci ;
+3. Idem pour chaque bureau (nombre de clients, nombre de commandes et montant total), avec en plus le nombre de clients d’un pays différent, s’il y en a ;
+4. Pour chaque produit, donner le nombre de commandes, la quantité totale commandée, et le nombre de clients différents ;
+5. Donner le nombre de commande pour chaque pays, ainsi que le montant total des commandes et le montant total payé : on veut conserver les clients n’ayant jamais commandé dans le résultat final ;
+6. On veut la table de contigence du nombre de commande entre la ligne de produits et le pays du client (utiliser la méthode `pivot_table` de `pandas`) ;
+7. On veut la même table croisant la ligne de produits et le pays du client, mais avec le montant total payé dans chaque cellule ;
+8. Donner les 10 produits pour lesquels la marge moyenne est la plus importante (cf buyPrice et priceEach) ;
+9. Lister les produits (avec le nom et le code du client) qui ont été vendus à perte :
+ - Si un produit a été dans cette situation plusieurs fois, il doit apparaître plusieurs fois,
+ - Une vente à perte arrive quand le prix de vente est inférieur au prix d’achat ;
+10. (bonus) Lister les clients pour lesquels le montant total payé est supérieur aux montants totals des achats ;
-L'objectif de cette séance est d'écrire le script de migration d'une base de données vers un modèle MongoDB (points 3 et 4).
-
-## Données
-
-Pour la suite du TP nous utiliserons les données `Gymnase2000` disponibles dans le fichier `Gymnase2000.sqlite` dont le schéma est ci-dessous.
-
-
-
- {% include figure.liquid loading="eager" path="assets/img/sae/Gymnase2000.png" title="Gymnase ER graph" class="img-fluid rounded z-depth-1" %}
-
-
-
-À partir de ce schéma, nous allons créer deux collections :
-- **Gymnases** : chaque document concerne un *gymnase*, dans lequel on ajoute les informations de toutes les *séances* prévues (sous la forme d'une liste)
-- **Sportifs** : chaque document concerne un *sportif.ve*, pour chaque sportif.ve on stockera les informations concernant les sports pour lesquels il/elle joue, les sports pour lesquel il/elle s'entraîne ainsi que ceux pour lesquels il/elle est arbitre.
-
-## Cluster MongoDB
-
-Avant de commencer le TP, connectez-vous à [MongoDB Atlas](https://account.mongodb.com/account/login?signedOut=true) et créez un cluster comme vu lors des TPs. Si vous avez besoin d'un rappel vous pouvez consulter la page [installation MongoDB](../../nosql/mongodb/).
-
-- Database name : `sae`
-- Collection name : `gymnases`
-
-### Création de la collection Gymnases
-
-On commence par se connecter à la base de données en local.
-
-```python
-import sqlite3
-import pandas
-import pymongo
-
-URI = "CONNEXION STRING TO REPLACE"
-client = pymongo.MongoClient(URI)
-db = client.sae
-
-# Création de la connexion
-conn = sqlite3.connect("Gymnase2000.sqlite")
-```
-
-Une fois ceci fait, nous pouvons récupérer les données en faisant des requêtes SQL, comme suit.
-
-```python
-gymnases = pandas.read_sql_query(
- """
- SELECT *
- FROM Gymnases;
- """,
- conn
-)
-```
-
-| IdGymnase | NomGymnase | Adresse | Ville | Surface |
-|-----------|---------------|----------------------------|--------------|---------|
-| 1 | PAUL ELUARD | 2 rue des pépines | STAINS | 200 |
-| 2 | ARAGON | Place du Chartres | MONTMORENCY | 450 |
-| 3 | SAINT EXUPERY | 47 bvd des brumes | PIERREFITTE | 400 |
-| 4 | PAUL ELUARD | Allée J.B. Lulli | SARCELLES | 500 |
-| 5 | BRASSENS | 153 square Loliot | SARCELLES | 620 |
-| 6 | VERLAINE | 14 bvd Serrault | STAINS | 400 |
-| 7 | JULES FERRY | 45 rue de la gare | PIERREFITTE | 360 |
-| 8 | PREVERT | 12 rue des collines | MONTMORENCY | 420 |
-| 9 | CAMUS | 3 esplanade des quatrans | SARCELLES | 620 |
-| 10 | RIMBAUD | 140 bvd Diderot | STAINS | 400 |
-| 11 | LAMARTINE | 7 rue de la souris verte | PIERREFITTE | 300 |
-| 12 | MOZART | 6 Allée Rosana | MONTMORENCY | 480 |
-| 13 | RAVEL | Place aux pommes | STAINS | 200 |
-| 14 | CHOPIN | 23 rue Carafelli | MONTMORENCY | 500 |
-| 15 | BREL | 4 rue de la miséricorde | PIERREFITTE | 400 |
-| 16 | SAMOURAI | 4 Allée des pendules | SARCELLES | 600 |
-| 17 | GARCIA LORCA | 45 bvd des Comes | STAINS | 400 |
-| 18 | PABLO NERUDA | 6 rue saint Jean | PIERREFITTE | 450 |
-| 19 | COCTEAU | 45 bis rue du moulin rouge | MONTMORENCY | 500 |
-| 20 | LUMIERES | 78 rue Vendôme | SARCELLES | 400 |
-| 21 | SIMON | 8 bvd général de Gaulle | STAINS | 400 |
-| 22 | BARBARA | 45 rue du bossu | SAINT DENIS | 500 |
-| 23 | ARAGON | 10 Bvd Lenoir | SAINT DENIS | 520 |
-| 24 | BELFEGOR | Place de Gaulle | SAINT DENIS | 450 |
-| 25 | DOLTO | 3 square Plaisir | VILLETANEUSE | 620 |
-| 26 | MERMOZ | 41 rue des ponts | VILLETANEUSE | 600 |
-| 27 | PASCAL | 20 rue de la pirogue | VILLETANEUSE | 350 |
-| 28 | BLAISE PASCAL | 2bis rue de la moulerie | GARGES | 400 |
-
-Après avoir récupéré les gymnases, nous allons récupérer les séances de sport.
-```python
-seances = pandas.read_sql_query(
- """
- SELECT *
- FROM Seances
- INNER JOIN Sports
- USING (IdSport);
- """,
- conn
-)
-```
-Voici les informations du gymnase `1` :
-
-| IdGymnase | IdSport | IdSportifEntraineur | Jour | Horaire | Duree | Libelle |
-|-----------|---------|---------------------|----------|---------|-------|-------------|
-| 1 | 1 | 149 | Samedi | 9.0 | 60 | Basket ball |
-| 1 | 3 | 1 | Lundi | 9.0 | 60 | Hand ball |
-| 1 | 3 | 1 | Lundi | 10.0 | 60 | Hand ball |
-| 1 | 3 | 1 | Lundi | 11.3 | 60 | Hand ball |
-| 1 | 3 | 1 | Lundi | 14.0 | 90 | Hand ball |
-| 1 | 3 | 1 | lundi | 17.3 | 120 | Hand ball |
-| 1 | 3 | 1 | Lundi | 19.3 | 120 | Hand ball |
-| 1 | 3 | 2 | Dimanche | 17.3 | 120 | Hand ball |
-| 1 | 3 | 2 | Dimanche | 19.3 | 120 | Hand ball |
-| 1 | 3 | 2 | mardi | 17.3 | 120 | Hand ball |
-| 1 | 3 | 2 | mercredi | 17.3 | 120 | Hand ball |
-| 1 | 3 | 2 | Samedi | 15.3 | 60 | Hand ball |
-| 1 | 3 | 2 | Samedi | 16.3 | 60 | Hand ball |
-| 1 | 3 | 2 | Samedi | 17.3 | 120 | Hand ball |
-| 1 | 3 | 3 | jeudi | 20.0 | 30 | Hand ball |
-| 1 | 3 | 3 | lundi | 14.0 | 60 | Hand ball |
-| 1 | 3 | 3 | lundi | 18.0 | 30 | Hand ball |
-| 1 | 3 | 3 | lundi | 19.0 | 30 | Hand ball |
-| 1 | 3 | 3 | lundi | 20.0 | 30 | Hand ball |
-| 1 | 5 | 7 | mercredi | 17.0 | 90 | Hockey |
-
-Maintenant, il faut ajouter une colonne `seances` dans `gymnases`. Pour cela, regardons le résultat attendu pour le gymnase ayant l'identifiant `6`.
-
-```python
-id = 6
-gym6 = seances.query('IdGymnase == @id')
-print(gym6)
-```
-
-Le résultat est le suivant :
-
-| IdGymnase | IdSport | IdSportifEntraineur | Jour | Horaire | Durée | Libelle |
-|-----------|---------|---------------------|----------|---------|-------|---------|
-| 6 | 5 | 6 | vendredi | 19.0 | 60 | Hockey |
-| 6 | 5 | 7 | jeudi | 17.0 | 90 | Hockey |
-
-On voit ici qu'il y a les identifiants du gymnase et du sport qui sont des informations redondantes car déjà présentes dans l'objet `gymnases`. Pour ce faire, nous allons les supprimer et les convertir en liste afin de les ajouter dans `gymnases` (grâce aux fonctions `drop` et `to_dict`).
-
-```python
-seances.query('IdGymnase == @id')\
- .drop(columns = ["IdGymnase", "IdSport"])\
- .to_dict(orient = "records")
-```
-
-Et on obtient
-
-```json
-[
- {
- 'IdSportifEntraineur': 6,
- 'Jour': 'vendredi',
- 'Horaire': 19.0,
- 'Duree': 60,
- 'Libelle': 'Hockey'
- },
- {
- 'IdSportifEntraineur': 7,
- 'Jour': 'jeudi',
- 'Horaire': 17.0,
- 'Duree': 90,
- 'Libelle': 'Hockey'
- }
-]
-```
-
-On peut faire cette opération au travers d'une liste compréhension pour toutes les séances :
-
-```python
-sessions = [
- seances.query('IdGymnase == @id')
- .drop(columns=["IdGymnase", "IdSport"])
- .to_dict(orient = "records")
- for id in gymnases.IdGymnase
-]
-print(sessions)
-```
-
-Il ne reste plus qu'à ajouter ce résultat dans `gymnases`.
-
-```python
-gymnases = gymnases.assign(Sessions = sessions)
-gymnases.head()
-```
-
-Voici le résultat obtenu pour les gymnases `2` et `3` :
-
-| IdGymnase | NomGymnase | Adresse | Ville | Surface | Sessions |
-|-----------|---------------|-------------------|-------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 2 | ARAGON | Place du Chartres | MONTMORENCY | 450 | \[{'IdSportifEntraineur': 57, 'Jour': 'dimanche', 'Horaire': 17.0, 'Duree': 60, 'Libelle': 'Volley ball'}\] |
-| 3 | SAINT EXUPERY | 47 bvd des brumes | PIERREFITTE | 400 | \[{'IdSportifEntraineur': 149, 'Jour': 'Mercredi', 'Horaire': 11.0, 'Duree': 30, 'Libelle': 'Basket ball'}, {'IdSportifEntraineur': 57, 'Jour': 'lundi', 'Horaire': 16.3, 'Duree': 90, 'Libelle': 'Volley ball'}, {'IdSportifEntraineur': 60, 'Jour': 'jeudi', 'Horaire': 19.0, 'Duree': 60, 'Libelle': 'Volley ball'}\] |
-
-Nous avons une nouvelle colonne `Sessions` contenant la liste `sessions` pour chaque gymnase. Ainsi, les données des séances sont bien intégrées dans les gymnases. Nous pouvons maintenant insérer les données dans MongoDB.
-
-### Insertion dans la base de données MongoDB
-
-Maintenant que les données sont préparées, nous pouvons insérer les données dans MongoDB.
-
-```python
-db.gymnases.insert_many(
- gymnases.to_dict(orient = "records")
-)
-```
-
-Vous pouvez aller voir le résultat dans MongoDB Compass. Vous pouvez aussi tester directement en interrogeant MongoDB pour le nombre de documents de la collection :
-
-```python
-db.gymnases.count_documents({})
-```
-
-Ainsi que le contenu de la collection :
-
-```python
-list(db.gymnases.find())
-```
-
-### TO-DO
-
-Maintenant, que nous avons créé la collection `gymnases`, créez la collection `sportifs`.
-1. Récupérer les données des tables `Sportifs`, `Joue`, `Entrainer` et `Arbitrer`.
-2. Créer une collection `sportifs` avec les informations des sportifs, des sports qu'ils pratiquent, des sports pour lesquels ils s'entraînent et des sports pour lesquels ils arbitrent.
-
-L'objectif est de créer une collection `sportifs` avec les informations suivantes (exemple fictif) :
-
-```json
-{
- "IdSportif": 256,
- "Nom": "BOUTAHAR",
- "Prenom": "Abderahim",
- "Sexe": "m",
- "Age": 30,
- "joue": ["Basket ball", "Volley ball"],
- "entraine": ["Basket ball"],
- "arbitre": ["Basket ball", "Tennis"]
-}
-```
-
-### Jointure entre deux collections
-
-Supposons que l'on ait deux collections `gymnases` et `sportifs` qui ont chacune l'identifiant de l'entraîneur. Si nous souhaitons récupérer les informations de l'entraîneur pour chaque séance, pour chaque gymnase, nous pouvons exécuter le code suivant :
-
-```python
-pandas.DataFrame(list(db.gymnases.aggregate([
- { "$limit": 1 },
- { "$unwind": "$Sessions" },
- { "$lookup": {
- "from": "sportifs",
- "localField": "Sessions.IdSportifEntraineur",
- "foreignField": "IdSportif",
- "as": "Entraineur"
- }}
-])))
-```
-
-**A noter** : le résultat d'un `lookup` est forcément un tableau, même s'il n'y a qu'une seule valeur. A vous de faire le travail pour l'extraire dans un littéral simple si vous le souhaitez (par exemple avec `$first`).
-
-# Retour sur ClassicModel
-
-Une fois que vous avez fini les questions sur la base de données `Gymnase2000` vous pouvez faire la migration sur votre base de données `ClassicModel` en suivant l'architecture cible définie lors de la séance 2.
+A FAIRE : Ecrire donc les requêtes SQL ci-dessus dans un programme Python. Pour certaines, il est nécessaire de ré-organiser le résultat de la requête avec du code Python ensuite.
diff --git a/_formations/sae/4_migration.md b/_formations/sae/4_migration.md
new file mode 100644
index 00000000..53f0c40e
--- /dev/null
+++ b/_formations/sae/4_migration.md
@@ -0,0 +1,278 @@
+---
+layout: page
+title: Migration
+description: Migration de la base de données
+importance: 4
+category: BUT 3 - SAÉ NoSQL
+related_publications: false
+permalink: /sae/migration/
+visible: true
+type: formation
+---
+
+# Migration de la base de données
+
+La première séance a permis de se familiariser avec la base de données *ClassicModel*. Durant la deuxième séance nous avons sélectionné la base de données NoSQL ainsi que son architecture cible.
+
+1. Création de requêtes SQL sur la BD initiale ;
+2. Réflexion sur le format des données à obtenir et l’algorithme à réaliser ;
+3. Écriture du script Python permettant le passage de SQLite à NoSQL ;
+4. Création des requêtes initiales au nouveau format NoSQL pour s’assurer que la migration s’est bien passée.
+
+L'objectif de cette séance est d'écrire le script de migration d'une base de données vers un modèle MongoDB (points 3 et 4).
+
+## Données
+
+Pour la suite du TP nous utiliserons les données `Gymnase2000` disponibles dans le fichier `Gymnase2000.sqlite` dont le schéma est ci-dessous.
+
+
+
+ {% include figure.liquid loading="eager" path="assets/img/sae/Gymnase2000.png" title="Gymnase ER graph" class="img-fluid rounded z-depth-1" %}
+
+
+
+À partir de ce schéma, nous allons créer deux collections :
+- **Gymnases** : chaque document concerne un *gymnase*, dans lequel on ajoute les informations de toutes les *séances* prévues (sous la forme d'une liste)
+- **Sportifs** : chaque document concerne un *sportif.ve*, pour chaque sportif.ve on stockera les informations concernant les sports pour lesquels il/elle joue, les sports pour lesquel il/elle s'entraîne ainsi que ceux pour lesquels il/elle est arbitre.
+
+## Cluster MongoDB
+
+Avant de commencer le TP, connectez-vous à [MongoDB Atlas](https://account.mongodb.com/account/login?signedOut=true) et créez un cluster comme vu lors des TPs. Si vous avez besoin d'un rappel vous pouvez consulter la page [installation MongoDB](../nosql/3_mongodb.md).
+
+- Database name : `sae`
+- Collection name : `gymnases`
+
+### Création de la collection Gymnases
+
+On commence par se connecter à la base de données en local.
+
+```python
+import sqlite3
+import pandas
+import pymongo
+
+URI = "CONNEXION STRING TO REPLACE"
+client = pymongo.MongoClient(URI)
+db = client.sae
+
+# Création de la connexion
+conn = sqlite3.connect("Gymnase2000.sqlite")
+```
+
+Une fois ceci fait, nous pouvons récupérer les données en faisant des requêtes SQL, comme suit.
+
+```python
+gymnases = pandas.read_sql_query(
+ """
+ SELECT *
+ FROM Gymnases;
+ """,
+ conn
+)
+```
+
+| IdGymnase | NomGymnase | Adresse | Ville | Surface |
+|-----------|---------------|----------------------------|--------------|---------|
+| 1 | PAUL ELUARD | 2 rue des pépines | STAINS | 200 |
+| 2 | ARAGON | Place du Chartres | MONTMORENCY | 450 |
+| 3 | SAINT EXUPERY | 47 bvd des brumes | PIERREFITTE | 400 |
+| 4 | PAUL ELUARD | Allée J.B. Lulli | SARCELLES | 500 |
+| 5 | BRASSENS | 153 square Loliot | SARCELLES | 620 |
+| 6 | VERLAINE | 14 bvd Serrault | STAINS | 400 |
+| 7 | JULES FERRY | 45 rue de la gare | PIERREFITTE | 360 |
+| 8 | PREVERT | 12 rue des collines | MONTMORENCY | 420 |
+| 9 | CAMUS | 3 esplanade des quatrans | SARCELLES | 620 |
+| 10 | RIMBAUD | 140 bvd Diderot | STAINS | 400 |
+| 11 | LAMARTINE | 7 rue de la souris verte | PIERREFITTE | 300 |
+| 12 | MOZART | 6 Allée Rosana | MONTMORENCY | 480 |
+| 13 | RAVEL | Place aux pommes | STAINS | 200 |
+| 14 | CHOPIN | 23 rue Carafelli | MONTMORENCY | 500 |
+| 15 | BREL | 4 rue de la miséricorde | PIERREFITTE | 400 |
+| 16 | SAMOURAI | 4 Allée des pendules | SARCELLES | 600 |
+| 17 | GARCIA LORCA | 45 bvd des Comes | STAINS | 400 |
+| 18 | PABLO NERUDA | 6 rue saint Jean | PIERREFITTE | 450 |
+| 19 | COCTEAU | 45 bis rue du moulin rouge | MONTMORENCY | 500 |
+| 20 | LUMIERES | 78 rue Vendôme | SARCELLES | 400 |
+| 21 | SIMON | 8 bvd général de Gaulle | STAINS | 400 |
+| 22 | BARBARA | 45 rue du bossu | SAINT DENIS | 500 |
+| 23 | ARAGON | 10 Bvd Lenoir | SAINT DENIS | 520 |
+| 24 | BELFEGOR | Place de Gaulle | SAINT DENIS | 450 |
+| 25 | DOLTO | 3 square Plaisir | VILLETANEUSE | 620 |
+| 26 | MERMOZ | 41 rue des ponts | VILLETANEUSE | 600 |
+| 27 | PASCAL | 20 rue de la pirogue | VILLETANEUSE | 350 |
+| 28 | BLAISE PASCAL | 2bis rue de la moulerie | GARGES | 400 |
+
+Après avoir récupéré les gymnases, nous allons récupérer les séances de sport.
+```python
+seances = pandas.read_sql_query(
+ """
+ SELECT *
+ FROM Seances
+ INNER JOIN Sports
+ USING (IdSport);
+ """,
+ conn
+)
+```
+Voici les informations du gymnase `1` :
+
+| IdGymnase | IdSport | IdSportifEntraineur | Jour | Horaire | Duree | Libelle |
+|-----------|---------|---------------------|----------|---------|-------|-------------|
+| 1 | 1 | 149 | Samedi | 9.0 | 60 | Basket ball |
+| 1 | 3 | 1 | Lundi | 9.0 | 60 | Hand ball |
+| 1 | 3 | 1 | Lundi | 10.0 | 60 | Hand ball |
+| 1 | 3 | 1 | Lundi | 11.3 | 60 | Hand ball |
+| 1 | 3 | 1 | Lundi | 14.0 | 90 | Hand ball |
+| 1 | 3 | 1 | lundi | 17.3 | 120 | Hand ball |
+| 1 | 3 | 1 | Lundi | 19.3 | 120 | Hand ball |
+| 1 | 3 | 2 | Dimanche | 17.3 | 120 | Hand ball |
+| 1 | 3 | 2 | Dimanche | 19.3 | 120 | Hand ball |
+| 1 | 3 | 2 | mardi | 17.3 | 120 | Hand ball |
+| 1 | 3 | 2 | mercredi | 17.3 | 120 | Hand ball |
+| 1 | 3 | 2 | Samedi | 15.3 | 60 | Hand ball |
+| 1 | 3 | 2 | Samedi | 16.3 | 60 | Hand ball |
+| 1 | 3 | 2 | Samedi | 17.3 | 120 | Hand ball |
+| 1 | 3 | 3 | jeudi | 20.0 | 30 | Hand ball |
+| 1 | 3 | 3 | lundi | 14.0 | 60 | Hand ball |
+| 1 | 3 | 3 | lundi | 18.0 | 30 | Hand ball |
+| 1 | 3 | 3 | lundi | 19.0 | 30 | Hand ball |
+| 1 | 3 | 3 | lundi | 20.0 | 30 | Hand ball |
+| 1 | 5 | 7 | mercredi | 17.0 | 90 | Hockey |
+
+Maintenant, il faut ajouter une colonne `seances` dans `gymnases`. Pour cela, regardons le résultat attendu pour le gymnase ayant l'identifiant `6`.
+
+```python
+id = 6
+gym6 = seances.query('IdGymnase == @id')
+print(gym6)
+```
+
+Le résultat est le suivant :
+
+| IdGymnase | IdSport | IdSportifEntraineur | Jour | Horaire | Durée | Libelle |
+|-----------|---------|---------------------|----------|---------|-------|---------|
+| 6 | 5 | 6 | vendredi | 19.0 | 60 | Hockey |
+| 6 | 5 | 7 | jeudi | 17.0 | 90 | Hockey |
+
+On voit ici qu'il y a les identifiants du gymnase et du sport qui sont des informations redondantes car déjà présentes dans l'objet `gymnases`. Pour ce faire, nous allons les supprimer et les convertir en liste afin de les ajouter dans `gymnases` (grâce aux fonctions `drop` et `to_dict`).
+
+```python
+seances.query('IdGymnase == @id')\
+ .drop(columns = ["IdGymnase", "IdSport"])\
+ .to_dict(orient = "records")
+```
+
+Et on obtient
+
+```json
+[
+ {
+ 'IdSportifEntraineur': 6,
+ 'Jour': 'vendredi',
+ 'Horaire': 19.0,
+ 'Duree': 60,
+ 'Libelle': 'Hockey'
+ },
+ {
+ 'IdSportifEntraineur': 7,
+ 'Jour': 'jeudi',
+ 'Horaire': 17.0,
+ 'Duree': 90,
+ 'Libelle': 'Hockey'
+ }
+]
+```
+
+On peut faire cette opération au travers d'une liste compréhension pour toutes les séances :
+
+```python
+sessions = [
+ seances.query('IdGymnase == @id')
+ .drop(columns=["IdGymnase", "IdSport"])
+ .to_dict(orient = "records")
+ for id in gymnases.IdGymnase
+]
+print(sessions)
+```
+
+Il ne reste plus qu'à ajouter ce résultat dans `gymnases`.
+
+```python
+gymnases = gymnases.assign(Sessions = sessions)
+gymnases.head()
+```
+
+Voici le résultat obtenu pour les gymnases `2` et `3` :
+
+| IdGymnase | NomGymnase | Adresse | Ville | Surface | Sessions |
+|-----------|---------------|-------------------|-------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 2 | ARAGON | Place du Chartres | MONTMORENCY | 450 | \[{'IdSportifEntraineur': 57, 'Jour': 'dimanche', 'Horaire': 17.0, 'Duree': 60, 'Libelle': 'Volley ball'}\] |
+| 3 | SAINT EXUPERY | 47 bvd des brumes | PIERREFITTE | 400 | \[{'IdSportifEntraineur': 149, 'Jour': 'Mercredi', 'Horaire': 11.0, 'Duree': 30, 'Libelle': 'Basket ball'}, {'IdSportifEntraineur': 57, 'Jour': 'lundi', 'Horaire': 16.3, 'Duree': 90, 'Libelle': 'Volley ball'}, {'IdSportifEntraineur': 60, 'Jour': 'jeudi', 'Horaire': 19.0, 'Duree': 60, 'Libelle': 'Volley ball'}\] |
+
+Nous avons une nouvelle colonne `Sessions` contenant la liste `sessions` pour chaque gymnase. Ainsi, les données des séances sont bien intégrées dans les gymnases. Nous pouvons maintenant insérer les données dans MongoDB.
+
+### Insertion dans la base de données MongoDB
+
+Maintenant que les données sont préparées, nous pouvons insérer les données dans MongoDB.
+
+```python
+db.gymnases.insert_many(
+ gymnases.to_dict(orient = "records")
+)
+```
+
+Vous pouvez aller voir le résultat dans MongoDB Compass. Vous pouvez aussi tester directement en interrogeant MongoDB pour le nombre de documents de la collection :
+
+```python
+db.gymnases.count_documents({})
+```
+
+Ainsi que le contenu de la collection :
+
+```python
+list(db.gymnases.find())
+```
+
+### TO-DO
+
+Maintenant, que nous avons créé la collection `gymnases`, créez la collection `sportifs`.
+1. Récupérer les données des tables `Sportifs`, `Joue`, `Entrainer` et `Arbitrer`.
+2. Créer une collection `sportifs` avec les informations des sportifs, des sports qu'ils pratiquent, des sports pour lesquels ils s'entraînent et des sports pour lesquels ils arbitrent.
+
+L'objectif est de créer une collection `sportifs` avec les informations suivantes (exemple fictif) :
+
+```json
+{
+ "IdSportif": 256,
+ "Nom": "BOUTAHAR",
+ "Prenom": "Abderahim",
+ "Sexe": "m",
+ "Age": 30,
+ "joue": ["Basket ball", "Volley ball"],
+ "entraine": ["Basket ball"],
+ "arbitre": ["Basket ball", "Tennis"]
+}
+```
+
+### Jointure entre deux collections
+
+Supposons que l'on ait deux collections `gymnases` et `sportifs` qui ont chacune l'identifiant de l'entraîneur. Si nous souhaitons récupérer les informations de l'entraîneur pour chaque séance, pour chaque gymnase, nous pouvons exécuter le code suivant :
+
+```python
+pandas.DataFrame(list(db.gymnases.aggregate([
+ { "$limit": 1 },
+ { "$unwind": "$Sessions" },
+ { "$lookup": {
+ "from": "sportifs",
+ "localField": "Sessions.IdSportifEntraineur",
+ "foreignField": "IdSportif",
+ "as": "Entraineur"
+ }}
+])))
+```
+
+**A noter** : le résultat d'un `lookup` est forcément un tableau, même s'il n'y a qu'une seule valeur. A vous de faire le travail pour l'extraire dans un littéral simple si vous le souhaitez (par exemple avec `$first`).
+
+# Retour sur ClassicModel
+
+Une fois que vous avez fini les questions sur la base de données `Gymnase2000` vous pouvez faire la migration sur votre base de données `ClassicModel` en suivant l'architecture cible définie lors de la séance 2.
diff --git a/_pages/formations.md b/_pages/formations.md
index e221a378..abbadcbf 100644
--- a/_pages/formations.md
+++ b/_pages/formations.md
@@ -5,10 +5,12 @@ permalink: /formations/
description: Ressources pédagogiques pour les étudiants du BUT Science des Données et du certificat CS103 Intelligence Artificielle en Santé du Cnam
nav: true
nav_order: 3
-#display_categories: [Git, BUT 2 - Programmation Orientée Objet, BUT 3 - NoSQL, BUT 3 - SAÉ NoSQL, "Cnam CS103 - Bases de données à grande échelle"]
display_categories: [
- "BUT 2 - Programmation Orientée Objet",
- "Cnam CS103 - Bases de données à grande échelle"
+ # Git,
+ BUT 2 - Programmation Orientée Objet,
+ BUT 3 - NoSQL,
+ BUT 3 - SAÉ NoSQL,
+ Cnam CS103 - Bases de données à grande échelle
]
horizontal: false
---
diff --git a/assets/img/nosql/mongodb/compass-add-connection.png b/assets/img/nosql/mongodb/compass-add-connection.png
new file mode 100644
index 00000000..d36eea74
Binary files /dev/null and b/assets/img/nosql/mongodb/compass-add-connection.png differ
diff --git a/assets/img/nosql/mongodb/compass-connection.png b/assets/img/nosql/mongodb/compass-connection.png
new file mode 100644
index 00000000..929c67cb
Binary files /dev/null and b/assets/img/nosql/mongodb/compass-connection.png differ
diff --git a/assets/img/nosql/mongodb/connection-method.png b/assets/img/nosql/mongodb/connection-method.png
new file mode 100644
index 00000000..f2a73f02
Binary files /dev/null and b/assets/img/nosql/mongodb/connection-method.png differ
diff --git a/assets/img/nosql/mongodb/create-cluster.png b/assets/img/nosql/mongodb/create-cluster.png
new file mode 100644
index 00000000..a4fa97d5
Binary files /dev/null and b/assets/img/nosql/mongodb/create-cluster.png differ
diff --git a/assets/img/nosql/mongodb/create-database.png b/assets/img/nosql/mongodb/create-database.png
new file mode 100644
index 00000000..933604cd
Binary files /dev/null and b/assets/img/nosql/mongodb/create-database.png differ
diff --git a/assets/img/mongodb/create-user.png b/assets/img/nosql/mongodb/create-user.png
similarity index 100%
rename from assets/img/mongodb/create-user.png
rename to assets/img/nosql/mongodb/create-user.png
diff --git a/assets/img/nosql/mongodb/creation-cluster.png b/assets/img/nosql/mongodb/creation-cluster.png
new file mode 100644
index 00000000..0dc52e89
Binary files /dev/null and b/assets/img/nosql/mongodb/creation-cluster.png differ
diff --git a/assets/img/mongodb/creation-project.png b/assets/img/nosql/mongodb/creation-project.png
similarity index 100%
rename from assets/img/mongodb/creation-project.png
rename to assets/img/nosql/mongodb/creation-project.png
diff --git a/assets/img/mongodb/creation-user.png b/assets/img/nosql/mongodb/creation-user.png
similarity index 100%
rename from assets/img/mongodb/creation-user.png
rename to assets/img/nosql/mongodb/creation-user.png
diff --git a/assets/img/nosql/mongodb/import-data.png b/assets/img/nosql/mongodb/import-data.png
new file mode 100644
index 00000000..8d4648a5
Binary files /dev/null and b/assets/img/nosql/mongodb/import-data.png differ
diff --git a/assets/img/nosql/mongodb/mongodb-driver.png b/assets/img/nosql/mongodb/mongodb-driver.png
new file mode 100644
index 00000000..eec1f288
Binary files /dev/null and b/assets/img/nosql/mongodb/mongodb-driver.png differ
diff --git a/assets/img/nosql/mongodb/user-created.png b/assets/img/nosql/mongodb/user-created.png
new file mode 100644
index 00000000..aa6390de
Binary files /dev/null and b/assets/img/nosql/mongodb/user-created.png differ