Un système de gestion de bases de données relationnelle (SGBDR) est un logiciel conçu pour interagir (stocker, manipuler, gérer)
avec une base de données relationnelle : Il sert d'intermédiaire entre l'utilisateur et les données de la base de données.
Un tel système doit implémenter quatre opérations de base :
Il existe plusieurs logiciels SGBDR, tous basés sur CRUD :
Ces logiciels utilisent le langage SQL (Structured Query Language, en français : langage de requêtes structurées), dont ils partagent les instructions de base mais présentent aussi des variations mentionnées dans leur documentation respective.
Au niveau conceptuel, les SGBD utilisent l'architecture Ansi/Sparc crée en 1975 par Charles Bachman, félicité par un prix Turing (équivalent d'un prix Nobel d'informatique).
Elle est divisée en trois niveaux, fréquentés par différents acteurs :
|
Cette architecture permet de :
Installé sur un serveur, un SGBD traditionnel (Oracle, MySQL, MariaDB, etc) est capable de manipuler plusieurs bases de données et de fournir des accès aux utilisateurs. L'administrateur peut gérer les bases de données à l'aide des instructions SQL suivantes :
Opération | Instruction SQL |
---|---|
Création d'une BD | CREATE DATABASE nom_base CHARACTER SET 'utf8' |
Suppression d'une BD | DROP DATABASE nom_base |
Utilisation d'une BD | USE nom_base |
Remarques :
USE
).
Nous allons utiliser la bibliothèque sqlite3, qui permet d'utiliser une base de données légère directement sous Python (sans avoir à installer de serveur dédié).
Références :
sqlite3
; un connecteur permet de manipuler le fichier .db
contenant la base. Le curseur permet de donner des instructions au connecteur : import sqlite3
# S'il n'existe pas encore, un fichier .db sera créé lors de la connexion.
NOM_BASE = "ma_base_sqlite.db"
# L'objet «connecteur» permet de se connecter à la base (fichier .db).
connecteur = sqlite3.connect(NOM_BASE)
# Un objet «curseur» permet d'exécuter des instructions en langage SQL, par l'intermédiaire du connecteur.
curseur = connecteur.cursor()
# L'instruction suivante affiche les noms des tables de la base de données :
reponse = curseur.execute("SELECT name FROM sqlite_schema WHERE type='table' ;").fetchall()
print(reponse)
# ...
# On termine en fermant la connexion :
connecteur.close()
base01
» ;
curseur.execute("instruction_sql")
pour exécuter une seule instruction SQL ; curseur.executemany("instruction_sql", liste)
pour exécuter plusieurs instructions dépendant d'une liste fournie en argument.curseur.executescript("script_sql")
pour exécuter un script d'instructions SQL. Voici des modalités d'implémentation :
mon_script_sql_1 = """
-- première possibilité : utiliser les triples quotes permet d'écrire
-- un script sql sur plusieurs lignes directement dans le script python.
"""
reponse_1 = curseur.executescript(mon_script_sql_1).fetchall()
# seconde possibilité : lire un fichier texte .sql
mon_script_sql_2 = ""
with open("mon_fichier.sql") as f:
mon_script_sql_2 = f.readlines()
reponse_2 = curseur.executescript(mon_script_sql_2).fetchall()"
|
Créer une table dans la BD. |
|
Supprimer une table de la BD. |
C'est à la création de la table que l'on précise les attributs, leurs types et les contraintes. Le choix d'un type à un impact important sur la taille de la base de données et sa performance tandis que le choix des contraintes participe à maintenir l'intégrité des données.
On peut appliquer une contrainte (par exemple : NOT NULL
directement sur
l'attribut lors de sa déclaration ou regrouper toutes les contraintes à la fin en
les appliquant à plusieurs attributs. L'important est d'être clair, pour faciliter la lecture du script par
d'autres développeurs, et organisé pour ne rien oublier.
Les types de données MySQL sont bien plus nombreux que les types de variable que vous connaissez en python. Cette grande variété sert à optimiser la taille d'une base de données.
NULL
désigne le vide («non rempli»).
Type | Min | Max | Taille en octet |
---|---|---|---|
INT ou INTEGER | -2147483648 | 2147483647 | 4 |
TINYINT | -128 | 127 | 1 |
TINYINT UNSIGNED | 0 | 255 | 1 |
SMALLINT | -32768 | 32767 | 2 |
SMALLINT UNSIGNED | 0 | 65535 | 2 |
MEDIUMINT | -8388608 | 8388607 | 3 |
MEDIUMINT UNSIGNED | 0 | 16777215 | 3 |
INT UNSIGNED ou INTEGER UNSIGNED | 0 | 4294967295 | 4 |
BIGINT | -9223372036854775808 | 9223372036854775807 | 8 |
BIGINT UNSIGNED | 0 | 18446744073709551615 | 8 |
BOOL
mais
cela revient à utiliser TINYINT(1)
. Les valeurs suivantes : true
, false
, TRUE
, FALSE
, True
, False
;
sont interprétées respectivement comme 1
, 0
, 1
, 0
, 1
, 0
. Type | Min négatif | Max négatif | 0 | Min positif | Max positif | Taille en octet |
---|---|---|---|---|---|---|
FLOAT | -3.402823466E+38 | -1.175494351E-38 | 0 | 1.175494351E-38 | 3.402823466E+38 | 4 |
DOUBLE | -1.7976931348623157E+308 | -2.2250738585072014E-308 | 0 | 2.2250738585072014E-308 | 1.7976931348623157E+308 | 8 |
'...'
. Pour écrire une apostrophe '
à l'intérieur d'une chaîne de caractères, on écrit ''
(2 apostrophes). Type | Taille en octet |
---|---|
CHAR(X) avec X le nombre de caractères | X (si chaque caractère est codé sur un seul octet)255 au maximum |
VARCHAR(X) avec X le nombre de caractères | ≤X (si chaque caractère est codé sur un seul octet)65532 au maximum |
TINYBLOB ou TINYTEXT | ≤255 |
BLOB données binaires brutes | ≤65535 |
TEXT texte codé selon la base (souvent utf8) | ≤65535 |
MEDIUMBLOB ou MEDIUMTEXT | ≤16777215 |
LONGBLOB ou LONGTEXT | ≤4294967295 |
Type | Min | Max | Précision | Taille en octet | Représentation |
---|---|---|---|---|---|
DATE | 1 Janvier 1000 | 31 Décembre 9999 | jour | 3 | AAAA-MM-JJ |
DATETIME | 1 Janvier 1000 00h00m00s | 31 Décembre 9999 23h59m59s | seconde | 8 | AAAA-MM-JJ HH:MM:SS |
TIME | -838h59m59s | 838h59m59s | 5 | HH:MM:SS | |
YEAR | 1901 | 2155 | année | 4 | AAAA |
NULL
Champ vide.
INTEGER
: Entier (avec signe) de 1 à 8 octets (équivalent de TINYINT
à BIGINT
).
REAL
: valeur en virgule flottante IEEE 8 octets (équivalent de FLOAT
).
TEXT
: Texte (codage de caractères régi par la base : UTF-8, UTF-16BE or UTF-16LE).
BLOB
: Données binaires brutes.
DATETIME
sera stockée, en pratique, sous un type sql>TEXT, sous la forme YYYY-MM-DD HH:MM:SS.SSS
(suivant la norme ISO8601). Une contrainte d'intégrité est une règle qui définit la cohérence d'une donnée ou d'un ensemble de données au sein d'une base de données, afin de garantir leurs pertinence et leur validité.
nom_attribut NOT NULL | l'attribut ne peut pas avoir la valeur NULL (être vide) |
CHECK (nom_attribut BETWEEN a AND b) | contrainte de domaine : ici, avoir des valeurs entre a et b |
UNIQUE (nom_attribut_1, ..., nom_attribut_n) | les n-uplets d'attributs doivent être uniques |
PRIMARY KEY (nom_attribut_1, ..., nom_attribut_n) | contrainte de relation : les attributs sont mis en clé primaire |
|
contrainte de référence : crée une clé étrangère les occurences sont mises à jour en cas de modification/suppression dans l'autre table ⚠ à écrire sur une même ligne en SQLite |
_
et pas d'espaces !
#
.
| pour insérer des données dans la table |
| Si vous ajouter des valeurs à toutes les colonnes de la table, il est possible de simplifier l'instruction en omettant le nom des attributs. Attention cependant à bien respecter l'ordre de ces attributs tels qu'ils ont été créés dans la table. |
| pour modifier des données ; par exemple : on peut remplacer modification_1 par nom=UPPER(nom) pour mettre les noms en majuscule ; la condition est optionnelle. |
|
pour supprimer des données ; ⚠ la condition est essentielle ! Toutes les lignes seront supprimées sinon. |
Remarque : Les conditions s'écrivent de la même manière qu'en python, sauf le test d'égalité qui est
un simple =
(pas d'ambiguïté en SQL). Les conjonctions AND
, OR
, NOT
, ... sont en majuscules.
Les fichiers CSV ci-dessous contiennent toutes les données à injecter dans la BD. Écrire la requête SQL permettant d'injecter au moins un enregistrement dans chaque table.
Données : employes.csv ; succursales.csv ; fournisseurs.csv Schémas relationnels :Attention : Avec SQLite, il est impossible de modifier une table pour insérer une clé étrangère. Les clés étrangères doivent être crées au niveau de la déclaration des tables.
|
pour ajouter un attribut |
|
pour modifier le type ou les contraintes d'un attribut |
|
pour modifier le nom d'un attribut |
|
pour supprimer un attribut |
|
pour créer une clé étrangère Attention : impossible avec SQLite. |
|
pour supprimer une clé étrangère. Attention : impossible avec SQLite. |
|
pour supprimer une table : ⚠ MySQL exige d'effacer les clés étrangères avant, pas SQLite |
BEGIN TRANSACTION
/ ROLLBACK
/ COMMIT
:
BEGIN TRANSACTION
crée un point de sauvegarde ; c'est une pratique utile à effectuer avant d'opérer une modification importante ou destructrice dans une base de données.
ROLLBACK
permet d'annuler toutes les modifications effectuées depuis BEGIN TRANSACTION
.
COMMIT
permet d'enregistrer définitivement les modifications courantes dans la base.
BEGIN TRANSACTION
et ROLLBACK
.
curseur.execute("BEGIN TRANSACTION ;")
curseur.execute("DROP TABLE employés ;")
reponse = curseur.execute("SELECT name FROM sqlite_schema WHERE type='table' ;").fetchall()
print("Après DROP TABLE : ", reponse)
curseur.execute("ROLLBACK ;")
reponse = curseur.execute("SELECT name FROM sqlite_schema WHERE type='table' ;").fetchall()
print("Après ROLLBACK : ", reponse)
PRAGMA foreign_keys = ON ;
; elles ne le sont pas par défaut 🚮. id
de succursale qui n'existe pas (par exemple 5) et observer le message d'erreur.
.csv
) en ayant activé les clés étrangères avant ?
SELECT
et les fonctions d'agrégationElle permet d'afficher des résultats de manière approfondie.
|
Affiche une liste de résultats concernés par une condition (optionnelle) classés dans un certain ordre (ascendant : ASC ou descendant : DESC )
SELECT * FROM ... permet d'afficher tous les attributs de la table
|
|
Le mot-clé DISTINCT permet d'éviter les doublons dans la liste affichée |
|
Le mot-clé CONCAT(a,b) permet de formater les résultats en concaténant 2 champs ou 1 champ et 'du texte'
(on peut l'utiliser plusieurs fois : CONCAT(CONCAT(a,b),c)) |
COUNT()
| Compte le nombre d'enregistrements |
MAX() et MIN() |
Récupèrent le max et le min sur un ensemble d'enregistrements |
SUM() et AVG() |
Récupèrent la somme et la moyenne sur un ensemble d'enregistrements |
nom prénom
triés par ordre alphabétique
Définition : Si X et Y sont deux ensembles, on note X×Y l'ensemble de tous les couples de valeurs (x,y) ou x est un élément de X et y un élément de Y.
Exemple : Pour X={a,b,c} et Y={u,v}, on a X×Y={(a,u), (b,u), (c,u), (a,v), (b,v),(c,v)}.
Remarque : Un produit cartésien peut être représenté par un tableau :
Éléments de X×Y :
X×Y | u | v |
---|---|---|
a | (a,u) | (a,v) |
b | (b,u) | (b,v) |
c | (c,u) | (c,v) |
SELECT DISTINCT e.nom,e.prénom,s.ville
FROM employés AS e, succursales AS s ;
DISTINCT
?
DISTINCT
peut il être remplacé par UNIQUE
?
AS
dans cet exemple ?
SELECT DISTINCT e1.nom,e2.prénom,s.ville
FROM employés AS e1, employés AS e2, succursales AS s ;
SELECT ... LIKE
: repérer un motifLIKE
permettant de repérer des motifs dans les chaînes de caractères (selon
le principe des expressions régulières mais la syntaxe est différente). Exemple :
SELECT nom
FROM employés
WHERE nom LIKE 'B%'
LIKE '%a'
les valeurs affichées se terminent par le caractère 'a'
.
LIKE 'a%'
les valeurs affichées débutent par le caractère 'a'
.
LIKE '%a%'
les valeurs affichées contiennent le caractère 'a'
.
LIKE 'pas%s%i%mal'
les valeurs affichées commencent par 'pas'
, suivi de 's'
puis 'i'
et se terminent par 'mal'
.
%
désigne un nombre inconnu de caractères ; ce nombre peut très bien être nul._
remplace un seul et unique caratère.
SELECT DISTINCT e.prénom
FROM employés AS e
WHERE UPPER(e.nom) LIKE '%B%_'
Comment interpréter le %_
à la fin de la requête ?
table1 JOIN table2
), l'opération consistant à rapprocher, selon une condition que l'on choisit (ON condition
),
des tuples issus de ces deux tables vérifiant la condition donnée. SELECT nom, fournisseur, fourniture
FROM employés JOIN fournisseurs
ON employés.succursale_id = fournisseurs.succursale_id