{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Bases de données avec Python\n", "\n", "> Cours NSI Terminale - Thème 2.\n", "- toc: true \n", "- badges: true\n", "- comments: false\n", "- categories: [python, NSI, Terminale, Bases de données, SQL, TP]\n", "- image: /images/nsi2.png" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "fd767a71ffe9d21dd310723f5a2d4299", "grade": false, "grade_id": "cell-913a611055c9ace2", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "# Exploiter une base de données avec Python\n", "\n", "Dans ce TP, nous allons reprendre notre base de données d'exemples sur les livres, mais nous allons utiliser Python pour exécuter et exploiter les requêtes SQL. Notre SGBD sera toujours SQLite : le module python que nous utiliserons se nomme **sqlite3**.\n", "\n", "Si vous ne possédez pas cette base des TP précédents, vous pouvez la récupérer [ici](https://www.lecluse.fr/nsi/NSI_T/bdd/livres_db)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "8ad2ee3e03d0a3a40fe2780c3934f9e5", "grade": false, "grade_id": "cell-d9c09986e34e000e", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "import sqlite3" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "28cad3c4a13594f8d89f4b0b0d5dc24a", "grade": false, "grade_id": "cell-d96a1207648fd4e7", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Le module étant importé, nous devons réaliser deux actions pour pouvoir commencer à utiliser notre base :\n", "- ouvrir le fichier de base de données\n", "- créer un curseur\n", "\n", "Le *curseur* est un objet python offrant des méthodes pour exécuter des requêtes et récupérer le ou les résultats de ces requêtes." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "01527bd442ea915f607c7206481d3557", "grade": false, "grade_id": "cell-61552e200fdb388a", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "bdd = sqlite3.connect(\"livres_db\")\n", "curseur = bdd.cursor()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "984179863afa15b052ad993802ac08a5", "grade": false, "grade_id": "cell-e5065aa1a786ea15", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "*livres_db* est le nom du fichier contenant la base de donnéees SQLite que nous allons exploiter. Si le fichier n'existe pas, une nouvelle base de données sera créée.\n", "\n", "## Exécuter des requêtes de sélection\n", "\n", "### Le principe\n", "\n", "Reste ensuite à exécuter notre première requête. Pour cela, nous utiliserons la méthode **execute()** du curseur, la requête étant une chaîne de caractères passée en paramètre." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "08efb7f266db16b1f6e9a1afa2613935", "grade": false, "grade_id": "cell-e386676405303097", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "requete = \"SELECT * FROM Livres;\"\n", "curseur.execute(requete)" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "ea8bcd5569ac7e550458228e93068a3d", "grade": false, "grade_id": "cell-eb1983544af58acb", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Pour visualiser le résultat de notre requête, nous utiliserons encore notre curseur. Deux méthodes permettent principalement de le faire :\n", "- **fetchone()** pour récupérer un résultat puis avancer le curseur d'un cran\n", "- **fetchall()** pour récupérer d'un coup tous les résultats. \n", "\n", "Regardez les exemples ci-dessous pour mieux comprendre comment fonctionne le curseur : il s'agit littéralement d'un curseur que l'on déplace de résultat en résultat. Vous vous en rendrez compte en exécutant plusieurs fois la cellule ci-dessous." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "b713e52beb49098f4a7ffa3abbc0df96", "grade": false, "grade_id": "cell-3511ba3e527fd088", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur.fetchone()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "dce82abb8e20f0592a3d230e9c7481ea", "grade": false, "grade_id": "cell-abb4d3bb12f0f6ab", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Vous constatez que le résultat est un *tuple* dont les éléments correspondent aux attributs sélectionnés : ici c'est \\*. Il n'est pas facile de se rappeler de l'ordre des attributs. Pour cela vous pouvez faire appel à la propriété :" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "b9fa9b7df9425c6bde91f32e4309112b", "grade": false, "grade_id": "cell-f9cd84d0448ba2fe", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur.description" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "b32be652270b41e86c95a907bc09d4dd", "grade": false, "grade_id": "cell-5cba17a48c54c049", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "et pour rendre la réponse plus lisible, une petite liste en compréhension ;). Et voilà les attributs de colonne en clair dans l'ordre ou ils apparaissent dans le résultat de la requête !" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "e3789dfacfd8666fe8cc494f26d76b30", "grade": false, "grade_id": "cell-adebe2b8e3f17588", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "[d[0] for d in curseur.description]" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "b2e8a0e2335338e7e8151c463b41d094", "grade": false, "grade_id": "cell-857d9bb92faea101", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "A présent, le fonctionnement de **fetchall()** ne devrait pas vous étonner : on récupère logiquement un tuple avec tous les résultats." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "1e8467b069469062f29ee5e5439ad2d2", "grade": false, "grade_id": "cell-2b7dbd28d8a2ee0f", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur.fetchall()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "c4f766c265effa4bfd3009a82fb2b150", "grade": false, "grade_id": "cell-a37b42ab77cdf572", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Si vous avez suivi les instructions précédentes, vous devriez constater qu'il manque des enregistrements. Pourquoi ?\n", "Un indice : si vous réexécutez une nouvelle fois la méthode **fetchall()** du curseur, celle-ci ne renverra rien !\n", "\n", "Et oui, c'est la notion de curseur qui se déplace au fur à mesure qu'unb résultat est donné : les précédents appels de **fetchone()** ont fait avancer le curseur, et de même, **fetchall()** positionne le curseur à la toute fin.\n", "\n", "Pour retrouver tous les résultats à nouveau, il faut réexécuter la requête. Evitez donc de mélanger **fetchone()** et **fetchall()** sous peine de ne plus trop savoir ou en est le curseur et ce que vous récupérez.\n", "\n", "Voici donc le moyen le plus simple de récupérer tous les résultats d'une requête d'un coup." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "9bfeb4ab536ec2ea9b0784f1c91d0ebd", "grade": false, "grade_id": "cell-5b28fa4bed4af0cf", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur.execute(requete)\n", "resultats = curseur.fetchall()\n", "resultats" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "75160755a7229b1fe0159dde43f79236", "grade": false, "grade_id": "cell-ec745ce19cc0ddea", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "### Construire des requêtes à partir de variables python\n", "\n", "Nous allons dans l'exemple suivant écrire une fonction **prenom()** \n", "- qui prend en paramètre un curseur et un nom d'auteur\n", "- qui renvoie son prénom\n", "\n", "Si le nom de l'auteur ne figure pas dans la table *Auteurs*, la fonction renverra **None**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "bdfa20620174a6919d04ce97edb39d0d", "grade": false, "grade_id": "cell-97db6913b27c6d89", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "def prenom(c, nom):\n", " requete = \"SELECT PrenomAuteur FROM Auteurs WHERE NomAuteur = ?\"\n", " c.execute(requete, [nom])\n", " r = c.fetchall()\n", " if len(r) == 0:\n", " return None\n", " elif len(r) == 1:\n", " return r[0][0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "b358a15ff3b04edd5d1e54251544fec6", "grade": false, "grade_id": "cell-45e8fa29507a0e4c", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "prenom(curseur, \"Verne\")" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "e0693d057e44084de9d2cea813703f7a", "grade": false, "grade_id": "cell-5bb04f1af5083209", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "### Explications\n", "\n", "Dans cet exemple, nous construisons une requête à partir d'une variable Python. SQLite propose un mécanisme de substitution sécurisé permettant d'injecter une ou plusieurs variables à l'intérieur d'une requête. **C'est ce mécanisme que vous devez utiliser** : ne construisez pas vous même la chaîne de caractère contenant la requête complète, c'est une mauvaise pratique qui vous conduira inévitablement à des problèmes.\n", "\n", "Pour utiliser ce mécanisme de substitution, vous devez\n", "- mettre des **?** dans votre requête à l'emplacement de la variable à insérer\n", "- passer en second paramètre la liste des valeurs à substituer dans la requête\n", "\n", "C'est simple, fiable et sécurisé, en particulier contre les [injections SQL](https://xkcd.com/327/) !" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "a1579343c3e6b0384650a224ec2c9559", "grade": false, "grade_id": "cell-d745f7db6b31989b", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "### A vous de jouer\n", "\n", "Ecrivez une fonction **romans()** \n", "- qui prend en paramètre un curseur et un nom d'auteur\n", "- qui renvoie une liste de *Titres* de romans écrits par cet auteur\n", "\n", "Si le nom de l'auteur ne figure pas dans la table *Auteurs*, la fonction renverra **None**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "nbgrader": { "cell_type": "code", "checksum": "e591b8be71d5f8f617641c8ec138e462", "grade": false, "grade_id": "cell-4dbc8667ccf5a4b9", "locked": false, "schema_version": 3, "solution": true, "task": false } }, "outputs": [], "source": [ "def romans(c, nom):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "72deedafe0151c6d90b17b3fe48d5936", "grade": true, "grade_id": "cell-d45629f6b668916f", "locked": true, "points": 1, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "assert romans(curseur, \"Asimov\") == ['Fondation', 'Les Robots', 'La Fin de l’éternité']\n", "assert romans(curseur, \"Verne\") == ['De la Terre à la Lune']" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "5ebdb9a5aa637de0fd22fd51d9b21a75", "grade": false, "grade_id": "cell-861c55abf9706f8c", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Insérer de nouveaux enregistrements\n", "\n", "Les requêtes de modification sur la base se font de la même manière que les requêtes de sélection, à une petite subtilité près : après exécution de la requête, il faudra faire appel à la méthode **commit()** de l'objet *bdd* (issu de la connexion) afin que les modifications soient prises en compte dans le fichier de base de données. \n", "\n", "**Attention** : Si vous oubliez l'appel à commit, vos modifications seront perdues lorsque vous quitterez votre programme car elles ne seront pas inscrites dans le fichier de la base de données." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "d3e7c4218e8e5eaee36cdf67f30323fe", "grade": false, "grade_id": "cell-7ecd744c03866b11", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "requete = \"\"\"\n", "INSERT INTO Auteurs \n", " (NomAuteur, PrenomAuteur, IdLangue, AnneeNaissance)\n", "VALUES\n", " ('Lecluse', 'Olivier', 2, 1870);\n", "\"\"\"\n", "\n", "curseur.execute(requete)\n", "bdd.commit()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "2d2146d5397e0e30fc3a502819573cd8", "grade": false, "grade_id": "cell-b114c7ac4f8f4b33", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "la propriété **lastrowid** peut être intéressante car elle donne accès à la clé primaire créée automatiquement pour notre nouvel enregistrement. En voici une utilisation :" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "92f434f82820651784682733354ea066", "grade": false, "grade_id": "cell-9e5b1774cd6ecf92", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "last_id = curseur.lastrowid\n", "last_id " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "237cd4ab3745001c977b6b19c1442358", "grade": false, "grade_id": "cell-4eaeef7474110fea", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "requete = \"SELECT * FROM Auteurs WHERE IdAuteur = ?\"\n", "curseur.execute(requete, [last_id])\n", "curseur.fetchone()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "4d6ab34fd3495387fda5a739340de9a3", "grade": false, "grade_id": "cell-5ff41da4672ecf02", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "### A vous de jouer\n", "Effacez de la table auteur ce dernier enregistrement que nous avons créé." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "nbgrader": { "cell_type": "code", "checksum": "348db37dcfa258a60a61c4ff853de265", "grade": false, "grade_id": "cell-56030c508fbdd29b", "locked": false, "schema_version": 3, "solution": true, "task": false } }, "outputs": [], "source": [ "# YOUR CODE HERE\n", "raise NotImplementedError()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "2f028a1cee8f1da904e8e8afef40ceb6", "grade": true, "grade_id": "cell-26bbe8cd16b5ed09", "locked": true, "points": 1, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "requete = \"SELECT COUNT(*) from Auteurs\"\n", "curseur.execute(requete)\n", "assert curseur.fetchone()[0] == 10" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "4fecdc73d812c1dd4e6284c569cc2ff8", "grade": false, "grade_id": "cell-ce9dd63e7685bf6b", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur.execute(\"SELECT * FROM Auteurs\")\n", "curseur.fetchall()" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "954a25a110048b19975e639b8512585c", "grade": false, "grade_id": "cell-d9de3f8d2ddcc9a3", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Pour Finir\n", "\n", "Notre travail sur la BDD exemple est à présent terminé. Afin de fermer le fichier proprement et de s'assurer que les données saisies seront bien inscrites dans le fichier, il faut *impérativement* appeler la méthode **close()** sur l'objet *bdd* :" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "7293dd6fe965c56e8b132142317532e1", "grade": false, "grade_id": "cell-2ed83f05dbe42d2e", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "bdd.close()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "d6d507e7794ec70458603db209f12961", "grade": false, "grade_id": "cell-f5dd8cfccd974b89", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "curseur" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "markdown", "checksum": "b2575bcfd3fcdda77eb6b85be544659e", "grade": false, "grade_id": "cell-6bad013fb4889bab", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "A partir de ce moment là, plus acune opération n'est possible sur la base de données comme le montre la cellule suivante :" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "deletable": false, "editable": false, "nbgrader": { "cell_type": "code", "checksum": "6b9bdd82bf7ce142641bbc3122459034", "grade": false, "grade_id": "cell-56604b1af394c97b", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [], "source": [ "requete = \"SELECT COUNT(*) from Auteurs\"\n", "curseur.execute(requete)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.9" } }, "nbformat": 4, "nbformat_minor": 4 }