Base de Données

SQL & MySQL

Maîtrisez les bases de données relationnelles avec SQL, MySQL et phpMyAdmin. De l'installation à l'optimisation avancée, devenez un expert en gestion de données.

🗄️ Bases de Données
📝 Requêtes SQL
🔧 Administration MySQL

Introduction aux Bases de Données

Comprenez les concepts fondamentaux des bases de données relationnelles et l'écosystème MySQL.

Qu'est-ce qu'une Base de Données ?

🗄️ Définition

Une base de données est un système organisé de stockage, de gestion et de récupération d'informations structurées de manière cohérente et sécurisée.

🔗 Modèle Relationnel

  • • Tables reliées entre elles
  • • Clés primaires et étrangères
  • • Intégrité des données
  • • Normalisation

Avantages

  • • Cohérence des données
  • • Accès concurrent
  • • Sécurité et permissions
  • • Sauvegarde et récupération

MySQL dans l'Écosystème des SGBD

Pourquoi MySQL ?

  • Open source et gratuit
  • Performance et fiabilité éprouvées
  • Large communauté et documentation
  • Compatible avec PHP, Python, Java...
  • Utilisé par Facebook, YouTube, Twitter

Versions MySQL :

MySQL 8.0
• Version actuelle LTS
• JSON natif, CTE, Window Functions
MySQL 5.7
• Version stable et éprouvée
• Support JSON, performance améliorée

Architecture MySQL :

1 Client se connecte au serveur MySQL
2 Analyseur SQL traite la requête
3 Optimiseur choisit le plan d'exécution
4 Moteur de stockage (InnoDB) exécute
5 Résultats renvoyés au client
Moteurs de stockage :
  • InnoDB : Transactions, clés étrangères (défaut)
  • MyISAM : Lecture rapide, pas de transactions
  • Memory : Données en RAM
  • Archive : Compression, données historiques

Installation MySQL sur Windows

Guide complet pour installer et configurer MySQL Server sur Windows avec les outils d'administration.

1 MySQL Installer (Recommandé)

Avantages de MySQL Installer :

  • Installation complète (Serveur + outils)
  • Configuration automatique
  • MySQL Workbench inclus
  • Mises à jour facilitées

Étapes d'installation :

  1. 1.
    Télécharger depuis dev.mysql.com
  2. 2. Choisir "Full" ou "Developer Default"
  3. 3. Configurer le mot de passe root
  4. 4. Créer un utilisateur pour le développement
  5. 5. Démarrer le service MySQL80

Configuration recommandée :

# Configuration de base
Port : 3306 (défaut)
Utilisateur root : root
Mot de passe : [votre_mot_de_passe]
# Utilisateur développeur
Nom : dev_user
Host : localhost
Privilèges : ALL sur *.*
Premier test :
# Ouvrir MySQL Command Line Client
mysql> SHOW DATABASES;
mysql> SELECT VERSION();
mysql> SELECT NOW();

2 Alternative : MySQL via XAMPP

Avantages XAMPP :

  • Pack complet Apache + MySQL + PHP
  • phpMyAdmin pré-installé
  • Configuration simplifiée
  • Idéal pour débuter

Accès à MySQL :

# XAMPP Control Panel
1. Démarrer Apache
2. Démarrer MySQL
# Accès phpMyAdmin
URL: http://localhost/phpmyadmin
User: root
Pass: (vide par défaut)

Sécurisation XAMPP :

-- Définir un mot de passe root
ALTER USER 'root'@'localhost'
IDENTIFIED BY 'votre_mot_de_passe';
-- Créer un utilisateur dédié
CREATE USER 'dev'@'localhost'
IDENTIFIED BY 'dev_password';
GRANT ALL PRIVILEGES ON *.*
TO 'dev'@'localhost';
FLUSH PRIVILEGES;
⚠️ Attention :

XAMPP est parfait pour le développement mais ne doit jamais être utilisé en production sans sécurisation complète.

phpMyAdmin - Interface Web

Maîtrisez phpMyAdmin pour administrer vos bases de données MySQL via une interface web intuitive.

Installation et Configuration phpMyAdmin

Installation séparée :

  1. 1. Télécharger depuis phpmyadmin.net
  2. 2. Extraire dans htdocs/phpmyadmin
  3. 3. Configurer config.inc.php
  4. 4. Accéder via http://localhost/phpmyadmin

Configuration de base :

// config.inc.php
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['port'] = '3306';
$cfg['Servers'][$i]['auth_type'] = 'cookie';
$cfg['blowfish_secret'] = '[32_caractères_aléatoires]';

Interface principale :

Navigation : Serveurs, bases, tables
SQL : Onglet pour exécuter des requêtes
Structure : Voir/modifier la structure des tables
Parcourir : Afficher les données des tables
Insérer : Ajouter des enregistrements
Exporter : Sauvegardes et exports
Raccourcis utiles :
  • • Ctrl+Enter : Exécuter la requête SQL
  • • Ctrl+S : Sauvegarder la requête
  • • F1 : Aide contextuelle
  • • Double-clic : Éditer une cellule

Fonctionnalités Avancées phpMyAdmin

1 Designer de Relations

Créer visuellement des relations entre tables

Plus > Designer
• Glisser-déposer des tables
• Créer des liens FK
• Exporter le schéma

2 Import/Export Avancé

Gestion complète des sauvegardes

Export formats :
• SQL (structure + données)
• CSV, Excel, PDF
• XML, JSON

3 Requêtes Sauvegardées

Bookmarks et historique SQL

Console > Bookmarks
• Sauvegarder les requêtes
• Organiser par dossiers
• Partager avec équipe

4 Monitoring et Performance

Surveillance des performances

Plus > Status
• Processus en cours
• Variables serveur
• Graphiques temps réel

5 Gestion des Utilisateurs

Administration des comptes

Comptes utilisateur
• Créer/modifier users
• Privilèges détaillés
• Audit des connexions

6 Outils de Développement

Aide au développement

Fonctionnalités :
• Générateur de requêtes
• Profileur SQL
• Analyseur de performances

⚡ Installation Ultra-Rapide d'Adminer

Adminer : l'alternative moderne et légère à phpMyAdmin. Un seul fichier PHP et c'est parti ! Compatible Windows, Linux et macOS.

Qu'est-ce qu'Adminer ?

Adminer en bref :

Adminer (anciennement phpMinAdmin) est un outil d'administration de bases de données écrit en PHP. Sa particularité : un seul fichier de 500 KB suffit pour administrer vos bases !

🎯 Philosophie : "Moins de fonctionnalités, mais mieux implémentées"

Avantages d'Adminer :

  • Ultra-léger : 500 KB vs 15 MB pour phpMyAdmin
  • Installation instantanée : Juste un fichier à téléverser
  • Multi-SGBD : MySQL, PostgreSQL, SQLite, Oracle, etc.
  • Interface moderne : Design épuré et responsive
  • Personnalisable : Thèmes et plugins disponibles
  • Sécurisé : Moins de surface d'attaque

Comparatif avec phpMyAdmin :

Critère Adminer phpMyAdmin
Taille 500 KB 15 MB
Installation 1 fichier Configuration complexe
Vitesse Très rapide Plus lent
Fonctionnalités Essentielles Très complètes
Interface Moderne Traditionnelle
SGBD supportés 8+ MySQL/MariaDB
🎯 Quand utiliser Adminer ?
  • Développement rapide : Pas le temps de configurer phpMyAdmin
  • Serveurs légers : Ressources limitées
  • Démo clients : Interface moderne et claire
  • Projets multi-SGBD : PostgreSQL, SQLite, etc.
  • Sécurité : Moins de risques, plus facile à sécuriser
🪟

Installation Windows (XAMPP/WAMP/Laragon)

Méthode 1 : Avec XAMPP

# 1. Télécharger Adminer
Aller sur : https://www.adminer.org/
Télécharger : adminer-4.8.1.php
# 2. Copier dans XAMPP
Copier vers : C:\xampp\htdocs\
Renommer en : adminer.php
# 3. Démarrer les services
Apache : START
MySQL : START
# 4. Accéder via le navigateur
http://localhost/adminer.php
Connexion rapide
  • Serveur : localhost
  • Utilisateur : root
  • Mot de passe : (vide par défaut)
  • Base : (laisser vide)

Méthode 2 : Avec WAMP/Laragon

# Pour WAMP Server
Dossier : C:\wamp64\www\
URL : http://localhost/adminer.php
# Pour Laragon
Dossier : C:\laragon\www\
URL : http://localhost/adminer.php
# Alternative avec sous-dossier
Créer : C:\xampp\htdocs\admin\
Placer : adminer.php dans admin\
Accès : http://localhost/admin/adminer.php
💡 Astuce Windows

Créez un raccourci sur le bureau pointant vers http://localhost/adminer.php pour un accès ultra-rapide !

🐧

Installation Linux/Unix

Installation directe

# Télécharger la dernière version
cd /var/www/html
wget https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1.php
# Renommer pour simplicité
mv adminer-4.8.1.php adminer.php
# Définir les permissions
chmod 644 adminer.php
# Accès navigateur
http://votre-serveur/adminer.php

Installation avec curl

# Méthode alternative avec curl
curl -L -o adminer.php \
https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1.php
# Ou via le site officiel
curl -o adminer.php \
https://www.adminer.org/latest.php
🍎

Installation macOS

Avec MAMP

# Télécharger Adminer
https://www.adminer.org/
# Copier dans MAMP
Dossier : /Applications/MAMP/htdocs/
URL : http://localhost:8888/adminer.php

Avec Homebrew

# Si Apache est installé via brew
brew services start httpd
cd /usr/local/var/www
curl -o adminer.php \
https://www.adminer.org/latest.php
🔒

Sécurisation et Configuration

Protection .htaccess

# Créer .htaccess dans le même dossier
# Restriction par IP
Order Deny,Allow
Deny from all
Allow from 127.0.0.1
Allow from 192.168.1.
# Authentification basique
AuthType Basic
AuthName "Zone Admin"
AuthUserFile /chemin/.htpasswd
Require valid-user

Personnalisation avancée

# Créer adminer-custom.php
<?php
// Personnalisations
function adminer_object() {
include_once "adminer.php";
return new AdminerCustom();
}
class AdminerCustom extends Adminer {
// Thème sombre
// Logo personnalisé
// Restrictions DB
}
?>
⚠️ Production

Ne JAMAIS laisser Adminer accessible en production sans authentification et restrictions IP !

💡 Développement

Parfait pour le développement local. Renommez le fichier en quelque chose de moins évident.

🔐 Sécurité

Utilisez HTTPS, authentification forte et supprimez après utilisation sur serveur distant.

📊 Adminer vs phpMyAdmin - Le Verdict

✅ Avantages Adminer

  • Léger : 1 seul fichier (500KB)
  • Installation : 30 secondes chrono
  • Multi-SGBD : MySQL, PostgreSQL, SQLite...
  • Interface : Moderne et épurée
  • Performance : Plus rapide que phpMyAdmin
  • Plugins : Système extensible

📋 Cas d'usage idéaux

  • Développement rapide : Prototypage, tests
  • Serveurs limités : Ressources restreintes
  • Multi-projets : Différents SGBD
  • Démos clients : Installation propre
  • Urgences : Dépannage rapide
  • Formation : Interface simple

🎯 Recommandation

Adminer pour le développement et les projets agiles, phpMyAdmin pour les environnements d'entreprise complexes. Pourquoi pas les deux ? Ils sont complémentaires !

Fonctionnalités Avancées d'Adminer

🔄 Support Multi-SGBD

Un seul outil pour tous vos SGBD

  • MySQL/MariaDB : Support complet
  • PostgreSQL : Fonctionnalités avancées
  • SQLite : Parfait pour le développement
  • MS SQL Server : Via extensions PHP
  • Oracle : Support professionnel
  • MongoDB : Version spécialisée

Performance et Optimisation

Outils d'analyse intégrés

  • EXPLAIN : Analyse des requêtes automatique
  • Profiler : Temps d'exécution détaillé
  • Index Advisor : Suggestions d'optimisation
  • Statistiques : Monitoring en temps réel
  • Cache : Gestion intelligente du cache

🎨 Interface Moderne

Design responsive et intuitif

  • Responsive : Fonctionne sur mobile/tablette
  • Thèmes : Interface personnalisable
  • Auto-complétion : SQL intelligent
  • Coloration syntaxique : Code lisible
  • Édition inline : Modification rapide

🔐 Sécurité Renforcée

Protection avancée

  • CSRF Protection : Tokens automatiques
  • XSS Prevention : Échappement des données
  • SQL Injection : Requêtes préparées
  • Session Security : Gestion sécurisée
  • IP Filtering : Restriction d'accès

🔧 Plugins et Extensions

Extensibilité maximale

  • AdminerEvo : Version communautaire étendue
  • Auto-login : Connexion sans saisie
  • Pretty JSON : Formatage JSON
  • Dump Date : Horodatage des exports
  • Edit Foreign : Édition des FK

📊 Outils de Développement

Pour les développeurs

  • SQL History : Historique des requêtes
  • Query Bookmarks : Requêtes favorites
  • Schema Diff : Comparaison de structures
  • Data Generator : Données de test
  • API Mode : Utilisation via REST

💡 Conseils d'utilisation d'Adminer

🚀
Développement

Parfait pour le développement local. Installation en 30 secondes.

Performance

Ultra-rapide sur tous les environnements, même les plus modestes.

🔐
Sécurité

Moins de code = moins de failles. Sécurisation simplifiée.

Fondamentaux SQL

Maîtrisez les requêtes SQL essentielles : CREATE, INSERT, SELECT, UPDATE, DELETE.

1 Création de Base et Tables (CREATE)

Créer une base de données :

-- Créer une nouvelle base
CREATE DATABASE boutique_en_ligne
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Utiliser la base
USE boutique_en_ligne;
-- Vérifier les bases existantes
SHOW DATABASES;

Types de données essentiels :

INT - Nombres entiers
VARCHAR(n) - Chaînes variables
TEXT - Texte long
DECIMAL(p,s) - Nombres décimaux précis
DATETIME - Date et heure
BOOLEAN - Vrai/Faux

Créer des tables :

-- Table des utilisateurs
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
mot_de_passe VARCHAR(255) NOT NULL,
date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
actif BOOLEAN DEFAULT TRUE
);
-- Table des produits
CREATE TABLE produits (
id INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(200) NOT NULL,
prix DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
description TEXT,
date_creation DATETIME DEFAULT CURRENT_TIMESTAMP
);
Contraintes importantes :
  • PRIMARY KEY : Identifiant unique
  • NOT NULL : Champ obligatoire
  • UNIQUE : Valeurs uniques
  • AUTO_INCREMENT : Incrémentation automatique
  • DEFAULT : Valeur par défaut

2 Insertion de Données (INSERT)

INSERT basique :

-- Insertion simple
INSERT INTO users
(nom, email, mot_de_passe)
VALUES
('Jean Dupont', 'jean@test.com', 'password123');
-- Insertion multiple
INSERT INTO produits
(nom, prix, stock) VALUES
('iPhone 14', 899.99, 50),
('Samsung Galaxy', 749.99, 30),
('Google Pixel', 599.99, 25);

INSERT avec gestion d'erreurs :

-- Ignorer les doublons
INSERT IGNORE INTO users
(nom, email) VALUES
('Utilisateur', 'existe@deja.com');
-- Mise à jour si existe
INSERT INTO produits
(nom, prix) VALUES
('Produit', 100.00)
ON DUPLICATE KEY UPDATE
prix = VALUES(prix);

INSERT depuis SELECT :

-- Copier des données
INSERT INTO produits_archives
(nom, prix, date_archivage)
SELECT nom, prix, NOW()
FROM produits
WHERE stock = 0;
-- Avec transformation
INSERT INTO commandes_resume
SELECT
user_id,
COUNT(*) AS nb_commandes,
SUM(total) AS montant_total
FROM commandes
GROUP BY user_id;
Bonnes pratiques INSERT :
  • • Toujours spécifier les colonnes
  • • Utiliser des transactions pour les gros volumes
  • • Valider les données avant insertion
  • • Gérer les erreurs avec IGNORE ou ON DUPLICATE
  • • Utiliser des requêtes préparées en PHP

3 Lecture de Données (SELECT)

SELECT de base :

-- Sélection simple
SELECT * FROM users;
-- Colonnes spécifiques
SELECT nom, email
FROM users;
-- Avec alias
SELECT
nom AS 'Nom complet',
email AS 'Adresse email'
FROM users AS u;

Filtres WHERE :

-- Conditions simples
SELECT * FROM produits
WHERE prix > 500;
-- Conditions multiples
SELECT * FROM produits
WHERE prix BETWEEN 100 AND 1000
AND stock > 0;
-- Recherche textuelle
SELECT * FROM produits
WHERE nom LIKE '%iPhone%'
OR description LIKE '%smartphone%';

Tri et limitation :

-- Tri simple
SELECT * FROM produits
ORDER BY prix DESC;
-- Tri multiple
SELECT * FROM users
ORDER BY date_creation DESC, nom ASC;
-- Limitation (pagination)
SELECT * FROM produits
ORDER BY date_creation DESC
LIMIT 10 OFFSET 20;
-- Top 5 des plus chers
SELECT nom, prix
FROM produits
ORDER BY prix DESC
LIMIT 5;

SELECT avancé :

-- Valeurs distinctes
SELECT DISTINCT ville
FROM users;
-- Conditions complexes
SELECT * FROM users
WHERE email IN (
'admin@site.com',
'manager@site.com'
)
AND actif = TRUE;
Opérateurs WHERE utiles :
=, !=, <, >, <=, >= : Comparaisons
BETWEEN...AND : Plage de valeurs
IN(...) : Liste de valeurs
LIKE : Recherche avec % et _
IS NULL / IS NOT NULL : Valeurs nulles

4 Modification et Suppression (UPDATE/DELETE)

UPDATE - Modifier des données :

-- Modification simple
UPDATE users
SET email = 'nouvel@email.com'
WHERE id = 1;
-- Modification multiple
UPDATE produits
SET
prix = prix * 0.9,
description = CONCAT(description, ' - PROMO')
WHERE stock > 50;
-- Avec conditions complexes
UPDATE users
SET actif = FALSE
WHERE date_creation < DATE_SUB(NOW(), INTERVAL 1 YEAR)
AND derniere_connexion IS NULL;
⚠️ Attention UPDATE :
  • • Toujours utiliser WHERE (sinon toute la table !)
  • • Tester avec SELECT avant UPDATE
  • • Sauvegarder avant gros UPDATE
  • • Utiliser LIMIT pour sécuriser

DELETE - Supprimer des données :

-- Suppression simple
DELETE FROM users
WHERE id = 5;
-- Suppression avec conditions
DELETE FROM produits
WHERE stock = 0
AND date_creation < DATE_SUB(NOW(), INTERVAL 6 MONTH);
-- Suppression limitée
DELETE FROM logs
WHERE date_log < DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY date_log ASC
LIMIT 1000;
-- Vider une table (DANGER !)
TRUNCATE TABLE table_temporaire;
🚨 Sécurité DELETE :
  • • JAMAIS DELETE sans WHERE !
  • • Compter avec SELECT COUNT(*) avant
  • • Sauvegarder avant suppression
  • • Préférer soft delete (flag actif=false)
  • • TRUNCATE vide complètement la table

Soft Delete (recommandé) :

-- Ajouter colonne deleted_at
ALTER TABLE users
ADD COLUMN deleted_at DATETIME NULL;
-- "Supprimer" un utilisateur
UPDATE users
SET deleted_at = NOW()
WHERE id = 1;
-- Lister seulement les actifs
SELECT * FROM users
WHERE deleted_at IS NULL;

Jointures et Relations

Maîtrisez les jointures SQL pour exploiter la puissance des bases de données relationnelles.

Types de Jointures

INNER JOIN (jointure interne) :

-- Tables de test
CREATE TABLE commandes (
id INT PRIMARY KEY,
user_id INT,
produit_id INT,
quantite INT,
date_commande DATETIME
);
-- INNER JOIN basique
SELECT
u.nom,
p.nom AS produit,
c.quantite
FROM commandes c
INNER JOIN users u ON c.user_id = u.id
INNER JOIN produits p ON c.produit_id = p.id;

INNER JOIN retourne seulement les lignes qui ont une correspondance dans les deux tables.

LEFT JOIN (jointure externe gauche) :

-- Tous les utilisateurs même sans commande
SELECT
u.nom,
u.email,
COUNT(c.id) AS nb_commandes
FROM users u
LEFT JOIN commandes c ON u.id = c.user_id
GROUP BY u.id, u.nom, u.email;
-- Utilisateurs sans commande
SELECT u.nom, u.email
FROM users u
LEFT JOIN commandes c ON u.id = c.user_id
WHERE c.user_id IS NULL;

LEFT JOIN retourne toutes les lignes de la table de gauche, avec NULL si pas de correspondance à droite.

RIGHT JOIN :

-- Tous les produits même non commandés
SELECT
p.nom,
COUNT(c.id) AS nb_ventes
FROM commandes c
RIGHT JOIN produits p ON c.produit_id = p.id
GROUP BY p.id, p.nom;

FULL OUTER JOIN :

-- MySQL n'a pas FULL OUTER, simulation :
SELECT u.nom, c.id AS commande_id
FROM users u
LEFT JOIN commandes c ON u.id = c.user_id
UNION
SELECT u.nom, c.id AS commande_id
FROM users u
RIGHT JOIN commandes c ON u.id = c.user_id;

Jointures Complexes et Optimisation

-- EXEMPLE COMPLET : E-COMMERCE
-- Requête complexe avec plusieurs jointures

-- Structure complète
SELECT 
    -- Informations utilisateur
    u.nom AS client_nom,
    u.email AS client_email,
    
    -- Informations commande
    c.id AS commande_numero,
    c.date_commande,
    c.statut,
    
    -- Détails produits
    p.nom AS produit_nom,
    p.prix AS prix_unitaire,
    cd.quantite,
    (p.prix * cd.quantite) AS sous_total,
    
    -- Informations catégorie
    cat.nom AS categorie,
    
    -- Adresse livraison
    a.rue,
    a.ville,
    a.code_postal

FROM commandes c

-- Jointure utilisateur (obligatoire)
INNER JOIN users u ON c.user_id = u.id

-- Jointure détails commande
INNER JOIN commande_details cd ON c.id = cd.commande_id

-- Jointure produits
INNER JOIN produits p ON cd.produit_id = p.id

-- Jointure catégories (peut être NULL)
LEFT JOIN categories cat ON p.categorie_id = cat.id

-- Jointure adresse livraison
LEFT JOIN adresses a ON c.adresse_livraison_id = a.id

-- Filtres
WHERE c.date_commande >= '2024-01-01'
  AND c.statut IN ('confirmee', 'expediee', 'livree')
  AND u.actif = TRUE

-- Groupement et tri
ORDER BY c.date_commande DESC, c.id, cd.id;

-- REQUÊTES D'ANALYSE AVANCÉES

-- 1. Chiffre d'affaires par mois et catégorie
SELECT 
    DATE_FORMAT(c.date_commande, '%Y-%m') AS mois,
    cat.nom AS categorie,
    COUNT(DISTINCT c.id) AS nb_commandes,
    SUM(cd.quantite * p.prix) AS chiffre_affaires
FROM commandes c
INNER JOIN commande_details cd ON c.id = cd.commande_id
INNER JOIN produits p ON cd.produit_id = p.id
LEFT JOIN categories cat ON p.categorie_id = cat.id
WHERE c.statut = 'livree'
GROUP BY DATE_FORMAT(c.date_commande, '%Y-%m'), cat.id
ORDER BY mois DESC, chiffre_affaires DESC;

-- 2. Top 10 clients par valeur
SELECT 
    u.nom,
    u.email,
    COUNT(c.id) AS nb_commandes,
    SUM(
        SELECT SUM(cd.quantite * p.prix) 
        FROM commande_details cd 
        INNER JOIN produits p ON cd.produit_id = p.id 
        WHERE cd.commande_id = c.id
    ) AS montant_total
FROM users u
INNER JOIN commandes c ON u.id = c.user_id
WHERE c.statut = 'livree'
GROUP BY u.id
ORDER BY montant_total DESC
LIMIT 10;

-- 3. Produits jamais vendus
SELECT p.nom, p.prix, p.stock
FROM produits p
LEFT JOIN commande_details cd ON p.id = cd.produit_id
WHERE cd.produit_id IS NULL
ORDER BY p.date_creation DESC;

-- 4. Analyse des ventes par région
SELECT 
    a.ville,
    a.code_postal,
    COUNT(DISTINCT c.id) AS nb_commandes,
    COUNT(DISTINCT c.user_id) AS nb_clients_uniques,
    AVG(total_commande.montant) AS panier_moyen
FROM commandes c
INNER JOIN adresses a ON c.adresse_livraison_id = a.id
INNER JOIN (
    SELECT 
        c.id,
        SUM(cd.quantite * p.prix) AS montant
    FROM commandes c
    INNER JOIN commande_details cd ON c.id = cd.commande_id
    INNER JOIN produits p ON cd.produit_id = p.id
    GROUP BY c.id
) total_commande ON c.id = total_commande.id
WHERE c.statut = 'livree'
GROUP BY a.ville, a.code_postal
HAVING nb_commandes >= 5
ORDER BY nb_commandes DESC;

-- OPTIMISATIONS IMPORTANTES

-- 1. Index sur les colonnes de jointure
CREATE INDEX idx_commandes_user_id ON commandes(user_id);
CREATE INDEX idx_commandes_date ON commandes(date_commande);
CREATE INDEX idx_commande_details_commande_id ON commande_details(commande_id);
CREATE INDEX idx_commande_details_produit_id ON commande_details(produit_id);
CREATE INDEX idx_produits_categorie_id ON produits(categorie_id);

-- 2. Index composé pour requêtes fréquentes
CREATE INDEX idx_commandes_statut_date ON commandes(statut, date_commande);
CREATE INDEX idx_users_actif_email ON users(actif, email);

-- 3. Optimisation des requêtes avec EXPLAIN
EXPLAIN SELECT 
    u.nom, 
    COUNT(c.id) as nb_commandes
FROM users u 
LEFT JOIN commandes c ON u.id = c.user_id 
GROUP BY u.id;

-- JOINTURES AVEC CONDITIONS COMPLEXES

-- 1. Jointure avec conditions dans ON vs WHERE
SELECT u.nom, c.id
FROM users u
LEFT JOIN commandes c ON u.id = c.user_id 
    AND c.date_commande >= '2024-01-01'  -- Condition dans ON
WHERE u.actif = TRUE;                    -- Condition dans WHERE

-- 2. Self-join pour hiérarchies
SELECT 
    c1.nom AS categorie,
    c2.nom AS sous_categorie
FROM categories c1
INNER JOIN categories c2 ON c1.id = c2.parent_id;

-- 3. Jointure avec sous-requête
SELECT 
    u.nom,
    commandes_recentes.nb_commandes
FROM users u
INNER JOIN (
    SELECT 
        user_id,
        COUNT(*) as nb_commandes
    FROM commandes 
    WHERE date_commande >= DATE_SUB(NOW(), INTERVAL 30 DAY)
    GROUP BY user_id
) commandes_recentes ON u.id = commandes_recentes.user_id;

-- UNION et UNION ALL

-- 1. Combiner différentes sources
SELECT nom, 'client' as type FROM users
UNION ALL
SELECT nom, 'produit' as type FROM produits;

-- 2. Statistiques combinées
SELECT 
    'Commandes aujourd\'hui' as metric,
    COUNT(*) as value
FROM commandes 
WHERE DATE(date_commande) = CURDATE()

UNION ALL

SELECT 
    'Nouveaux clients cette semaine',
    COUNT(*)
FROM users 
WHERE date_creation >= DATE_SUB(NOW(), INTERVAL 7 DAY);

Fonctions et Agrégation

Exploitez la puissance des fonctions MySQL pour manipuler et analyser vos données.

Fonctions d'Agrégation

Fonctions de base :

-- Compter les enregistrements
SELECT
COUNT(*) AS total_users,
COUNT(email) AS users_avec_email,
COUNT(DISTINCT ville) AS nb_villes
FROM users;
-- Sommes et moyennes
SELECT
SUM(prix) AS total_inventaire,
AVG(prix) AS prix_moyen,
MIN(prix) AS prix_min,
MAX(prix) AS prix_max
FROM produits;

GROUP BY et HAVING :

-- Ventes par catégorie
SELECT
c.nom AS categorie,
COUNT(p.id) AS nb_produits,
AVG(p.prix) AS prix_moyen
FROM categories c
LEFT JOIN produits p ON c.id = p.categorie_id
GROUP BY c.id, c.nom
HAVING COUNT(p.id) > 5
ORDER BY nb_produits DESC;

Requêtes analytiques :

-- Évolution mensuelle des ventes
SELECT
DATE_FORMAT(date_commande, '%Y-%m') AS mois,
COUNT(DISTINCT id) AS nb_commandes,
COUNT(DISTINCT user_id) AS clients_uniques,
SUM(total) AS chiffre_affaires
FROM commandes
WHERE statut = 'livree'
GROUP BY DATE_FORMAT(date_commande, '%Y-%m')
ORDER BY mois DESC;
-- Calcul de percentiles
SELECT
MIN(prix) AS prix_min,
(
SELECT prix FROM produits
ORDER BY prix
LIMIT 1 OFFSET (COUNT(*) * 0.25)
) AS q1,
MAX(prix) AS prix_max
FROM produits;
Différence WHERE vs HAVING :
  • WHERE : Filtre avant groupement
  • HAVING : Filtre après groupement
  • WHERE : Peut pas utiliser fonctions d'agrégation
  • HAVING : Peut utiliser COUNT, SUM, etc.

Fonctions de Manipulation de Données

Fonctions de chaînes :

-- Manipulation de texte
SELECT
UPPER(nom) AS nom_majuscule,
LOWER(email) AS email_minuscule,
LENGTH(nom) AS longueur_nom,
TRIM(description) AS desc_propre
FROM users;
-- Recherche et extraction
SELECT
SUBSTRING(email, 1, LOCATE('@', email)-1) AS username,
SUBSTRING(email, LOCATE('@', email)+1) AS domain,
REPLACE(telephone, '-', ' ') AS tel_espace
FROM users;
-- Concaténation
SELECT CONCAT(prenom, ' ', nom) AS nom_complet
FROM users;

Fonctions mathématiques :

-- Calculs et arrondis
SELECT
prix,
ROUND(prix * 1.2, 2) AS prix_ttc,
CEIL(prix) AS prix_plafond,
FLOOR(prix) AS prix_plancher,
ABS(stock - 50) AS ecart_stock
FROM produits;
-- Fonctions avancées
SELECT
POWER(prix, 2) AS prix_carre,
SQRT(surface) AS cote,
MOD(id, 10) AS reste_division
FROM produits;

Fonctions de date :

-- Date actuelle
SELECT
NOW() AS maintenant,
CURDATE() AS aujourdhui,
CURTIME() AS heure_actuelle;
-- Extraction de parties
SELECT
YEAR(date_creation) AS annee,
MONTH(date_creation) AS mois,
DAY(date_creation) AS jour,
DAYNAME(date_creation) AS nom_jour
FROM commandes;
-- Calculs de dates
SELECT
DATE_ADD(NOW(), INTERVAL 30 DAY) AS dans_30_jours,
DATE_SUB(NOW(), INTERVAL 1 YEAR) AS il_y_a_un_an,
DATEDIFF(NOW(), date_creation) AS jours_ecoules;

Fonctions conditionnelles :

-- IF simple
SELECT
nom,
IF(stock > 0, 'En stock', 'Rupture') AS statut
FROM produits;
-- CASE WHEN complexe
SELECT
nom,
prix,
CASE
WHEN prix < 50 THEN 'Budget'
WHEN prix BETWEEN 50 AND 200 THEN 'Standard'
WHEN prix BETWEEN 200 AND 500 THEN 'Premium'
ELSE 'Luxe'
END AS gamme
FROM produits;
-- Gestion des valeurs NULL
SELECT
IFNULL(description, 'Aucune description') AS desc_clean,
COALESCE(telephone, email, 'Pas de contact') AS contact
FROM users;

Optimisation et Performance

Optimisez vos bases de données pour des performances maximales avec les index, l'analyse des requêtes et les bonnes pratiques.

Index et Clés

Création d'index :

-- Index simple sur une colonne
CREATE INDEX idx_users_email
ON users(email);
-- Index composé (plusieurs colonnes)
CREATE INDEX idx_commandes_user_date
ON commandes(user_id, date_commande);
-- Index unique
CREATE UNIQUE INDEX idx_users_email_unique
ON users(email);
-- Index partiel avec condition
CREATE INDEX idx_users_actifs
ON users(date_creation)
WHERE actif = TRUE;
-- Supprimer un index
DROP INDEX idx_users_email ON users;
📈 Règles d'optimisation :
  • • Index sur colonnes de WHERE, JOIN, ORDER BY
  • • Ordre important dans index composés
  • • Pas trop d'index (ralentit INSERT/UPDATE)
  • • Analyser les requêtes avec EXPLAIN

Types d'index MySQL :

B-Tree (défaut InnoDB) :
  • • Recherches par égalité et plage
  • • ORDER BY efficace
  • • Préfixes de chaînes
HASH (Memory) :
  • • Très rapide pour égalité exacte
  • • Pas de recherches par plage
  • • Idéal pour lookup tables
FULLTEXT :
  • • Recherche textuelle avancée
  • • MATCH...AGAINST
  • • Pertinence et ranking

Vérification des index :

-- Voir tous les index d'une table
SHOW INDEX FROM users;
-- Analyser l'utilisation des index
EXPLAIN SELECT * FROM users
WHERE email = 'test@example.com';
-- Statistiques des index
ANALYZE TABLE users;

Analyse et Optimisation des Requêtes

EXPLAIN - Analyse d'exécution :

-- Analyse basique
EXPLAIN SELECT * FROM users
WHERE email = 'test@test.com';
-- Analyse détaillée (JSON)
EXPLAIN FORMAT=JSON
SELECT u.nom, p.nom
FROM users u
JOIN commandes c ON u.id = c.user_id
JOIN produits p ON c.produit_id = p.id;
-- Analyse avec coût réel
EXPLAIN ANALYZE
SELECT COUNT(*) FROM commandes
WHERE date_commande >= '2024-01-01';
🔍 Colonnes EXPLAIN importantes :
type : const > eq_ref > ref > range > ALL
key : Index utilisé (NULL = pas d'index)
rows : Nombre de lignes examinées
Extra : Infos additionnelles importantes

Optimisations courantes :

✅ Requêtes optimisées :
-- Utiliser LIMIT pour pagination
SELECT * FROM produits
ORDER BY date_creation DESC
LIMIT 20 OFFSET 40;
-- Préférer EXISTS à IN avec sous-requête
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM commandes c
WHERE c.user_id = u.id
);
❌ À éviter :
-- SELECT * au lieu de colonnes spécifiques
SELECT * FROM users; -- Éviter
-- Fonctions dans WHERE (empêche index)
WHERE YEAR(date_creation) = 2024; -- Éviter
-- Préférer :
WHERE date_creation >= '2024-01-01'
AND date_creation < '2025-01-01';

Monitoring des performances :

-- Requêtes lentes activées
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
-- Statistiques des requêtes
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW PROCESSLIST;

Configuration et Maintenance

Variables importantes :

-- Configuration mémoire
SET GLOBAL innodb_buffer_pool_size = 1073741824; -- 1GB
SET GLOBAL key_buffer_size = 268435456; -- 256MB
-- Connections
SET GLOBAL max_connections = 200;
SET GLOBAL connect_timeout = 10;
-- Optimisations requêtes
SET GLOBAL query_cache_size = 67108864; -- 64MB
SET GLOBAL tmp_table_size = 134217728; -- 128MB
-- Voir la configuration actuelle
SHOW VARIABLES LIKE 'innodb%';
SHOW STATUS LIKE '%connection%';

Maintenance régulière :

-- Analyser et optimiser les tables
ANALYZE TABLE users, produits, commandes;
OPTIMIZE TABLE users;
-- Vérifier l'intégrité
CHECK TABLE users;
REPAIR TABLE users;
-- Nettoyage des logs
PURGE BINARY LOGS BEFORE '2024-01-01';
RESET QUERY CACHE;
-- Statistiques d'usage
SELECT
table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size_MB'
FROM information_schema.tables
GROUP BY table_schema;
📅 Planning de maintenance :
  • Quotidien : Vérifier les logs d'erreur
  • Hebdomadaire : ANALYZE TABLE sur tables actives
  • Mensuel : OPTIMIZE TABLE, nettoyage logs
  • Trimestriel : Audit complet des performances

Administration MySQL

Maîtrisez la gestion des utilisateurs, la sécurité, les sauvegardes et la surveillance de votre serveur MySQL.

Gestion des Utilisateurs et Privilèges

Création et gestion des utilisateurs :

-- Créer un utilisateur
CREATE USER 'dev_user'@'localhost'
IDENTIFIED BY 'password123!';
-- Utilisateur avec accès depuis n'importe où
CREATE USER 'app_user'@'%'
IDENTIFIED BY 'StrongPass2024!';
-- Modifier le mot de passe
ALTER USER 'dev_user'@'localhost'
IDENTIFIED BY 'nouveauMotDePasse!';
-- Supprimer un utilisateur
DROP USER 'old_user'@'localhost';
-- Lister les utilisateurs
SELECT User, Host FROM mysql.user;

Attribution des privilèges :

-- Tous les privilèges sur une base
GRANT ALL PRIVILEGES ON boutique_en_ligne.*
TO 'dev_user'@'localhost';
-- Privilèges spécifiques
GRANT SELECT, INSERT, UPDATE
ON boutique_en_ligne.produits
TO 'app_user'@'%';
-- Lecture seule
GRANT SELECT ON boutique_en_ligne.*
TO 'readonly_user'@'%';
-- Retirer des privilèges
REVOKE INSERT, UPDATE ON boutique_en_ligne.*
FROM 'app_user'@'%';
-- Appliquer les changements
FLUSH PRIVILEGES;
-- Voir les privilèges d'un user
SHOW GRANTS FOR 'dev_user'@'localhost';
🔐 Bonnes pratiques :
  • • Mots de passe forts (12+ caractères)
  • • Principe du moindre privilège
  • • Utilisateurs spécifiques par application
  • • Limiter l'accès réseau ('@localhost')
⚠️ Sécurité :
  • • Changer le mot de passe root
  • • Supprimer les comptes anonymes
  • • Désactiver l'accès root distant
  • • Renommer le compte root
📊 Privilèges principaux :
  • SELECT : Lecture des données
  • INSERT/UPDATE/DELETE : Modification
  • CREATE/DROP : Structure BD
  • GRANT OPTION : Donner privilèges

Sauvegarde et Restauration

mysqldump - Sauvegarde logique :

# Sauvegarde complète d'une base
mysqldump -u root -p boutique_en_ligne > backup.sql
# Sauvegarde avec structure seulement
mysqldump -u root -p --no-data boutique_en_ligne > structure.sql
# Sauvegarde des données seulement
mysqldump -u root -p --no-create-info boutique_en_ligne > data.sql
# Sauvegarde d'une table spécifique
mysqldump -u root -p boutique_en_ligne users > users_backup.sql
# Sauvegarde de toutes les bases
mysqldump -u root -p --all-databases > full_backup.sql
# Sauvegarde compressée
mysqldump -u root -p boutique_en_ligne | gzip > backup.sql.gz

Options importantes de mysqldump :

--single-transaction - Cohérence InnoDB
--routines - Inclut procédures/fonctions
--triggers - Inclut les triggers
--events - Inclut les événements

Restauration :

# Restaurer une base complète
mysql -u root -p boutique_en_ligne < backup.sql
# Créer la base puis restaurer
mysql -u root -p -e "CREATE DATABASE boutique_test;"
mysql -u root -p boutique_test < backup.sql
# Restaurer depuis fichier compressé
gunzip < backup.sql.gz | mysql -u root -p boutique_en_ligne
# Restaurer avec surveillance des erreurs
mysql -u root -p --force boutique_en_ligne < backup.sql

Script de sauvegarde automatique :

#!/bin/bash
# backup_mysql.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/mysql"
DB_NAME="boutique_en_ligne"
# Créer le dossier si nécessaire
mkdir -p $BACKUP_DIR
# Sauvegarde avec compression
mysqldump -u backup_user -p$MYSQL_PASS \
--single-transaction --routines --triggers \
$DB_NAME | gzip > \
$BACKUP_DIR/$DB_NAME_$DATE.sql.gz
# Supprimer les sauvegardes > 7 jours
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
⏰ Automatisation (cron) :
# Tous les jours à 2h00
0 2 * * * /usr/local/bin/backup_mysql.sh

Surveillance et Logs

Configuration des logs :

-- Activer les logs d'erreur
SET GLOBAL log_error = '/var/log/mysql/error.log';
-- Log des requêtes lentes
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 2;
-- Log général (attention aux performances !)
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = '/var/log/mysql/mysql.log';
-- Log binaire (réplication)
SHOW VARIABLES LIKE 'log_bin%';
SHOW MASTER STATUS;

Surveillance des performances :

-- Processus en cours
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;
-- Statistiques globales
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW GLOBAL STATUS LIKE 'Queries';
SHOW GLOBAL STATUS LIKE 'Uptime';
-- Usage mémoire
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_%';
-- Connexions
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Max_used_connections';

Surveillance système :

# Surveiller l'espace disque
df -h /var/lib/mysql
# Taille des bases de données
SELECT
table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size_MB'
FROM information_schema.tables
GROUP BY table_schema;
# Surveiller la charge CPU
top -p $(pgrep mysqld)
# Analyser les logs d'erreur
tail -f /var/log/mysql/error.log
grep -i error /var/log/mysql/error.log

Script de monitoring :

#!/bin/bash
# monitor_mysql.sh
echo "=== MySQL Status $(date) ==="
# Vérifier si MySQL fonctionne
mysqladmin -u root -p ping
# Connexions actives
CONNECTIONS=$(mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';" | tail -1 | awk '{print $2}')
echo "Connexions actives: $CONNECTIONS"
# Requêtes lentes
SLOW_QUERIES=$(mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Slow_queries';" | tail -1 | awk '{print $2}')
echo "Requêtes lentes: $SLOW_QUERIES"
# Alerte si > 100 connexions
if [[ $CONNECTIONS -gt 100 ]]; then
echo "ALERT: Too many connections!"
fi
📊 Métriques importantes :
  • Connexions : Actives vs Max
  • Requêtes : QPS, requêtes lentes
  • Mémoire : Buffer pool, cache
  • Disque : I/O, espace libre

Projet Pratique : E-Commerce

Mettez en pratique vos connaissances SQL en créant une base de données complète pour un site e-commerce avec toutes les fonctionnalités essentielles.

🏗️ Architecture de la Base de Données

Schéma conceptuel :

USERS
id, nom, email, password, date_creation
↓ 1:N
COMMANDES
id, user_id, date_commande, statut, total
↓ N:M
PRODUITS
id, nom, prix, stock, categorie_id

Tables principales :

  • users - Clients et administrateurs
  • categories - Classification des produits
  • produits - Catalogue des articles
  • commandes - En-tête des commandes
  • commande_details - Lignes de commande
  • adresses - Livraison et facturation

Création de la base :

-- Créer la base du projet
DROP DATABASE IF EXISTS ecommerce;
CREATE DATABASE ecommerce
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE ecommerce;
-- Configuration de session
SET foreign_key_checks = 1;
SET sql_mode = 'STRICT_TRANS_TABLES';

Règles de conception :

✅ Bonnes pratiques :
  • • Noms de tables au pluriel (users, produits)
  • • Clés primaires AUTO_INCREMENT
  • • Contraintes de clés étrangères
  • • Index sur colonnes de recherche
⚠️ Points d'attention :
  • • Mots de passe hashés (jamais en clair)
  • • Prix en DECIMAL, pas FLOAT
  • • Dates avec timezone
  • • Validation côté application ET base

Code SQL Complet du Projet

-- =====================================================
-- PROJET E-COMMERCE - BASE DE DONNÉES COMPLÈTE
-- DevWeb Academy - Guide SQL MySQL
-- =====================================================

-- 1. CRÉATION DE LA BASE ET CONFIGURATION
-- =====================================================

DROP DATABASE IF EXISTS ecommerce;
CREATE DATABASE ecommerce
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

USE ecommerce;

SET foreign_key_checks = 1;
SET sql_mode = 'STRICT_TRANS_TABLES';

-- 2. TABLE DES CATÉGORIES
-- =====================================================

CREATE TABLE categories (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nom VARCHAR(100) NOT NULL,
    description TEXT,
    slug VARCHAR(100) UNIQUE NOT NULL,
    parent_id INT NULL,
    actif BOOLEAN DEFAULT TRUE,
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL,
    INDEX idx_categories_slug (slug),
    INDEX idx_categories_parent (parent_id)
);

-- 3. TABLE DES UTILISATEURS
-- =====================================================

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nom VARCHAR(100) NOT NULL,
    prenom VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    mot_de_passe VARCHAR(255) NOT NULL, -- Hash bcrypt
    telephone VARCHAR(20),
    date_naissance DATE,
    genre ENUM('M', 'F', 'Autre') NULL,
    
    -- Statuts et permissions
    actif BOOLEAN DEFAULT TRUE,
    email_verifie BOOLEAN DEFAULT FALSE,
    role ENUM('client', 'admin', 'manager') DEFAULT 'client',
    
    -- Dates de suivi
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    derniere_connexion DATETIME NULL,
    
    -- Index pour optimisation
    INDEX idx_users_email (email),
    INDEX idx_users_actif (actif),
    INDEX idx_users_role (role)
);

-- 4. TABLE DES ADRESSES
-- =====================================================

CREATE TABLE adresses (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    type ENUM('livraison', 'facturation') DEFAULT 'livraison',
    nom VARCHAR(100) NOT NULL,
    prenom VARCHAR(100) NOT NULL,
    entreprise VARCHAR(100) NULL,
    rue VARCHAR(255) NOT NULL,
    ville VARCHAR(100) NOT NULL,
    code_postal VARCHAR(10) NOT NULL,
    pays VARCHAR(50) DEFAULT 'France',
    telephone VARCHAR(20),
    
    -- Adresse par défaut
    defaut BOOLEAN DEFAULT FALSE,
    
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_adresses_user_id (user_id),
    INDEX idx_adresses_type (type)
);

-- 5. TABLE DES PRODUITS
-- =====================================================

CREATE TABLE produits (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nom VARCHAR(200) NOT NULL,
    slug VARCHAR(200) UNIQUE NOT NULL,
    description_courte TEXT,
    description_longue LONGTEXT,
    
    -- Prix et stock
    prix DECIMAL(10,2) NOT NULL,
    prix_promo DECIMAL(10,2) NULL,
    stock INT DEFAULT 0,
    stock_min INT DEFAULT 5,
    
    -- Classification
    categorie_id INT NOT NULL,
    marque VARCHAR(100),
    reference VARCHAR(50) UNIQUE,
    code_barre VARCHAR(20) UNIQUE,
    
    -- Caractéristiques physiques
    poids DECIMAL(8,2), -- en grammes
    dimensions VARCHAR(50), -- L×l×h en cm
    couleur VARCHAR(50),
    
    -- SEO et images
    meta_title VARCHAR(160),
    meta_description VARCHAR(320),
    image_principale VARCHAR(255),
    
    -- Statuts
    actif BOOLEAN DEFAULT TRUE,
    en_vedette BOOLEAN DEFAULT FALSE,
    nouveau BOOLEAN DEFAULT TRUE,
    
    -- Dates
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (categorie_id) REFERENCES categories(id) ON DELETE RESTRICT,
    
    -- Index pour recherche et performance
    INDEX idx_produits_categorie (categorie_id),
    INDEX idx_produits_prix (prix),
    INDEX idx_produits_stock (stock),
    INDEX idx_produits_actif (actif),
    INDEX idx_produits_slug (slug),
    INDEX idx_produits_reference (reference),
    
    -- Index composés pour requêtes fréquentes
    INDEX idx_produits_actif_categorie (actif, categorie_id),
    INDEX idx_produits_actif_prix (actif, prix)
);

-- 6. TABLE DES IMAGES PRODUITS
-- =====================================================

CREATE TABLE produit_images (
    id INT PRIMARY KEY AUTO_INCREMENT,
    produit_id INT NOT NULL,
    nom_fichier VARCHAR(255) NOT NULL,
    alt_text VARCHAR(255),
    ordre INT DEFAULT 0,
    actif BOOLEAN DEFAULT TRUE,
    
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (produit_id) REFERENCES produits(id) ON DELETE CASCADE,
    INDEX idx_produit_images_produit (produit_id),
    INDEX idx_produit_images_ordre (produit_id, ordre)
);

-- 7. TABLE DES COMMANDES
-- =====================================================

CREATE TABLE commandes (
    id INT PRIMARY KEY AUTO_INCREMENT,
    numero_commande VARCHAR(20) UNIQUE NOT NULL,
    user_id INT NOT NULL,
    
    -- Statut de la commande
    statut ENUM('panier', 'confirmee', 'payee', 'preparee', 'expediee', 'livree', 'annulee') 
           DEFAULT 'panier',
    
    -- Adresses (copies pour historique)
    adresse_livraison_id INT,
    adresse_facturation_id INT,
    
    -- Montants
    sous_total DECIMAL(10,2) NOT NULL DEFAULT 0,
    frais_livraison DECIMAL(10,2) DEFAULT 0,
    taxe DECIMAL(10,2) DEFAULT 0,
    remise DECIMAL(10,2) DEFAULT 0,
    total DECIMAL(10,2) NOT NULL DEFAULT 0,
    
    -- Informations de livraison
    transporteur VARCHAR(100),
    numero_suivi VARCHAR(100),
    date_expedition DATETIME NULL,
    date_livraison_prevue DATE NULL,
    date_livraison_reelle DATETIME NULL,
    
    -- Paiement
    methode_paiement ENUM('carte', 'paypal', 'virement', 'cheque') NULL,
    transaction_id VARCHAR(100),
    date_paiement DATETIME NULL,
    
    -- Commentaires
    commentaire_client TEXT,
    commentaire_admin TEXT,
    
    -- Dates de suivi
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT,
    FOREIGN KEY (adresse_livraison_id) REFERENCES adresses(id) ON DELETE SET NULL,
    FOREIGN KEY (adresse_facturation_id) REFERENCES adresses(id) ON DELETE SET NULL,
    
    INDEX idx_commandes_user_id (user_id),
    INDEX idx_commandes_statut (statut),
    INDEX idx_commandes_numero (numero_commande),
    INDEX idx_commandes_date_creation (date_creation),
    
    -- Index composés pour rapports
    INDEX idx_commandes_statut_date (statut, date_creation),
    INDEX idx_commandes_user_statut (user_id, statut)
);

-- 8. TABLE DES DÉTAILS DE COMMANDES
-- =====================================================

CREATE TABLE commande_details (
    id INT PRIMARY KEY AUTO_INCREMENT,
    commande_id INT NOT NULL,
    produit_id INT NOT NULL,
    
    -- Informations du produit au moment de la commande
    nom_produit VARCHAR(200) NOT NULL,
    prix_unitaire DECIMAL(10,2) NOT NULL,
    quantite INT NOT NULL DEFAULT 1,
    
    -- Sous-total de la ligne
    sous_total DECIMAL(10,2) GENERATED ALWAYS AS (prix_unitaire * quantite) STORED,
    
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (commande_id) REFERENCES commandes(id) ON DELETE CASCADE,
    FOREIGN KEY (produit_id) REFERENCES produits(id) ON DELETE RESTRICT,
    
    INDEX idx_commande_details_commande (commande_id),
    INDEX idx_commande_details_produit (produit_id),
    
    -- Éviter les doublons
    UNIQUE KEY unique_commande_produit (commande_id, produit_id)
);

-- 9. TABLE DES AVIS CLIENTS
-- =====================================================

CREATE TABLE avis (
    id INT PRIMARY KEY AUTO_INCREMENT,
    produit_id INT NOT NULL,
    user_id INT NOT NULL,
    commande_id INT,
    
    note INT CHECK (note BETWEEN 1 AND 5),
    titre VARCHAR(200),
    commentaire TEXT,
    
    -- Modération
    approuve BOOLEAN DEFAULT FALSE,
    signale BOOLEAN DEFAULT FALSE,
    
    -- Utilité
    votes_utiles INT DEFAULT 0,
    votes_inutiles INT DEFAULT 0,
    
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (produit_id) REFERENCES produits(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (commande_id) REFERENCES commandes(id) ON DELETE SET NULL,
    
    INDEX idx_avis_produit (produit_id),
    INDEX idx_avis_user (user_id),
    INDEX idx_avis_note (note),
    INDEX idx_avis_approuve (approuve),
    
    -- Un seul avis par user/produit
    UNIQUE KEY unique_user_produit (user_id, produit_id)
);

-- 10. TABLE DES COUPONS DE RÉDUCTION
-- =====================================================

CREATE TABLE coupons (
    id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(50) UNIQUE NOT NULL,
    description VARCHAR(255),
    
    -- Type de réduction
    type ENUM('fixe', 'pourcentage') NOT NULL,
    valeur DECIMAL(10,2) NOT NULL,
    
    -- Conditions d'utilisation
    montant_minimum DECIMAL(10,2) DEFAULT 0,
    usage_max INT NULL, -- NULL = illimité
    usage_par_user INT DEFAULT 1,
    
    -- Validité
    date_debut DATE NOT NULL,
    date_fin DATE NOT NULL,
    actif BOOLEAN DEFAULT TRUE,
    
    -- Statistiques
    nb_utilisations INT DEFAULT 0,
    
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_coupons_code (code),
    INDEX idx_coupons_dates (date_debut, date_fin),
    INDEX idx_coupons_actif (actif)
);

-- 11. TABLE DES UTILISATIONS DE COUPONS
-- =====================================================

CREATE TABLE coupon_utilisations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    coupon_id INT NOT NULL,
    user_id INT NOT NULL,
    commande_id INT NOT NULL,
    montant_reduction DECIMAL(10,2) NOT NULL,
    
    date_utilisation DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (coupon_id) REFERENCES coupons(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (commande_id) REFERENCES commandes(id) ON DELETE CASCADE,
    
    INDEX idx_coupon_utilisations_coupon (coupon_id),
    INDEX idx_coupon_utilisations_user (user_id)
);

-- =====================================================
-- 12. DONNÉES DE TEST
-- =====================================================

-- Insertion des catégories
INSERT INTO categories (nom, description, slug) VALUES
('Électronique', 'Produits électroniques et high-tech', 'electronique'),
('Vêtements', 'Mode et vêtements', 'vetements'),
('Maison & Jardin', 'Articles pour la maison et le jardin', 'maison-jardin'),
('Sports & Loisirs', 'Équipements sportifs et loisirs', 'sports-loisirs'),
('Livres', 'Livres et publications', 'livres');

-- Insertion des utilisateurs de test
INSERT INTO users (nom, prenom, email, mot_de_passe, role) VALUES
('Admin', 'System', 'admin@ecommerce.com', '$2y$10$example_hash', 'admin'),
('Dupont', 'Jean', 'jean.dupont@email.com', '$2y$10$example_hash', 'client'),
('Martin', 'Marie', 'marie.martin@email.com', '$2y$10$example_hash', 'client'),
('Bernard', 'Pierre', 'pierre.bernard@email.com', '$2y$10$example_hash', 'client');

-- Insertion des adresses
INSERT INTO adresses (user_id, nom, prenom, rue, ville, code_postal, defaut) VALUES
(2, 'Dupont', 'Jean', '123 Rue de la Paix', 'Paris', '75001', TRUE),
(3, 'Martin', 'Marie', '456 Avenue des Champs', 'Lyon', '69001', TRUE),
(4, 'Bernard', 'Pierre', '789 Boulevard Victor Hugo', 'Marseille', '13001', TRUE);

-- Insertion des produits
INSERT INTO produits (nom, slug, description_courte, prix, stock, categorie_id, reference) VALUES
('iPhone 14 Pro', 'iphone-14-pro', 'Smartphone Apple dernière génération', 1099.00, 25, 1, 'APPLE-IP14P'),
('MacBook Air M2', 'macbook-air-m2', 'Ordinateur portable ultra-fin Apple', 1499.00, 15, 1, 'APPLE-MBA-M2'),
('Jean Slim Bleu', 'jean-slim-bleu', 'Jean coupe slim en denim bleu', 79.90, 50, 2, 'JEAN-SLIM-001'),
('Robe d\'été Florale', 'robe-ete-florale', 'Robe légère à motifs floraux', 59.90, 30, 2, 'ROBE-ETE-001'),
('Aspirateur Robot', 'aspirateur-robot', 'Robot aspirateur intelligent', 299.00, 20, 3, 'ROBOT-ASP-001');

-- Insertion des commandes de test
INSERT INTO commandes (numero_commande, user_id, statut, sous_total, frais_livraison, total, adresse_livraison_id) VALUES
('CMD-2024-001', 2, 'livree', 1178.90, 9.90, 1188.80, 1),
('CMD-2024-002', 3, 'expediee', 359.80, 7.90, 367.70, 2),
('CMD-2024-003', 4, 'confirmee', 79.90, 5.90, 85.80, 3);

-- Insertion des détails de commandes
INSERT INTO commande_details (commande_id, produit_id, nom_produit, prix_unitaire, quantite) VALUES
(1, 1, 'iPhone 14 Pro', 1099.00, 1),
(1, 3, 'Jean Slim Bleu', 79.90, 1),
(2, 5, 'Aspirateur Robot', 299.00, 1),
(2, 4, 'Robe d\'été Florale', 59.90, 1),
(3, 3, 'Jean Slim Bleu', 79.90, 1);

-- Insertion des avis
INSERT INTO avis (produit_id, user_id, note, titre, commentaire, approuve) VALUES
(1, 2, 5, 'Excellent produit !', 'iPhone de très bonne qualité, je recommande vivement.', TRUE),
(3, 3, 4, 'Bon rapport qualité-prix', 'Jean confortable et bien taillé.', TRUE),
(5, 4, 5, 'Révolutionnaire !', 'Cet aspirateur robot change la vie, très efficace.', TRUE);

-- Insertion des coupons
INSERT INTO coupons (code, description, type, valeur, montant_minimum, date_debut, date_fin) VALUES
('WELCOME10', 'Réduction de 10% pour les nouveaux clients', 'pourcentage', 10.00, 50.00, '2024-01-01', '2024-12-31'),
('NOEL2024', 'Réduction de 20€ pour Noël', 'fixe', 20.00, 100.00, '2024-12-01', '2024-12-31'),
('BLACKFRIDAY', 'Black Friday - 25% de réduction', 'pourcentage', 25.00, 0.00, '2024-11-29', '2024-11-29');

-- =====================================================
-- 13. REQUÊTES D'ANALYSE ET RAPPORTS
-- =====================================================

-- Chiffre d'affaires par mois
SELECT 
    DATE_FORMAT(date_creation, '%Y-%m') AS mois,
    COUNT(*) AS nb_commandes,
    SUM(total) AS chiffre_affaires
FROM commandes 
WHERE statut IN ('payee', 'expediee', 'livree')
GROUP BY DATE_FORMAT(date_creation, '%Y-%m')
ORDER BY mois DESC;

-- Top 10 des produits les plus vendus
SELECT 
    p.nom,
    p.prix,
    SUM(cd.quantite) AS total_vendu,
    SUM(cd.sous_total) AS ca_produit
FROM produits p
INNER JOIN commande_details cd ON p.id = cd.produit_id
INNER JOIN commandes c ON cd.commande_id = c.id
WHERE c.statut IN ('payee', 'expediee', 'livree')
GROUP BY p.id, p.nom, p.prix
ORDER BY total_vendu DESC
LIMIT 10;

-- Clients les plus actifs
SELECT 
    u.nom,
    u.prenom,
    u.email,
    COUNT(c.id) AS nb_commandes,
    SUM(c.total) AS montant_total,
    AVG(c.total) AS panier_moyen
FROM users u
INNER JOIN commandes c ON u.id = c.user_id
WHERE c.statut IN ('payee', 'expediee', 'livree')
GROUP BY u.id
ORDER BY montant_total DESC;

-- Analyse des stocks critiques
SELECT 
    p.nom,
    p.reference,
    p.stock,
    p.stock_min,
    c.nom AS categorie,
    CASE 
        WHEN p.stock = 0 THEN 'Rupture'
        WHEN p.stock <= p.stock_min THEN 'Stock faible'
        ELSE 'OK'
    END AS statut_stock
FROM produits p
INNER JOIN categories c ON p.categorie_id = c.id
WHERE p.actif = TRUE AND p.stock <= p.stock_min
ORDER BY p.stock ASC;

-- Performance des catégories
SELECT 
    c.nom AS categorie,
    COUNT(p.id) AS nb_produits,
    AVG(p.prix) AS prix_moyen,
    SUM(COALESCE(cd.quantite, 0)) AS total_ventes,
    SUM(COALESCE(cd.sous_total, 0)) AS ca_categorie
FROM categories c
LEFT JOIN produits p ON c.id = p.categorie_id
LEFT JOIN commande_details cd ON p.id = cd.produit_id
LEFT JOIN commandes cmd ON cd.commande_id = cmd.id 
    AND cmd.statut IN ('payee', 'expediee', 'livree')
GROUP BY c.id, c.nom
ORDER BY ca_categorie DESC;

-- =====================================================
-- 14. PROCÉDURES STOCKÉES UTILES
-- =====================================================

DELIMITER //

-- Procédure pour mettre à jour le total d'une commande
CREATE PROCEDURE UpdateCommandeTotal(IN commande_id_param INT)
BEGIN
    UPDATE commandes 
    SET sous_total = (
        SELECT COALESCE(SUM(sous_total), 0)
        FROM commande_details 
        WHERE commande_id = commande_id_param
    ),
    total = sous_total + frais_livraison + taxe - remise
    WHERE id = commande_id_param;
END//

-- Fonction pour calculer la note moyenne d'un produit
CREATE FUNCTION GetNoyenneProduit(produit_id_param INT) 
RETURNS DECIMAL(3,2)
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE moyenne DECIMAL(3,2) DEFAULT 0;
    
    SELECT AVG(note) INTO moyenne
    FROM avis 
    WHERE produit_id = produit_id_param AND approuve = TRUE;
    
    RETURN COALESCE(moyenne, 0);
END//

-- Trigger pour décrémenter le stock après une commande
CREATE TRIGGER after_commande_detail_insert 
AFTER INSERT ON commande_details
FOR EACH ROW
BEGIN
    UPDATE produits 
    SET stock = stock - NEW.quantite 
    WHERE id = NEW.produit_id;
END//

-- Trigger pour incrementer le stock si annulation
CREATE TRIGGER after_commande_detail_delete 
AFTER DELETE ON commande_details
FOR EACH ROW
BEGIN
    UPDATE produits 
    SET stock = stock + OLD.quantite 
    WHERE id = OLD.produit_id;
END//

DELIMITER ;

-- =====================================================
-- 15. VUES UTILES POUR L'APPLICATION
-- =====================================================

-- Vue des produits avec informations complètes
CREATE VIEW vue_produits_complet AS
SELECT 
    p.id,
    p.nom,
    p.slug,
    p.description_courte,
    p.prix,
    p.prix_promo,
    p.stock,
    p.image_principale,
    c.nom AS categorie,
    c.slug AS categorie_slug,
    GetNoyenneProduit(p.id) AS note_moyenne,
    COUNT(a.id) AS nb_avis,
    CASE 
        WHEN p.prix_promo IS NOT NULL THEN p.prix_promo 
        ELSE p.prix 
    END AS prix_final
FROM produits p
INNER JOIN categories c ON p.categorie_id = c.id
LEFT JOIN avis a ON p.id = a.produit_id AND a.approuve = TRUE
WHERE p.actif = TRUE
GROUP BY p.id;

-- Vue des commandes avec détails clients
CREATE VIEW vue_commandes_details AS
SELECT 
    c.id,
    c.numero_commande,
    c.statut,
    c.total,
    c.date_creation,
    u.nom AS client_nom,
    u.prenom AS client_prenom,
    u.email AS client_email,
    COUNT(cd.id) AS nb_articles,
    SUM(cd.quantite) AS total_quantite
FROM commandes c
INNER JOIN users u ON c.user_id = u.id
LEFT JOIN commande_details cd ON c.id = cd.commande_id
GROUP BY c.id;

-- =====================================================
-- FIN DU SCRIPT - BASE PRÊTE À L'EMPLOI !
-- =====================================================