ParcoursOuvrir l'appli

SQL dans tes programmes

SQL et bases de données · 50 min

Modifier les données

Jusqu'ici, on a surtout lu. Trois commandes modifient les données :

Commande Effet Exemple
INSERT INTO ajoute une ligne INSERT INTO vendeurs (nom, telephone) VALUES ('Junior', '699001122')
UPDATE ... SET modifie des lignes UPDATE produits SET prix_vente = 1300 WHERE nom = 'riz'
DELETE FROM supprime des lignes DELETE FROM ventes WHERE id = 12

Le danger : l'injection SQL

Voici une fonction de connexion, comme on en voit encore dans des sites mal écrits :

def connexion(nom, mot_de_passe):
    requete = f"SELECT * FROM comptes WHERE nom = '{nom}' AND mdp = '{mot_de_passe}'"
    return len(conn.execute(requete).fetchall()) > 0

Elle a l'air correcte. Mais que se passe-t-il si quelqu'un tape, comme mot de passe : ' OR '1'='1 ? La requête devient :

SELECT * FROM comptes WHERE nom = 'Awa' AND mdp = '' OR '1'='1'

'1'='1' est toujours vrai, donc la condition est vraie pour toutes les lignes : l'attaquant entre sans connaître le mot de passe.

La parade : les paramètres

On ne colle jamais une donnée de l'utilisateur dans le texte d'une requête. On met des points d'interrogation à la place, et on passe les valeurs à part :

requete = "SELECT * FROM comptes WHERE nom = ? AND mdp = ?"
conn.execute(requete, (nom, mot_de_passe))

(Un vrai site ne stocke d'ailleurs jamais les mots de passe en clair : il stocke leur empreinte, ou hachage. Tu verras ça dans le module Sécurité.)

Exercice : Corriger la faille

La fonction connexion ci-dessous est vulnérable. Lance le code : l'attaque réussit. Puis corrige la fonction avec une requête paramétrée.

Voir une solution
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE comptes (nom TEXT, mdp TEXT)")
conn.execute("INSERT INTO comptes VALUES ('Awa', 'ndole2026')")
conn.execute("INSERT INTO comptes VALUES ('Paul', 'eru-et-waterfufu')")


def connexion(nom, mot_de_passe):
    """Renvoie True si le nom et le mot de passe correspondent à un compte."""
    requete = "SELECT * FROM comptes WHERE nom = ? AND mdp = ?"
    return len(conn.execute(requete, (nom, mot_de_passe)).fetchall()) > 0


print("Bon mot de passe :", connexion("Awa", "ndole2026"))
print("Mauvais mot de passe :", connexion("Awa", "1234"))
print("Attaque :", connexion("Awa", "' OR '1'='1"))

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

Exercice : Enregistrer une vente proprement

Écris ajouter_vente(date, produit_id, vendeur_id, quantite) qui insère une vente dans la table ventes de boutique2.sql avec une requête paramétrée, puis valide avec conn.commit().

La colonne id est une clé primaire entière : si on ne la donne pas, SQLite lui attribue automatiquement le numéro suivant. Il faut donc nommer les colonnes qu'on remplit : INSERT INTO ventes (date, produit_id, vendeur_id, quantite) VALUES (?, ?, ?, ?).

Voir une solution
import sqlite3

conn = sqlite3.connect(":memory:")
conn.executescript(open("boutique2.sql", encoding="utf-8").read())


def ajouter_vente(date, produit_id, vendeur_id, quantite):
    """Enregistre une vente avec une requête paramétrée."""
    conn.execute(
        "INSERT INTO ventes (date, produit_id, vendeur_id, quantite) VALUES (?, ?, ?, ?)",
        (date, produit_id, vendeur_id, quantite),
    )
    conn.commit()


ajouter_vente("2026-04-01", 7, 4, 3)    # Junior vend 3 sachets de sel
print(conn.execute("SELECT * FROM ventes ORDER BY id DESC LIMIT 1").fetchall())

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

SQL et pandas ensemble

Dans la vraie vie, on combine souvent les deux : SQL pour extraire les bonnes données d'une grosse base, pandas pour analyser le résultat.

import pandas as pd

df = pd.read_sql_query("""
    SELECT v.date, p.nom AS produit, d.nom AS vendeur, v.quantite, p.prix_vente
    FROM ventes v
    JOIN produits p ON v.produit_id = p.id
    JOIN vendeurs d ON v.vendeur_id = d.id
""", conn)

À partir de là, tout le module pandas s'applique. Faut-il regrouper en SQL ou en pandas ? Règle pratique : quand la base est énorme et distante, on fait faire le maximum de travail à la base (filtrer, regrouper), pour ne rapatrier que le résultat. Quand les données tiennent en mémoire, on choisit ce qui est le plus lisible.

Exercice : De la base au DataFrame

Avec pd.read_sql_query, récupère dans df toutes les ventes avec ces colonnes, dans cet ordre : date, produit (le nom du produit), vendeur (le nom du vendeur), quantite, prix_vente.

Puis, avec pandas, calcule ca_vendeur : un dictionnaire du chiffre d'affaires (quantité x prix de vente) par vendeur.

Voir une solution
import sqlite3

conn = sqlite3.connect(":memory:")
conn.executescript(open("boutique2.sql", encoding="utf-8").read())
import pandas as pd

df = pd.read_sql_query("""
    SELECT v.date, p.nom AS produit, d.nom AS vendeur, v.quantite, p.prix_vente
    FROM ventes v
    JOIN produits p ON v.produit_id = p.id
    JOIN vendeurs d ON v.vendeur_id = d.id
""", conn)

df["montant"] = df["quantite"] * df["prix_vente"]
ca_vendeur = df.groupby("vendeur")["montant"].sum().to_dict()
print(df.head())
print(ca_vendeur)

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice