Version en ligne

Tutoriel : Administrez vos bases de données avec MySQL

Table des matières

Administrez vos bases de données avec MySQL
Introduction
Concepts de base
Présentation succincte de MySQL...
... et de ses concurrents
Organisation d'une base de données
Installation de MySQL
Avant-propos
Installation du logiciel
Connexion à MySQL
Syntaxe SQL et premières commandes
Les types de données
Types numériques
Types alphanumériques
Types temporels
Création d'une base de données
Avant-propos : conseils et conventions
Création et suppression d'une base de données
Création de tables
Définition des colonnes
Introduction aux clés primaires
Les moteurs de tables
Syntaxe de CREATE TABLE
Suppression d'une table
Modification d'une table
Syntaxe de la requête
Ajout et suppression d'une colonne
Modification de colonne
Insertion de données
Syntaxe de INSERT
Syntaxe alternative de MySQL
Utilisation de fichiers externes
Remplissage de la base
Sélection de données
Syntaxe de SELECT
La clause WHERE
Tri des données
Éliminer les doublons
Restreindre les résultats
Élargir les possibilités de la clause WHERE
Recherche approximative
Recherche dans un intervalle
Set de critères
Suppression et modification de données
Sauvegarde d'une base de données
Suppression
Modification
Index
Qu'est-ce qu'un index ?
Les différents types d'index
Création et suppression des index
Recherches avec FULLTEXT
Clés primaires et étrangères
Clés primaires, le retour
Clés étrangères
Modification de notre base
Jointures
Principe des jointures et notion d'alias
Jointure interne
Jointure externe
Syntaxes alternatives
Exemples d'application et exercices
Sous-requêtes
Sous-requêtes dans le FROM
Sous-requêtes dans les conditions
Sous-requêtes corrélées
Jointures et sous-requêtes : modification de données
Insertion
Modification
Suppression
Union de plusieurs requêtes
Syntaxe
UNION ALL
LIMIT et ORDER BY
Options des clés étrangères
Option sur suppression des clés étrangères
Option sur modification des clés étrangères
Utilisation de ces options dans notre base
Violation de contrainte d'unicité
Ignorer les erreurs
Remplacer l'ancienne ligne
Modifier l'ancienne ligne
Rappels et introduction
Rappels et manipulation simple de nombres
Définition d'une fonction
Quelques fonctions générales
Fonctions scalaires
Manipulation de nombres
Manipulation de chaînes de caractères
Exemples d'application et exercices
Fonctions d'agrégation
Fonctions statistiques
Concaténation
Regroupement
Regroupement sur un critère
Regroupement sur plusieurs critères
Super-agrégats
Conditions sur les fonctions d'agrégation
Exercices sur les agrégats
Du simple…
…Vers le complexe
Obtenir la date/l'heure actuelle
Rappels
Date actuelle
Heure actuelle
Date et heure actuelles
Formater une donnée temporelle
Extraire une information précise
Formater une date facilement
Créer une date à partir d'une chaîne de caractères
Calculs sur les données temporelles
Différence entre deux dates/heures
Ajout et retrait d'un intervalle de temps
Divers
Exercices
Commençons par le format
Passons aux calculs
Et pour finir, mélangeons le tout
Transactions
Principe
Syntaxe et utilisation
Validation implicite et commandes non-annulables
ACID
Verrous
Principe
Syntaxe et utilisation : verrous de table
Syntaxe et utilisation : verrous de ligne
Niveaux d'isolation
Requêtes préparées
Variables utilisateur
Principe et syntaxe des requêtes préparées
Usage et utilité
Procédures stockées
Création et utilisation d'une procédure
Les paramètres d'une procédure stockée
Suppression d'une procédure
Avantages, inconvénients et usage des procédures stockées
Structurer ses instructions
Blocs d'instructions et variables locales
Structures conditionnelles
Boucles
Gestionnaires d'erreurs, curseurs et utilisation avancée
Gestion des erreurs
Curseurs
Utilisation avancée des blocs d'instructions
Triggers
Principe et usage
Création des triggers
Suppression des triggers
Exemples
Restrictions
Vues
Création d'une vue
Sélection des données d'une vue
Modification et suppression d'une vue
Utilité des vues
Algorithmes
Modification des données d'une vue
Tables temporaires
Principe, règles et comportement
Méthodes alternatives de création des tables
Utilité des tables temporaires
Vues matérialisées
Principe
Mise à jour des vues matérialisées
Gain de performance
Gestion des utilisateurs
Introduction
Création, modification et suppression des utilisateurs
Les privilèges - introduction
Ajout et révocation de privilèges
Privilèges particuliers
Options supplémentaires
Informations sur la base de données et les requêtes
Commandes de description
La base de données information_schema
Déroulement d'une requête de sélection
Configuration et options
Variables système
Modification des variables système avec SET
Options au démarrage du client mysql
Options au démarrage du serveur mysqld
Fichiers de configuration

Administrez vos bases de données avec MySQL

Vous avez de nombreuses données à traiter et vous voulez les organiser correctement, avec un outil adapté ?
Les bases de données ont été créées pour vous !

Ce tutoriel porte sur MySQL, qui est un Système de Gestion de Bases de Données Relationnelles (abrégé SGBDR). C'est-à-dire un logiciel qui permet de gérer des bases de données, et donc de gérer de grosses quantités d'informations. Il utilise pour cela le langage SQL.
Il s'agit d'un des SGBDR les plus connus et les plus utilisés (Wikipédia et Adobe utilisent par exemple MySQL). Et c'est certainement le SGBDR le plus utilisé à ce jour pour réaliser des sites web dynamiques. C'est d'ailleurs MySQL qui est présenté dans le tutoriel Concevez votre site web avec PHP et MySQL écrit par Mathieu Nebra, fondateur de ce site.

MySQL peut donc s'utiliser seul, mais est la plupart du temps combiné à un autre langage de programmation : PHP par exemple pour de nombreux sites web, mais aussi Java, Python, C++, et beaucoup, beaucoup d'autres.

MySQL avec l'interface PHPMyAdminMySQL avec l'interface PHPMyAdmin

MySQL avec une console windowsMySQL avec une console windows

Différentes façons d'utiliser MySQL

Quelques exemples d'applications

Vous gérez une boîte de location de matériel audiovisuel, et afin de toujours savoir où vous en êtes dans votre stock, vous voudriez un système informatique vous permettant de gérer les entrées et sorties de matériel, mais aussi éventuellement les données de vos clients. MySQL est une des solutions possibles pour gérer tout ça.

Vous voulez créer un site web dynamique en HTML/CSS/PHP avec un espace membre, un forum, un système de news ou même un simple livre d'or. Une base de données vous sera presque indispensable.

Vous créez un super logiciel en Java qui va vous permettre de gérer vos dépenses afin de ne plus jamais être à découvert, ou devoir vous affamer pendant trois semaines pour pouvoir payer le cadeau d'anniversaire du petit frère. Vous pouvez utiliser une base de données pour stocker les dépenses déjà effectuées, les dépenses à venir, les rentrées régulières, ...

Votre tantine éleveuse d'animaux voudrait un logiciel simple pour gérer ses bestioles, vous savez programmer en python et lui proposez vos services dans l'espoir d'avoir un top cadeau à Noël. Une base de données vous aidera à retenir que Poupouche le Caniche est né le 13 décembre 2007, que Sami le Persan a des poils blancs et que Igor la tortue est le dernier représentant d'une race super rare !

Points abordés dans ce tutoriel

La conception et l'utilisation de bases de données est un vaste sujet, il a fallu faire des choix sur les thèmes à aborder. Voici les compétences que ce tutoriel vise à vous faire acquérir :

Introduction

Concepts de base

Avant de pouvoir joyeusement jouer avec des données, il vous faut connaître quelques concepts de base.

À la fin de ce chapitre, vous devriez :

Concepts de base

Introduction Présentation succincte de MySQL...

Concepts de base

Base de données

Une base de données informatique est un ensemble de données qui ont été stockées sur un support informatique, et organisées et structurées de manière à pouvoir facilement consulter et modifier leur contenu.

Prenons l'exemple d'un site web avec un système de news et de membres. On va utiliser une base de données MySQL pour stocker toutes les données du site : les news (avec la date de publication, le titre, le contenu, éventuellement l'auteur,…) et les membres (leurs noms, leurs emails,…).
Tout ceci va constituer notre base de données pour le site. Mais il ne suffit pas que la base de données existe. Il faut aussi pouvoir la gérer, interagir avec cette base. Il faut pouvoir envoyer des message à MySQL (messages qu'on appellera "requêtes"), afin de pouvoir ajouter des news, modifier des membres, supprimer, et tout simplement afficher des éléments de la base.

Une base de données seule ne suffit donc pas, il est nécessaire d'avoir également :

SGBD

Un Système de Gestion de Base de Données (SGBD) est un logiciel (ou un ensemble de logiciels) permettant de manipuler les données d'une base de données. Manipuler, c'est-à-dire sélectionner et afficher des informations tirées de cette base, modifier des données, en ajouter ou en supprimer (ce groupe de quatre opérations étant souvent appelé "CRUD", pour Create, Read, Update, Delete).
MySQL est un système de gestion de bases de données.

Le paradigme client - serveur

La plupart des SGBD sont basés sur un modèle Client - Serveur. C'est-à-dire que la base de données se trouve sur un serveur qui ne sert qu'à ça, et pour interagir avec cette base de données, il faut utiliser un logiciel "client" qui va interroger le serveur et transmettre la réponse que le serveur lui aura donnée. Le serveur peut être installé sur une machine différente du client ; c'est souvent le cas lorsque les bases de données sont importantes. Ce n'est cependant pas obligatoire, ne sautez pas sur votre petit frère pour lui emprunter son ordinateur. Dans ce tutoriel, nous installerons les logiciels serveur et client sur un seul et même ordinateur.
Par conséquent, lorsque vous installez un SGBD basé sur ce modèle (c'est le cas de MySQL), vous installez en réalité deux choses (au moins) : le serveur, et le client. Chaque requête (insertion/modification/lecture de données) est faite par l'intermédiaire du client. Jamais vous ne discuterez directement avec le serveur (d'ailleurs, il ne comprendrait rien à ce que vous diriez).
Vous avez donc besoin d'un langage pour discuter avec le client, pour lui donner les requêtes que vous souhaitez effectuer. Dans le cas de MySQL, ce langage est le SQL.

SGBDR

Le R de SGBDR signifie "relationnel". Un SGBDR est un SGBD qui implémente la théorie relationnelle. MySQL implémente la théorie relationnelle ; c'est donc un SGBDR.

La théorie relationnelle dépasse le cadre de ce tutoriel, mais ne vous inquiétez pas, il n'est pas nécessaire de la maîtriser pour être capable d'utiliser convenablement un SGBDR. Il vous suffit de savoir que dans un SGBDR, les données sont contenues dans ce qu'on appelle des relations, qui sont représentées sous forme de tables. Une relation est composée de deux parties, l'en-tête et le corps. L'en-tête est lui-même composé de plusieurs attributs. Par exemple, pour la relation "Client", on peut avoir l'en-tête suivant :

Numéro

Nom

Prénom

Email

Quant au corps, il s'agit d'un ensemble de lignes (ou n-uplets) composées d'autant d'éléments qu'il y a d'attributs dans le corps. Voici donc quatre lignes pour la relation "Client" :

Numéro

Nom

Prénom

Email

1

Jean

Dupont

[email protected]

2

Marie

Malherbe

[email protected]

3

Nicolas

Jacques

[email protected]

4

Hadrien

Piroux

[email protected]

Différentes opérations peuvent alors être appliquées à ces relations, ce qui permet d'en tirer des informations. Parmi les opérations les plus utilisées, on peut citer (soient A et B deux relations) :

Un petit exemple pour illustrer la jointure : si l'on veut stocker des informations sur les clients d'une société, ainsi que les commandes passées par ces clients, on utilisera deux relations : client et commande, la relation commande étant liée à la relation client par une référence au client ayant passé commande.
Un petit schéma clarifiera tout ça !

Schéma bdd relationnelle

Le client numéro 3, M. Nicolas Jacques, a donc passé une commande de trois tubes de colle, tandis que Mme Marie Malherbe (cliente numéro 2) a passé deux commandes, pour du papier et des ciseaux.

Le langage SQL

Le SQL (Structured Query Language) est un langage informatique qui permet d'interagir avec des bases de données relationnelles. C'est le langage pour base de données le plus répandu, et c'est bien sûr celui utilisé par MySQL. C'est donc le langage que nous allons utiliser pour dire au client MySQL d'effectuer des opérations sur la base de données stockée sur le serveur MySQL

Il a été créé dans les années 1970 et c'est devenu standard en 1986 (pour la norme ANSI - 1987 en ce qui concerne la norme ISO). Il est encore régulièrement amélioré.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Introduction Présentation succincte de MySQL...

Présentation succincte de MySQL...

Concepts de base ... et de ses concurrents

Présentation succincte de MySQL...

Image utilisateurMySQL est donc un Système de Gestion de Bases de Données Relationnelles, qui utilise le langage SQL. C'est un des SGBDR les plus utilisés. Sa popularité est due en grande partie au fait qu'il s'agit d'un logiciel Open Source, ce qui signifie que son code source est librement disponible et que quiconque qui en ressent l'envie et/ou le besoin peut modifier MySQL pour l'améliorer ou l'adapter à ses besoins. Une version gratuite de MySQL est par conséquent disponible. À noter qu'une version commerciale payante existe également.

Le logo de MySQL est un dauphin, nommé Sakila suite au concours Name the dolphin ("Nommez le dauphin").

Un peu d'histoire

Image utilisateurDavid Axmark, fondateur de MySQLLe développement de MySQL commence en 1994 par David Axmark et Michael Widenius. EN 1995, la société MySQL AB est fondée par ces deux développeurs, et Allan Larsson. C'est la même année que sort la première version officielle de MySQL.
En 2008, MySQL AB est rachetée par la société Sun Microsystems, qui est elle-même rachetée par Oracle Corporation en 2010.
On craint alors la fin de la gratuité de MySQL, étant donné qu'Oracle Corporation édite un des grands concurrents de MySQL : Oracle Database, qui est payant (et très cher). Oracle a cependant promis de continuer à développer MySQL et de conserver la double licence GPL (libre) et commerciale jusqu'en 2015 au moins.

Mise en garde

MySQL est très utilisé, surtout par les débutants. Vous pourrez faire de nombreuses choses avec ce logiciel, et il convient tout à fait pour découvrir la gestion de bases de données. Sachez cependant que MySQL est loin d'être parfait. En effet, il ne suit pas toujours la norme officielle. Certaines syntaxes peuvent donc être propres à MySQL et ne pas fonctionner sous d'autres SGBDR. J'essayerai de le signaler lorsque le cas se présentera, mais soyez conscients de ce problème.
Par ailleurs, il n'implémente pas certaines fonctionnalités avancées, qui pourraient vous être utiles pour un projet un tant soit peu ambitieux. Enfin, il est très permissif, et acceptera donc des requêtes qui généreraient une erreur sous d'autres SGBDR.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Concepts de base ... et de ses concurrents

... et de ses concurrents

Présentation succincte de MySQL... Organisation d'une base de données

... et de ses concurrents

Il existe des dizaines de SGBDR, chacun ayant ses avantages et ses inconvénients. Je présente ici succinctement quatre d'entre eux, parmi les plus connus. Je m'excuse tout de suite auprès des fans (et même simples utilisateurs) des nombreux SGBDR que j'ai omis.

Oracle database

Logo d'OracleOracle, édité par Oracle Corporation (qui, je rappelle, édite également MySQL) est un SGBDR payant. Son coût élevé fait qu'il est principalement utilisé par des entreprises.
Oracle gère très bien de grands volumes de données. Il est inutile d'acheter une licence oracle pour un projet de petite taille, car les performances ne seront pas bien différentes de celles de MySQL ou d'un autre SGBDR. Par contre, pour des projets conséquents (plusieurs centaines de Go de données), Oracle sera bien plus performant.
Par ailleurs, Oracle dispose d'un langage procédural très puissant (du moins plus puissant que le langage procédural de MySQL) : le PL/SQL.

PostgreSQL

Logo PostgreSQLComme MySQL, PostgreSQL est un logiciel Open Source. Il est cependant moins utilisé, notamment par les débutants, car moins connu. La raison de cette méconnaissance réside sans doute en partie dans le fait que PostgreSQL a longtemps été disponible uniquement sous Unix. La première version Windows n'est apparue qu'à la sortie de la version 8.0 du logiciel, en 2005.
PostgreSQL a longtemps été plus performant que MySQL, mais ces différences tendent à diminuer. MySQL semble être aujourd'hui équivalent à PostgreSQL en terme de performances sauf pour quelques opérations telles que l'insertion de données et la création d'index.
Le langage procédural utilisé par PostgreSQL s'appelle le PL/pgSQL.

MS Access

Logo MS Access

MS Access ou Microsoft Access est un logiciel édité par Microsoft (comme son nom l'indique…) Par conséquent, c'est un logiciel payant qui ne fonctionne que sous Windows. Il n'est pas du tout adapté pour gérer un grand volume de données et a beaucoup moins de fonctionnalités que les autres SGBDR. Son avantage principal est l'interface graphique intuitive qui vient avec le logiciel.

SQLite

Logo SQLiteLa particularité de SQLite est de ne pas utiliser le schéma client-serveur utilisé par la majorité des SGBDR. SQLite stocke toutes les données dans de simples fichiers. Par conséquent, il ne faut pas installer de serveur de base de données, ce qui n'est pas toujours possible (certains hébergeurs web ne le permettent pas).
Pour de très petits volumes de données, SQLite est très performant. Cependant, le fait que les informations soient simplement stockées dans des fichiers rend le système difficile à sécuriser (autant au niveau des accès, qu'au niveau de la gestion de plusieurs utilisateurs utilisant la base simultanément).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Présentation succincte de MySQL... Organisation d'une base de données

Organisation d'une base de données

... et de ses concurrents Installation de MySQL

Organisation d'une base de données

Bon, vous savez qu'une base de données sert à gérer les données. Très bien. Mais comment ?? Facile ! Comment organisez-vous vos données dans la "vie réelle" ?? Vos papiers par exemple ? Chacun son organisation bien sûr, mais je suppose que vous les classez d'une manière ou d'une autre.

Toutes les factures ensemble, tous les contrats ensemble, etc. Ensuite on subdivise : les factures d’électricité, les factures pour la voiture. Ou bien dans l'autre sens : tous les papiers concernant la voiture ensemble, puis subdivision en taxes, communication avec l'assureur, avec le garagiste, ...

Une base de données, c'est pareil ! On classe les informations. MySQL étant un SGBDR, je ne parlerai que de l'organisation des bases de données relationnelles.

Comme je vous l'ai dit précédemment, on représente les données sous forme de tables. Une base va donc contenir plusieurs tables (elle peut n'en contenir qu'une bien sûr, mais c'est rarement le cas). Si je reprends mon exemple précédent, on a donc une table représentant des clients (donc des personnes).
Chaque table définit un certain nombre de colonnes, qui sont les caractéristiques de l'objet représenté par la table (les attributs de l'en-tête dans la théorie relationnelle). On a donc ici une colonne "Nom", une colonne "Prénom", une colonne "Email" et une colonne "Numéro" qui nous permettent d'identifier les clients individuellement (les noms et prénoms ne suffisent pas toujours).

Numéro

Nom

Prénom

Email

1

Jean

Dupont

[email protected]

2

Marie

Malherbe

[email protected]

3

Nicolas

Jacques

[email protected]

4

Hadrien

Piroux

[email protected]

Si je récapitule, dans une base nous avons donc des tables, et dans ces tables, on a des colonnes. Dans ces tables, vous introduisez vos données. Chaque donnée introduite le sera sous forme de ligne dans une table, définissant la valeur de chaque colonne pour cette donnée.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

... et de ses concurrents Installation de MySQL

Installation de MySQL

Organisation d'une base de données Avant-propos

Maintenant qu'on sait à peu près de quoi on parle, il est temps d'installer MySQL sur l'ordinateur, et de commencer à l'utiliser.
Au programme de ce chapitre :

Avant-propos

Installation de MySQL Installation du logiciel

Avant-propos

Il existe plusieurs manières d'utiliser MySQL. La première, que je vais utiliser tout au long du tutoriel, est l'utilisation en ligne de commande.

Ligne de commande

Mais qu'est-ce donc ? o_O

Eh bien il s'agit d'une fenêtre toute simple, dans laquelle toutes les instructions sont tapées à la main. Pas de bouton, pas de zone de saisie. Juste votre clavier.

Les utilisateurs de Linux connaissent très certainement. Pour Mac, il faut utiliser l'application "Terminal" que vous trouverez dans Applications > Utilitaires. Quant aux utilisateurs de Windows, c'est le "Command Prompt" que vous devez trouver (Démarrer > Tous les programmes > Accessoires).

Commande prompt (Windows)
Interface graphique

Si l'on ne veut pas utiliser la ligne de commande (il faut bien avouer que ce n'est pas très sympathique cette fenêtre monochrome), on peut utiliser une interface graphique, qui permet d'exécuter pas mal de choses simples de manière intuitive sur une base de données.

Comme interface graphique pour MySQL, on peut citer MySQL Workbench, PhpMyAdmin (souvent utilisé pour créer un site web en combinant MySQL et PHP) ou MySQL Front par exemple.

Pourquoi utiliser la ligne de commande ?

C'est vrai ça, pourquoi ? Si c'est plus simple et plus convivial avec une interface graphique ? :euh:

Deux raisons :

Bien sûr, si vous voulez utiliser une interface graphique, je ne peux guère vous en empêcher. Mais je vous encourage vivement à commencer par utiliser la ligne de commande, ou au minimum à faire l'effort de décortiquer les requêtes que vous laisserez l'interface graphique construire pour vous. Ceci afin de pouvoir les écrire vous-mêmes le jour où vous en aurez besoin (ce jour viendra, je vous le prédis).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Installation de MySQL Installation du logiciel

Installation du logiciel

Avant-propos Connexion à MySQL

Installation du logiciel

Pour télécharger MySQL, vous pouvez vous rendre sur le site suivant :

http://dev.mysql.com/downloads/mysql/#downloads

Sélectionnez l'OS sur lequel vous travaillez (Windows, Mac OS ou Linux).

Windows

Téléchargez MySQL avec l'installeur (MSI Installer), puis exécutez le fichier téléchargé. L'installeur démarre et vous guide lors de l'installation.
Lorsqu'il vous demande de choisir entre trois types d'installation, choisissez "Typical". Cela installera tout ce dont nous pourrions avoir besoin.

Choix du type d'installation

L'installation se lance. Une fois qu'elle est terminée, cliquez sur "Terminer" après vous être assurés que la case "lancer l'outil de configuration MySQL" est cochée.

Lancement outil de configuration

Dans cet outil de configuration, choisissez la configuration standard, et à l'étape suivante, cochez l'option "Include Bin Directory in Windows PATH"

Options configurations

On vous propose alors de définir un nouveau mot de passe pour l'utilisateur "root". Choisissez un mot de passe et confirmez-le. Ne cochez aucune autre option à cette étape. Cliquez ensuite sur "Execute" pour lancer la configuration.

Mac OS

Téléchargez l'archive DMG qui vous convient (32 ou 64 bits), double-cliquez ensuite sur ce .dmg pour ouvrir l'image disque.
Vous devriez y trouver 4 fichiers dont deux .pkg. Celui qui nous intéresse s'appelle mysql-5.5.9-osx10.6-x86_64.pkg (les chiffres peuvent changer selon la version de MySQL téléchargée et votre ordinateur). Ouvrez ce fichier qui est en fait l'installateur de MySQL, et suivez les instructions.

Une fois le programme installé, vous pouvez ouvrir votre terminal (pour rappel, il se trouve dans Applications -> Utilitaires).

Tapez les commandes et exécutez les instructions suivantes :

cd /usr/local/mysql
sudo ./bin/mysqld_safe
bg

MySQL est prêt à être utilisé !

Configuration

Par défaut, aucun mot de passe n'est demandé pour se connecter, même avec l'utilisateur root (qui a tous les droits). Je vous propose donc de définir un mot de passe pour cet utilisateur :

/usr/local/mysql/bin/mysqladmin -u root password <votre_mot_de_passe>

Ensuite, pour pouvoir accéder directement au logiciel client depuis la console, sans devoir aller dans le dossier où est installé le client, il vous faut ajouter ce dossier à votre variable d'environnement PATH. Pour cela, tapez la commande suivante dans le terminal :

echo 'export PATH=/usr/local/mysql/bin:$PATH' >> ~/.profile
/usr/local/mysql/bin

est donc le dossier dans lequel se trouve le logiciel client (plusieurs logiciels clients en fait). Redémarrez votre terminal pour que le changement prenne effet.

Linux
Sous Debian ou Ubuntu

Exécuter la commande suivante pour installer MySQL :

sudo apt-get install mysql-server mysql-client

Une fois votre mot de passe introduit, MySQL va être installé.

Sous RedHat

Exécuter la commande suivante pour installer MySQL :

sudo yum install mysql mysql-server

Une fois votre mot de passe introduit, MySQL va être installé.

Dans tous les cas, après installation

Pensez ensuite à modifier le mot de passe de l'utilisateur root (administrateur ayant tous les droits) avec la commande suivante :

sudo mysqladmin -u root -h localhost password '<votre mot de passe>'
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Avant-propos Connexion à MySQL

Connexion à MySQL

Installation du logiciel Syntaxe SQL et premières commandes

Connexion à MySQL

Je vous ai dit que MySQL était basé sur un modèle client - serveur, comme la plupart des SGBD. Cela implique donc que votre base de données se trouve sur un serveur auquel vous n'avez pas accès directement, il faut passer par un client qui fera la liaison entre vous et le serveur.
Lorsque vous installez MySQL, plusieurs choses sont donc installées sur votre ordinateur :

Connexion au client

Parmi ces clients, celui dont nous allons parler à présent est mysql (original comme nom o_O ). C'est celui que vous utiliserez tout au long de ce cours pour vous connecter à votre base de données et y insérer, consulter et modifier des données. La commande pour lancer le client est tout simplement son nom :

mysql

Cependant cela ne suffit pas. Il vous faut également préciser un certain nombre de paramètres. Le client mysql a besoin d'au minimum trois paramètres :

L'hôte et l'utilisateur ont des valeurs par défaut, et ne sont donc pas toujours indispensables. La valeur par défaut de l'hôte est "localhost", ce qui signifie que le serveur est sur le même ordinateur que le client. C'est bien notre cas, donc nous n'aurons pas à préciser ce paramètre. Pour le nom d'utilisateur, la valeur par défaut dépend de votre système. Sous Windows, l'utilisateur courant est "ODBC", tandis que pour les systèmes Unix (Mac et Linux), il s'agit de votre nom d'utilisateur (le nom qui apparaît dans l'invite de commande).
Pour votre première connexion à MySQL, il faudra vous connecter avec l'utilisateur "root", pour lequel vous avez normalement défini un mot de passe (si vous ne l'avez pas fait, inutile d'utiliser ce paramètre, mais ce n'est pas très sécurisé). Par la suite, nous créerons un nouvel utilisateur.
Pour chacun des trois paramètres, deux syntaxes sont possibles :

########
# Hôte #
########

--hote=nom_hote
 
# ou 

-h nom_hote

########
# User #
########

--user=nom_utilisateur

# ou 

-u nom_utilisateur

################
# Mot de passe #
################

--password=password

# ou 

-ppassword

Remarquez l'absence d'espace entre -p et le mot de passe. C'est voulu (mais uniquement pour ce paramètre-là), et souvent source d'erreurs.
La commande complète pour se connecter est donc :

mysql -h localhost -u root -pmotdepassetopsecret

# ou

mysql --host=localhost --user=root --password=motdepassetopsecret

# ou un mélange des paramètres courts et longs si ça vous amuse

mysql -h localhost --user=root -pmotdepassetopsecret

J'utiliserai uniquement les paramètres courts à partir de maintenant. Choisissez ce qui vous convient le mieux.
Notez que pour le mot de passe, il est possible (et c'est même très conseillé) de préciser uniquement que vous utilisez le paramètre, sans lui donner de valeur :

mysql -h localhost -u root -p

Apparaissent alors dans la console les mots suivants :

Enter password:

Tapez donc votre mot de passe, et là, vous pouvez constater que les lettres que vous tapez ne s'affichent pas. C'est normal, cessez donc de martyriser votre clavier, il n'y peut rien le pauvre :colere: . Cela permet simplement de cacher votre mot de passe à d'éventuels curieux qui regarderaient par-dessus votre épaule.

Donc pour résumer, pour me connecter à mysql, je tape la commande suivante :

mysql -u root -p

J'ai omis l'hôte, puisque mon serveur est sur mon ordinateur. Je n'ai plus qu'à taper mon mot de passe et je suis connecté.

Déconnexion

Pour se déconnecter du client, il suffit d'utiliser la commande quit ou exit.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Installation du logiciel Syntaxe SQL et premières commandes

Syntaxe SQL et premières commandes

Connexion à MySQL Les types de données

Syntaxe SQL et premières commandes

Maintenant que vous savez vous connecter, vous allez enfin pouvoir discuter avec le serveur MySQL (en langage SQL évidemment). Donc, reconnectez-vous si vous êtes déconnectés.

Vous pouvez constater que vous êtes connectés grâce au joli (quoiqu'un peu formel) message de bienvenue, ainsi qu'au changement de l'invite de commande. On voit maintenant mysql>.

"Hello World !"

Traditionnellement, lorsque l'on apprend un langage informatique, la première chose que l'on fait, c'est afficher le célèbre message "Hello World !". Pour ne pas déroger à la règle, je vous propose de taper la commande suivante (sans oublier le ; à la fin) :

SELECT 'Hello World !';
SELECT

est la commande qui permet la sélection de données, mais aussi l'affichage. Vous devriez donc voir s'afficher "Hello World !"

Hello World !

Hello World !

Comme vous le voyez, "Hello World !" s'affiche en réalité deux fois. C'est parce que MySQL représente les données sous forme de table. Il affiche donc une table avec une colonne, qu'il appelle "Hello World !" faute de meilleure information. Et dans cette table nous avons une ligne de données, le "Hello World !" que nous avons demandé.

Syntaxe

Avant d'aller plus loin, voici quelques règles générales à retenir concernant le SQL qui, comme tout langage informatique, obéit à des règles syntaxiques très strictes.

Fin d'une instruction

Pour signifier à MySQL qu'une instruction est terminée, il faut mettre le caractère ;. Tant qu'il ne rencontre pas ce caractère, le client MySQL pense que vous n'avez pas fini d'écrire votre commande et attend gentiment que vous continuiez.

Par exemple, la commande suivante devrait afficher 100. Mais tant que MySQL ne recevra pas de ;, il attendra simplement la suite.

SELECT 100

En appuyant sur la touche Entrée vous passez à la ligne suivante, mais la commande ne s'effectue pas. Remarquez au passage le changement dans l'invite de commande. mysql> signifie que vous allez entrer une commande, tandis que -> signifie que vous allez entrer la suite d'une commande commencée précédemment.

Tapez maintenant ; puis appuyer sur Entrée. Ca y est, la commande est envoyée, l'affichage se fait !

Ce caractère de fin d'instruction obligatoire va vous permettre :

Commentaires

Les commentaires sont des parties de code qui ne sont pas interprétées. Ils servent principalement à vous repérer dans votre code. En SQL, les commentaires sont introduits par -- (deux tirets). Cependant, MySQL déroge un peu à la règle SQL et accepte deux syntaxes :

Afin de suivre au maximum la norme SQL, ce sont les -- qui seront utilisés tout au long de ce tutoriel.

Chaînes de caractères

Lorsque vous écrivez une chaîne de caractères dans une commande SQL, il faut absolument l'entourer de guillemets simples (donc des apostrophes).

Exemple : la commande suivante sert à afficher "Bonjour petit Zéro !"

SELECT 'Bonjour petit Zéro !';

Par ailleurs, si vous désirez utiliser un caractère spécial dans une chaîne, il vous faudra l'échapper avec \. Par exemple, si vous entourez votre chaîne de caractères de guillemets simples mais voulez utiliser un tel guillemet à l'intérieur de votre chaîne :

SELECT 'Salut l'ami';  -- Pas bien !
SELECT 'Salut l\'ami'; -- Bien !

Quelques autres caractères spéciaux :

retour à la ligne

tabulation

\

antislash (eh oui, il faut échapper le caractère d'échappement…)

%

pourcent (vous verrez pourquoi plus tard)

_

souligné (vous verrez pourquoi plus tard aussi)

Notez que pour échapper un guillemet simple (et uniquement ce caractère), vous pouvez également l'écrire deux fois. Cette façon d'échapper les guillemets correspond d'ailleurs à la norme SQL. Je vous encourage par conséquent à essayer de l'utiliser au maximum.

SELECT 'Salut l'ami';  -- ne fonctionne pas !
SELECT 'Salut l\'ami'; -- fonctionne !
SELECT 'Salut l''ami'; -- fonctionne aussi et correspond à la norme !
Un peu de math

MySQL est également doué en calcul :

SELECT (5+3)*2;

Pas de guillemets cette fois puisqu'il s'agit de nombres. MySQL calcule pour nous et nous affiche :

(5+3)*2

16

MySQL est sensible à la priorité des opérations, comme vous pourrez le constater en tapant cette commande :

SELECT (5+3)*2, 5+3*2;

Résultat :

(5+3)*2

5+3*2

16

11

Utilisateur

Il n'est pas très conseillé de travailler en tant que "root" dans MySQL, à moins d'en avoir spécifiquement besoin. En effet, "root" a tous les droits. Ce qui signifie que vous pouvez faire n'importe quelle bêtise dans n'importe quelle base de données pendant que j'ai le dos tourné. Pour éviter ça, nous allons créer un nouvel utilisateur, qui aura des droits très restreints. Je l’appellerai "sdz", mais libre à vous de lui donner le nom que vous préférez. Pour ceux qui sont sous Unix, notez que si vous créez un utilisateur du même nom que votre utilisateur Unix, vous pourrez dès lors omettre ce paramètre lors de votre connexion à mysql.

Je vous demande ici de me suivre aveuglément, car je ne vous donnerai que très peu d'explications. En effet, la gestion des droits et des utilisateurs fera l'objet d'un chapitre entier dans une prochaine partie du cours. Tapez donc cette commande dans mysql, en remplaçant sdz par le nom d'utilisateur que vous avez choisi, et mot_de_passe par le mot de passe que vous voulez lui attribuer :

GRANT ALL PRIVILEGES ON elevage.* TO 'sdz'@'localhost' IDENTIFIED BY 'mot_de_passe';

Je décortique donc rapidement :

Pour vous connecter à mysql avec ce nouvel utilisateur, il faut donc taper la commande suivante (après s'être déconnecté bien sûr) :

mysql -u sdz -p
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Connexion à MySQL Les types de données

Les types de données

Syntaxe SQL et premières commandes Types numériques

Nous avons vu dans l'introduction qu'une base de données contenait des tables qui, elles-mêmes sont organisées en colonnes, dans lesquelles sont stockées des données.
En SQL (et dans la plupart des langages informatiques), les données sont séparées en plusieurs types (par exemple : texte, nombre entier, date…). Lorsque l'on définit une colonne dans une table de la base, il faut donc lui donner un type, et toutes les données stockées dans cette colonne devront correspondre au type de la colonne. Nous allons donc voir les différents types de données existant dans MySQL.

Types numériques

Les types de données Types alphanumériques

Types numériques

On peut subdiviser les types numériques en deux sous-catégories : les nombres entiers, et les nombres décimaux.

Nombres entiers

Les types de données qui acceptent des nombres entiers comme valeur sont désignés par le mot-clé INT, et ses déclinaisons TINYINT, SMALLINT, MEDIUMINT et BIGINT. La différence entre ces types est le nombre d'octets (donc la place en mémoire) réservés à la valeur du champ. Voici un tableau reprenant ces informations, ainsi que l'intervalle dans lequel la valeur peut être comprise pour chaque type.

Type

Nombre d'octets

Minimum

Maximum

TINYINT

1

-128

127

SMALLINT

2

-32768

32767

MEDIUMINT

3

-8388608

8388607

INT

4

-2147483648

2147483647

BIGINT

8

-9223372036854775808

9223372036854775807

L'attribut UNSIGNED

Vous pouvez également préciser que vos colonnes sont UNSIGNED, c'est-à-dire qu'on ne précise pas s'il s'agit d'une valeur positive ou négative (on aura donc toujours une valeur positive). Dans ce cas, la longueur de l'intervalle reste la même, mais les valeurs possibles sont décalées, le minimum valant 0. Pour les TINYINT, on pourra par exemple aller de 0 à 255.

Limiter la taille d'affichage et l'attribut ZEROFILL

Il est possible de préciser le nombre de chiffres minimum à l'affichage d'une colonne de type INT (ou un de ses dérivés). Il suffit alors de préciser ce nombre entre parenthèses : INT(x). Notez bien que cela ne change pas les capacités de stockage dans la colonne. Si vous déclarez un INT(2), vous pourrez toujours y stocker 45282 par exemple. Simplement, si vous stockez un nombre avec un nombre de chiffres inférieur au nombre défini, le caractère par défaut sera ajouté à gauche du chiffre, pour qu'il prenne la bonne taille. Sans précision, le caractère par défaut est l'espace.

Cette taille d'affichage est généralement utilisée en combinaison avec l'attribut ZEROFILL. Cet attribut ajoute des zéros à gauche du nombre lors de son affichage, il change donc le caractère par défaut par '0'. Donc, si vous déclarez une colonne comme étant

INT(4) ZEROFILL

Vous aurez l'affichage suivant :

Nombre stocké

Nombre affiché

45

0045

4156

4156

785164

785164

Nombres décimaux

Cinq mots-clés permettent de stocker des nombres décimaux dans une colonne : DECIMAL, NUMERIC, FLOAT, REAL et DOUBLE.

NUMERIC et DECIMAL

NUMERIC et DECIMAL sont équivalents et acceptent deux paramètres : la précision et l'échelle.

Dans un champ DECIMAL(5,3), on peut donc stocker des nombres de 5 chiffres significatifs maximum, dont 3 chiffres sont après la virgule. Par exemple : 12.354, -54.258, 89.2 ou -56.
DECIMAL(4) équivaut à écrire DECIMAL(4, 0).

Comme pour les nombres entiers, si l'on entre un nombre qui n'est pas dans l'intervalle supporté par la colonne, MySQL le remplacera par le plus proche supporté. Donc si la colonne est définie comme un DECIMAL(5,3) et que le nombre est trop loin dans les positifs (1012,43 par exemple), 999.999 sera stocké, et -99.999 si le nombre est trop loin dans les négatifs.
S'il y a trop de chiffres après la virgule, MySQL arrondira à l'échelle définie.

FLOAT, DOUBLE et REAL

Le mot-clé FLOAT peut s'utiliser sans paramètre, auquel cas quatre octets sont utilisés pour stocker les valeurs de la colonne. Il est cependant possible de spécifier une précision et une échelle, de la même manière que pour DECIMAL et NUMERIC.

Quant à REAL et DOUBLE, ils ne supportent pas de paramètres. DOUBLE est normalement plus précis que REAL (stockage dans 8 octets contre stockage dans 4 octets), mais ce n'est pas le cas avec MySQL qui utilise 8 octets dans les deux cas. Je vous conseille donc d'utiliser DOUBLE pour éviter les surprises en cas de changement de SGBDR.

Valeurs exactes vs. valeurs approchées

Les nombres stockés en tant que NUMERIC ou DECIMAL sont stockés sous forme de chaînes de caractères. Par conséquent, c'est la valeur exacte qui est stockée. Par contre, les types FLOAT, DOUBLE et REAL sont stockés sous forme de nombres, et c'est une valeur approchée qui est stockée.
Cela signifie que si vous stockez par exemple 56,6789 dans une colonne de type FLOAT, en réalité, MySQL stockera une valeur qui se rapproche de 56,6789 (par exemple, 56,678900000000000001). Cela peut poser problème pour des comparaison notamment (56,678900000000000001 n'étant pas égal à 56,6789). S'il est nécessaire de conserver la précision exacte de vos données (l'exemple type est celui des données bancaires), il est donc conseillé d'utiliser un type numérique à valeur exacte (NUMERIC ou DECIMAL donc).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les types de données Types alphanumériques

Types alphanumériques

Types numériques Types temporels

Types alphanumériques

Chaînes de type texte
CHAR et VARCHAR

Pour stocker un texte relativement court (moins de 255 octets), vous pouvez utiliser les types CHAR et VARCHAR. Ces deux types s'utilisent avec un paramètre qui précise la taille que peut prendre votre texte (entre 1 et 255). La différence entre CHAR et VARCHAR est la manière dont ils sont stockés en mémoire. Un CHAR(x) stockera toujours x octets, en remplissant si nécessaire le texte avec des espaces vides pour le compléter, tandis qu'un VARCHAR(x) stockera jusqu'à x octets (entre 0 et x), et stockera en plus en mémoire la taille du texte stocké.
Si vous entrez un texte plus long que la taille maximale définie pour le champ, celui-ci sera tronqué.

Petit tableau explicatif, en prenant l'exemple d'un CHAR ou d'un VARCHAR de 5 octets maximum :

Texte

CHAR(5)

Mémoire requise

VARCHAR(5)

Mémoire requise

''

'   '

5 octets

''

1 octet

'tex'

'tex  '

5 octets

'tex'

4 octets

'texte'

'texte'

5 octets

'texte'

6 octets

'texte trop long'

'texte'

5 octets

'texte'

6 octets

Vous voyez donc que dans le cas où le texte fait la longueur maximale autorisée, un CHAR(x) prend moins de place en mémoire qu'un VARCHAR(x). Préférez donc le CHAR(x) dans le cas où vous savez que vous aurez toujours x octets (par exemple si vous stockez un code postal). Par contre, si la longueur de votre texte risque de varier d'une ligne à l'autre, définissez votre colonne comme un VARCHAR(x).

TEXT

Et si je veux pouvoir stocker des textes de plus de 255 octets ?

Il suffit alors d'utiliser le type TEXT, ou un de ses dérivés TINYTEXT, MEDIUMTEXT ou LONGTEXT. La différence entre ceux-ci étant la place qu'ils permettent d'occuper en mémoire. Petit tableau habituel :

Type

Longueur maximale

Mémoire occupée

TINYTEXT

2^8 octets

Longueur de la chaîne + 1 octet

TEXT

2^16 octets

Longueur de la chaîne + 2 octets

MEDIUMTEXT

2^24 octets

Longueur de la chaîne + 3 octets

LONGTEXT

2^32 octets

Longueur de la chaîne + 4 octets

Chaînes de type binaire

Comme les chaînes de type texte que l'on vient de voir, une chaîne binaire n'est rien d'autre qu'une suite de caractères.
Cependant, si les textes sont affectés par l'encodage et l'interclassement, ce n'est pas le cas des chaînes binaires. Une chaîne binaire n'est rien d'autre qu'une suite d'octets. Aucune interprétation n'est faite sur ces octets. Ceci a deux conséquences principales.

Par conséquent, les types binaires sont parfaits pour stocker des données "brutes" comme des images par exemple, tandis que les chaînes de texte sont parfaites pour stocker...du texte ! :D

Les types binaires sont définis de la même façon que les types de chaînes de texte. VARBINARY(x) et BINARY(x) permettent de stocker des chaînes binaires de x caractères maximum (avec une gestion de la mémoire identique à VARCHAR(x) et CHAR(x)). Pour les chaînes plus longues, il existe les types TINYBLOB, BLOB, MEDIUMBLOB et LONGBLOB, également avec les mêmes limites de stockage que les types TEXT.

SET et ENUM
ENUM

Une colonne de type ENUM est une colonne pour laquelle on définit un certain nombre de valeurs autorisées, de type "chaîne de caractère". Par exemple, si l'on définit une colonne espece (pour une espèce animale) de la manière suivante :

espece ENUM('chat', 'chien', 'tortue')

La colonne espece pourra alors contenir les chaînes "chat", "chien" ou "tortue", mais pas les chaînes "lapin" ou "cheval".

En plus de "chat", "chien" et "tortue", la colonne espece pourrait prendre deux autres valeurs :

Pour remplir un champ de type ENUM, deux possibilités s'offrent à vous :

Valeur

Index

NULL

NULL

''

0

'chat'

1

'chien'

2

'tortue'

3

Afin que tout soit bien clair : si vous voulez stocker "chien" dans votre champ, vous pouvez donc y insérer "chien" ou insérer 2 (sans guillemets, il s'agit d'un nombre, pas d'un caractère).

SET

SET est fort semblable à ENUM. Une colonne SET est en effet une colonne qui permet de stocker une chaîne de caractères dont les valeurs possibles sont prédéfinies par l'utilisateur. La différence avec ENUM, c'est qu'on peut stocker dans la colonne entre 0 et x valeur(s), x étant le nombre de valeurs autorisées.

Donc, si l'on définit une colonne de type SET de la manière suivante :

espece SET('chat', 'chien', 'tortue')

On pourra stocker dans cette colonne :

Vous remarquerez que lorsqu'on stocke plusieurs valeurs, il faut les séparer par une virgule, sans espace et entourer la totalité des valeurs par des guillemets (non pas chaque valeur séparément). Par conséquent, les valeurs autorisées d'une colonne SET ne peuvent pas contenir de virgule elles-mêmes.

Les colonnes SET utilisent également un système d'index, quoiqu'un peu plus complexe que pour le type ENUM. SET utilise en effet un système d'index binaire. Concrètement, la présence/absence des valeurs autorisées va être enregistrée sous forme de bits, mis à 1 si la valeur correspondante est présente, à 0 si la valeur correspondante est absente.
Si l'on reprend notre exemple, on a donc :

espece SET('chat', 'chien', 'tortue')

Trois valeurs sont autorisées. Il nous faut donc trois bits pour savoir quelles valeurs sont stockées dans le champ. Le premier, à droite, correspondra à "chat", le second (au milieu) à "chien" et le dernier (à gauche) à "tortue".

Par ailleurs, ces suites de bits représentent des nombres en binaire convertibles en décimal. Ainsi 000 en binaire correspond à 0 en nombre décimal, 001 correspond à 1, 010 correspond à 2, 011 à 3...

Puisque j'aime bien les tableaux, je vous en fais un, ce sera peut-être plus clair.

Valeur

Binaire

Décimal

'chat'

001

1

'chien'

010

2

'tortue'

100

4

Pour stocker 'chat' et 'tortue' dans un champ, on peut donc utiliser 'chat,tortue' ou 101 (addition des nombres binaires correspondants) ou 5 (addition des nombres décimaux correspondants).
Notez que cette utilisation des binaires a pour conséquence que l'ordre dans lequel vous rentrez vos valeurs n'a pas d'importance. Que vous écriviez 'chat,tortue' ou 'tortue,chat' ne fait aucune différence. Lorsque vous récupérerez votre champ, vous aurez 'chat,tortue' (dans le même ordre que lors de la définition du champ).

Avertissement

SET et ENUM sont des types propres à MySQL. Ils sont donc à utiliser avec une grande prudence !

Pourquoi avoir inventé ces types propres à MySQL ?

La plupart des SGBD implémentent ce qu'on appelle des contraintes d'assertions, qui permettent de définir les valeurs que peuvent prendre une colonne (par exemple, on pourrait définir une contrainte pour une colonne contenant un âge, devant être compris entre 0 et 130).
MySQL n'implémente pas ce type de contrainte et a par conséquent créé deux types de données spécifiques (SET et ENUM), pour pallier en partie ce manque.

Dans quelles situations faut-il utiliser ENUM ou SET ?

La meilleure réponse à cette question est : jamais ! Je déconseille fortement l'utilisation des SET et des ENUM. Je vous ai présenté ces deux types par souci d'exhaustivité, mais il faut toujours éviter autant que possible les fonctionnalités propres à un seul SGBD. Ceci afin d'éviter les problèmes si un jour vous voulez en utiliser un autre.

Mais ce n'est pas la seule raison. Imaginez que vous vouliez utiliser un ENUM ou un SET pour un système de catégories. Vous avez donc des éléments qui peuvent appartenir à une catégorie (dans ce cas, vous utilisez une colonne ENUM pour la catégorie) ou appartenir à plusieurs catégories (et vous utilisez SET).

categorie ENUM("Soupes", "Viandes", "Tarte", "Dessert")
categorie SET("Soupes", "Viandes", "Tarte", "Dessert")

Tout se passe plutôt bien tant que vos éléments appartiennent aux catégories que vous avez définies au départ. Et puis tout à coup, vous vous retrouvez avec un élément qui ne correspond à aucune de vos catégories, mais qui devrait plutôt se trouver dans la catégorie "Entrées". Avec SET ou ENUM, il vous faut modifier la colonne categorie pour ajouter "Entrées" aux valeurs possibles. Or, une des règles de base à respecter lorsque l'on conçoit une base de données, est que la structure de la base (donc les tables, les colonnes) ne doit pas changer lorsque l'on ajoute des données. Par conséquent, tout ce qui est susceptible de changer doit être une donnée, et non faire partie de la structure de la base.

Il existe deux solutions pour éviter les ENUM, et une solution pour éviter les SET.

Pour éviter ENUM

Pour éviter SET
La solution consiste en la création de deux tables : une table Categorie, qui reprend les catégories possibles, et une table qui lie les éléments aux catégories auxquels ils appartiennent.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Types numériques Types temporels

Types temporels

Types alphanumériques Création d'une base de données

Types temporels

Pour les données temporelles, MySQL dispose de cinq types qui permettent, lorsqu'ils sont bien utilisés, de faire énormément de choses.

Avant d'entrer dans le vif du sujet, une petite remarque importante : lorsque vous stockez une date dans MySQL, certaines vérifications sont faites sur la validité de la date entrée. Cependant, ce sont des vérifications de base : le jour doit être compris entre 1 et 31 et le mois entre 1 et 12. Il vous est tout à fait possible d'entrer une date telle que le 31 février 2011. Soyez donc prudents avec les dates que vous entrez et récupérez.

Les cinq types temporels de MySQL sont DATE, DATETIME, TIME, TIMESTAMP et YEAR.

DATE, TIME et DATETIME

Comme son nom l'indique, DATE sert à stocker une date. TIME sert quant à lui à stocker une heure, et DATETIME stocke...une date ET une heure ! :D

DATE

Pour entrer une date, l'ordre des données est la seule contrainte. Il faut donner d'abord l'année (deux ou quatre chiffres), ensuite le mois (deux chiffres) et pour finir, le jour (deux chiffres), sous forme de nombre ou de chaîne de caractères. S'il s'agit d'une chaîne de caractères, n'importe quelle ponctuation peut être utilisée pour délimiter les parties (ou aucune). Voici quelques exemples d'expressions correctes (A représente les années, M les mois et J les jours) :

L'année peut donc être donnée avec deux ou quatre chiffres. Dans ce cas, le siècle n'est pas précisé, et c'est MySQL qui va décider de ce qu'il utilisera, selon ces critères :

DATETIME

Très proche de DATE, ce type permet de stocker une heure, en plus d'une date. Pour entrer un DATETIME, c'est le même principe que pour DATE : pour la date, année-mois-jour, et pour l'heure, il faut donner d'abord l'heure, ensuite les minutes, puis les secondes. Si on utilise une chaîne de caractères, il faut séparer la date et l'heure par une espace. Quelques exemples corrects (H représente les heures, M les minutes et S les secondes) :

TIME

Le type TIME est un peu plus compliqué, puisqu'il permet non seulement de stocker une heure précise, mais aussi un intervalle de temps. On n'est donc pas limité à 24 heures, et il est même possible de stocker un nombre de jours ou un intervalle négatif. Comme dans DATETIME, il faut d'abord donner l'heure, puis les minutes, puis les secondes, chaque partie pouvant être séparée des autres par le caractère :. Dans le cas où l'on précise également un nombre de jours, alors les jours sont en premier et séparés du reste par une espace. Exemples :

YEAR

Si vous n'avez besoin de retenir que l'année, YEAR est un type intéressant car il ne prend qu'un seul octet en mémoire. Cependant, un octet ne pouvant contenir que 256 valeurs différentes, YEAR est fortement limité : on ne peut y stocker que des années entre 1901 et 2155. Ceci dit, ça devrait suffire à la majorité d'entre vous pour au moins les cent prochaines années.

On peut entrer une donnée de type YEAR sous forme de chaîne de caractères ou d'entiers, avec 2 ou 4 chiffres. Si l'on ne précise que deux chiffres, le siècle est ajouté par MySQL selon les mêmes critères que pour DATE et DATETIME, à une exception près : si l'on entre 00 (un entier donc), il sera interprété comme la valeur par défaut de YEAR 0000. Par contre, si l'on entre '00' (une chaîne de caractères), elle sera bien interprétée comme l'année 2000.
Plus de précisions sur les valeurs par défaut des types temporels dans quelques instants !

TIMESTAMP

Par définition, le timestamp d'une date est le nombre de secondes écoulées depuis le 1er janvier 1970, 0h0min0s (TUC) et la date en question.
Les timestamps étant stockés sur 4 octets, il existe une limite supérieure : le 19 janvier 2038 à 3h14min7s. Par conséquent, vérifiez bien que vous êtes dans l'intervalle de validité avant d'utiliser un timestamp.

Le type TIMESTAMP de MySQL est cependant un peu particulier. Prenons par exemple le 4 octobre 2011, à 21h05min51s.
Entre cette date et le 1er janvier 1970, 0h0min0s, il s'est écoulé exactement 1317755151 secondes. Le nombre 1317755151 est donc, par définition, le timestamp de cette date du 4 octobre 2011, 21h05min51s.
Pourtant, pour stocker cette date dans un TIMESTAMP SQL, ce n'est pas 1317755151 qu'on utilisera, mais 20111004210551. C'est-à-dire l'équivalent, au format numérique, du DATETIME '2011-10-04 21:05:51'.
Le TIMESTAMP SQL n'a donc de timestamp que le nom. Il ne sert pas à stocker un nombre de secondes, mais bien une date sous format numérique AAAAMMJJHHMMSS (alors qu'un DATETIME est donc stocké sous forme de chaîne de caractères).

Il n'est donc pas possible de stocker un "vrai" timestamp dans une colonne de type TIMESTAMP. C'est évidemment contre-intuitif, et source d'erreur.
Notez que malgré cela, le TIMESTAMP SQL a les même limites qu'un vrai timestamp : il n'acceptera que des date entre le 1e janvier 1970 à 00h00min00s et le 19 janvier 2038 à 3h14min7s.

La date par défaut

Lorsque MySQL rencontre une date/heure incorrecte, ou qui n'est pas dans l'intervalle de validité du champ, la valeur par défaut est stockée à la place. Il s'agit de la valeur "zéro" du type. On peut se référer à cette valeur par défaut en utilisant '0' (caractère), 0 (nombre) ou la représentation du "zéro" correspondant au type de la colonne (voir tableau ci-dessous).

Type

Date par défaut ("zéro")

DATE

'0000-00-00'

DATETIME

'0000-00-00 00:00:00'

TIME

'00:00:00'

YEAR

0000

TIMESTAMP

00000000000000

Une exception toutefois, si vous insérez un TIME qui dépasse l'intervalle de validité, MySQL ne le remplacera pas par le "zéro", mais par la plus proche valeur appartenant à l'intervalle de validité (-838:59:59 ou 838:59:59).

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Types alphanumériques Création d'une base de données

Création d'une base de données

Types temporels Avant-propos : conseils et conventions

Ça y est, le temps est venu d'écrire vos premières lignes de commande.
Dans ce chapitre plutôt court, je vous donnerai pour commencer quelques conseils indispensables. Ensuite, je vous présenterai la problématique sur laquelle nous allons travailler tout au long de ce tutoriel : la base de données d'un élevage d'animaux.
Pour finir, nous verrons comment créer et supprimer une base de données.

La partie purement théorique est donc bientôt finie. Gardez la tête et les mains à l'intérieur du véhicule. C'est parti !

Avant-propos : conseils et conventions

Création d'une base de données Création et suppression d'une base de données

Avant-propos : conseils et conventions

Conseils
Noms de tables et de colonnes

N'utilisez jamais, au grand jamais, d'espaces ou d'accents dans vos noms de bases, tables ou colonnes. Au lieu d'avoir une colonne "date de naissance", préférez "date_de_naissance" ou "date_naissance". Et au lieu d'avoir une colonne "prénom", utilisez "prenom". Avouez que ça reste lisible, et ça vous évitera pas mal d'ennuis.

Évitez également d'utiliser des mots réservés comme nom de colonnes/tables/bases. Par "mot réservé", j'entends un mot-clé SQL, donc un mot qui sert à définir quelque chose dans le langage SQL. Vous trouverez une liste exhaustive des mots réservés dans la documentation officielle. Parmi les plus fréquents : date, text, type. Ajoutez donc une précision à vos noms dans ces cas-là (date_naissance, text_article ou type_personnage par exemple).
Notez que MySQL permet l'utilisation de mots-clés comme noms de tables ou de colonnes, à condition que ce nom soit entouré de ` (accent grave/backquote). Cependant, ceci est propre à MySQL et ne devrait pas être utilisé.

Soyez cohérents

Vous vous y retrouverez bien mieux si vous restez cohérents dans votre base. Par exemple, mettez tous vos noms de tables au singulier, ou au contraire au pluriel. Choisissez, mais tenez-vous-y. Même chose pour les noms de colonnes. Et lorsqu'un nom de table ou de colonne nécessite plusieurs mots, séparez les toujours avec '_' (ex : date_naissance) ou bien toujours avec une majuscule (ex : dateNaissance).
Ce ne sont que quelques exemples de situations dans lesquelles vous devez décider d'une marche à suivre, et la garder tout au long de votre projet (voire pour tous vos projets futurs). Vous gagnerez énormément de temps en prenant de telles habitudes.

Conventions
Mots-clés

Une convention largement répandue veut que les commandes et mots-clés SQL soient écrits complètement en majuscules. Je respecterai cette convention et vous encourage à le faire également. Il est plus facile de relire une commande de 5 lignes lorsqu'on peut différencier au premier coup d’œil les commandes SQL des noms de tables et de colonnes.

Noms de bases, de tables et de colonnes

Je viens de vous dire que les mots-clés SQL seront écrits en majuscule pour les différencier du reste, donc évidemment, les noms de bases, tables et colonnes seront écrits en minuscule.
Toutefois, par habitude et parce que je trouve cela plus clair, je mettrai une majuscule à la première lettre de mes noms de tables (et uniquement pour les tables : ni pour la base de données ni pour les colonnes). Notez que MySQL n'est pas nécessairement sensible à la casse en ce qui concerne les noms de tables et de colonnes. En fait, il est très probable que si vous travaillez sous Windows, MySQL ne soit pas sensible à la casse pour les noms de tables et de colonnes. Sous Mac et Linux par contre, c'est le contraire qui est le plus probable.
Quoi qu'il en soit, j'utiliserai des majuscules pour la première lettre de mes noms de tables. Libre à vous de me suivre ou non.

Options facultatives

Lorsque je commencerai à vous montrer les commandes SQL à utiliser pour interagir avec votre base de données, vous verrez que certaines commandes ont des options facultatives. Dans ces cas-là, j'utiliserai des crochets [ ] pour indiquer ce qui est facultatif. La même convention est utilisée dans la documentation officielle MySQL (et dans beaucoup d'autres documentations d'ailleurs). La requête suivante signifie donc que vous pouvez commander votre glace vanille toute seule, ou avec du chocolat, ou avec de la chantilly, ou avec du chocolat ET de la chantilly.

COMMANDE glace vanille [avec chocolat] [avec chantilly]
Mise en situation

Histoire que nous soyons sur la même longueur d'onde, je vous propose de baser le cours sur une problématique bien précise. Nous allons créer une base de données qui permettra de gérer un élevage d'animaux. Pourquoi un élevage ? Tout simplement parce que j'ai dû moi-même créer une telle base pour le laboratoire de biologie pour lequel je travaillais. Par conséquent, j'ai une assez bonne idée des problèmes qu'on peut rencontrer avec ce type de bases, et je pourrai donc appuyer mes explications sur des problèmes réalistes, plutôt que d'essayer d'en inventer.

Nous nous occupons donc d'un élevage d'animaux. On travaille avec plusieurs espèces : chats, chiens, tortues entre autres (tiens, ça me rappelle quelque chose :-° ). Dans la suite de cette partie, nous nous contenterons de créer une table Animal qui contiendra les caractéristiques principales des animaux présents dans l'élevage, mais dès le début de la deuxième partie, d'autres tables seront créées afin de pouvoir gérer un grand nombre de données complexes.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Création d'une base de données Création et suppression d'une base de données

Création et suppression d'une base de données

Avant-propos : conseils et conventions Création de tables

Création et suppression d'une base de données

Création

Nous allons donc créer notre base de données, que nous appellerons elevage. Rappelez-vous, lors de la création de votre utilisateur MySQL, vous lui avez donné tous les droits sur la base elevage, qui n'existait pas encore. Si vous choisissez un autre nom de base, vous n'aurez aucun droit dessus.

La commande SQL pour créer une base de données est la suivante :

CREATE DATABASE nom_base;

Avouez que je ne vous surmène pas le cerveau pour commencer…

Cependant, attendez avant de créer votre base de données elevage. Je vous rappelle qu'il faut également définir l'encodage utilisé (l'UTF-8 dans notre cas). Voici donc la commande complète à taper pour créer votre base :

CREATE DATABASE elevage CHARACTER SET 'utf8';

Lorsque nous créerons nos tables dans la base de données, automatiquement elles seront encodées également en UTF-8.

Suppression

Si vous avez envie d'essayer cette commande, faites-le maintenant, tant qu'il n'y a rien dans votre base de données. Soyez très prudents, car vous effacez tous les fichiers créés par MySQL qui servent à stocker les informations de votre base.

DROP DATABASE elevage;

Si vous essayez cette commande alors que la base de données elevage n'existe pas, MySQL vous affichera une erreur :

mysql> DROP DATABASE elevage;
ERROR 1008 (HY000) : Can't drop database 'elevage'; database doesn't exist
mysql>

Pour éviter ce message d'erreur, si vous n'êtes pas sûrs que la base de données existe, vous pouvez utiliser l'option IF EXISTS, de la manière suivante :

DROP DATABASE IF EXISTS elevage;

Si la base de données existe, vous devriez alors avoir un message du type :

Query OK, 0 rows affected (0.00 sec)

Si elle n'existe pas, vous aurez :

Query OK, 0 rows affected, 1 warning (0.00 sec)

Pour afficher les warnings de MySQL, il faut utiliser la commande

SHOW WARNINGS;

Cette commande affiche un tableau :

Level

Code

Message

Note

1008

Can't drop database 'elevage'; database doesn't exist

Utilisation d'une base de données

Vous avez maintenant créé une base de données (si vous l'avez effacée avec DROP DATABASE, recréez-la). Mais pour pouvoir agir sur cette base, vous devez encore avertir MySQL que c'est bien sûr cette base-là que vous voulez travailler. Une fois de plus, la commande est très simple :

USE elevage

C'est tout ! À partir de maintenant, toutes les actions effectuées le seront sur la base de données elevage (création et modification de tables par exemple).

Notez que vous pouvez spécifier la base de données sur laquelle vous allez travailler lors de la connexion à MySQL. Il suffit d'ajouter le nom de la base à la fin de la commande de connexion :

mysql -u sdz -p elevage
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Avant-propos : conseils et conventions Création de tables

Création de tables

Création et suppression d'une base de données Définition des colonnes

Dans ce chapitre, nous allons créer, étape par étape, une table Animal, qui servira à stocker les animaux présents dans notre élevage.
Soyez gentils avec cette table, car c'est elle qui vous accompagnera tout au long de la première partie (on apprendra à jongler avec plusieurs tables dans la deuxième partie).

Pour commencer, il faudra définir de quelles colonnes (et leur type) la table sera composée. Ne négligez pas cette étape, c'est la plus importante. Une base de données mal conçue est un cauchemar à utiliser.
Ensuite, petit passage obligé par de la théorie : vous apprendrez ce qu'est une clé primaire et à quoi ça sert, et découvrirez cette fonctionnalité exclusive de MySQL que sont les moteurs de table.
Enfin, la table Animal sera créée, et la requête de création des tables décortiquée. Et dans la foulée, nous verrons également comment supprimer une table.

Définition des colonnes

Création de tables Introduction aux clés primaires

Définition des colonnes

Type de colonne

Avant de choisir le type des colonnes, il faut choisir les colonnes que l'on va définir. On va donc créer une table Animal. Qu'est-ce qui caractérise un animal ? Son espèce, son sexe, sa date de naissance. Quoi d'autre ? Une éventuelle colonne commentaires qui peut servir de fourre-tout. Dans le cas d'un élevage sentimental, on peut avoir donné un nom à nos bestioles.
Disons que c'est tout pour le moment. Examinons donc les colonnes afin d'en choisir le type au mieux.

NULL or NOT NULL ?

Il faut maintenant déterminer si l'on autorise les colonnes à ne pas stocker de valeur (ce qui est donc représenté par NULL).

Récapitulatif

Comme d'habitude, un petit tableau pour récapituler tout ça :

Caractéristique

Nom de la colonne

Type

NULL?

Espèce

espece

VARCHAR(40)

Non

Sexe

sexe

CHAR(1)

Oui

Date de naissance

date_naissance

DATETIME

Non

Commentaires

commentaires

TEXT

Oui

Nom

nom

VARCHAR(30)

Oui

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Création de tables Introduction aux clés primaires

Introduction aux clés primaires

Définition des colonnes Les moteurs de tables

Introduction aux clés primaires

On va donc définir cinq colonnes : espece, sexe, date_naissance, commentaires et nom. Ces colonnes permettront de caractériser nos animaux. Mais que se passe-t-il si deux animaux sont de la même espèce, du même sexe, sont nés exactement le même jour, et ont exactement les mêmes commentaires et le même nom ? Comment les différencier ? Évidemment, on pourrait s'arranger pour que deux animaux n'aient jamais le même nom. Mais imaginez la situation suivante : une chatte vient de donner naissance à sept petits. On ne peut pas encore définir leur sexe, on n'a pas encore trouvé de nom pour certains d'entre eux et il n'y a encore aucun commentaire à faire à leur propos. Ils auront donc exactement les mêmes caractéristiques. Pourtant, ce ne sont pas les mêmes individus. Il faut donc les différencier. Pour cela, on va ajouter une colonne à notre table.

Identité

Imaginez que quelqu'un ait le même nom de famille que vous, le même prénom, soit né dans la même ville et ait la même taille. En dehors de la photo et de la signature, quelle sera la différence entre vos deux cartes d'identité ? Son numéro !
Suivant le même principe, on va donner à chaque animal un numéro d'identité. La colonne qu'on ajoutera s’appellera donc id, et il s'agira d'un INT, toujours positif donc UNSIGNED. Selon la taille de l'élevage (la taille actuelle mais aussi la taille qu'on imagine qu'il pourrait avoir dans le futur !), il peut être plus intéressant d'utiliser un SMALLINT, voire un MEDIUMINT. Comme il est peu probable que l'on dépasse les 65000 animaux, on utilisera SMALLINT. Attention, il faut bien considérer tous les animaux qui entreront un jour dans la base, pas uniquement le nombre d'animaux présents en même temps dans l'élevage. En effet, si l'on supprime pour une raison ou une autre un animal de la base, il n'est pas question de réutiliser son numéro d'identité.

Ce champ ne pourra bien sûr pas être NULL, sinon il perdrait toute son utilité.

Clé primaire

La clé primaire d'une table est une contrainte d'unicité, composée d'une ou plusieurs colonnes. La clé primaire d'une ligne permet d'identifier de manière unique cette ligne dans la table. Si l'on parle de la ligne dont la clé primaire vaut x, il ne doit y avoir aucun doute quant à la ligne dont on parle. Lorsqu'une table possède une clé primaire (et il est extrêmement conseillé de définir une clé primaire pour chaque table créée), celle-ci doit être définie.
Cette définition correspond exactement au numéro d'identité dont nous venons de parler. Nous définirons donc id comme la clé primaire de la table Animal, en utilisant les mots-clés PRIMARY KEY(id).
Lorsque vous insérerez une nouvelle ligne dans la table, MySQL vérifiera que vous insérez bien un id, et que cet id n'existe pas encore dans la table. Si vous ne respectez pas ces deux contraintes, MySQL n’insérera pas la ligne et vous renverra une erreur.

Par exemple, dans le cas où vous essayez d'insérer un id qui existe déjà, vous obtiendrez l'erreur suivante :

ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

Je n'en dirai pas plus pour l'instant sur les clés primaires mais j'y reviendrai de manière détaillée dans la seconde partie de ce cours.

Auto-incrémentation

Il faut donc, pour chaque animal, décider d'une valeur pour id. Le plus simple, et le plus logique, est de donner le numéro 1 au premier individu enregistré, puis le numéro 2 au second, etc.
Mais si vous ne vous souvenez pas quel numéro vous avez utilisé en dernier, pour insérer un nouvel animal il faudra récupérer cette information dans la base, ensuite seulement vous pourrez ajouter une ligne en lui donnant comme id le dernierid utilisé + 1.
C'est bien sûr faisable, mais c'est fastidieux… Heureusement, il est possible de demander à MySQL de faire tout ça pour nous !
Comment ? En utilisant l'auto-incrémentation des colonnes. Incrémenter veut dire "ajouter une valeur fixée". Donc, si l'on déclare qu'une colonne doit s'auto-incrémenter (grâce au mot-clé AUTO_INCREMENT), plus besoin de chercher quelle valeur on va mettre dedans lors de la prochaine insertion. MySQL va chercher ça tout seul comme un grand en prenant la dernière valeur insérée et en l'incrémentant de 1.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Définition des colonnes Les moteurs de tables

Les moteurs de tables

Introduction aux clés primaires Syntaxe de CREATE TABLE

Les moteurs de tables

Les moteurs de tables sont une spécificité de MySQL. Ce sont des moteurs de stockage. Cela permet de gérer différemment les tables selon l'utilité qu'on en a. Je ne vais pas vous détailler tous les moteurs de tables existant. Si vous voulez plus d'informations, je vous renvoie à la documentation officielle.
Les deux moteurs les plus connus sont MyISAM et InnoDB.

MyISAM

C'est le moteur par défaut. Les commandes d'insertion et sélection de données sont particulièrement rapides sur les tables utilisant ce moteur. Cependant, il ne gère pas certaines fonctionnalités importantes comme les clés étrangères, qui permettent de vérifier l'intégrité d'une référence d'une table à une autre table (voir la deuxième partie du cours) ou les transactions, qui permettent de réaliser des séries de modifications "en bloc" ou au contraire d'annuler ces modifications (voir la cinquième partie du cours).

InnoDB

Plus lent et plus gourmand en ressources que MyISAM, ce moteur gère les clés étrangères et les transactions. Étant donné que nous nous servirons des clés étrangères dès la deuxième partie, c'est celui-là que nous allons utiliser.
De plus, en cas de crash du serveur, il possède un système de récupération automatique des données.

Préciser un moteur lors de la création de la table

Pour qu'une table utilise le moteur de notre choix, il suffit d'ajouter ceci à la fin de la commande de création :

ENGINE = moteur;

En remplaçant bien sûr "moteur" par le nom du moteur que nous voulons utiliser, ici InnoDB :

ENGINE = INNODB;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Introduction aux clés primaires Syntaxe de CREATE TABLE

Syntaxe de CREATE TABLE

Les moteurs de tables Suppression d'une table

Syntaxe de CREATE TABLE

Avant de voir la syntaxe permettant de créer une table, résumons un peu. Nous voulons donc créer une table Animal avec six colonnes telles que décrites dans le tableau suivant.

Caractéristique

Nom du champ

Type

NULL?

Divers

Numéro d'identité

id

SMALLINT

Non

Clé primaire + auto-incrément + UNSIGNED

Espèce

espece

VARCHAR(40)

Non

-

Sexe

sexe

CHAR(1)

Oui

-

Date de naissance

date_naissance

DATETIME

Non

-

Commentaires

commentaires

TEXT

Oui

-

Nom

nom

VARCHAR(30)

Oui

-

Syntaxe

Par souci de clarté, je vais diviser l'explication de la syntaxe de CREATE TABLE en deux. La première partie vous donne la syntaxe globale de la commande, et la deuxième partie s'attarde sur la description des colonnes créées dans la table.

Création de la table
CREATE TABLE [IF NOT EXISTS] Nom_table (
    colonne1 description_colonne1,
    [colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...,]
    [PRIMARY KEY (colonne_clé_primaire)]
)
[ENGINE=moteur];

Le IF NOT EXISTS est facultatif (d'où l'utilisation de crochets [ ]), et a le même rôle que dans la commande CREATE DATABASE : si une table de ce nom existe déjà dans la base de données, la requête renverra un warning plutôt qu'une erreur si IF NOT EXISTS est spécifié.
Ce n'est pas non plus une erreur de ne pas préciser la clé primaire directement à la création de la table. Il est tout à fait possible de l'ajouter par la suite. Nous verrons comment un peu plus tard.

Définition des colonnes

Pour définir une colonne, il faut donc donner son nom en premier, puis sa description. La description est constituée au minimum du type de la colonne. Exemple :

nom VARCHAR(30),
sexe CHAR(1)

C'est aussi dans la description que l'on précise si la colonne peut contenir NULL ou pas (par défaut, NULL est autorisé). Exemple :

espece VARCHAR(40) NOT NULL,
date_naissance DATETIME NOT NULL

L'auto-incrémentation se définit également à cet endroit. Notez qu'il est également possible de définir une colonne comme étant la clé primaire dans sa description. Il ne faut alors plus l'indiquer après la définition de toutes les colonnes. Je vous conseille néanmoins de ne pas l'indiquer à cet endroit, nous verrons plus tard pourquoi.

id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT [PRIMARY KEY]

Enfin, on peut donner une valeur par défaut au champ. Si lorsque l'on insère une ligne, aucune valeur n'est précisée pour le champ, c'est la valeur par défaut qui sera utilisée. Notez que si une colonne est autorisée à contenir NULL et qu'on ne précise pas de valeur par défaut, alors NULL est implicitement considéré comme valeur par défaut.

Exemple :

espece VARCHAR(40) NOT NULL DEFAULT 'chien'
Application : création de Animal

Si l'on met tout cela ensemble pour créer la table Animal (je rappelle que nous utiliserons le moteur InnoDB), on a donc :

CREATE TABLE Animal (
    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    espece VARCHAR(40) NOT NULL,
    sexe CHAR(1),
    date_naissance DATETIME NOT NULL,
    nom VARCHAR(30),
    commentaires TEXT,
    PRIMARY KEY (id)
)
ENGINE=INNODB;
Vérifications

Au cas où vous ne me croiriez pas (et aussi un peu parce que cela pourrait vous être utile un jour), voici deux commandes vous permettant de vérifier que vous avez bien créé une jolie table Animal avec les six colonnes que vous vouliez.

SHOW TABLES;      -- liste les tables de la base de données

DESCRIBE Animal;  -- liste les colonnes de la table avec leurs caractéristiques
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les moteurs de tables Suppression d'une table

Suppression d'une table

Syntaxe de CREATE TABLE Modification d'une table

Suppression d'une table

La commande pour supprimer une table est la même que celle pour supprimer une base de données. Elle est, bien sûr, à utiliser avec prudence, car irréversible.

DROP TABLE Animal;
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe de CREATE TABLE Modification d'une table

Modification d'une table

Suppression d'une table Syntaxe de la requête

La création et la suppression de tables étant acquises, parlons maintenant des requêtes permettant de modifier une table.
Plus précisément, ce chapitre portera sur la modification des colonnes d'une table (ajout d'une colonne, modification, suppression de colonnes).
Il est possible de modifier d'autres éléments (des contraintes, ou des index par exemple), mais cela nécessite des notions que vous ne possédez pas encore, aussi n'en parlerai-je pas ici.

Notez qu'idéalement, il faut penser à l'avance à la structure de votre base et créer toutes vos tables directement et proprement, de manière à ne les modifier qu'exceptionnellement.

Syntaxe de la requête

Modification d'une table Ajout et suppression d'une colonne

Syntaxe de la requête

Lorsque l'on modifie une table, on peut vouloir lui ajouter, retirer ou modifier quelque chose. Dans les trois cas, c'est la commande ALTER TABLE qui sera utilisée, une variante existant pour chacune des opérations :

ALTER TABLE nom_table ADD ... -- permet d'ajouter quelque chose (une colonne par exemple)

ALTER TABLE nom_table DROP ... -- permet de retirer quelque chose 

ALTER TABLE nom_table CHANGE ...
ALTER TABLE nom_table MODIFY ... -- permettent de modifier une colonne
Créons une table pour faire joujou

Dans la seconde partie de ce tutoriel, nous devrons faire quelques modifications sur notre table Animal, mais en attendant, je vous propose d'utiliser la table suivante, si vous avez envie de tester les différentes possibilités d'ALTER TABLE :

CREATE TABLE Test_tuto (
	id INT NOT NULL,
	nom VARCHAR(10) NOT NULL,
        PRIMARY KEY(id)
);
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Modification d'une table Ajout et suppression d'une colonne

Ajout et suppression d'une colonne

Syntaxe de la requête Modification de colonne

Ajout et suppression d'une colonne

Ajout

On utilise la syntaxe suivante :

ALTER TABLE nom_table 
ADD [COLUMN] nom_colonne description_colonne;

Le [COLUMN] est facultatif, donc si à la suite de ADD vous ne précisez pas ce que vous voulez ajouter, MySQL considérera qu'il s'agit d'une colonne.
description_colonne correspond à la même chose que lorsque l'on crée une table. Il contient le type de donnée et éventuellement NULL ou NOT NULL, etc.

Ajoutons une colonne date_insertion à notre table de test. Il s'agit d'une date, donc une colonne de type DATE convient parfaitement. Disons que cette colonne ne peut pas être NULL (si c'est dans la table, ça a forcément été inséré). Cela nous donne :

ALTER TABLE Test_tuto 
ADD COLUMN date_insertion DATE NOT NULL;

Un petit DESCRIBE Test_tuto; vous permettra de vérifier les changements apportés.

Suppression

La syntaxe de ALTER TABLE ... DROP ... est très simple :

ALTER TABLE nom_table 
DROP [COLUMN] nom_colonne;

Comme pour les ajouts, le mot COLUMN est facultatif. Par défaut, MySQL considérera que vous parlez d'une colonne.

Exemple : nous allons supprimer la colonne date_insertion, que nous remercions pour son passage éclair dans le cours.

ALTER TABLE Test_tuto 
DROP COLUMN date_insertion; -- Suppression de la colonne date_insertion
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe de la requête Modification de colonne

Modification de colonne

Ajout et suppression d'une colonne Insertion de données

Modification de colonne

Changement du nom de la colonne

Vous pouvez utiliser la commande suivante pour changer le nom d'une colonne :

ALTER TABLE nom_table 
CHANGE ancien_nom nouveau_nom description_colonne;

Par exemple, pour renommer la colonne nom en prenom, vous pouvez écrire

ALTER TABLE Test_tuto 
CHANGE nom prenom VARCHAR(10) NOT NULL;

Attention, la description de la colonne doit être complète, sinon elle sera également modifiée. Si vous ne précisez pas NOT NULL dans la commande précédente, prenom pourra contenir NULL, alors que du temps où elle s'appelait nom, cela lui était interdit.

Changement du type de données

Les mots-clés CHANGE et MODIFY peuvent être utilisés pour changer le type de donnée de la colonne, mais aussi changer la valeur par défaut ou ajouter/supprimer une propriété AUTO_INCREMENT. Si vous utilisez CHANGE, vous pouvez, comme on vient de le voir, renommer la colonne en même temps. Si vous ne désirez pas la renommer, il suffit d'indiquer deux fois le même nom.
Voici les syntaxes possibles :

ALTER TABLE nom_table 
CHANGE ancien_nom nouveau_nom nouvelle_description;

ALTER TABLE nom_table 
MODIFY nom_colonne nouvelle_description;

Des exemples pour illustrer :

ALTER TABLE Test_tuto 
CHANGE prenom nom VARCHAR(30) NOT NULL; -- Changement du type + changement du nom

ALTER TABLE Test_tuto 
CHANGE id id BIGINT NOT NULL; -- Changement du type sans renommer

ALTER TABLE Test_tuto
MODIFY id BIGINT NOT NULL AUTO_INCREMENT; -- Ajout de l'auto-incrémentation

ALTER TABLE Test_tuto 
MODIFY nom VARCHAR(30) NOT NULL DEFAULT 'Blabla'; -- Changement de la description (même type mais ajout d'une valeur par défaut)

Il existe pas mal d'autres possibilités et combinaisons pour la commande ALTER TABLE mais en faire la liste complète ne rentre pas dans le cadre de ce cours. Si vous ne trouvez pas votre bonheur ici, je vous conseille de le chercher dans la documentation officielle.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Ajout et suppression d'une colonne Insertion de données

Insertion de données

Modification de colonne Syntaxe de INSERT

Ce chapitre est consacré à l'insertion de données dans une table. Rien de bien compliqué, mais c'est évidemment crucial. En effet, que serait une base de données sans données ?

Nous verrons entre autres :

Et pour terminer, nous peuplerons notre table Animal d'une soixantaine de petites bestioles sur lesquelles nous pourrons tester toutes sortes de tortures requêtes dans la suite de ce tutoriel. :diable:

Syntaxe de INSERT

Insertion de données Syntaxe alternative de MySQL

Syntaxe de INSERT

Deux possibilités s'offrent à nous lorsque l'on veut insérer une ligne dans une table : soit donner une valeur pour chaque colonne de la ligne, soit ne donner les valeurs que de certaines colonnes, auquel cas il faut bien sûr préciser de quelles colonnes il s'agit.

Insertion sans préciser les colonnes

Je rappelle pour les distraits que notre table Animal est composée de six colonnes : id, espece, sexe, date_naissance, nom et commentaires.

Voici donc la syntaxe à utiliser pour insérer une ligne dans Animal, sans renseigner les colonnes pour lesquelles on donne une valeur (implicitement, MySQL considère que l'on donne une valeur pour chaque colonne de la table).

INSERT INTO Animal 
VALUES (1, 'chien', 'M', '2010-04-05 13:43:00', 'Rox', 'Mordille beaucoup');

Deuxième exemple : cette fois-ci, on ne connaît pas le sexe et on n'a aucun commentaire à faire sur la bestiole :

INSERT INTO Animal 
VALUES (2, 'chat', NULL, '2010-03-24 02:23:00', 'Roucky', NULL);

Troisième et dernier exemple : on donne NULL comme valeur d'id, ce qui en principe est impossible puisque id est défini comme NOT NULL et comme clé primaire. Cependant, l'auto-incrémentation fait que MySQL va calculer tout seul comme un grand quel id il faut donner à la ligne (ici : 3).

INSERT INTO Animal 
VALUES (NULL , 'chat', 'F', '2010-09-13 15:02:00', 'Schtroumpfette', NULL);

Vous avez maintenant trois animaux dans votre table :

Id

Espèce

Sexe

Date de naissance

Nom

Commentaires

1

chien

M

2010-04-05 13:43:00

Rox

Mordille beaucoup

2

chat

NULL

2010-03-24 02:23:00

Roucky

NULL

3

chat

F

2010-09-13 15:02:00

Schtroumpfette

NULL

Pour vérifier, vous pouvez utiliser la requête suivante :

SELECT * FROM Animal;

Deux choses importantes à retenir ici.

Insertion en précisant les colonnes

Dans la requête, nous allons donc écrire explicitement à quelle(s) colonne(s) nous donnons une valeur. Ceci va permettre deux choses.

Quelques exemples :

INSERT INTO Animal (espece, sexe, date_naissance) 
    VALUES ('tortue', 'F', '2009-08-03 05:12:00');
INSERT INTO Animal (nom, commentaires, date_naissance, espece) 
    VALUES ('Choupi', 'Né sans oreille gauche', '2010-10-03 16:44:00', 'chat');
INSERT INTO Animal (espece, date_naissance, commentaires, nom, sexe) 
    VALUES ('tortue', '2009-06-13 08:17:00', 'Carapace bizarre', 'Bobosse', 'F');

Ce qui vous donne trois animaux supplémentaires (donc six en tout, il faut suivre !)

Insertion multiple

Si vous avez plusieurs lignes à introduire, il est possible de le faire en une seule requête de la manière suivante :

INSERT INTO Animal (espece, sexe, date_naissance, nom) 
VALUES ('chien', 'F', '2008-12-06 05:18:00', 'Caroline'),
        ('chat', 'M', '2008-09-11 15:38:00', 'Bagherra'),
        ('tortue', NULL, '2010-08-23 05:18:00', NULL);

Bien entendu, vous êtes alors obligés de préciser les mêmes colonnes pour chaque entrée, quitte à mettre NULL pour certaines. Mais avouez que ça fait quand même moins à écrire !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Insertion de données Syntaxe alternative de MySQL

Syntaxe alternative de MySQL

Syntaxe de INSERT Utilisation de fichiers externes

Syntaxe alternative de MySQL

MySQL propose une syntaxe alternative à INSERT INTO ... VALUES ... pour insérer des données dans une table.

INSERT INTO Animal 
SET nom='Bobo', espece='chien', sexe='M', date_naissance='2010-07-21 15:41:00';

Cette syntaxe présente deux avantages.

En effet, cette syntaxe présente deux défauts majeurs.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe de INSERT Utilisation de fichiers externes

Utilisation de fichiers externes

Syntaxe alternative de MySQL Remplissage de la base

Utilisation de fichiers externes

Maintenant que vous savez insérer des données, je vous propose de remplir un peu cette table, histoire qu'on puisse s'amuser par la suite.
Rassurez-vous, je ne vais pas vous demander d'inventer cinquante bestioles et d'écrire une à une les requêtes permettant de les insérer. Je vous ai prémâché le boulot. De plus, ça nous permettra d'avoir, vous et moi, la même chose dans notre base. Ce sera ainsi plus facile de vérifier que vos requêtes font bien ce qu'elles doivent faire.
Et pour éviter d'écrire vous-mêmes toutes les requêtes d'insertion, nous allons donc voir comment on peut utiliser un fichier texte pour interagir avec notre base de données.

Exécuter des commandes SQL à partir d'un fichier

Écrire toutes les commandes à la main dans la console, ça peut vite devenir pénible. Quand c'est une petite requête, pas de problème. Mais quand vous avez une longue requête, ou beaucoup de requêtes à faire, ça peut être assez long.

Une solution sympathique est d'écrire les requêtes dans un fichier texte, puis de dire à MySQL d'exécuter les requêtes contenues dans ce fichier. Et pour lui dire ça, c'est facile :

SOURCE monFichier.sql;

Ou

\. monFichier.sql;

Ces deux commandes sont équivalentes et vont exécuter le fichier monFichier.sql. Il n'est pas indispensable de lui donner l'extension .sql, mais je préfère le faire pour repérer mes fichiers SQL directement. De plus, si vous utilisez un éditeur de texte un peu plus évolué que le bloc-note (ou textEdit sur Mac), cela colorera votre code SQL, ce qui vous facilitera aussi les choses.

Attention : si vous ne lui indiquez pas le chemin, MySQL va aller chercher votre fichier dans le dossier où vous étiez lors de votre connexion.

Exemple : on donne le chemin complet vers le fichier

SOURCE C:\Document and Settings\dossierX\monFichier.sql;
Insérer des données à partir d'un fichier formaté

Par fichier formaté, j'entends un fichier qui suit certaines règles de format. Un exemple typique serait les fichiers .csv. Ces fichiers contiennent un certain nombre de données et sont organisés en tables. Chaque ligne correspond à une entrée, et les colonnes de la table sont séparées par un caractère défini (souvent une virgule ou un point-virgule). Ceci par exemple, est un format csv :

nom;prenom;date_naissance
Charles;Myeur;1994-12-30
Bruno;Debor;1978-05-12
Mireille;Franelli;1990-08-23

Ce type de fichier est facile à produire (et à lire) avec un logiciel de type tableur (Microsoft Excel, ExcelViewer, Numbers…). La bonne nouvelle est qu'il est aussi possible de lire ce type de fichier avec MySQL, afin de remplir une table avec les données contenues dans le fichier.
La commande SQL permettant cela est LOAD DATA INFILE, dont voici la syntaxe :

LOAD DATA [LOCAL] INFILE 'nom_fichier'
INTO TABLE nom_table
[FIELDS
    [TERMINATED BY '\t']
    [ENCLOSED BY '']
    [ESCAPED BY '\\' ]
]
[LINES 
    [STARTING BY '']    
    [TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];

Le mot-clé LOCAL sert à spécifier si le fichier se trouve côté client (dans ce cas, on utilise LOCAL) ou côté serveur (auquel cas, on ne met pas LOCAL dans la commande). Si le fichier se trouve du côté serveur, il est obligatoire, pour des raisons de sécurité, qu'il soit dans le répertoire de la base de données, c'est-à-dire dans le répertoire créé par MySQL à la création de la base de données, et qui contient les fichiers dans lesquels sont stockées les données de la base. Pour ma part, j'utiliserai toujours LOCAL, afin de pouvoir mettre simplement mes fichiers dans mon dossier de travail.

Les clauses FIELDS et LINES permettent de définir le format de fichier utilisé. FIELDS se rapporte aux colonnes, et LINES aux lignes (si si ^^ ). Ces deux clauses sont facultatives. Les valeurs que j'ai mises ci-dessus sont les valeurs par défaut.

Si vous précisez une clause FIELDS, il faut lui donner au moins une des trois "sous-clauses".

De même pour LINES, si vous l'utilisez, il faut lui donner une ou deux sous-clauses.

La clause IGNORE nombre LINES permet… d'ignorer un certain nombre de lignes. Par exemple, si la première ligne de votre fichier contient les noms des colonnes, vous ne voulez pas l'insérer dans votre table. Il suffit alors d'utiliser IGNORE 1 LINES.

Enfin, vous pouvez préciser le nom des colonnes présentes dans votre fichier. Attention évidemment à ce que les colonnes absentes acceptent NULL ou soient auto-incrémentées.

Si je reprends mon exemple, en imaginant que nous ayons une table Personne contenant les colonnes id (clé primaire auto-incrémentée), nom, prenom, date_naissance et adresse (qui peut être NULL).

nom;prenom;date_naissance
Charles;Myeur;1994-12-30
Bruno;Debor;1978-05-12
Mireille;Franelli;1990-08-23

Si ce fichier est enregistré sous le nom personne.csv, il vous suffit d'exécuter la commande suivante pour enregistrer ces trois lignes dans la table Personne, en spécifiant si nécessaire le chemin complet vers personne.csv :

LOAD DATA LOCAL INFILE 'personne.csv'
INTO TABLE Personne
FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' -- ou '\r\n' selon l'ordinateur et le programme utilisés pour créer le fichier
IGNORE 1 LINES
(nom,prenom,date_naissance);
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe alternative de MySQL Remplissage de la base

Remplissage de la base

Utilisation de fichiers externes Sélection de données

Remplissage de la base

Nous allons utiliser les deux techniques que je viens de vous montrer pour remplir un peu notre base. N'oubliez pas de modifier les commandes données pour ajouter le chemin vers vos fichiers,

Exécution de commandes SQL

Voici donc le code que je vous demande de copier-coller dans votre éditeur de texte préféré, puis de le sauver sous le nom remplissageAnimal.sql (ou un autre nom de votre choix).

INSERT INTO Animal (espece, sexe, date_naissance, nom, commentaires) VALUES 
('chien', 'F', '2008-02-20 15:45:00' , 'Canaille', NULL),
('chien', 'F','2009-05-26 08:54:00'  , 'Cali', NULL),
('chien', 'F','2007-04-24 12:54:00' , 'Rouquine', NULL),
('chien', 'F','2009-05-26 08:56:00' , 'Fila', NULL),
('chien', 'F','2008-02-20 15:47:00' , 'Anya', NULL),
('chien', 'F','2009-05-26 08:50:00' ,'Louya' , NULL),
('chien', 'F', '2008-03-10 13:45:00','Welva' , NULL),
('chien', 'F','2007-04-24 12:59:00' ,'Zira' , NULL),
('chien', 'F', '2009-05-26 09:02:00','Java' , NULL),
('chien', 'M','2007-04-24 12:45:00' ,'Balou' , NULL),
('chien', 'M','2008-03-10 13:43:00' ,'Pataud' , NULL),
('chien', 'M','2007-04-24 12:42:00' , 'Bouli', NULL),
('chien', 'M', '2009-03-05 13:54:00','Zoulou' , NULL),
('chien', 'M','2007-04-12 05:23:00' ,'Cartouche' , NULL),
('chien', 'M', '2006-05-14 15:50:00', 'Zambo', NULL),
('chien', 'M','2006-05-14 15:48:00' ,'Samba' , NULL),
('chien', 'M', '2008-03-10 13:40:00','Moka' , NULL),
('chien', 'M', '2006-05-14 15:40:00','Pilou' , NULL),
('chat', 'M','2009-05-14 06:30:00' , 'Fiero', NULL),
('chat', 'M','2007-03-12 12:05:00' ,'Zonko', NULL),
('chat', 'M','2008-02-20 15:45:00' , 'Filou', NULL),
('chat', 'M','2007-03-12 12:07:00' , 'Farceur', NULL),
('chat', 'M','2006-05-19 16:17:00' ,'Caribou' , NULL),
('chat', 'M','2008-04-20 03:22:00' , 'Capou', NULL),
('chat', 'M','2006-05-19 16:56:00' , 'Raccou', 'Pas de queue depuis la naissance');

Vous n'avez alors qu'à taper :

SOURCE remplissageAnimal.sql;
LOAD DATA INFILE

À nouveau, copiez-collez le texte ci-dessous dans votre éditeur de texte, et enregistrez le fichier. Cette fois, sous le nom animal.csv.

"chat";"M";"2009-05-14 06:42:00";"Boucan";NULL
"chat";"F";"2006-05-19 16:06:00";"Callune";NULL
"chat";"F";"2009-05-14 06:45:00";"Boule";NULL
"chat";"F";"2008-04-20 03:26:00";"Zara";NULL
"chat";"F";"2007-03-12 12:00:00";"Milla";NULL
"chat";"F";"2006-05-19 15:59:00";"Feta";NULL
"chat";"F";"2008-04-20 03:20:00";"Bilba";"Sourde de l'oreille droite à 80%"
"chat";"F";"2007-03-12 11:54:00";"Cracotte";NULL
"chat";"F";"2006-05-19 16:16:00";"Cawette";NULL
"tortue";"F";"2007-04-01 18:17:00";"Nikki";NULL
"tortue";"F";"2009-03-24 08:23:00";"Tortilla";NULL
"tortue";"F";"2009-03-26 01:24:00";"Scroupy";NULL
"tortue";"F";"2006-03-15 14:56:00";"Lulla";NULL
"tortue";"F";"2008-03-15 12:02:00";"Dana";NULL
"tortue";"F";"2009-05-25 19:57:00";"Cheli";NULL
"tortue";"F";"2007-04-01 03:54:00";"Chicaca";NULL
"tortue";"F";"2006-03-15 14:26:00";"Redbul";"Insomniaque"
"tortue";"M";"2007-04-02 01:45:00";"Spoutnik";NULL
"tortue";"M";"2008-03-16 08:20:00";"Bubulle";NULL
"tortue";"M";"2008-03-15 18:45:00";"Relou";"Surpoids"
"tortue";"M";"2009-05-25 18:54:00";"Bulbizard";NULL
"perroquet";"M";"2007-03-04 19:36:00";"Safran";NULL
"perroquet";"M";"2008-02-20 02:50:00";"Gingko";NULL
"perroquet";"M";"2009-03-26 08:28:00";"Bavard";NULL
"perroquet";"F";"2009-03-26 07:55:00";"Parlotte";NULL

Exécutez ensuite la commande suivante :

LOAD DATA LOCAL INFILE 'animal.csv'
INTO TABLE Animal
FIELDS TERMINATED BY ';' ENCLOSED BY '"'
LINES TERMINATED BY '\n' -- ou '\r\n' selon l'ordinateur et le programme utilisés pour créer le fichier
(espece, sexe, date_naissance, nom, commentaires);

Et hop ! Vous avez plus d'une cinquantaine d'animaux dans votre table.

Si vous voulez vérifier, je rappelle que vous pouvez utiliser la commande suivante, qui vous affichera toutes les données contenues dans la table Animal.

SELECT * FROM Animal;

Nous pouvons maintenant passer au chapitre suivant !

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Utilisation de fichiers externes Sélection de données

Sélection de données

Remplissage de la base Syntaxe de SELECT

Comme son nom l'indique, ce chapitre traitera de la sélection et de l'affichage de données.

Au menu :

Motivés ? Alors c'est parti !!! ^^

Syntaxe de SELECT

Sélection de données La clause WHERE

Syntaxe de SELECT

La requête qui permet de sélectionner et afficher des données s'appelle SELECT. Nous l'avons déjà un peu utilisée dans le chapitre d'installation, ainsi que pour afficher tout le contenu de la table Animal.

SELECT permet donc d'afficher des données directement. Des chaînes de caractères, des résultats de calculs, etc.

Exemple

SELECT 'Hello World !';
SELECT 3+2;
SELECT

permet également de sélectionner des données à partir d'une table. Pour cela, il faut ajouter une clause à la commande SELECT : la clause FROM, qui définit de quelle structure (dans notre cas, une table) viennent les données.

SELECT colonne1, colonne2, ... 
FROM nom_table;

Par exemple, si l'on veut sélectionner l'espèce, le nom et le sexe des animaux présents dans la table Animal, on utilisera :

SELECT espece, nom, sexe 
FROM Animal;
Sélectionner toutes les colonnes

Si vous désirez sélectionner toutes les colonnes, vous pouvez utiliser le caractère * dans votre requête :

SELECT * 
FROM Animal;

Il est cependant déconseillé d'utiliser SELECT * trop souvent. Donner explicitement le nom des colonnes dont vous avez besoin présente deux avantages :

Le désavantage est bien sûr que vous avez plus à écrire, mais le jeu en vaut la chandelle.

Comme vous avez pu le constater, les requêtes SELECT faites jusqu'à présent sélectionnent toutes les lignes de la table. Or, bien souvent, on ne veut qu'une partie des données. Dans la suite de ce chapitre, nous allons voir ce que nous pouvons ajouter à cette requête SELECT pour faire des sélections à l'aide de critères.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Sélection de données La clause WHERE

La clause WHERE

Syntaxe de SELECT Tri des données

La clause WHERE

La clause WHERE ("où" en anglais) permet de restreindre les résultats selon des critères de recherche. On peut par exemple vouloir ne sélectionner que les chiens :

SELECT * 
FROM Animal 
WHERE espece='chien';
Les opérateurs de comparaison

Les opérateurs de comparaison sont les symboles que l'ont utilise pour définir les critères de recherche (le = dans notre exemple précédent). Huit opérateurs simples peuvent être utilisés.

Opérateur

Signification

=

égal

<

inférieur

<=

inférieur ou égal

>

supérieur

>=

supérieur ou égal

<> ou !=

différent

<=>

égal (valable pour NULL aussi)

Exemples :

SELECT * 
FROM Animal 
WHERE date_naissance < '2008-01-01'; -- Animaux nés avant 2008

SELECT * 
FROM Animal 
WHERE espece <> 'chat'; -- Tous les animaux sauf les chats
Combinaisons de critères

Tout ça c'est bien beau, mais comment faire si on veut les chats et les chiens par exemple ? Faut-il faire deux requêtes ?
Non bien sûr, il suffit de combiner les critères. Pour cela, il faut des opérateurs logiques, qui sont au nombre de quatre :

Opérateur

Symbole

Signification

AND

&&

ET

OR

||

OU

XOR

OU exclusif

NOT

!

NON

Voici quelques exemples, sûrement plus efficaces qu'un long discours.

AND

Je veux sélectionner toutes les chattes. Je veux donc sélectionner les animaux qui sont à la fois des chats ET des femelles. J'utilise l'opérateur AND :

SELECT * 
FROM Animal 
WHERE espece='chat' 
    AND sexe='F';
-- OU
SELECT * 
FROM Animal 
WHERE espece='chat' 
    && sexe='F';
OR

Sélection des tortues et des perroquets. Je désire donc obtenir les animaux qui sont des tortues OU des perroquets :

SELECT * 
FROM Animal 
WHERE espece='tortue' 
    OR espece='perroquet';
-- OU
SELECT * 
FROM Animal 
WHERE espece='tortue' 
    || espece='perroquet';
NOT

Sélection de tous les animaux femelles sauf les chiennes.

SELECT * 
FROM Animal 
WHERE sexe='F' 
    AND NOT espece='chien';
-- OU
SELECT * 
FROM Animal 
WHERE sexe='F' 
    AND ! espece='chien';
XOR

Sélection des animaux qui sont soit des mâles, soit des perroquets (mais pas les deux) :

SELECT * 
FROM Animal 
WHERE sexe='M' 
    XOR espece='perroquet';

Et voilà pour les opérateurs logiques. Rien de bien compliqué, et pourtant, c'est souvent source d'erreur. Pourquoi ? Tout simplement parce que tant que vous n'utilisez qu'un seul opérateur logique, tout va très bien. Mais on a souvent besoin de combiner plus de deux critères, et c'est là que ça se corse.

Sélection complexe

Lorsque vous utilisez plusieurs critères, et que vous devez donc combiner plusieurs opérateurs logiques, il est extrêmement important de bien structurer la requête. En particulier, il faut placer des parenthèses au bon endroit. En effet, cela n'a pas de sens de mettre plusieurs opérateurs logiques différents sur un même niveau.
Petit exemple simple :
Critères : rougeANDvertORbleu

Qu'accepte-t-on ?

Dans le premier cas, [rouge, vert] et [bleu] seraient acceptés. Dans le deuxième, c'est [rouge, vert] et [rouge, bleu] qui seront acceptés, et non [bleu].

En fait, le premier cas correspond à (rougeANDvert) ORbleu, et le deuxième cas à rougeAND (vertORbleu).

Avec des parenthèses, pas moyen de se tromper sur ce qu'on désire sélectionner !

Exercice/Exemple

Alors, imaginons une requête bien tordue…

Je voudrais les animaux qui sont, soit nés après 2009, soit des chats mâles ou femelles, mais dans le cas des femelles, elles doivent être nées avant juin 2007.

Je vous conseille d'essayer d'écrire cette requête tout seuls. Si vous n'y arrivez pas, voici une petite aide : l'astuce, c'est de penser en niveaux. Je vais donc découper ma requête.

Je cherche :

C'est mon premier niveau. L'opérateur logique sera OR puisqu'il faut que les animaux répondent à un seul des deux critères pour être sélectionnés.

On continue à découper. Le premier critère ne peut plus être subdivisé, contrairement au deuxième. Je cherche :

Et voilà, vous avez bien défini les différents niveaux, il n'y a plus qu'à écrire la requête avec les bons opérateurs logiques !

SELECT * 
FROM Animal 
WHERE date_naissance > '2009-12-31'
    OR
    ( espece='chat'
         AND
        ( sexe='M'
            OR
            ( sexe='F' AND date_naissance < '2007-06-01' )
        )
    );
Le cas de NULL

Vous vous souvenez sans doute de la liste des opérateurs de comparaison que je vous ai présentée (sinon, retournez au début de la partie sur la clause WHERE). Vous avez probablement été un peu étonnés de voir dans cette liste l'opérateur <=> : égal (valable aussi pour NULL). D'autant plus que j'ai fait un peu semblant de rien et ne vous ai pas donné d'explication sur cette mystérieuse précision "aussi valable pour NULL" :-° . Mais je vais me rattraper maintenant !
En fait, c'est très simple, le marqueur NULL (qui représente donc "pas de valeur") est un peu particulier. En effet, vous ne pouvez pas tester directement colonne = NULL. Essayons donc :

SELECT * 
FROM Animal 
WHERE nom = NULL; -- sélection des animaux sans nom

SELECT * 
FROM Animal  
WHERE commentaires <> NULL; -- sélection des animaux pour lesquels un commentaire existe

Comme vous pouvez vous en douter après ma petite introduction, ces deux requêtes ne renvoient pas les résultats que l'on pourrait espérer. En fait, elles ne renvoient aucun résultat. C'est donc ici qu'intervient notre opérateur de comparaison un peu spécial <=> qui permet de reconnaître NULL. Une autre possibilité est d'utiliser les mots-clés IS NULL, et si l'on veut exclure les NULL : IS NOT NULL. Nous pouvons donc réécrire nos requêtes, correctement cette fois-ci :

SELECT * 
FROM Animal 
WHERE nom <=> NULL; -- sélection des animaux sans nom
-- OU
SELECT * 
FROM Animal 
WHERE nom IS NULL;

SELECT * 
FROM Animal 
WHERE commentaires IS NOT NULL; -- sélection des animaux pour lesquels un commentaire existe

Cette fois-ci, ça fonctionne parfaitement !

id

espece

sexe

date_naissance

nom

commentaires

4

tortue

F

2009-08-03 05:12:00

NULL

NULL

9

tortue

NULL

2010-08-23 05:18:00

NULL

NULL

id

espece

sexe

date_naissance

nom

commentaires

1

chien

M

2010-04-05 13:43:00

Rox

Mordille beaucoup

5

chat

NULL

2010-10-03 16:44:00

Choupi

Né sans oreille gauche

6

tortue

F

2009-06-13 08:17:00

Bobosse

Carapace bizarre

35

chat

M

2006-05-19 16:56:00

Raccou

Pas de queue depuis la naissance

52

tortue

F

2006-03-15 14:26:00

Redbul

Insomniaque

55

tortue

M

2008-03-15 18:45:00

Relou

Surpoids

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe de SELECT Tri des données

Tri des données

La clause WHERE Éliminer les doublons

Tri des données

Lorsque vous faites un SELECT, les données sont récupérées dans un ordre défini par MySQL, mais qui n'a aucun sens pour vous. Vous avez sans doute l'impression que MySQL renvoie tout simplement les lignes dans l'ordre dans lequel elles ont été insérées, mais ce n'est pas exactement le cas. En effet, si vous supprimez des lignes, puis en ajoutez de nouvelles, les nouvelles lignes viendront remplacer les anciennes dans l'ordre de MySQL. Or, bien souvent, vous voudrez trier à votre manière. Par date de naissance par exemple, ou bien par espèce, ou par sexe, etc.

Pour trier vos données, c'est très simple, il suffit d'ajouter ORDER BY tri à votre requête (après les critères de sélection de WHERE s'il y en a) et de remplacer "tri" par la colonne sur laquelle vous voulez trier vos données bien sûr.

Par exemple, pour trier par date de naissance :

SELECT * 
FROM Animal 
WHERE espece='chien' 
ORDER BY date_naissance;

Et hop ! Vos données sont triées, les plus vieux chiens sont récupérés en premier, les jeunes à la fin.

Tri ascendant ou descendant

Tout ça c'est bien beau, j'ai mes chiens triés du plus vieux au plus jeune. Et si je veux le contraire ?
Pour déterminer le sens du tri effectué, SQL possède deux mots-clés : ASC pour ascendant, et DESC pour descendant. Par défaut, si vous ne précisez rien, c'est un tri ascendant qui est effectué : du plus petit nombre au plus grand, de la date la plus ancienne à la plus récente, et pour les chaînes de caractères et les textes, c'est l'ordre alphabétique normal qui est utilisé.
Si par contre vous utilisez le mot DESC, l'ordre est inversé : plus grand nombre d'abord, date la plus récente d'abord, et ordre anti-alphabétique pour les caractères.

SELECT * 
FROM Animal 
WHERE espece='chien' 
    AND nom IS NOT NULL 
ORDER BY nom DESC;
Trier sur plusieurs colonnes

Il est également possible de trier sur plusieurs colonnes. Par exemple, si vous voulez que les résultats soient triés par espèce et, dans chaque espèce, triés par date de naissance, il suffit de donner les deux colonnes correspondantes à ORDER BY :

SELECT * 
FROM Animal 
ORDER BY espece, date_naissance;

Vous pouvez trier sur autant de colonnes que vous voulez.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

La clause WHERE Éliminer les doublons

Éliminer les doublons

Tri des données Restreindre les résultats

Éliminer les doublons

Il peut arriver que MySQL vous donne plusieurs fois le même résultat. Non pas parce que MySQL fait des bêtises, mais tout simplement parce que certaines informations sont présentes plusieurs fois dans la table.

Petit exemple très parlant : vous voulez savoir quelles sont les espèces que vous possédez dans votre élevage. Facile, une petite requête :

SELECT espece 
FROM Animal;

En effet, vous allez bien récupérer toutes les espèces que vous possédez, mais si vous avez 500 chiens, vous allez récupérer 500 lignes 'chien'.
Un peu embêtant lorsque la table devient bien remplie.

Heureusement, il y a une solution : le mot-clé DISTINCT.
Ce mot-clé se place juste après SELECT et permet d'éliminer les doublons.

SELECT DISTINCT espece 
FROM Animal;

Ceci devrait gentiment vous ramener quatre lignes avec les quatre espèces qui se trouvent dans la table. C'est quand même plus clair non ?

Attention cependant, pour éliminer un doublon, il faut que toute la ligne sélectionnée soit égale à une autre ligne du jeu de résultats. Ça peut paraître logique, mais cela en perd plus d'un. Ne seront donc prises en compte que les colonnes que vous avez précisées dans votre SELECT. Uniquement espece donc, dans notre exemple.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Tri des données Restreindre les résultats

Restreindre les résultats

Éliminer les doublons Élargir les possibilités de la clause WHERE

Restreindre les résultats

En plus de restreindre une recherche en lui donnant des critères grâce à la clause WHERE, il est possible de restreindre le nombre de lignes récupérées.
Cela se fait grâce à la clause LIMIT.

Syntaxe

LIMIT s'utilise avec deux paramètres.

LIMIT nombre_de_lignes [OFFSET decalage];

Exemple :

SELECT * 
FROM Animal 
ORDER BY id 
LIMIT 6 OFFSET 0;

SELECT * 
FROM Animal 
ORDER BY id 
LIMIT 6 OFFSET 3;

Avec la première requête, vous devriez obtenir six lignes, les six plus petits id puisque nous n'avons demandé aucun décalage (OFFSET 0).

id

espece

sexe

date_naissance

nom

commentaires

1

chien

M

2010-04-05 13:43:00

Rox

Mordille beaucoup

2

chat

NULL

2010-03-24 02:23:00

Roucky

NULL

3

chat

F

2010-09-13 15:02:00

Schtroumpfette

NULL

4

tortue

F

2009-08-03 05:12:00

NULL

NULL

5

chat

NULL

2010-10-03 16:44:00

Choupi

Né sans oreille gauche

6

tortue

F

2009-06-13 08:17:00

Bobosse

Carapace bizarre

Par contre, dans la deuxième, vous récupérez toujours six lignes, mais vous devriez commencer au quatrième plus petit id, puisqu'on a demandé un décalage de trois lignes.

id

espece

sexe

date_naissance

nom

commentaires

4

tortue

F

2009-08-03 05:12:00

NULL

NULL

5

chat

NULL

2010-10-03 16:44:00

Choupi

Né sans oreille gauche

6

tortue

F

2009-06-13 08:17:00

Bobosse

Carapace bizarre

7

chien

F

2008-12-06 05:18:00

Caroline

NULL

8

chat

M

2008-09-11 15:38:00

Bagherra

NULL

9

tortue

NULL

2010-08-23 05:18:00

NULL

NULL

Exemple avec un seul paramètre :

SELECT * 
FROM Animal 
ORDER BY id 
LIMIT 10;

Cette requête est donc équivalente à :

SELECT * 
FROM Animal 
ORDER BY id 
LIMIT 10 OFFSET 0;
Syntaxe alternative

MySQL accepte une autre syntaxe pour la clause LIMIT. Ce n'est cependant pas la norme SQL donc idéalement vous devriez toujours utiliser la syntaxe officielle. Vous vous apercevrez toutefois que cette syntaxe est énormément usitée, je ne pouvais donc pas ne pas la mentionner

SELECT * 
FROM Animal 
ORDER BY id 
LIMIT [decalage, ]nombre_de_lignes;

Tout comme pour la syntaxe officielle, le décalage n'est pas obligatoire, et vaudra 0 par défaut. Si vous le précisez, n'oubliez pas la virgule entre le décalage et le nombre de lignes désirées.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Éliminer les doublons Élargir les possibilités de la clause WHERE

Élargir les possibilités de la clause WHERE

Restreindre les résultats Recherche approximative

Dans le chapitre précédent, vous avez découvert la commande SELECT, ainsi que plusieurs clauses permettant de restreindre et d'ordonner les résultats selon différents critères. Nous allons maintenant revenir plus particulièrement sur la clause WHERE.
Jusqu'ici, les conditions permises par WHERE étaient très basiques. Mais cette clause offre bien d'autres possibilités parmi lesquelles :

Recherche approximative

Élargir les possibilités de la clause WHERE Recherche dans un intervalle

Recherche approximative

Pour l'instant, nous avons vu huit opérateurs de comparaison :

Opérateur

Signification

=

égal

<

inférieur

<=

inférieur ou égal

>

supérieur

>=

supérieur ou égal

<> ou !=

différent

<=>

égal (valable pour NULL aussi)

À l'exception de <=> qui est un peu particulier, ce sont les opérateurs classiques, que vous retrouverez dans tous les langages informatiques. Cependant, il arrive que ces opérateurs ne soient pas suffisants. En particulier pour des recherches sur des chaînes de caractères. En effet, comment faire lorsqu'on ne sait pas si le mot que l'on recherche est au singulier ou au pluriel par exemple ? Ou si l'on cherche toutes les lignes dont le champ "commentaires" contient un mot particulier ?

Pour ce genre de recherches, l'opérateur LIKE est très utile, car il permet de faire des recherches en utilisant des "jokers", c'est-à-dire des caractères qui représentent n'importe quel caractère.

Deux jokers existent pour LIKE :

Quelques exemples :

Rechercher '%' ou '_'

Comment faire si vous cherchez une chaîne de caractères contenant '%' ou '_' ? Évidemment, si vous écrivez LIKE '%' ou LIKE '_', MySQL vous donnera absolument toutes les chaînes de caractères dans le premier cas, et toutes les chaînes de 0 ou 1 caractère dans le deuxième.
Il faut donc signaler à MySQL que vous ne désirez pas utiliser % ou _ en tant que joker, mais bien en tant que caractère de recherche. Pour ça, il suffit de mettre le caractère d'échappement \, dont je vous ai déjà parlé, devant le '%' ou le '_'.

Exemple :

SELECT * 
FROM Animal 
WHERE commentaires LIKE '%\%%';

Résultat :

id

espece

sexe

date_naissance

nom

commentaires

42

chat

F

2008-04-20 03:20:00

Bilba

Sourde de l'oreille droite à 80%

Exclure une chaîne de caractères

C'est logique, mais je précise quand même (et puis ça fait un petit rappel) : l'opérateur logique NOT est utilisable avec LIKE. Si l'on veut rechercher les animaux dont le nom ne contient pas la lettre a, on peut donc écrire :

SELECT * 
FROM Animal 
WHERE nom NOT LIKE '%a%';
Sensibilité à la casse

Vous l'aurez peut-être remarqué en faisant des essais, LIKE 'chaîne de caractères' n'est pas sensible à la casse (donc aux différences majuscules-minuscules). Pour rappel, ceci est dû à l'interclassement. Nous avons gardé l'interclassement par défaut du jeu de caractère UTF-8, qui n'est pas sensible à la casse.
Si vous désirez faire une recherche sensible à la casse, vous pouvez définir votre chaîne de recherche comme une chaîne de type binaire, et non plus comme une simple chaîne de caractères :

SELECT * 
FROM Animal 
WHERE nom LIKE '%Lu%'; -- insensible à la casse

SELECT * 
FROM Animal 
WHERE nom LIKE BINARY '%Lu%'; -- sensible à la casse
Recherche dans les numériques

Vous pouvez bien entendu utiliser des chiffres dans une chaîne de caractères. Après tout, ce sont des caractères comme les autres. Par contre, utiliser LIKE sur un type numérique (INT par exemple), c'est déjà plus étonnant.
Et pourtant, MySQL le permet. Attention cependant, il s'agit bien d'une particularité MySQL, qui prend souvent un malin plaisir à étendre la norme SQL pure.

LIKE '1%' sur une colonne de type numérique trouvera donc des nombres comme 10, 1000, 153

SELECT * 
FROM Animal 
WHERE id LIKE '1%';
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Élargir les possibilités de la clause WHERE Recherche dans un intervalle

Recherche dans un intervalle

Recherche approximative Set de critères

Recherche dans un intervalle

Il est possible de faire une recherche sur un intervalle à l'aide uniquement des opérateurs de comparaison >= et <=. Par exemple, on peut rechercher les animaux qui sont nés entre le 5 janvier 2008 et le 23 mars 2009 de la manière suivante :

SELECT * 
FROM Animal 
WHERE date_naissance <= '2009-03-23' 
    AND date_naissance >= '2008-01-05';

Ça fonctionne très bien. Cependant, SQL dispose d'un opérateur spécifique pour les intervalles, qui pourrait vous éviter les erreurs d'inattention classiques (< au lieu de > par exemple) en plus de rendre votre requête plus lisible et plus performante : BETWEEN minimum AND maximum (between signifie "entre" en anglais). La requête précédente peut donc s'écrire :

SELECT * 
FROM Animal 
WHERE date_naissance BETWEEN '2008-01-05' AND '2009-03-23';
BETWEEN

peut s'utiliser avec des dates, mais aussi avec des nombres (BETWEEN 0 AND 100) ou avec des chaînes de caractères (BETWEEN 'a' AND 'd') auquel cas c'est l'ordre alphabétique qui sera utilisé (toujours insensible à la casse sauf si l'on utilise des chaînes binaires : BETWEEN BINARY 'a' AND BINARY 'd').
Bien évidemment, on peut aussi exclure un intervalle avec NOT BETWEEN.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Recherche approximative Set de critères

Set de critères

Recherche dans un intervalle Suppression et modification de données

Set de critères

Le dernier opérateur à utiliser dans la clause WHERE que nous verrons dans ce chapitre est IN. Ce petit mot de deux lettres, bien souvent méconnu des débutants, va probablement vous permettre d'économiser du temps et des lignes.

Imaginons que vous vouliez récupérer les informations des animaux répondant aux doux noms de Moka, Bilba, Tortilla, Balou, Dana, Redbul et Gingko. Jusqu'à maintenant, vous auriez sans doute fait quelque chose comme ça :

SELECT * 
FROM Animal 
WHERE nom = 'Moka' 
    OR nom = 'Bilba' 
    OR nom = 'Tortilla' 
    OR nom = 'Balou' 
    OR nom = 'Dana' 
    OR nom = 'Redbul' 
    OR nom = 'Gingko';

Un peu fastidieux non :( ? Eh bien réjouissez-vous, car IN est dans la place ! Cet opérateur vous permet de faire des recherches parmi une liste de valeurs. Parfait pour nous donc, qui voulons rechercher les animaux correspondant à une liste de noms. Voici la manière d'utiliser IN :

SELECT * 
FROM Animal 
WHERE nom IN ('Moka', 'Bilba', 'Tortilla', 'Balou', 'Dana', 'Redbul', 'Gingko');

C'est quand même plus agréable à écrire ! :soleil:

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Recherche dans un intervalle Suppression et modification de données

Suppression et modification de données

Set de critères Sauvegarde d'une base de données

Vous savez comment insérer des données, vous savez comment les sélectionner et les ordonner selon les critères de votre choix, il est temps maintenant d'apprendre à les supprimer et les modifier !
Avant cela, un petit détour par le client mysqldump, qui vous permet de sauvegarder vos bases de données. Je ne voudrais en effet pas vous lâcher dans le chapitre de suppression de données sans que vous n'ayez la possibilité de faire un backup de votre base. Je vous connais, vous allez faire des bêtises, et vous direz encore que c'est de ma faute… ;)

Sauvegarde d'une base de données

Suppression et modification de données Suppression

Sauvegarde d'une base de données

Il est bien utile de pouvoir sauvegarder facilement sa base de données, et très important de la sauvegarder régulièrement. Une mauvaise manipulation (ou un méchant pirate :pirate: s'il s'agit d'un site web) et toutes les données peuvent disparaître. MySQL dispose donc d'un outil spécialement dédié à la sauvegarde des données sous forme de fichiers texte : mysqldump.

Cette fonction de sauvegarde s'utilise à partir de la console. Vous devez donc être déconnectés de MySQL pour la lancer. Si c'est votre cas, tapez simplement exit

Vous êtes maintenant dans la console Windows (ou Mac, ou Linux).
La manière classique de faire une sauvegarde d'une base de données est de taper la commande suivante :

mysqldump -u user -p --opt nom_de_la_base > sauvegarde.sql

Décortiquons cette commande.

Lancez la commande suivante pour sauvegarder elevage dans votre dossier courant (c'est-à-dire le dossier dans lequel vous étiez au moment de la connexion) :

mysqldump -u sdz -p --opt elevage > elevage_sauvegarde.sql

Puis, allez voir dans le dossier. Vous devriez y trouver un fichier elevage_sauvegarde.sql. Ouvrez-le avec un éditeur de texte.
Vous pouvez voir nombre de commandes SQL qui servent à la création des tables de la base de données, ainsi qu'à l'insertion des données. S'ajoutent à cela quelques commandes qui vont sélectionner le bon encodage, etc.

La base de données est donc sauvegardée. Notez que la commande pour créer la base elle-même n'est pas sauvée. Donc, si vous effacez votre base par mégarde, il vous faut d'abord recréer la base de données (avec CREATE DATABASE nom_base), puis exécuter la commande suivante (dans la console) :

mysql nom_base < chemin_fichier_de_sauvegarde.sql

Concrètement, dans notre cas :

mysql elevage < elevage_sauvegarde.sql

Ou, directement à partir de MySQL :

USE nom_base;
source fichier_de_sauvegarde.sql;

Donc :

USE elevage;
source elevage_sauvegarde.sql;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Suppression et modification de données Suppression

Suppression

Sauvegarde d'une base de données Modification

Suppression

La commande utilisée pour supprimer des données est DELETE. Cette opération est irréversible, soyez très prudents !
On utilise la clause WHERE de la même manière qu'avec la commande SELECT pour préciser quelles lignes doivent être supprimées.

DELETE FROM nom_table 
WHERE critères;

Par exemple : Zoulou est mort, paix à son âme :ange: ... Nous allons donc le retirer de la base de données.

DELETE FROM Animal 
WHERE nom = 'Zoulou';

Et voilà, plus de Zoulou :'( !

Si vous désirez supprimer toutes les lignes d'une table, il suffit de ne pas préciser de clause WHERE.

DELETE FROM Animal;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Sauvegarde d'une base de données Modification

Modification

Suppression Index

Modification

La modification des données se fait grâce à la commande UPDATE, dont la syntaxe est la suivante :

UPDATE nom_table 
SET col1 = val1 [, col2 = val2, ...] 
[WHERE ...];

Par exemple, vous étiez persuadés que ce petit Pataud était un mâle mais, quelques semaines plus tard, vous vous rendez compte de votre erreur. Il vous faut donc modifier son sexe, mais aussi son nom. Voici la requête qui va vous le permettre :

UPDATE Animal 
SET sexe='F', nom='Pataude' 
WHERE id=21;

Vérifiez d'abord chez vous que l'animal portant le numéro d'identification 21 est bien Pataud. J'utilise ici la clé primaire (donc id) pour identifier la ligne à modifier, car c'est la seule manière d'être sûr que je ne modifierai que la ligne que je désire. En effet, il est possible que plusieurs animaux aient pour nom "Pataud". Ce n'est a priori pas notre cas, mais prenons tout de suite de bonnes habitudes.

La requête suivante changerait donc le commentaire de tous les animaux stockés dans la table Animal (ne l'exécutez pas).

UPDATE Animal 
SET commentaires='modification de toutes les lignes';
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Suppression Index

Index

Modification Qu'est-ce qu'un index ?

Un index est une structure qui reprend la liste ordonnée des valeurs auxquelles il se rapporte.
Les index sont utilisés pour accélérer les requêtes (notamment les requêtes impliquant plusieurs tables, ou les requêtes de recherche), et sont indispensables à la création de clés, étrangères et primaires, qui permettent de garantir l'intégrité des données de la base et dont nous discuterons au chapitre suivant.

Au programme de ce chapitre :

Etat actuelle de la base de données
Note : les tables de test ne sont pas reprises

SET NAMES utf8;

DROP TABLE IF EXISTS Animal;

CREATE TABLE Animal (
  id smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT,
  espece varchar(40) NOT NULL,
  sexe char(1) DEFAULT NULL,
  date_naissance datetime NOT NULL,
  nom varchar(30) DEFAULT NULL,
  commentaires text,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8;

LOCK TABLES Animal WRITE;
INSERT INTO Animal VALUES 
(1,'chien','M','2010-04-05 13:43:00','Rox','Mordille beaucoup'),(2,'chat',NULL,'2010-03-24 02:23:00','Roucky',NULL),(3,'chat','F','2010-09-13 15:02:00','Schtroumpfette',NULL),
(4,'tortue','F','2009-08-03 05:12:00',NULL,NULL),(5,'chat',NULL,'2010-10-03 16:44:00','Choupi','Né sans oreille gauche'),(6,'tortue','F','2009-06-13 08:17:00','Bobosse','Carapace bizarre'),
(7,'chien','F','2008-12-06 05:18:00','Caroline',NULL),(8,'chat','M','2008-09-11 15:38:00','Bagherra',NULL),(9,'tortue',NULL,'2010-08-23 05:18:00',NULL,NULL),
(10,'chien','M','2010-07-21 15:41:00','Bobo',NULL),(11,'chien','F','2008-02-20 15:45:00','Canaille',NULL),(12,'chien','F','2009-05-26 08:54:00','Cali',NULL),
(13,'chien','F','2007-04-24 12:54:00','Rouquine',NULL),(14,'chien','F','2009-05-26 08:56:00','Fila',NULL),(15,'chien','F','2008-02-20 15:47:00','Anya',NULL),
(16,'chien','F','2009-05-26 08:50:00','Louya',NULL),(17,'chien','F','2008-03-10 13:45:00','Welva',NULL),(18,'chien','F','2007-04-24 12:59:00','Zira',NULL),
(19,'chien','F','2009-05-26 09:02:00','Java',NULL),(20,'chien','M','2007-04-24 12:45:00','Balou',NULL),(21,'chien','F','2008-03-10 13:43:00','Pataude',NULL),
(22,'chien','M','2007-04-24 12:42:00','Bouli',NULL),(24,'chien','M','2007-04-12 05:23:00','Cartouche',NULL),(25,'chien','M','2006-05-14 15:50:00','Zambo',NULL),
(26,'chien','M','2006-05-14 15:48:00','Samba',NULL),(27,'chien','M','2008-03-10 13:40:00','Moka',NULL),(28,'chien','M','2006-05-14 15:40:00','Pilou',NULL),
(29,'chat','M','2009-05-14 06:30:00','Fiero',NULL),(30,'chat','M','2007-03-12 12:05:00','Zonko',NULL),(31,'chat','M','2008-02-20 15:45:00','Filou',NULL),
(32,'chat','M','2007-03-12 12:07:00','Farceur',NULL),(33,'chat','M','2006-05-19 16:17:00','Caribou',NULL),(34,'chat','M','2008-04-20 03:22:00','Capou',NULL),
(35,'chat','M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance'),(36,'chat','M','2009-05-14 06:42:00','Boucan',NULL),(37,'chat','F','2006-05-19 16:06:00','Callune',NULL),
(38,'chat','F','2009-05-14 06:45:00','Boule',NULL),(39,'chat','F','2008-04-20 03:26:00','Zara',NULL),(40,'chat','F','2007-03-12 12:00:00','Milla',NULL),
(41,'chat','F','2006-05-19 15:59:00','Feta',NULL),(42,'chat','F','2008-04-20 03:20:00','Bilba','Sourde de l''oreille droite à 80%'),(43,'chat','F','2007-03-12 11:54:00','Cracotte',NULL),
(44,'chat','F','2006-05-19 16:16:00','Cawette',NULL),(45,'tortue','F','2007-04-01 18:17:00','Nikki',NULL),(46,'tortue','F','2009-03-24 08:23:00','Tortilla',NULL),
(47,'tortue','F','2009-03-26 01:24:00','Scroupy',NULL),(48,'tortue','F','2006-03-15 14:56:00','Lulla',NULL),(49,'tortue','F','2008-03-15 12:02:00','Dana',NULL),
(50,'tortue','F','2009-05-25 19:57:00','Cheli',NULL),(51,'tortue','F','2007-04-01 03:54:00','Chicaca',NULL),(52,'tortue','F','2006-03-15 14:26:00','Redbul','Insomniaque'),
(53,'tortue','M','2007-04-02 01:45:00','Spoutnik',NULL),(54,'tortue','M','2008-03-16 08:20:00','Bubulle',NULL),(55,'tortue','M','2008-03-15 18:45:00','Relou','Surpoids'),
(56,'tortue','M','2009-05-25 18:54:00','Bulbizard',NULL),(57,'perroquet','M','2007-03-04 19:36:00','Safran',NULL),(58,'perroquet','M','2008-02-20 02:50:00','Gingko',NULL),
(59,'perroquet','M','2009-03-26 08:28:00','Bavard',NULL),(60,'perroquet','F','2009-03-26 07:55:00','Parlotte',NULL);
UNLOCK TABLES;

Qu'est-ce qu'un index ?

Index Les différents types d'index

Qu'est-ce qu'un index ?

Revoyons la définition d'un index.

Citation : Définition

Structure de données qui reprend la liste ordonnée des valeurs auxquelles il se rapporte.

Lorsque vous créez un index sur une table, MySQL stocke cet index sous forme d'une structure particulière, contenant les valeurs des colonnes impliquées dans l'index. Cette structure stocke les valeurs triées et permet d'accéder à chacune de manière efficace et rapide. Petit schéma explicatif dans le cas d'un index sur l'id de la table Animal (je ne prends que les neuf premières lignes pour ne pas surcharger).

Index sur l'id

Les données d'Animal ne sont pas stockées suivant un ordre intelligible pour nous. Par contre, l'index sur l'id est trié simplement par ordre croissant. Cela permet de grandement accélérer toute recherche faite sur cet id.
Imaginons en effet, que nous voulions récupérer toutes les lignes dont l'id est inférieur ou égal à 5. Sans index, MyQSL doit parcourir toutes les lignes une à une. Par contre, grâce à l'index, dès qu'il tombe sur la ligne dont l'id est 6, il sait qu'il peut s'arrêter, puisque toutes les lignes suivantes auront un id supérieur ou égal à 6. Dans cet exemple, on ne gagne que quelques lignes, mais imaginez une table contenant des milliers de lignes. Le gain de temps peut être assez considérable.
Par ailleurs, avec les id triés par ordre croissant, pour rechercher un id particulier, MySQL n'est pas obligé de simplement parcourir les données ligne par ligne. Il peut utiliser des algorithmes de recherche puissants (comme la recherche dichotomique), toujours afin d’accélérer la recherche.

Mais pourquoi ne pas simplement trier la table complète sur la base de la colonne id ? Pourquoi créer et stocker une structure spécialement pour l'index ? Tout simplement parce qu'il peut y avoir plusieurs index sur une même table, et que l'ordre des lignes pour chacun de ces index n'est pas nécessairement le même. Par exemple, nous pouvons créer un second index pour notre table Animal, sur la colonne date_naissance.

Index sur id et date_naissance

Comme vous pouvez le voir, l'ordre n'est pas du tout le même.

Intérêt des index

Vous devriez avoir compris maintenant que tout l'intérêt des index est d'accélérer les requêtes qui utilisent des colonnes indexées comme critères de recherche. Par conséquent, si vous savez que dans votre application, vous ferez énormément de recherches sur la colonne X, ajoutez donc un index sur cette colonne, vous ne vous en porterez que mieux.
Les index permettent aussi d'assurer l'intégrité des données de la base. Pour cela, il existe en fait plusieurs types d'index différents, et deux types de "clés". Lorsque je parle de garantir l'intégrité de vos données, cela signifie en gros garantir la qualité de vos données, vous assurer que vos données ont du sens. Par exemple, soyez sûrs que vous ne faites pas référence à un client dans la table Commande, alors qu'en réalité, ce client n'existe absolument pas dans la table Client.

Désavantages

Si tout ce que fait un index, c'est accélérer les requêtes utilisant des critères de recherche correspondants, autant en mettre partout, et en profiter à chaque requête ! Sauf qu'évidemment, ce n'est pas si simple, les index ont deux inconvénients.

Par conséquent, n'ajoutez pas d'index lorsque ce n'est pas vraiment utile.

Index sur plusieurs colonnes

Reprenons l'exemple d'une table appelée Client, qui reprend les informations des clients d'une société. Elle se présente comme suit :

id

nom

prenom

init_2e_prenom

email

1

Dupont

Charles

T

[email protected]

2

François

Damien

V

[email protected]

3

Vandenbush

Guillaume

A

[email protected]

4

Dupont

Valérie

C

[email protected]

5

Dupont

Valérie

G

[email protected]

6

François

Martin

D

[email protected]

7

Caramou

Arthur

B

[email protected]

8

Boulian

Gérard

M

[email protected]

9

Loupiot

Laura

F

[email protected]

10

Sunna

Christine

I

[email protected]

Vous avez bien sûr un index sur la colonne id, mais vous constatez que vous faites énormément de recherches par nom, prénom et initiale du second prénom. Vous pourriez donc faire trois index, un pour chacune de ces colonnes. Mais, si vous faites souvent des recherches sur les trois colonnes à la fois, il vaut encore mieux faire un seul index, sur les trois colonnes (nom, prenom, init_2e_prenom). L'index contiendra donc les valeurs des trois colonnes et sera trié par nom, ensuite par prénom, et enfin par initiale (l'ordre des colonnes a donc de l'importance !).

Index sur trois colonnes

Du coup, lorsque vous cherchez "Dupont Valérie C.", grâce à l'index MySQL trouvera rapidement tous les "Dupont", parmi lesquels il trouvera toutes les "Valérie" (toujours en se servant du même index), parmi lesquelles il trouvera celle (ou celles) dont le second prénom commence par "C".

Tirer parti des "index par la gauche"

Tout ça c'est bien beau si on fait souvent des recherches à la fois sur le nom, le prénom et l'initiale. Mais comment fait-on si l'on fait aussi souvent des recherches uniquement sur le nom, ou uniquement sur le prénom, ou sur le nom et le prénom en même temps mais sans l'initiale ? Faut-il créer un nouvel index pour chaque type de recherche ?
Hé bien non ! MySQl est beaucoup trop fort : il est capable de tirer parti de votre index sur (nom, prenom, init_2e_prenom) pour certaines autres recherches. En effet, si l'on représente les index sous forme de tables, voici ce que cela donnerait pour les quatre index (prenom), (nom), (nom, prenom) et (nom, prenom, init_2e_prenom).

Index par la gauche

Remarquez-vous quelque chose d'intéressant ?

Oui ! Bien vu ! L'index (nom, prenom) correspond exactement aux deux premières colonnes de l'index (nom, prenom, init_2e_prenom) ; pas seulement au niveau des valeurs mais surtout au niveau de l'ordre. Et l'index (nom) correspond à la première colonne de l'index (nom, prenom, init_2e_prenom).
Or, je vous ai dit que lorsque vous faites une recherche sur le nom, le prénom et l'initiale avec l'index (nom, prenom, init_2e_prenom), MySQL regarde d'abord le nom, puis le prénom, et pour finir l'initiale. Donc, si vous ne faites une recherche que sur le nom et le prénom, MySQL va intelligemment utiliser l'index (nom, prenom, init_2e_prenom) : il va simplement laisser tomber l'étape de l'initiale du second prénom. Idem si vous faites une recherche sur le nom : MySQL se basera uniquement sur la première colonne de l'index existant.
Par conséquent, pas besoin de définir un index (nom, prenom) ou un index (nom). Ils sont en quelque sorte déjà présents.

Mais qu'en est-il des index (prenom), ou (prenom, init_2e_prenom) ? Vous pouvez voir que la table contenant l'index (prenom) ne correspond à aucune colonne d'un index existant (au niveau de l'ordre des lignes). Par conséquent, si vous voulez un index sur (prenom), il vous faut le créer. Même chose pour (prenom, init_2e_prenom) ou (nom, init_2e_prenom).

Index par la gauche

On parle d'index "par la gauche". Donc si l'on prend des sous-parties d'index existant en prenant des colonnes "par la gauche", ces index existent. Mais si l'on commence par la droite ou que l'on "saute" une colonne, ils n'existent pas et doivent éventuellement être créés.

Index sur des colonnes de type alphanumérique
Types CHAR et VARCHAR

Lorsque l'on indexe une colonne de type VARCHAR ou CHAR (comme la colonne nom de la table Client par exemple), on peut décomposer l'index de la manière suivante :

1e lettre

2e lettre

3e lettre

4e lettre

5e lettre

6e lettre

7e lettre

8e lettre

9e lettre

10e lettre

B

o

u

l

i

a

n

C

a

r

a

m

o

u

D

u

p

o

n

t

D

u

p

o

n

t

D

u

p

o

n

t

F

r

a

n

ç

o

i

s

F

r

a

n

ç

o

i

s

L

o

u

p

i

o

t

S

u

n

n

a

V

a

n

d

e

n

b

u

s

h

nom

Boulian

Caramou

Dupont

Dupont

Dupont

François

François

Loupiot

Sunna

Vandenbush

En quoi cela nous intéresse-t-il ?

C'est très simple. Ici, nous n'avons que des noms assez courts. La colonne nom peut par exemple être de type VARCHAR(30). Mais imaginez une colonne de type VARCHAR(150), qui contient des titres de livres par exemple. Si l'on met un index dessus, MySQL va indexer jusqu'à 150 caractères. Or, il est fort probable que les 25-30 premiers caractères du titre suffisent à trier ceux-ci. Au pire, un ou deux ne seront pas exactement à la bonne place, mais les requêtes en seraient déjà grandement accélérées.
Il serait donc plutôt pratique de dire à MySQL : "Indexe cette colonne, mais base-toi seulement sur les x premiers caractères". Et c'est possible évidemment (sinon je ne vous en parlerais pas :p ), et c'est même très simple. Lorsque l'on créera l'index sur la colonne titre_livre, il suffira d'indiquer un nombre entre parenthèses : titre_livre(25) par exemple. Ce nombre étant bien sûr le nombre de caractères (dans le sens de la lecture, donc à partir de la gauche) à prendre en compte pour l'index.

Le fait d'utiliser ces index partiels sur des champs alphanumériques permet de gagner de la place (un index sur 150 lettres prend évidemment plus de place qu'un index sur 20 lettres), et si la longueur est intelligemment définie, l'accélération permise par l'index sera la même que si l'on avait pris la colonne entière.

Types BLOB et TEXT (et dérivés)

Si vous mettez un index sur une colonne de type BLOB ou TEXT (ou un de leurs dérivés), MySQL exige que vous précisiez un nombre de caractères à prendre en compte. Et heureusement… Vu la longueur potentielle de ce que l'on stocke dans de telles colonnes.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Index Les différents types d'index

Les différents types d'index

Qu'est-ce qu'un index ? Création et suppression des index

Les différents types d'index

En plus des index "simples", que je viens de vous décrire, il existe trois types d'index qui ont des propriétés particulières. Les index UNIQUE, les index FULLTEXT, et enfin les index SPATIAL.
Je ne détaillerai pas les propriétés et utilisations des index SPATIAL. Sachez simplement qu'il s'agit d'un type d'index utilisé dans des bases de données recensant des données spatiales (tiens donc ! :p ), donc des points, des lignes, des polygones…

Sur ce, c'est parti !

Index UNIQUE

Avoir un index UNIQUE sur une colonne (ou plusieurs) permet de s'assurer que jamais vous n’insérerez deux fois la même valeur (ou combinaison de valeurs) dans la table.

Par exemple, vous créez un site internet, et vous voulez le doter d'un espace membre. Chaque membre devra se connecter grâce à un pseudo et un mot de passe. Vous avez donc une table Membre, qui contient 4 colonnes : id, pseudo, mot_de_passe et date_inscription.
Deux membres peuvent avoir le même mot de passe, pas de problème. Par contre, que se passerait-il si deux membres avaient le même pseudo ? Lors de la connexion, il serait impossible de savoir quel mot de passe utiliser et sur quel compte connecter le membre.
Il faut donc absolument éviter que deux membres utilisent le même pseudo. Pour cela, on va utiliser un index UNIQUE sur la colonne pseudo de la table Membre.

Autre exemple, dans notre table Animal cette fois. Histoire de ne pas confondre les animaux, vous prenez la décision de ne pas nommer de la même manière deux animaux de la même espèce. Il peut donc n'y avoir qu'une seule tortue nommée Toto. Un chien peut aussi se nommer Toto, mais un seul. La combinaison (espèce, nom) doit n'exister qu'une et une seule fois dans la table. Pour s'en assurer, il suffit de créer un index unique sur les colonnes espece et nom (un seul index, sur les deux colonnes).

Contraintes

Lorsque vous mettez un index UNIQUE sur une table, vous ne mettez pas seulement un index, vous ajoutez surtout une contrainte.
Les contraintes sont une notion importante en SQL. Sans le savoir, ou sans savoir que c'était appelé comme ça, vous en avez déjà utilisé. En effet, lorsque vous empêchez une colonne d'accepter NULL, vous lui mettez une contrainte NOT NULL.
De même, les valeurs par défaut que vous pouvez donner aux colonnes sont des contraintes. Vous contraignez la colonne à prendre une certaine valeur si aucune autre n'est précisée.

Index FULLTEXT

Un index FULLTEXT est utilisé pour faire des recherches de manière puissante et rapide sur un texte. Seules les tables utilisant le moteur de stockage MyISAM peuvent avoir un index FULLTEXT, et cela uniquement sur les colonnes de type CHAR, VARCHAR ou TEXT (ce qui est plutôt logique).

Une différence importante (très importante !!! :diable: ) entre les index FULLTEXT et les index classiques (et UNIQUE) est que l'on ne peut plus utiliser les fameux "index par la gauche" dont je vous ai parlé précédemment. Donc, si vous voulez faire des recherches "fulltext" sur deux colonnes (parfois l'une, parfois l'autre, parfois les deux ensemble), il vous faudra créer trois index FULLTEXT : (colonne1), (colonne2) et (colonne1, colonne2).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Qu'est-ce qu'un index ? Création et suppression des index

Création et suppression des index

Les différents types d'index Recherches avec FULLTEXT

Création et suppression des index

Les index sont représentés par le mot-clé INDEX (surprise ! o_O ) ou KEY et peuvent être créés de deux manières :

Ajout des index lors de la création de la table

Ici aussi, deux possibilités : vous pouvez préciser dans la description de la colonne qu'il s'agit d'un index, ou lister les index par la suite.

Index dans la description de la colonne

Je rappelle que la description de la colonne se rapporte à l'endroit où vous indiquez le type de données, si la colonne peut contenir NULL, etc. Il est donc possible, à ce même endroit, de préciser si la colonne est un index.

CREATE TABLE nom_table (
    colonne1 INT KEY,                  -- Crée un index simple sur colonne1
    colonne2 VARCHAR(40) UNIQUE,       -- Crée un index unique sur colonne2
);

Quelques petites remarques ici :

Liste d'index

L'autre possibilité est d'ajouter les index à la suite des colonnes, en séparant chaque élément par une virgule :

CREATE TABLE nom_table (
    colonne1 description_colonne1,
    [colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...,]
    [PRIMARY KEY (colonne_clé_primaire)],
    [INDEX [nom_index] (colonne1_index [, colonne2_index, ...]]
)
[ENGINE=moteur];

Exemple : si l'on avait voulu créer la table Animal avec un index sur la date de naissance, et un autre sur les 10 premières lettres du nom, on aurait pu utiliser la commande suivante :

CREATE TABLE Animal (
    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    espece VARCHAR(40) NOT NULL,
    sexe CHAR(1),
    date_naissance DATETIME NOT NULL,
    nom VARCHAR(30),
    commentaires TEXT,
    PRIMARY KEY (id),
    INDEX ind_date_naissance (date_naissance),  -- index sur la date de naissance
    INDEX ind_nom (nom(10))                     -- index sur le nom (le chiffre entre parenthèses étant le nombre de caractères pris en compte)
)
ENGINE=INNODB;

Vous n'êtes pas obligés de préciser un nom pour votre index. Si vous ne le faites pas, MySQL en créera un automatiquement pour vous.
Je préfère nommer mes index moi-même plutôt que de laisser MySQL créer un nom par défaut, et respecter certaines conventions personnelles. Ainsi, mes index auront toujours le préfixe "ind" suivi du ou des nom(s) des colonnes concernées, le tout séparé par des "_". Il vous appartient de suivre vos propres conventions bien sûr. L'important étant de vous y retrouver.

Et pour ajouter des index UNIQUE ou FULLTEXT, c'est le même principe :

CREATE TABLE nom_table (
    colonne1 INT NOT NULL,   
    colonne2 VARCHAR(40), 
    colonne3 TEXT,
    UNIQUE [INDEX] ind_uni_col2 (colonne2),     -- Crée un index UNIQUE sur la colonne2, INDEX est facultatif
    FULLTEXT [INDEX] ind_full_col3 (colonne3)   -- Crée un index FULLTEXT sur la colonne3, INDEX est facultatif
)
ENGINE=MyISAM;

Exemple : création de la table Animal comme précédemment, en ajoutant un index UNIQUE sur (nom, espece).

CREATE TABLE Animal (
    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    espece VARCHAR(40) NOT NULL,
    sexe CHAR(1),
    date_naissance DATETIME NOT NULL,
    nom VARCHAR(30),
    commentaires TEXT,
    PRIMARY KEY (id),
    INDEX ind_date_naissance (date_naissance),  
    INDEX ind_nom (nom(10)),                    
    UNIQUE INDEX ind_uni_nom_espece (nom, espece)  -- Index sur le nom et l'espece
)
ENGINE=INNODB;
Ajout des index après création de la table

En dehors du fait que parfois vous ne penserez pas à tout au moment de la création de votre table, il peut parfois être intéressant de créer les index après la table.
En effet, je vous ai dit que l'ajout d'index sur une table ralentissait l'exécution des requêtes d'écriture (insertion, suppression, modification de données). Par conséquent, si vous créez une table, que vous comptez remplir avec un grand nombre de données immédiatement, grâce à la commande LOAD DATA INFILE par exemple, il vaut bien mieux créer la table, la remplir, et ensuite seulement créer les index voulus sur cette table.

Il existe deux commandes permettant de créer des index sur une table existante : ALTER TABLE, que vous connaissez déjà un peu, et CREATE INDEX. Ces deux commandes sont équivalentes, utilisez celle qui vous parle le plus.

Ajout avec ALTER TABLE
ALTER TABLE nom_table
ADD INDEX [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index simple

ALTER TABLE nom_table
ADD UNIQUE [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index UNIQUE

ALTER TABLE nom_table
ADD FULLTEXT [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index FULLTEXT

Contrairement à ce qui se passait pour l'ajout d'une colonne, il est ici obligatoire de préciser INDEX (ou UNIQUE, ou FULLTEXT) après ADD.
Dans le cas d'un index multi-colonnes, il suffit comme d'habitude de toutes les indiquer entre parenthèses, séparées par des virgules.

Reprenons la table Test_tuto, utilisée pour tester ALTER TABLE, et ajoutons-lui un index sur la colonne nom :

ALTER TABLE Test_tuto 
ADD INDEX ind_nom (nom);

Si vous affichez maintenant la description de votre table Test_tuto, vous verrez que dans la colonne "Key", il est indiqué MUL pour la colonne nom. L'index a donc bien été créé.

Ajout avec CREATE INDEX

La syntaxe de CREATE INDEX est très simple :

CREATE INDEX nom_index
ON nom_table (colonne_index [, colonne2_index ...]);  -- Crée un index simple

CREATE UNIQUE INDEX nom_index
ON nom_table (colonne_index [, colonne2_index ...]);  -- Crée un index UNIQUE


CREATE FULLTEXT INDEX nom_index
ON nom_table (colonne_index [, colonne2_index ...]);  -- Crée un index FULLTEXT

Exemple : l'équivalent de la commande ALTER TABLE que nous avons utilisée pour ajouter un index sur la colonne nom est donc :

CREATE INDEX ind_nom 
ON Test_tuto (nom);
Complément pour la création d'un index UNIQUE - le cas des contraintes

Vous vous rappelez, j'espère, que les index UNIQUE sont ce qu'on appelle des contraintes.

Or, lorsque vous créez un index UNIQUE, vous pouvez explicitement créer une contrainte. C'est fait automatiquement bien sûr si vous ne le faites pas, mais ne soyez donc pas surpris de voir apparaître le mot CONSTRAINT, c'est à ça qu'il se réfère.

Pour pouvoir créer explicitement une contrainte lors de la création d'un index UNIQUE, vous devez créer cet index soit lors de la création de la table, en listant l'index (et la contrainte) à la suite des colonnes, soit après la création de la table, avec la commande ALTER TABLE.

CREATE TABLE nom_table (
    colonne1 INT NOT NULL,   
    colonne2 VARCHAR(40), 
    colonne3 TEXT,
    CONSTRAINT [symbole_contrainte] UNIQUE [INDEX] ind_uni_col2 (colonne2)
);

ALTER TABLE nom_table
ADD CONSTRAINT [symbole_contrainte] UNIQUE ind_uni_col2 (colonne2);

Il n'est pas obligatoire de donner un symbole (un nom en fait) à la contrainte. D'autant plus que dans le cas des index, vous pouvez donner un nom à l'index (ici : ind_uni_col).

Suppression d'un index

Rien de bien compliqué :

ALTER TABLE nom_table 
DROP INDEX nom_index;

Notez qu'il n'existe pas de commande permettant de modifier un index. Le cas échéant, il vous faudra supprimer, puis recréer votre index avec vos modifications.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les différents types d'index Recherches avec FULLTEXT

Recherches avec FULLTEXT

Création et suppression des index Clés primaires et étrangères

Recherches avec FULLTEXT

Nous allons maintenant voir comment utiliser la recherche FULLTEXT, qui est un outil très puissant, et qui peut se révéler très utile.

Quelques rappels d'abord :

Ça, c'est fait ! Nous allons maintenant passer à la recherche proprement dite, mais avant, je vais vous demander d'exécuter les instructions SQL suivantes, qui servent à créer la table que nous utiliserons pour illustrer ce chapitre. Nous sortons ici du contexte de l'élevage d'animaux. En effet, toutes les tables que nous créerons pour l'élevage utiliseront le moteur de stockage InnoDB.

Pour illustrer la recherche FULLTEXT, je vous propose de créer la table Livre, contenant les colonnes id (clé primaire), titre et auteur. Les recherches se feront sur les colonnes auteur et titre, séparément ou ensemble. Il faut donc créer trois index FULLTEXT : (auteur), (titre) et (auteur, titre).

CREATE TABLE Livre (
	id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
	auteur VARCHAR(50),
	titre VARCHAR(200)
) ENGINE = MyISAM;

INSERT INTO Livre (auteur, titre)
VALUES ('Daniel Pennac', 'Au bonheur des ogres'),
('Daniel Pennac', 'La Fée Carabine'),
('Daniel Pennac', 'Comme un roman'),
('Daniel Pennac', 'La Petite marchande de prose'),
('Jacqueline Harpman', 'Le Bonheur est dans le crime'),
('Jacqueline Harpman', 'La Dormition des amants'),
('Jacqueline Harpman', 'La Plage d''Ostende'),
('Jacqueline Harpman', 'Histoire de Jenny'),
('Terry Pratchett', 'Les Petits Dieux'),
('Terry Pratchett', 'Le Cinquième éléphant'),
('Terry Pratchett', 'La Vérité'),
('Terry Pratchett', 'Le Dernier héros'),
('Terry Goodkind', 'Le Temple des vents'),
('Jules Verne', 'De la Terre à la Lune'),
('Jules Verne', 'Voyage au centre de la Terre'),
('Henri-Pierre Roché', 'Jules et Jim');

CREATE FULLTEXT INDEX ind_full_titre
ON Livre (titre);

CREATE FULLTEXT INDEX ind_full_aut
ON Livre (auteur);

CREATE FULLTEXT INDEX ind_full_titre_aut
ON Livre (titre, auteur);
Comment fonctionne la recherche FULLTEXT ?

Lorsque vous faites une recherche FULLTEXT sur une chaîne de caractères, cette chaîne est découpée en mots. Est considéré comme un mot : toute suite de caractères composée de lettres, chiffres, tirets bas _ et apostrophes '. Par conséquent, un mot composé, comme "porte-clés" par exemple, sera considéré comme deux mots : "porte" et "clés".
Chacun de ces mots sera ensuite comparé avec les valeurs des colonnes sur lesquelles se fait la recherche. Si la colonne contient un des mots recherchés, on considère alors qu'elle correspond à la recherche.

Lorsque MySQL compare la chaîne de caractères que vous lui avez donnée, et les valeurs dans votre table, il ne tient pas compte de tous les mots qu'il rencontre. Les règles sont les suivantes :

Par conséquent, si vous voulez faire des recherches sur une table, il est nécessaire que cette table comporte au moins trois lignes, sinon chacun des mots sera présent dans au moins la moitié des lignes et aucun ne sera pris en compte.

Il est possible de redéfinir la longueur minimale des mots pris en compte, ainsi que la liste des mots trop communs. Je n'entrerai pas dans ces détails ici, vous trouverez ces informations dans la documentation officielle.

Les types de recherche

Il existe trois types de recherche FULLTEXT : la recherche naturelle, la recherche avec booléen, et enfin la recherche avec extension de requête.

Recherche naturelle

Lorsque l'on fait une recherche naturelle, il suffit qu'un seul mot de la chaîne de caractères recherchée se retrouve dans une ligne pour que celle-ci apparaisse dans les résultats. Attention cependant au fait que le mot exact doit se retrouver dans la valeur des colonnes de l'index FULLTEXT examiné.

Voici la syntaxe utilisée pour faire une recherche FULLTEXT :

SELECT *                                  -- Vous mettez évidemment les colonnes que vous voulez.
FROM nom_table
WHERE MATCH(colonne1[, colonne2, ...])   -- La (ou les) colonne(s) dans laquelle (ou lesquelles) on veut faire la recherche (index FULLTEXT correspondant nécessaire).
AGAINST ('chaîne recherchée');            -- La chaîne de caractères recherchée, entre guillemets bien sûr.

Si l'on veut préciser qu'on fait une recherche naturelle, on peut ajouter IN NATURAL LANGUAGE MODE. Ce n'est cependant pas obligatoire puisque la recherche naturelle est le mode de recherche par défaut.

SELECT *                                  
FROM nom_table
WHERE MATCH(colonne1[, colonne2, ...])  
AGAINST ('chaîne recherchée' IN NATURAL LANGUAGE MODE);

Premier exemple : on recherche "Terry" dans la colonne auteur de la table Livre .

SELECT *
FROM Livre
WHERE MATCH(auteur)
AGAINST ('Terry');

id

auteur

titre

8

Terry Pratchett

Les Petits Dieux

9

Terry Pratchett

Le Cinquième éléphant

10

Terry Pratchett

La Vérité

11

Terry Pratchett

Le Dernier héros

12

Terry Goodkind

Le Temple des vents

Deuxième exemple : On recherche d'abord "Petite", puis "Petit" dans la colonne titre.

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('Petite');

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('Petit');

Résultat de la première requête :

id

auteur

titre

3

Daniel Pennac

La Petite marchande de prose

La deuxième requête (avec "Petit") ne renvoie aucun résultat. En effet, bien que "Petit" se retrouve deux fois dans la table (dans "La Petite marchande de prose" et "Les Petits Dieux"), il s'agit chaque fois d'une partie d'un mot, pas du mot exact.

Troisième exemple : on recherche "Henri" dans la colonne auteur.

SELECT *
FROM Livre
WHERE MATCH(auteur)
AGAINST ('Henri');

id

auteur

titre

16

Henri-Pierre Roché

Jules et Jim

Ici par contre, on retrouve bien Henri-Pierre Roché en faisant une recherche sur "Henri", puisque Henri et Pierre sont considérés comme deux mots.

Quatrième exemple : on recherche "Jules", puis "Jules Verne" dans les colonnes titre et auteur.

SELECT *
FROM Livre
WHERE MATCH(auteur, titre)
AGAINST ('Jules');

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Jules Verne');

id

auteur

titre

14

Jules Verne

De la Terre à la Lune

16

Henri-Pierre Roché

Jules et Jim

15

Jules Verne

Voyage au centre de la Terre

id

auteur

titre

14

Jules Verne

De la Terre à la Lune

15

Jules Verne

Voyage au centre de la Terre

16

Henri-Pierre Roché

Jules et Jim

Ces deux requêtes retournent les mêmes lignes. Vous pouvez donc voir que l'ordre des colonnes dans MATCH n'a aucune importance, du moment qu'un index FULLTEXT existe sur ces deux colonnes. Par ailleurs, la recherche se fait bien sur les deux colonnes, et sur chaque mot séparément, puisque les premières et troisièmes lignes contiennent 'Jules Verne' dans l'auteur, tandis que la deuxième contient uniquement 'Jules' dans le titre.

Par contre, l'ordre des lignes renvoyées par ces deux requêtes n'est pas le même. Lorsque vous utilisez MATCH... AGAINST dans une clause WHERE, les résultats sont par défaut triés par pertinence.

La pertinence est une valeur supérieure ou égale à 0 qui qualifie le résultat d'une recherche FULLTEXT sur une ligne. Si la ligne ne correspond pas du tout à la recherche, sa pertinence sera de 0. Si par contre elle correspond à la recherche, sa pertinence sera supérieure à 0. Ensuite, plus la ligne correspond bien à la recherche (nombre de mots trouvés par exemple), plus la pertinence sera élevée.
Vous pouvez voir la pertinence attribuée à une ligne en mettant l'expression MATCH... AGAINST dans le SELECT.

Cinquième exemple : affichage de la pertinence de la recherche

SELECT *, MATCH(titre, auteur) AGAINST ('Jules Verne Lune')
FROM Livre;

id

auteur

titre

MATCH(titre, auteur) AGAINST ('Jules Verne Lune')

1

Daniel Pennac

Au bonheur des ogres

0

2

Daniel Pennac

La Fée Carabine

0

3

Daniel Pennac

La Petite marchande de prose

0

4

Jacqueline Harpman

Le Bonheur est dans le crime

0

5

Jacqueline Harpman

La Dormition des amants

0

6

Jacqueline Harpman

La Plage d'Ostende

0

7

Jacqueline Harpman

Histoire de Jenny

0

8

Terry Pratchett

Les Petits Dieux

0

9

Terry Pratchett

Le Cinquième éléphant

0

10

Terry Pratchett

La Vérité

0

11

Terry Pratchett

Le Dernier héros

0

12

Terry Goodkind

Le Temple des vents

0

13

Daniel Pennac

Comme un roman

0

14

Jules Verne

De la Terre à la Lune

5.851144790649414

15

Jules Verne

Voyage au centre de la Terre

3.2267112731933594

16

Henri-Pierre Roché

Jules et Jim

1.4018518924713135

En fait, écrire :

WHERE MATCH(colonne(s)) AGAINST (mot(s) recherché(s))

Cela revient à écrire :

WHERE MATCH(colonne(s)) AGAINST (mot(s) recherché(s)) > 0

Donc seules les lignes ayant une pertinence supérieure à 0 (donc correspondant à la recherche) seront sélectionnées.

Recherche avec booléens

La recherche avec booléens possède les caractéristiques suivantes :

Pour faire une recherche avec booléens, il suffit d'ajouter IN BOOLEAN MODE après la chaîne recherchée.

SELECT * 
FROM nom_table
WHERE MATCH(colonne) 
AGAINST('chaîne recherchée' IN BOOLEAN MODE);  -- IN BOOLEAN MODE à l'intérieur des parenthèses !

La recherche avec booléens permet d'être à la fois plus précis et plus approximatif dans ses recherches.

Pour exiger la présence ou l'absence de certains mots, on utilise les caractères + et -. Un mot précédé par +devra être présent dans la ligne et inversement, précédé par - il ne pourra pas être présent.

Exemple : Recherche sur le titre, qui doit contenir "bonheur" mais ne peut pas contenir "ogres".

SELECT * 
FROM Livre
WHERE MATCH(titre)
AGAINST ('+bonheur -ogres' IN BOOLEAN MODE);

id

auteur

titre

4

Jacqueline Harpman

Le Bonheur est dans le crime

Seule une ligne est ici sélectionnée, bien que "bonheur" soit présent dans deux. En effet, le second livre dont le titre contient "bonheur" est "Le Bonheur des ogres", qui contient le mot interdit "ogres".

Pour spécifier un groupe de mots exigés, on utilise les doubles guillemets. Tous les mots entre doubles guillemets devront non seulement être présents mais également apparaître dans l'ordre donné, et sans rien entre eux. Il faudra donc que l'on retrouve exactement ces mots pour avoir un résultat.

Exemple : recherche sur titre, qui doit contenir tout le groupe de mot entre guillemets doubles.

SELECT * 
FROM Livre 
WHERE MATCH(titre) 
AGAINST ('"Terre à la Lune"' IN BOOLEAN MODE);

SELECT * 
FROM Livre 
WHERE MATCH(titre) 
AGAINST ('"Lune à la Terre"' IN BOOLEAN MODE);

SELECT * 
FROM Livre 
WHERE MATCH(titre) 
AGAINST ('"Terre la Lune"' IN BOOLEAN MODE);

Résultat première requête :

id

auteur

titre

14

Jules Verne

De la Terre à la Lune

La première requête renverra bien un résultat, contrairement à la seconde (car les mots ne sont pas dans le bon ordre) et à la troisième (car il manque le "à" dans la recherche - ou il y a un "à" en trop dans la ligne, ça dépend du point de vue).
"Voyage au centre de la Terre" n'est pas un résultat puisque seul le mot "Terre" est présent.

Pour utiliser l'astérisque, il suffit d'écrire le début du mot dont on est sûr, et de compléter avec un astérisque.

Exemple : recherche sur titre, sur tous les mots commençant par "petit".

SELECT * 
FROM Livre
WHERE MATCH(titre)
AGAINST ('petit*' IN BOOLEAN MODE);

id

auteur

titre

3

Daniel Pennac

La Petite marchande de prose

8

Terry Pratchett

Les Petits Dieux

"Petite" et "Petits" sont trouvés.

Exemple : recherche sur titre et auteur, de tous les mots commençant par "d".

SELECT * 
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('d*' IN BOOLEAN MODE);

id

auteur

titre

1

Daniel Pennac

Au bonheur des ogres

2

Daniel Pennac

La Fée Carabine

3

Daniel Pennac

La Petite marchande de prose

13

Daniel Pennac

Comme un roman

4

Jacqueline Harpman

Le Bonheur est dans le crime

11

Terry Pratchett

Le Dernier héros

8

Terry Pratchett

Les Petits Dieux

5

Jacqueline Harpman

La Dormition des amants

Chacun des résultats contient au moins un mot commençant par "d" dans son titre ou son auteur ("Daniel", "Dormition"...).
Mais qu'en est-il de "Voyage au centre de la Terre" par exemple ? Avec le "de", il aurait dû être sélectionné. Mais c'est sans compter la règle qui dit que les mots de moins de quatre lettres sont ignorés. "De" n'ayant que deux lettres, ce résultat est ignoré.

Pour en finir avec la recherche avec booléens, sachez qu'il est bien sûr possible de mixer ces différentes possibilités. Les combinaisons sont nombreuses.

Exemple : recherche sur titre, qui doit contenir un mot commençant par "petit", mais ne peut pas contenir le mot "prose".

SELECT * 
FROM Livre
WHERE MATCH(titre)
AGAINST ('+petit* -prose' IN BOOLEAN MODE); -- mix d'un astérisque avec les + et -

id

auteur

titre

8

Terry Pratchett

Les Petits Dieux

Recherche avec extension de requête

Le dernier type de recherche est un peu particulier. En effet la recherche avec extension de requête se déroule en deux étapes.

  1. Une simple recherche naturelle est effectuée.

  2. Les résultats de cette recherche sont utilisés pour faire une seconde recherche naturelle.

Un exemple étant souvent plus clair qu'un long discours, passons-y tout de suite.

Une recherche naturelle effectuée avec la chaîne "Daniel" sur les colonnes auteur et titre donnerait ceci :

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Daniel');

id

auteur

titre

2

Daniel Pennac

La Fée Carabine

1

Daniel Pennac

Au bonheur des ogres

13

Daniel Pennac

Comme un roman

3

Daniel Pennac

La Petite marchande de prose

Par contre, si l'on utilise l'extension de requête, en ajoutant WITH QUERY EXPANSION, on obtient ceci :

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Daniel' WITH QUERY EXPANSION);

id

auteur

titre

3

Daniel Pennac

La Petite marchande de prose

13

Daniel Pennac

Comme un roman

1

Daniel Pennac

Au bonheur des ogres

2

Daniel Pennac

La Fée Carabine

4

Jacqueline Harpman

Le Bonheur est dans le crime

En effet, dans la seconde étape, une recherche naturelle a été faite avec les chaînes "Daniel Pennac", "La Petite marchande de prose", "Comme un roman", "Au bonheur des ogres" et "La Fée Carabine", puisque ce sont les résultats de la première étape (une recherche naturelle sur "Daniel").
"Le Bonheur est dans le crime" a donc été ajouté aux résultats, à cause de la présence du mot "bonheur" dans son titre ("Bonheur" étant également présent dans "Au bonheur des ogres")

Ainsi s'achève la présentation des requêtes FULLTEXT, ainsi que le chapitre sur les index.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Création et suppression des index Clés primaires et étrangères

Clés primaires et étrangères

Recherches avec FULLTEXT Clés primaires, le retour

Maintenant que les index n'ont plus de secret pour vous, nous allons passer à une autre notion très importante : les clés.
Les clés sont, vous allez le voir, intimement liées aux index. Et tout comme NOT NULL et les index UNIQUE, les clés font partie de ce qu'on appelle les contraintes.
Il existe deux types de clés :

Il s'agit à nouveau d'un chapitre avec beaucoup de blabla, mais je vous promets qu'après celui-ci, on s'amusera à nouveau ! Donc un peu de courage. ;)

Clés primaires, le retour

Clés primaires et étrangères Clés étrangères

Clés primaires, le retour

Les clés primaires ont déjà été introduites dans le chapitre de création des tables. Je vous avais alors donné la définition suivante :

La clé primaire d'une table est une contrainte d'unicité, composée d'une ou plusieurs colonnes, et qui permet d'identifier de manière unique chaque ligne de la table.

Examinons plus en détail cette définition.

Ces quelques considérations résument très bien l'essence des clés primaires. En gros, une clé primaire est un index UNIQUE sur une colonne qui ne peut pas être NULL.
D'ailleurs, vous savez déjà que l'on définit une clé primaire grâce aux mots-clés PRIMARY KEY. Or, nous avons vu dans le précédent chapitre que KEY s'utilise pour définir un index. Par conséquent, lorsque vous définissez une clé primaire, pas besoin de définir en plus un index sur la (les) colonne(s) qui compose(nt) celle-ci, c'est déjà fait ! Et pas besoin non plus de rajouter une contrainte NOT NULL
Pour le dire différemment, une contrainte de clé primaire est donc une combinaison de deux des contraintes que nous avons vues jusqu'à présent : UNIQUE et NOT NULL.

Choix de la clé primaire

Le choix d'une clé primaire est une étape importante dans la conception d'une table. Ce n'est pas parce que vous avez l'impression qu'une colonne, ou un groupe de colonnes, pourrait faire une bonne clé primaire que c'est le cas. Reprenons l'exemple d'une table Client, qui contient le nom, le prénom, la date de naissance et l'email des clients d'une société.

Chaque client a bien sûr un nom et un prénom. Est-ce que (nom, prenom) ferait une bonne clé primaire ? Non bien sûr : il est évident ici que vous risquez des doublons.
Et si on ajoute la date de naissance ? Les chances de doublons sont alors quasi nulles. Mais quasi nul, ce n'est pas nul… Qu'arrivera-t-il le jour où vous voyez débarquer un client qui a les mêmes nom et prénom qu'un autre, et qui est né le même jour ? On refait toute la base de données ? Non, bien sûr.
Et l'email alors ? Il est impossible que deux personnes aient la même adresse email, donc la contrainte d'unicité est respectée. Par contre, tout le monde n'est pas obligé d'avoir une adresse email. Difficile donc de mettre une contrainte NOT NULL sur cette colonne.

Par conséquent, on est bien souvent obligé d'ajouter une colonne pour jouer le rôle de la clé primaire. C'est cette fameuse colonne id, auto-incrémentée que nous avons déjà vue pour la table Animal.

Il y a une autre raison d'utiliser une colonne spéciale auto-incrémentée, de type INT (ou un de ses dérivés) pour la clé primaire. En effet, si l'on définit une clé primaire, c'est en partie dans le but d'utiliser au maximum cette clé pour faire des recherches dans la table. Bien sûr, parfois ce n'est pas possible, parfois vous ne connaissez pas l'id du client, et vous êtes obligés de faire une recherche par nom. Cependant, vous verrez bientôt que les clés primaires peuvent servir à faire des recherches de manière indirecte sur la table. Du coup, comme les recherches sont beaucoup plus rapides sur des nombres que sur des textes, il est souvent intéressant d'avoir une clé primaire composée de colonnes de type INT.

Enfin, il y a également l'argument de l'auto-incrémentation. Si vous devez remplir vous-mêmes la colonne de la clé primaire, étant donné que vous êtes humains (comment ça pas du tout ? :waw: ), vous risquez de faire une erreur. Avec une clé primaire auto-incrémentée, vous ne risquez rien : MySQL fait tout pour vous. De plus, on ne peut définir une colonne comme auto-incrémentée que si elle est de type INT et qu'il existe un index dessus. Dans le cas d'une clé primaire auto-incrémentée, on définit généralement la colonne comme un entier UNSIGNED, comme on l'a fait pour la table Animal.

PRIMARY KEY or not PRIMARY KEY

Je me dois de vous dire que d'un point de vue technique, avoir une clé primaire sur chaque table n'est pas obligatoire. Vous pourriez travailler toute votre vie sur une base de données sans aucune clé primaire, et ne jamais voir un message d'erreur à ce propos.
Cependant, d'un point de vue conceptuel, ce serait une grave erreur. Ce n'est pas le propos de ce tutoriel que de vous enseigner les étapes de conception d'une base de données mais, s'il vous plaît, pensez à mettre une clé primaire sur chacune de vos tables. Si l'utilité n'en est pas complètement évidente pour vous pour le moment, elle devrait le devenir au fur et à mesure de votre lecture.

Création d'une clé primaire

La création des clés primaires étant extrêmement semblable à la création d'index simples, j'espère que vous me pardonnerez si je ne détaille pas trop mes explications. :ange:

Donc, à nouveau, la clé primaire peut être créée en même temps que la table, ou par la suite.

Lors de la création de la table

On peut donc préciser PRIMARY KEY dans la description de la colonne qui doit devenir la clé primaire (pas de clé composite dans ce cas) :

CREATE TABLE [IF NOT EXISTS] Nom_table (
    colonne1 description_colonne1 PRIMARY KEY [,
    colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...,]
)
[ENGINE=moteur];

Exemple : création de la table Animal en donnant la clé primaire dans la description de la colonne.

CREATE TABLE Animal (
    id SMALLINT AUTO_INCREMENT PRIMARY KEY,
    espece VARCHAR(40) NOT NULL,
    sexe CHAR(1),
    date_naissance DATETIME NOT NULL,
    nom VARCHAR(30),
    commentaires TEXT
)
ENGINE=InnoDB;

Ou bien, on ajoute la clé à la suite des colonnes.

CREATE TABLE [IF NOT EXISTS] Nom_table (
    colonne1 description_colonne1 [,
    colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...],
    [CONSTRAINT [symbole_contrainte]] PRIMARY KEY (colonne_pk1 [, colonne_pk2, ...])  -- comme pour les index UNIQUE, CONSTRAINT est facultatif
)
[ENGINE=moteur];

C'est ce que nous avions fait d'ailleurs pour la table Animal. Cette méthode permet bien sûr la création d'une clé composite (avec plusieurs colonnes).

Exemple : création de Animal.

CREATE TABLE Animal (
    id SMALLINT AUTO_INCREMENT,
    espece VARCHAR(40) NOT NULL,
    sexe CHAR(1),
    date_naissance DATETIME NOT NULL,
    nom VARCHAR(30),
    commentaires TEXT,
    PRIMARY KEY (id)                 
)
ENGINE=InnoDB;
Après création de la table

On peut toujours utiliser ALTER TABLE. Par contre, CREATE INDEX n'est pas utilisable pour les clés primaires.

ALTER TABLE nom_table
ADD [CONSTRAINT [symbole_contrainte]] PRIMARY KEY (colonne_pk1 [, colonne_pk2, ...]);
Suppression de la clé primaire
ALTER TABLE nom_table
DROP PRIMARY KEY

Pas besoin de préciser de quelle clé il s'agit, puisqu'il ne peut y en avoir qu'une seule par table !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Clés primaires et étrangères Clés étrangères

Clés étrangères

Clés primaires, le retour Modification de notre base

Clés étrangères

Les clés étrangères ont pour fonction principale la vérification de l'intégrité de votre base. Elles permettent de s'assurer que vous n'insérez pas de bêtises…

Reprenons l'exemple dans lequel on a une table Client et une table Commande. Dans la table Commande, on a une colonne qui contient une référence au client. Ici, le client numéro 3, M. Nicolas Jacques, a donc passé une commande de trois tubes de colle, tandis que Mme Marie Malherbe (cliente numéro 2) a passé deux commandes, pour du papier et des ciseaux.

Référence d'une table à une autre

C'est bien joli, mais que se passe-t-il si M. Hadrien Piroux passe une commande de 15 tubes de colle, et qu'à l'insertion dans la table Commande, votre doigt dérape et met 45 comme numéro de client ? C'est l'horreur ! Vous avez dans votre base de données une commande passée par un client inexistant, et vous passez votre après-midi du lendemain à vérifier tous vos bons de commande de la veille pour retrouver qui a commandé ces 15 tubes de colle.
Magnifique perte de temps !

Ce serait quand même sympathique si, à l'insertion d'une ligne dans la table Commande, un gentil petit lutin allait vérifier que le numéro de client indiqué correspond bien à quelque chose dans la table Client, non ?
Ce lutin, ou plutôt cette lutine, existe ! Elle s'appelle "clé étrangère".

Par conséquent, si vous créez une clé étrangère sur la colonne client de la table Commande, en lui donnant comme référence la colonne numero de la table Client, MySQL ne vous laissera plus jamais insérer un numéro de client inexistant dans la table Commande. Il s'agit bien d'une contrainte !

Avant d'entamer une danse de joie, parce que quand même le SQL c'est génial, restez concentrés cinq minutes, le temps de lire et retenir quelques points importants.

Création

Une clé étrangère est un peu plus complexe à créer qu'un index ou une clé primaire, puisqu'il faut deux éléments :

Lors de la création de la table

Du fait de la présence de deux paramètres, une clé étrangère ne peut que s'ajouter à la suite des colonnes, et pas directement dans la description d'une colonne. Par ailleurs, je vous conseille ici de créer explicitement une contrainte (grâce au mot-clé CONSTRAINT) et de lui donner un symbole. En effet, pour les index, on pouvait utiliser leur nom pour les identifier ; pour les clés primaires, le nom de la table suffisait puisqu'il n'y en a qu'une par table. Par contre, pour différencier facilement les clés étrangères d'une table, il est utile de leur donner un nom, à travers la contrainte associée.
À nouveau, je respecte certaines conventions de nommage : mes clés étrangères ont des noms commençant par fk (pour FOREIGN KEY), suivi du nom de la colonne dans la table puis (si elle s'appelle différemment) du nom de la colonne de référence, le tout séparé par des _ (fk_client_numero par exemple).

CREATE TABLE [IF NOT EXISTS] Nom_table (
    colonne1 description_colonne1,
    [colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...,]
    [ [CONSTRAINT [symbole_contrainte]]  FOREIGN KEY (colonne(s)_clé_étrangère) REFERENCES table_référence (colonne(s)_référence)]
)
[ENGINE=moteur];

Donc si on imagine les tables Client et Commande, pour créer la table Commande avec une clé étrangère ayant pour référence la colonne numero de la table Client, on utilisera :

CREATE TABLE Commande (
    numero INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    client INT UNSIGNED NOT NULL,
    produit VARCHAR(40),
    quantite SMALLINT DEFAULT 1,
    CONSTRAINT fk_client_numero          -- On donne un nom à notre clé
        FOREIGN KEY (client)             -- Colonne sur laquelle on crée la clé
        REFERENCES Client(numero)        -- Colonne de référence
)
ENGINE=InnoDB;                          -- MyISAM interdit, je le rappelle encore une fois !
Après création de la table

Tout comme pour les clés primaires, pour créer une clé étrangère après création de la table, il faut utiliser ALTER TABLE.

ALTER TABLE Commande
ADD CONSTRAINT fk_client_numero FOREIGN KEY client REFERENCES Client(numero);
Suppression d'une clé étrangère

Il peut y avoir plusieurs clés étrangères par table. Par conséquent, lors d'une suppression il faut identifier la clé à détruire. Cela se fait grâce au symbole de la contrainte.

ALTER TABLE nom_table
DROP FOREIGN KEY symbole_contrainte
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Clés primaires, le retour Modification de notre base

Modification de notre base

Clés étrangères Jointures

Modification de notre base

Maintenant que vous connaissez les clés étrangères, nous allons en profiter pour modifier notre base et ajouter quelques tables afin de préparer le prochain chapitre, qui portera sur les jointures.

Jusqu'à maintenant, la seule information sur l'espèce des animaux de notre élevage était son nom courant. Nous voudrions maintenant stocker aussi son nom latin, ainsi qu'une petite description. Que faire ? Ajouter deux colonnes à notre table ? nom_latin_espece et description_espece ?
J'espère que vous n'avez envisagé cette possibilité qu'une demi-seconde, car il est assez évident que c'est une très mauvaise solution. En effet, ça obligerait à stocker la même description pour chaque chien, chaque chat, etc. Ainsi que le même nom latin. Nous le faisions déjà avec le nom courant, et ça aurait déjà pu poser problème. Imaginez que pour un animal vous fassiez une faute d'orthographe au nom de l'espèce, "chein" ou lieu de "chien" par exemple. L'animal en question n'apparaîtrait jamais lorsque vous feriez une recherche par espèce.
Il faudrait donc un système qui nous permette de ne pas répéter la même information plusieurs fois, et qui limite les erreurs que l'on pourrait faire.

La bonne solution est de créer une seconde table : la table Espece. Cette table aura 4 colonnes : le nom courant, le nom latin, une description, et un numéro d'identification (qui sera la clé primaire de cette table).

La table Espece

Voici donc la commande que je vous propose d'exécuter pour créer la table Espece :

CREATE TABLE Espece (
    id SMALLINT UNSIGNED AUTO_INCREMENT,
    nom_courant VARCHAR(40) NOT NULL,
    nom_latin VARCHAR(40) NOT NULL UNIQUE,
    description TEXT,
    PRIMARY KEY(id)
)
ENGINE=InnoDB;

On met un index UNIQUE sur la colonne nom_latin pour être sûr que l'on ne rentrera pas deux fois la même espèce. Pourquoi sur le nom latin et pas sur le nom courant ? Tout simplement parce que le nom latin est beaucoup plus rigoureux et réglementé que le nom courant. On peut avoir plusieurs dénominations courantes pour une même espèce ; ce n'est pas le cas avec le nom latin.

Bien, remplissons donc cette table avec les espèces déjà présentes dans la base :

INSERT INTO Espece (nom_courant, nom_latin, description) VALUES
    ('Chien', 'Canis canis', 'Bestiole à quatre pattes qui aime les caresses et tire souvent la langue'),
    ('Chat', 'Felis silvestris', 'Bestiole à quatre pattes qui saute très haut et grimpe aux arbres'),
    ('Tortue d''Hermann', 'Testudo hermanni', 'Bestiole avec une carapace très dure'),
    ('Perroquet amazone', 'Alipiopsitta xanthops', 'Joli oiseau parleur vert et jaune');

Ce qui nous donne la table suivante :

id

nom_courant

nom_latin

description

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

Vous aurez remarqué que j'ai légèrement modifié les noms des espèces "perroquet" et "tortue". En effet, et j'espère que les biologistes pourront me pardonner, "perroquet" et "tortue" ne sont pas des espèces, mais des ordres. J'ai donc précisé un peu (si je donne juste l'ordre, c'est comme si je mettais "carnivore" au lieu de "chat" - ou "chien" d'ailleurs).

Bien, mais cela ne suffit pas ! Il faut également modifier notre table Animal. Nous allons ajouter une colonne espece_id, qui contiendra l'id de l'espèce à laquelle appartient l'animal, et remplir cette colonne. Ensuite nous pourrons supprimer la colonne espece, qui n'aura plus de raison d'être.
La colonne espece_id sera une clé étrangère ayant pour référence la colonne id de la table Espece. Je rappelle donc que ça signifie qu'il ne sera pas possible d'insérer dans espece_id un nombre qui n'existe pas dans la colonne id de la table Espece.

La table Animal

Je vous conseille d'essayer d'écrire vous-même les requêtes avant de regarder comment je fais.

-- Ajout d'une colonne espece_id
ALTER TABLE Animal ADD COLUMN espece_id SMALLINT UNSIGNED; -- même type que la colonne id de Espece

-- Remplissage de espece_id
UPDATE Animal SET espece_id = 1 WHERE espece = 'chien';
UPDATE Animal SET espece_id = 2 WHERE espece = 'chat';
UPDATE Animal SET espece_id = 3 WHERE espece = 'tortue';
UPDATE Animal SET espece_id = 4 WHERE espece = 'perroquet';

-- Suppression de la colonne espece
ALTER TABLE Animal DROP COLUMN espece;

-- Ajout de la clé étrangère
ALTER TABLE Animal
ADD CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES Espece(id);

Pour tester l'efficacité de la clé étrangère, essayons d'ajouter un animal dont l'espece_id est 5 (qui n'existe donc pas dans la table Espece) :

INSERT INTO Animal (nom, espece_id, date_naissance)
VALUES ('Caouette', 5, '2009-02-15 12:45:00');
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`elevage`.`animal`, CONSTRAINT `fk_espece_id` FOREIGN KEY (`espece_id`) REFERENCES `espece` (`id`))

Elle est pas belle la vie ?

J'en profite également pour ajouter une contrainte NOT NULL sur la colonne espece_id. Après tout, si espece ne pouvait pas être NULL, pas de raison qu'espece_id puisse l'être ! Ajoutons également l'index UNIQUE sur (nom, espece_id) dont on a déjà discuté.

ALTER TABLE Animal MODIFY espece_id SMALLINT UNSIGNED NOT NULL;

CREATE UNIQUE INDEX ind_uni_nom_espece_id ON Animal (nom, espece_id);

Notez qu'il n'était pas possible de mettre la contrainte NOT NULL à la création de la colonne, puisque tant que l'on n'avait pas rempli espece_id, elle contenait NULL pour toutes les lignes.

Voilà, nos deux tables sont maintenant prêtes. Mais avant de vous lâcher dans l'univers merveilleux des jointures et des sous-requêtes, nous allons encore compliquer un peu les choses. Parce que c'est bien joli pour un éleveur de pouvoir reconnaître un chien d'un chat, mais il serait de bon ton de reconnaître également un berger allemand d'un teckel, non ?

Par conséquent, nous allons encore ajouter deux choses à notre base. D'une part, nous allons ajouter une table Race, basée sur le même schéma que la table Espece. Il faudra également ajouter une colonne à la table Animal, qui contiendra l'id de la race de l'animal. Contrairement à la colonne espece_id, celle-ci pourra être NULL. Il n'est pas impossible que nous accueillions des bâtards, ou que certaines espèces que nous élevons ne soient pas classées en plusieurs races différentes.
Ensuite, nous allons garder une trace du pedigree de nos animaux. Pour ce faire, il faut pouvoir connaître ses parents. Donc, nous ajouterons deux colonnes à la table Animal : pere_id et mere_id, qui contiendront respectivement l'id du père et l'id de la mère de l'animal.

Ce sont toutes des commandes que vous connaissez, donc je ne détaille pas plus.

-- --------------------------
-- CREATION DE  LA TABLE Race
-- --------------------------
CREATE TABLE Race (
    id SMALLINT UNSIGNED AUTO_INCREMENT,
    nom VARCHAR(40) NOT NULL,
    espece_id SMALLINT UNSIGNED NOT NULL,     -- pas de nom latin, mais une référence vers l'espèce
    description TEXT,
    PRIMARY KEY(id),
    CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece(id)  -- pour assurer l'intégrité de la référence
)
ENGINE = InnoDB;

-- -----------------------
-- REMPLISSAGE DE LA TABLE
-- -----------------------
INSERT INTO Race (nom, espece_id, description)
VALUES ('Berger allemand', 1, 'Chien sportif et élégant au pelage dense, noir-marron-fauve, noir ou gris.'),
('Berger blanc suisse', 1, 'Petit chien au corps compact, avec des pattes courtes mais bien proportionnées et au pelage tricolore ou bicolore.'),
('Boxer', 1, 'Chien de taille moyenne, au poil ras de couleur fauve ou bringé avec quelques marques blanches.'),
('Bleu russe', 2, 'Chat aux yeux verts et à la robe épaisse et argentée.'),
('Maine coon', 2, 'Chat de grande taille, à poils mi-longs.'),
('Singapura', 2, 'Chat de petite taille aux grands yeux en amandes.'),
('Sphynx', 2, 'Chat sans poils.');

-- ---------------------------------------------
-- AJOUT DE LA COLONNE race_id A LA TABLE Animal
-- ---------------------------------------------
ALTER TABLE Animal ADD COLUMN race_id SMALLINT UNSIGNED;

ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id);

-- -------------------------
-- REMPLISSAGE DE LA COLONNE
-- -------------------------
UPDATE Animal SET race_id = 1 WHERE id IN (1, 13, 20, 18, 22, 25, 26, 28);
UPDATE Animal SET race_id = 2 WHERE id IN (12, 14, 19, 7);
UPDATE Animal SET race_id = 3 WHERE id IN (23, 17, 21, 27);
UPDATE Animal SET race_id = 4 WHERE id IN (33, 35, 37, 41, 44, 31, 3);
UPDATE Animal SET race_id = 5 WHERE id IN (43, 40, 30, 32, 42, 34, 39, 8);
UPDATE Animal SET race_id = 6 WHERE id IN (29, 36, 38);

-- -------------------------------------------------------
-- AJOUT DES COLONNES mere_id ET pere_id A LA TABLE Animal
-- -------------------------------------------------------
ALTER TABLE Animal ADD COLUMN mere_id SMALLINT UNSIGNED;

ALTER TABLE Animal
ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal(id);

ALTER TABLE Animal ADD COLUMN pere_id SMALLINT UNSIGNED;

ALTER TABLE Animal
ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal(id);

-- -------------------------------------------
-- REMPLISSAGE DES COLONNES mere_id ET pere_id
-- -------------------------------------------
UPDATE Animal SET mere_id = 18, pere_id = 22 WHERE id = 1;
UPDATE Animal SET mere_id = 7, pere_id = 21 WHERE id = 10;
UPDATE Animal SET mere_id = 41, pere_id = 31 WHERE id = 3;
UPDATE Animal SET mere_id = 40, pere_id = 30 WHERE id = 2;

A priori, la seule chose qui pourrait vous avoir surpris dans ces requêtes ce sont les clés étrangères sur mere_id et pere_id qui référencent toutes deux une autre colonne de la même table.

Bien, maintenant que nous avons trois tables et des données sympathiques à exploiter, nous allons passer aux choses sérieuses avec les jointures d'abord, puis les sous-requêtes.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Clés étrangères Jointures

Jointures

Modification de notre base Principe des jointures et notion d'alias

Vous vous attaquez maintenant au plus important chapitre de cette partie. Le principe des jointures est plutôt simple à comprendre (quoiqu'on puisse faire des requêtes très compliquées avec), et totalement indispensable.

Les jointures vont vous permettre de jongler avec plusieurs tables dans la même requête.
Pour commencer, nous verrons le principe général des jointures. Puis, nous ferons un petit détour par les alias, qui servent beaucoup dans les jointures (mais pas seulement). Ensuite, retour sur le sujet avec les deux types de jointures : internes et externes. Et pour finir, après un rapide tour d'horizon des syntaxes possibles pour faire une jointure, je vous propose quelques exercices pour mettre tout ça en pratique.

Principe des jointures et notion d'alias

Jointures Jointure interne

Principe des jointures et notion d'alias

Principe des jointures

Sans surprise, le principe des jointures est de joindre plusieurs tables. Pour ce faire, on utilise les informations communes des tables.

Par exemple, lorsque nous avons ajouté dans notre base les informations sur les espèces (leur nom latin et leur description), je vous ai dit que ce serait une très mauvaise idée de tout mettre dans la table Animal, car il nous faudrait alors répéter la même description pour tous les chiens, la même pour toutes les tortues, etc.
Cependant, vous avez sans doute remarqué que du coup, si vous voulez afficher la description de l'espèce de Cartouche (votre petit préféré), vous avez besoin de deux requêtes.

Étape 1 : on trouve l'id de l'espèce de Cartouche grâce à la table Animal.

SELECT espece_id FROM Animal WHERE nom = 'Cartouche';

espece_id

1

Étape 2 : on trouve la description de l'espèce grâce à son id.

SELECT description FROM Espece WHERE id = 1;

description

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

Ne serait-ce pas merveilleux de pouvoir faire tout ça (et plus encore) en une seule requête ?
C'est là que les jointures entrent en jeu ; on va utiliser l'information commune entre les deux tables : l'id de l'espèce, qui est présente dans Animal avec la colonne espece_id, et dans Espece avec la colonne id.

SELECT Espece.description 
FROM Espece 
INNER JOIN Animal 
    ON Espece.id = Animal.espece_id 
WHERE Animal.nom = 'Cartouche';

Et voilà le travail !

description

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

En fait, lorsque l'on fait une jointure, on crée une table virtuelle et temporaire qui reprend les colonnes des tables liées. Le schéma ci-dessous illustre ce principe.

Au départ, on a deux tables : Animal (id, sexe, nom, race_id, espece_id) et Espece (id, nom_courant, nom_latin). Les deux premières lignes d'Animal correspondent à la première ligne d'Espece, et la troisième ligne d'Animal à la deuxième ligne d'Espece. Une fois les deux tables jointes, on obtient une table possédant toutes les colonnes d'Animal et toutes les colonnes d'Espece, avec les valeurs correspondantes de chaque table. On peut voir que les cinquième et sixième colonnes de la table de jointure ont les mêmes valeurs.
Ensuite, de cette table virtuelle, on peut extraire ce que l'on veut. La colonne nom_latin pour la ligne ayant "Caribou" dans la colonne nom, par exemple.

Principe des jointures
Principe des jointures
Notion d'alias

Je fais ici une petite parenthèse avant de vous expliquer en détail le fonctionnement des jointures pour vous parler d'un petit truc bien utile : les alias.

Les alias sont des noms de remplacement, que l'on donne de manière temporaire (le temps d'une requête en fait) à une colonne, une table, une donnée. Les alias sont introduits par le mot-clé AS. Ce mot-clé est facultatif, vous pouvez très bien définir un alias sans utiliser AS, mais je préfère personnellement toujours le mettre. Je trouve qu'on y voit plus clair.

Comment ça marche ?

Prenons cette requête toute simple :

SELECT 5+3;

5+3

8

Imaginons que ce calcul savant représente en fait le nombre de chiots de Cartouche, qui a eu une première portée de 5 chiots, et une seconde de seulement 3 chiots. Nous voudrions donc indiquer qu'il s'agit bien de ça, et non pas d'un calcul inutile destiné simplement à illustrer une notion obscure.

Facile ! Il suffit d'utiliser les alias :

SELECT 5+3 AS Chiots_Cartouche;

-- OU, sans utiliser AS

SELECT 5+3 Chiots_Cartouche;

Chiots_Cartouche

8

Bon, tout ça c'est bien joli, mais pour l'instant ça n'a pas l'air très utile…
Prenons un exemple plus parlant : retournez voir le schéma qui explique le principe des jointures un peu plus haut. La table virtuelle résultant de la jointure des tables Espece et Animal possède plusieurs colonnes :
id sexe nom race_id espece_id id nom_courant nom_latin
Mais que vois-je ? J'ai deux colonnes id ! Comment faire pour les différencier ? Comment être sûr de savoir à quoi elles se rapportent ?
Avec les alias pardi ! Il suffit de donner l'alias espece_id à la colonne id de la table Espece, et animal_id à la colonne id de la table Animal.
Tout à coup, ça vous semble plus intéressant non ?

Je vais vous laisser sur ce sentiment. Il n'y a pas grand-chose de plus à dire sur les alias, vous en comprendrez toute l'utilité à travers les nombreux exemples dans la suite de ce cours. L'important pour l'instant est que vous sachiez que ça existe et comment les définir.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Jointures Jointure interne

Jointure interne

Principe des jointures et notion d'alias Jointure externe

Jointure interne

L'air de rien, dans l'introduction, je vous ai déjà montré comment faire une jointure. La première émotion passée, vous devriez vous être dit "Tiens, mais ça n'a pas l'air bien compliqué en fait, les jointures".
En effet, une fois que vous aurez compris comment réfléchir aux jointures, tout se fera tout seul. Personnellement, ça m'aide vraiment d'imaginer la table virtuelle créée par la jointure, et de travailler sur cette table pour tout ce qui est conditions, tris, etc.

Revoici la jointure que je vous ai fait faire, et qui est en fait une jointure interne.

SELECT Espece.description 
FROM Espece 
INNER JOIN Animal 
    ON Espece.id = Animal.espece_id 
WHERE Animal.nom = 'Cartouche';

Décomposons !

Si vous avez compris ça, vous avez tout compris !

Syntaxe

Comme d'habitude, voici donc la syntaxe à utiliser pour faire des requêtes avec jointure(s) interne(s).

SELECT *                                   -- comme d'habitude, vous sélectionnez les colonnes que vous voulez
FROM nom_table1   
[INNER] JOIN nom_table2                    -- INNER explicite le fait qu'il s'agit d'une jointure interne, mais c'est facultatif
    ON colonne_table1 = colonne_table2     -- sur quelles colonnes se fait la jointure
                                           -- vous pouvez mettre colonne_table2 = colonne_table1, l'ordre n'a pas d'importance

[WHERE ...]                               
[ORDER BY ...]                            -- les clauses habituelles sont bien sûr utilisables !
[LIMIT ...]
Condition de jointure

La clause ON sert à préciser la condition de la jointure. C'est-à-dire sur quel(s) critère(s) les deux tables doivent être jointes. Dans la plupart des cas, il s'agira d'une condition d'égalité simple, comme ON Animal.espece_id = Espece.id. Il est cependant tout à fait possible d'avoir plusieurs conditions à remplir pour lier les deux tables. On utilise alors les opérateurs logiques habituels. Par exemple, une jointure peut très bien se faire sur plusieurs colonnes :

SELECT *
FROM nom_table1
INNER JOIN nom_table2
   ON colonne1_table1 = colonne1_table2
      AND colonne2_table1 = colonne2_table2
      [AND ...];
Expliciter le nom des colonnes

Il peut arriver que vous ayez dans vos deux tables des colonnes portant le même nom. C'est le cas dans notre exemple, puisque la table Animal comporte une colonne id, tout comme la table Espece. Il est donc important de préciser de quelle colonne on parle dans ce cas-là.
Vous l'avez vu dans notre requête, on utilise pour ça l'opérateur . (nom_table.nom_colonne). Pour les colonnes ayant un nom non-ambigu (qui n'existe dans aucune autre table de la jointure), il n'est pas obligatoire de préciser la table.
En général, je précise la table quand il s'agit de grosses requêtes avec plusieurs jointures. En revanche, pour les petites jointures courantes, il est vrai que c'est moins long à écrire si on ne précise pas la table.

Exemple : sélection du nom des animaux commençant par "Ch", ainsi que de l'id et la description de leur espèce.

SELECT Espece.id,                   -- ici, pas le choix, il faut préciser
       Espece.description,          -- ici, on pourrait mettre juste description
       Animal.nom                   -- idem, la précision n'est pas obligatoire. C'est cependant plus clair puisque les espèces ont un nom aussi
FROM Espece   
INNER JOIN Animal
     ON Espece.id = Animal.espece_id
WHERE Animal.nom LIKE 'Ch%';

id

description

nom

2

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

Choupi

3

Bestiole avec une carapace très dure

Cheli

3

Bestiole avec une carapace très dure

Chicaca

Utiliser les alias

Les alias sont souvent utilisés avec les jointures. Ils permettent notamment de renommer les tables, et ainsi d'écrire moins de code.

Exemple : on renomme la table Espece "e", et la table Animal "a".

SELECT e.id,                  
       e.description,          
       a.nom                   
FROM Espece AS e          -- On donne l'alias "e" à Espece
INNER JOIN Animal AS a    -- et l'alias "a" à Animal.
     ON e.id = a.espece_id
WHERE a.nom LIKE 'Ch%';

Comme vous le voyez, le code est plus compact. Ici encore, c'est quelque chose que j'utilise souvent pour de petites requêtes ponctuelles. Par contre, pour de grosses requêtes, je préfère les noms explicites ; c'est ainsi plus facile de s'y retrouver.

Une autre utilité des alias est de renommer les colonnes pour que le résultat soit plus clair. Observez le résultat de la requête précédente. Vous avez trois colonnes : id, description et nom. Le nom de la table dont provient la colonne n'est indiqué nulle part. A priori, vous savez ce que vous avez demandé, surtout qu'il n'y a pas encore trop de colonnes, mais imaginez que vous sélectionniez une vingtaine de colonnes. Ce serait quand même mieux de savoir de quel id on parle, s'il s'agit du nom de l'animal, de son maître, du père, du fils ou du Saint-Esprit !
Il est intéressant là aussi d'utiliser les alias.

Exemple : on donne des alias aux colonnes (id_espece pour id de la table Espece, description_espece pour Espece.description et nom_bestiole pour Animal.nom).

SELECT Espece.id AS id_espece,                  
       Espece.description AS description_espece,          
       Animal.nom AS nom_bestiole                   
FROM Espece   
INNER JOIN Animal
     ON Espece.id = Animal.espece_id
WHERE Animal.nom LIKE 'Ch%';

id_espece

description_espece

nom_bestiole

2

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

Choupi

3

Bestiole avec une carapace très dure

Cheli

3

Bestiole avec une carapace très dure

Chicaca

C'est tout de suite plus clair !

Pourquoi "interne" ?

INNER JOIN permet donc de faire une jointure interne sur deux tables. Mais que signifie donc ce "interne" ?

C'est très simple ! Lorsque l'on fait une jointure interne, cela veut dire qu'on exige qu'il y ait des données de part et d'autre de la jointure. Donc, si l'on fait une jointure sur la colonne a de la table A et la colonne b de la table B :

SELECT * 
FROM A
INNER JOIN B 
    ON A.a = B.b

Ceci retournera uniquement les lignes pour lesquelles A.a et B.b correspondent.

Exemple : on veut connaître la race des chats :

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Animal
INNER JOIN Race
    ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2             -- ceci correspond aux chats
ORDER BY Race.nom, Animal.nom;

nom_animal

race

Callune

Bleu russe

Caribou

Bleu russe

Cawette

Bleu russe

Feta

Bleu russe

Filou

Bleu russe

Raccou

Bleu russe

Schtroumpfette

Bleu russe

Bagherra

Maine coon

Bilba

Maine coon

Capou

Maine coon

Cracotte

Maine coon

Farceur

Maine coon

Milla

Maine coon

Zara

Maine coon

Zonko

Maine coon

Boucan

Singapura

Boule

Singapura

Fiero

Singapura

On peut voir ici que les chats Choupi et Roucky pour lesquels je n'ai pas d'information sur la race (race_id est NULL), ne sont pas repris dans les résultats. De même, aucun des chats n'est de la race "Sphynx", celle-ci n'est donc pas reprise. Si je veux les inclure, je dois utiliser une jointure externe.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Principe des jointures et notion d'alias Jointure externe

Jointure externe

Jointure interne Syntaxes alternatives

Jointure externe

Comme je viens de vous le dire, une jointure externe permet de sélectionner également les lignes pour lesquelles il n'y a pas de correspondance dans une des tables jointes. MySQL permet deux types de jointures externes : les jointures par la gauche et les jointures par la droite.

Jointures par la gauche

Lorsque l'on fait une jointure par la gauche (grâce aux mots-clés LEFT JOIN ou LEFT OUTER JOIN), cela signifie que l'on veut toutes les lignes de la table de gauche (sauf restrictions dans une clause WHERE bien sûr), même si certaines n'ont pas de correspondance avec une ligne de la table de droite.

Alors, table de gauche, table de droite, laquelle est laquelle ? C'est très simple, nous lisons de gauche à droite, donc la table de gauche est la première table mentionnée dans la requête, c'est-à-dire, en général, la table donnée dans la clause FROM.

Si l'on veut de nouveau connaître la race des chats, mais que cette fois-ci nous voulons également afficher les chats qui n'ont pas de race, on peut utiliser la jointure suivante (je ne prends que les chats dont le nom commence par "C" afin de réduire le nombre de lignes, mais vous pouvez choisir les conditions que vous voulez) :

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Animal                                               -- Table de gauche
LEFT JOIN Race                                            -- Table de droite
    ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2 
    AND Animal.nom LIKE 'C%'
ORDER BY Race.nom, Animal.nom;

-- OU 

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Animal                                               -- Table de gauche
LEFT OUTER JOIN Race                                      -- Table de droite
    ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2
    AND Animal.nom LIKE 'C%'
ORDER BY Race.nom, Animal.nom;

nom_animal

race

Choupi

NULL

Callune

Bleu russe

Caribou

Bleu russe

Cawette

Bleu russe

Capou

Maine coon

Cracotte

Maine coon

On ne connaît pas la race de Choupi, et pourtant il fait bien partie des lignes sélectionnées alors qu'avec la jointure interne il n'apparaissait pas. Simplement, les colonnes qui viennent de la table Race (la colonne Race.nom AS race dans ce cas-ci) sont NULL pour les lignes qui n'ont pas de correspondance (la ligne de Choupi ici).

Jointures par la droite

Les jointures par la droite (RIGHT JOIN ou RIGHT OUTER JOIN), c'est évidemment le même principe, sauf que ce sont toutes les lignes de la table de droite qui sont sélectionnées même s'il n'y a pas de correspondance dans la table de gauche.

Exemple : toujours avec les races de chats.

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Animal                                                -- Table de gauche
RIGHT JOIN Race                                            -- Table de droite
    ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;

- OU

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Animal                                              -- Table de gauche
RIGHT OUTER JOIN Race                                    -- Table de droite
    ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;

nom_animal

race

Callune

Bleu russe

Caribou

Bleu russe

Cawette

Bleu russe

Feta

Bleu russe

Filou

Bleu russe

Raccou

Bleu russe

Schtroumpfette

Bleu russe

Bagherra

Maine coon

Bilba

Maine coon

Capou

Maine coon

Cracotte

Maine coon

Farceur

Maine coon

Milla

Maine coon

Zara

Maine coon

Zonko

Maine coon

Boucan

Singapura

Boule

Singapura

Fiero

Singapura

NULL

Sphynx

On a bien une ligne avec la race "Sphynx", bien que nous n'ayons aucun sphynx dans notre table Animal.

À noter que toutes les jointures par la droite peuvent être faites grâce à une jointure par la gauche (et vice versa). Voici l'équivalent avec une jointure par la gauche de la requête que nous venons d'écrire :

SELECT Animal.nom AS nom_animal, Race.nom AS race
FROM Race                                               -- Table de gauche
LEFT JOIN Animal                                        -- Table de droite
    ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Jointure interne Syntaxes alternatives

Syntaxes alternatives

Jointure externe Exemples d'application et exercices

Syntaxes alternatives

Les syntaxes que je vous ai montrées jusqu'ici, avec [INNER] JOIN et LEFT|RIGHT [OUTER] JOIN, sont les syntaxes classiques que vous retrouverez le plus souvent. Il existe cependant d'autres manières de faire des jointures.

Jointures avec USING

Lorsque les colonnes qui servent à joindre les deux tables ont le même nom, vous pouvez utiliser la clause USING au lieu de la clause ON.

SELECT *
FROM table1
[INNER | LEFT | RIGHT] JOIN table2 USING (colonneJ);  -- colonneJ est présente dans les deux tables

-- équivalent à 

SELECT *
FROM table1
[INNER | LEFT | RIGHT] JOIN table2 ON Table1.colonneJ = table2.colonneJ;
Jointures naturelles

Comme pour les jointures avec USING, il est possible d'utiliser les jointures naturelles dans le cas où les colonnes servant à la jointure ont le même nom dans les deux tables.
Simplement, dans le cas d'une jointure naturelle, on ne donne pas la (les) colonne(s) sur laquelle (lesquelles) joindre les tables : c'est déterminé automatiquement. Donc si on a les trois tables suivantes :

Exemple 1 : jointure de table1 et table2 (une colonne ayant le même nom : B).

SELECT * 
FROM table1
NATURAL JOIN table2;

-- EST ÉQUIVALENT À

SELECT *
FROM table1
INNER JOIN table2 
    ON table1.B = table2.B;

Exemple 2 : jointure de table1 et table3 (deux colonnes ayant le même nom : A et C).

SELECT * 
FROM table1
NATURAL JOIN table3;

-- EST ÉQUIVALENT À

SELECT *
FROM table1
INNER JOIN table3
    ON table1.A = table3.A AND table1.C = table3.C;

Pour utiliser ce type de jointure, il faut donc être certain que toutes les colonnes nécessaires à la jointure ont le même nom dans les deux tables, mais aussi que les colonnes ayant le même nom sont uniquement celles qui servent à la jointure.

Notez que vous pouvez également réaliser une jointure externe par la gauche avec les jointures naturelles à l'aide de NATURAL LEFT JOIN.

Jointures sans JOIN

Cela peut paraître absurde, mais il est tout à fait possible de faire une jointure sans utiliser le mot JOIN. Ce n'est cependant possible que pour les jointures internes.
Il suffit de mentionner les tables que l'on veut joindre dans la clause FROM (séparées par des virgules), et de mettre la condition de jointure dans la clause WHERE (sans clause ON).

SELECT *
FROM table1, table2
WHERE table1.colonne1 = table2.colonne2;

-- équivalent à 

SELECT *
FROM table1
[INNER] JOIN table2
    ON table1.colonne1 = table2.colonne2;

Je vous déconseille cependant d'utiliser cette syntaxe. En effet, lorsque vous ferez des grosses jointures, avec plusieurs conditions dans la clause WHERE, vous serez bien contents de pouvoir différencier au premier coup d’œil les conditions de jointures des conditions "normales".

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Jointure externe Exemples d'application et exercices

Exemples d'application et exercices

Syntaxes alternatives Sous-requêtes

Exemples d'application et exercices

Maintenant que vous savez comment faire une jointure, on va un peu s'amuser. Cette partie sera en fait un mini-TP. Je vous dis à quel résultat vous devez parvenir en utilisant des jointures, vous essayez, et ensuite, vous allez voir la réponse et les explications.

Techniquement, vous avez vu toute la théorie nécessaire pour réaliser toutes les requêtes que je vous demanderai ici. Cependant, il y aura des choses que je ne vous ai pas "montrées" explicitement, comme les jointures avec plus de deux tables, ou les auto-jointures (joindre une table avec elle-même).
C'est voulu !

Je ne m'attends pas à ce que vous réussissiez à construire toutes les requêtes sans jeter un œil à la solution. Le but ici est de vous faire réfléchir, et surtout de vous faire prendre conscience qu'on peut faire pas mal de chose en SQL, en combinant plusieurs techniques par exemple.
Si un jour vous vous dites "Tiens, ce serait bien si en SQL on pouvait…", arrêtez de vous le dire et faites-le, simplement ! Vous serez probablement plus souvent limités par votre imagination que par SQL.

Bien, ça c'est dit, donc allons-y !

A/ Commençons par des choses faciles

Des jointures sur deux tables, avec différentes conditions à respecter.

1. Moi, j'aime bien les chiens de berger

Vous devez obtenir la liste des races de chiens qui sont des chiens de berger.

SELECT Race.nom AS Race 
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id
WHERE Espece.nom_courant = 'chien' AND Race.nom LIKE '%berger%';

Bon, c'était juste un échauffement. Normalement vous ne devriez pas avoir eu de difficultés avec cette requête. Peut-être avez-vous oublié la condition Espece.nom_courant = 'chien' ? On ne sait jamais, une race de chat (ou autre) pourrait très bien contenir "berger", or j'ai explicitement demandé les chiens.

2. Mais de quelle couleur peut bien être son pelage ?

Vous devez obtenir la liste des animaux (leur nom, date de naissance et race) pour lesquels nous n'avons aucune information sur la couleur que devrait avoir leur pelage.

SELECT Animal.nom AS nom_animal, Animal.date_naissance, Race.nom AS race
FROM Animal
LEFT JOIN Race
    ON Animal.race_id = Race.id
WHERE (Race.description NOT LIKE '%poil%' 
        AND Race.description NOT LIKE '%robe%'
        AND Race.description NOT LIKE '%pelage%'
      )
      OR Race.id IS NULL;

Il faut donc :

1/ On ne connaît pas la race

Les animaux dont on ne connaît pas la race ont NULL dans la colonne race_id. Mais vu que l'on fait une jointure sur cette colonne, il ne faut pas oublier de faire une jointure externe, sinon tous ces animaux sans race seront éliminés.
Une fois que c'est fait, pour les sélectionner, il suffit de mettre la condition Animaux.race_id IS NULL par exemple, ou simplement Race.#n'importe quelle colonne# IS NULL vu qu'il n'y a pas de correspondance avec Race.

2/ Pas d'information sur le pelage dans la description de la race

Je vous ai donné comme indice le fait que j'utilisais les mots "pelage", "poil" ou "robe" dans les descriptions des espèces. Il fallait donc sélectionner les races pour lesquelles la description ne contient pas ces mots. D'où l'utilisation de NOT LIKE.

Si on met tout ça ensemble : il fallait faire une jointure externe des tables Animal et Race, et ensuite sélectionner les animaux qui répondaient à l'une ou l'autre des conditions (opérateur logique OR).

B/ Compliquons un peu les choses

Jointures sur deux tables, ou plus !

1. La race ET l'espèce

Vous devez obtenir la liste des chats et des perroquets amazones, avec leur sexe, leur espèce (nom latin) et leur race s'ils en ont une. Regroupez les chats ensemble, les perroquets ensemble et, au sein de l'espèce, regroupez les races.

SELECT Animal.nom as nom_animal, Animal.sexe, Espece.nom_latin as espece, Race.nom as race
FROM Animal
INNER JOIN Espece
    ON Animal.espece_id = Espece.id
LEFT JOIN Race
    ON Animal.race_id = Race.id
WHERE Espece.nom_courant IN ('Perroquet amazone', 'Chat')
ORDER BY Espece.nom_latin, Race.nom;

Comme vous voyez, c'est non seulement très simple de faire des jointures sur plus d'une table, mais c'est également possible de mélanger jointures internes et externes. Si ça vous pose problème, essayez vraiment de vous imaginer les étapes.
D'abord, on fait la jointure d'Animal et d'Espece. On se retrouve alors avec une grosse table qui possède toutes les colonnes d'Animal et toutes les colonnes d'Espece. Ensuite, à cette grosse table (à la fois virtuelle et intermédiaire), on joint la table Race, grâce à la colonne Animal.race_id.
Notez que l'ordre dans lequel vous faites les jointures n'est pas important.

En ce qui concerne la clause ORDER BY, j'ai choisi de trier par ordre alphabétique, mais il est évident que vous pouviez également trier sur les id de l'espèce et de la race. L'important ici était de trier d'abord sur une colonne d'Espece, ensuite sur une colonne de Race.

2. Futures génitrices

Vous devez obtenir la liste des chiennes dont on connaît la race, et qui sont en âge de procréer (c'est-à-dire nées avant juillet 2010). Affichez leur nom, date de naissance et race.

SELECT Animal.nom AS nom_chienne, Animal.date_naissance, Race.nom AS race
FROM Animal
INNER JOIN Espece
    ON Animal.espece_id = Espece.id
INNER JOIN Race
    ON Animal.race_id = Race.id
WHERE Espece.nom_courant = 'chien'
    AND Animal.date_naissance < '2010-07-01'
    AND Animal.sexe = 'F';

Cette fois, il fallait faire une jointure interne avec Race puisqu'on voulait que la race soit connue. Le reste de la requête ne présentait pas de difficulté majeure.

C/ Et maintenant, le test ultime !

Jointures sur deux tables ou plus, avec éventuelles auto-jointures.

Je vous ai fait rajouter, à la fin du chapitre précédent, deux jolies petites colonnes dans la table Animal : mere_id et pere_id. Le moment est venu de les utiliser !

1. Mon père, ma mère, mes frères et mes sœurs (Wohooooo)

Vous devez obtenir la liste des chats dont on connaît les parents, ainsi que le nom de ces parents.

SELECT Animal.nom, Pere.nom AS Papa, Mere.nom AS Maman
FROM Animal
INNER JOIN Animal AS Pere
    ON Animal.pere_id = Pere.id
INNER JOIN Animal AS Mere
    ON Animal.mere_id = Mere.id
INNER JOIN Espece
    ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'chat';

Si celle-là, vous l'avez trouvée tout seuls, je vous félicite ! Sinon, c'est un peu normal. Vous voici face au premier cas dans lequel les alias sont obligatoires. En effet, vous aviez sans doute compris que vous pouviez faire FROM Animal INNER JOIN Animal, puisque j'avais mentionné les auto-jointures, mais vous avez probablement bloqué sur la clause ON. Comment différencier les colonnes d'Animal dans FROM des colonnes d'Animal dans JOIN ? Vous savez maintenant qu'il suffit d'utiliser des alias.
Il faut faire une jointure sur trois tables puisqu'au final, vous avez besoin des noms de trois animaux. Or en liant deux tables Animal ensemble, vous avez deux colonnes nom. Pour pouvoir en avoir trois, il faut lier trois tables.

Prenez le temps de bien comprendre les auto-jointures, le pourquoi du comment et le comment du pourquoi. Faites des schémas si besoin, imaginez les tables intermédiaires.

2. Je suis ton père

Histoire de se détendre un peu, vous devez maintenant obtenir la liste des enfants de Bouli (nom, sexe et date de naissance).

SELECT Animal.nom, Animal.sexe, Animal.date_naissance
FROM Animal
INNER JOIN Animal AS Pere
    ON Animal.pere_id = Pere.id
WHERE Pere.nom = 'Bouli';

Après la requête précédente, celle-ci devrait vous sembler plutôt facile ! Notez qu'il y a plusieurs manières de faire bien sûr. En voici une autre :

SELECT Enfant.nom, Enfant.sexe, Enfant.date_naissance
FROM Animal
INNER JOIN Animal AS Enfant
    ON Enfant.pere_id = Animal.id
WHERE Animal.nom = 'Bouli';

L'important, c'est le résultat ! Évidemment, si vous avez utilisé 45 jointures et 74 conditions, alors ce n'est pas bon non plus. Du moment que vous n'avez joint que deux tables, ça devrait être bon.

3. C'est un pure race ?

Courage, c'est la dernière (et la plus thrash :ninja: )!

Vous devez obtenir la liste des animaux dont on connaît le père, la mère, la race, la race du père, la race de la mère. Affichez le nom et la race de l'animal et de ses parents, ainsi que l'espèce de l'animal (pas des parents).

SELECT Espece.nom_courant AS espece, Animal.nom AS nom_animal, Race.nom AS race_animal,
    Pere.nom AS papa, Race_pere.nom AS race_papa,
    Mere.nom AS maman, Race_mere.nom AS race_maman
FROM Animal
INNER JOIN Espece
    ON Animal.espece_id = Espece.id
INNER JOIN Race
    ON Animal.race_id = Race.id
INNER JOIN Animal AS Pere
    ON Animal.pere_id = Pere.id
INNER JOIN Race AS Race_pere
    ON Pere.race_id = Race_pere.id
INNER JOIN Animal AS Mere
    ON Animal.mere_id = Mere.id
INNER JOIN Race AS Race_mere
    ON Mere.race_id = Race_mere.id;

Pfiou :euh: ! Le principe est exactement le même que pour avoir le nom des parents. Il suffit de rajouter une jointure avec Race pour le père, pour la mère, et pour l'enfant, en n'oubliant pas de bien utiliser les alias bien sûr.
C'est avec ce genre de requête que l'on se rend compte à quel point il est important de bien structurer et indenter sa requête, et à quel point un choix d'alias intelligent peut clarifier les choses.

Si vous avez survécu jusqu'ici, vous devriez maintenant avoir compris en profondeur le principe des jointures, et être capables de manipuler de nombreuses tables sans faire tout tomber par terre.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxes alternatives Sous-requêtes

Sous-requêtes

Exemples d'application et exercices Sous-requêtes dans le FROM

Nous allons maintenant apprendre à imbriquer plusieurs requêtes, ce qui vous permettra de faire en une seule fois ce qui vous aurait, jusqu'ici, demandé plusieurs étapes.

Une sous-requête est une requête à l'intérieur d'une autre requête. Avec le SQL, vous pouvez construire des requêtes imbriquées sur autant de niveaux que vous voulez. Vous pouvez également mélanger jointures et sous-requêtes. Tant que votre requête est correctement structurée, elle peut être aussi complexe que vous voulez.

Une sous-requête peut être faite dans une requête de type SELECT, INSERT, UPDATE ou DELETE (et quelques autres que nous n'avons pas encore vues). Nous ne verrons dans ce chapitre que les requêtes de sélection. Les jointures et sous-requêtes pour la modification, l'insertion et la suppression de données étant traitées dans le prochain chapitre.

La plupart des requêtes de sélection que vous allez voir dans ce chapitre sont tout à fait réalisables autrement, souvent avec une jointure. Certains préfèrent les sous-requêtes aux jointures parce que c'est légèrement plus clair comme syntaxe, et peut-être plus intuitif. Il faut cependant savoir qu'une jointure sera toujours au moins aussi rapide que la même requête faite avec une sous-requête. Par conséquent, s'il est important pour vous d'optimiser les performances de votre application, utilisez plutôt des jointures lorsque c'est possible.

Sous-requêtes dans le FROM

Sous-requêtes Sous-requêtes dans les conditions

Sous-requêtes dans le FROM

Lorsque l'on fait une requête de type SELECT, le résultat de la requête nous est envoyé sous forme de table. Et grâce aux sous-requêtes, il est tout à fait possible d'utiliser cette table et de refaire une recherche uniquement sur les lignes de celle-ci.

Exemple : on sélectionne toutes les femelles parmi les perroquets et les tortues .

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id
FROM Animal
INNER JOIN Espece
    ON Espece.id = Animal.espece_id
WHERE sexe = 'F' 
AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

id

sexe

date_naissance

nom

espece_id

4

F

2009-08-03 05:12:00

NULL

3

6

F

2009-06-13 08:17:00

Bobosse

3

45

F

2007-04-01 18:17:00

Nikki

3

46

F

2009-03-24 08:23:00

Tortilla

3

47

F

2009-03-26 01:24:00

Scroupy

3

48

F

2006-03-15 14:56:00

Lulla

3

49

F

2008-03-15 12:02:00

Dana

3

50

F

2009-05-25 19:57:00

Cheli

3

51

F

2007-04-01 03:54:00

Chicaca

3

52

F

2006-03-15 14:26:00

Redbul

3

60

F

2009-03-26 07:55:00

Parlotte

4

Parmi ces femelles perroquets et tortues, on veut connaître la date de naissance de la plus âgée. On va donc faire une sélection dans la table des résultats de la requête.

SELECT MIN(date_naissance)
FROM (
    SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id
    FROM Animal
    INNER JOIN Espece
        ON Espece.id = Animal.espece_id
    WHERE sexe = 'F'
    AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;

MIN(date_naissance)

2006-03-15 14:26:00

Les règles à respecter
Parenthèses

Une sous-requête doit toujours se trouver dans des parenthèses, afin de définir clairement ses limites.

Alias

Dans le cas des sous-requêtes dans le FROM, il est également obligatoire de préciser un alias pour la table intermédiaire (le résultat de notre sous-requête). Si vous ne le faites pas, MySQL déclenchera une erreur. Ici, on l'a appelée tortues_perroquets_F.
Nommer votre table intermédiaire permet de plus de vous y référer si vous faites une jointure dessus, ou si certains noms de colonnes sont ambigus et que le nom de la table doit être précisé. Attention au fait qu'il ne s'agit pas de la table Animal, mais bien d'une table tirée d'Animal.
Par conséquent, si vous voulez préciser le nom de la table dans le SELECT principal, vous devez écrire SELECT MIN(tortues_perroquets_F.date_naissance), et non pas SELECT MIN(Animal.date_naissance).

Cohérence des colonnes

Les colonnes sélectionnées dans le SELECT "principal" doivent bien sûr être présentes dans la table intermédiaire. La requête suivante, par exemple, ne fonctionnera pas :

SELECT MIN(date_naissance)
FROM (
    SELECT Animal.id, Animal.nom
    FROM Animal
    INNER JOIN Espece
        ON Espece.id = Animal.espece_id
    WHERE sexe = 'F'
    AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;

En effet, tortues_perroquets_F n'a que deux colonnes : id et nom. Il est donc impossible de sélectionner la colonne date_naissance de cette table.

Noms ambigus

Pour finir, attention aux noms de colonnes ambigus. Une table, même intermédiaire, ne peut pas avoir deux colonnes ayant le même nom. Si deux colonnes ont le même nom, il est nécessaire de renommer explicitement au moins l'une des deux.

Donc, si on veut sélectionner la colonne Espece.id en plus dans la sous-requête, on peut procéder ainsi :

SELECT MIN(date_naissance)
FROM (
    SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id, 
            Espece.id AS espece_espece_id         -- On renomme la colonne id de Espece, donc il n'y a plus de doublons.
    FROM Animal                                   -- Attention de ne pas la renommer espece_id, puisqu'on sélectionne aussi la colonne espece_id dans Animal !
    INNER JOIN Espece
        ON Espece.id = Animal.espece_id
    WHERE sexe = 'F'
    AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Sous-requêtes Sous-requêtes dans les conditions

Sous-requêtes dans les conditions

Sous-requêtes dans le FROM Sous-requêtes corrélées

Sous-requêtes dans les conditions

Je vous ai donc dit que lorsque vous faites une requête SELECT, le résultat est sous forme de table. Ces tables de résultats peuvent avoir :

Les sous-requêtes renvoyant plusieurs lignes et plusieurs colonnes ne sont utilisées que dans les clauses FROM. Nous allons ici nous intéresser aux trois autres possibilités uniquement.

Comparaisons

Pour rappel, voici un tableau des opérateurs de comparaison.

Opérateur

Signification

=

égal

<

inférieur

<=

inférieur ou égal

>

supérieur

>=

supérieur ou égal

<> ou !=

différent

<=>

égal (valable pour NULL aussi)

On peut utiliser des comparaisons de ce type avec des sous-requêtes qui donnent comme résultat soit une valeur (c'est-à-dire une seule ligne et une seule colonne), soit une ligne (plusieurs colonnes mais une seule ligne).

Sous-requête renvoyant une valeur

Le cas le plus simple est évidemment d'utiliser une sous-requête qui renvoie une valeur.

SELECT id, sexe, nom, commentaires, espece_id, race_id
FROM Animal
WHERE race_id = 
    (SELECT id FROM Race WHERE nom = 'Berger Allemand');  -- la sous-requête renvoie simplement 1

Remarquez que cette requête peut également s'écrire avec une jointure plutôt qu'une sous-requête :

SELECT Animal.id, sexe, Animal.nom, commentaires, Animal.espece_id, race_id
FROM Animal
INNER JOIN Race ON Race.id = Animal.race_id
WHERE Race.nom = 'Berger Allemand';

Voici un exemple de requête avec sous-requête qu'il est impossible de faire avec une simple jointure :

SELECT id, nom, espece_id
FROM Race
WHERE espece_id = (
    SELECT MIN(id)     -- Je rappelle que MIN() permet de récupérer la plus petite valeur de la colonne parmi les lignes sélectionnées
    FROM Espece);

id

nom

espece_id

1

Berger allemand

1

2

Berger blanc suisse

1

3

Boxer

1

En ce qui concerne les autres opérateurs de comparaison, le principe est exactement le même :

SELECT id, nom, espece_id
FROM Race
WHERE espece_id < (
    SELECT id    
    FROM Espece
    WHERE nom_courant = 'Tortue d''Hermann');

id

nom

espece_id

1

Berger allemand

1

2

Berger blanc suisse

1

3

Boxer

1

4

Bleu russe

2

5

Maine coon

2

6

Singapura

2

7

Sphynx

2

Ici la sous-requête renvoie 3, donc nous avons bien les races dont l'espèce a un id inférieur à 3 (donc 1 et 2 :p ).

Sous-requête renvoyant une ligne

Dans le cas d'une sous-requête dont le résultat est une ligne, la syntaxe est la suivante :

SELECT *
FROM nom_table1
WHERE [ROW](colonne1, colonne2) = (    -- le ROW n'est pas obligatoire
    SELECT colonneX, colonneY
    FROM nom_table2
    WHERE...);                         -- Condition qui ne retourne qu'UNE SEULE LIGNE

Cette requête va donc renvoyer toutes les lignes de la table1 dont la colonne1 = la colonneX de la ligne résultat de la sous-requête ET la colonne2 = la colonneY de la ligne résultat de la sous-requête.

Vous voulez un exemple peut-être ? Allons-y !

SELECT id, sexe, nom, espece_id, race_id 
FROM Animal
WHERE (id, race_id) = (
    SELECT id, espece_id
    FROM Race
    WHERE id = 7);

id

sexe

nom

espece_id

race_id

7

F

Caroline

1

2

Décomposons calmement. Voyons d'abord ce que la sous-requête donne comme résultat.

SELECT id, espece_id
FROM Race
WHERE id = 7;

id

espece_id

7

2

Et comme condition, on a WHERE (id, race_id) = #le résultat de la sous-requête#.
Donc la requête renverra les lignes de la table Animal pour lesquelles id vaut 7 et race_id vaut 2.

Conditions avec IN et NOT IN
IN

Vous connaissez déjà l'opérateur IN, qui compare une colonne avec une liste de valeurs.

Exemple

SELECT Animal.id, Animal.nom, Animal.espece_id
FROM Animal
INNER JOIN Espece
    ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

Cet opérateur peut également s'utiliser avec une sous-requête dont le résultat est une colonne ou une valeur. On peut donc réécrire la requête ci-dessus en utilisant une sous-requête plutôt qu'une jointure :

SELECT id, nom, espece_id
FROM Animal
WHERE espece_id IN (
    SELECT id 
    FROM Espece
    WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);

Le fonctionnement est plutôt facile à comprendre. La sous-requête donne les résultats suivants :

SELECT id            -- On ne sélectionne bien qu'UNE SEULE COLONNE.
FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

id

3

4

Ainsi, la requête principale sélectionnera les lignes qui ont un espece_id parmi ceux renvoyés par la sous-requête, donc 3 ou 4.

NOT IN

Si l'on utilise NOT IN, c'est bien sûr le contraire, on exclut les lignes qui correspondent au résultat de la sous-requête. La requête suivante nous renverra donc les animaux dont l'espece_id n'est pas 3 ou 4.

SELECT id, nom, espece_id
FROM Animal
WHERE espece_id NOT IN (
    SELECT id 
    FROM Espece
    WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);
Conditions avec ANY, SOME et ALL

Les conditions avec IN et NOT IN sont un peu limitées, puisqu'elles ne permettent que des comparaisons de type "est égal" ou "est différent". Avec ANY et ALL, on va pouvoir utiliser les autres comparateurs (plus grand, plus petit, etc.).

ANY (ou SOME)

La requête suivante signifie donc "Sélectionne les lignes de la table Animal, dont l'espece_id est inférieur à au moins une des valeurs sélectionnées dans la sous-requête". C'est-à-dire inférieur à 3 ou à 4. Vous aurez donc dans les résultats toutes les lignes dont l'espece_id vaut 1, 2 ou 3 (puisque 3 est inférieur à 4).

SELECT *
FROM Animal
WHERE espece_id < ANY (
    SELECT id
    FROM Espece
    WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);
ALL

Par contre, si vous utilisez ALL plutôt que ANY, cela signifiera "Sélectionne les lignes de la table Animal, dont l'espece_id est inférieur à toutes les valeurs sélectionnées dans la sous-requête". Donc inférieur à 3 et à 4. Vous n'aurez donc plus que les lignes dont l'espece_id vaut 1 ou 2.

SELECT *
FROM Animal
WHERE espece_id < ALL (
    SELECT id
    FROM Espece
    WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);
Remarque : lien avec IN

Remarquez que = ANY est l'équivalent de IN, tandis que <> ALL est l'équivalent de NOT IN. Attention cependant que ANY et ALL (et SOME) ne peuvent s'utiliser qu'avec des sous-requêtes, et non avec des valeurs comme on peut le faire avec IN.
On ne peut donc pas faire ceci :

SELECT id
FROM Espece
WHERE nom_courant = ANY ('Tortue d''Hermann', 'Perroquet amazone');
#1064 - You have an error in your SQL syntax;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Sous-requêtes dans le FROM Sous-requêtes corrélées

Sous-requêtes corrélées

Sous-requêtes dans les conditions Jointures et sous-requêtes : modification de données

Sous-requêtes corrélées

Une sous-requête corrélée est une sous-requête qui fait référence à une colonne (ou une table) qui n'est pas définie dans sa clause FROM, mais bien ailleurs dans la requête dont elle fait partie.

Vu que ce n'est pas une définition extrêmement claire de prime abord, voici un exemple de requête avec une sous-requête corrélée :

SELECT colonne1 
FROM tableA
WHERE colonne2 IN (
    SELECT colonne3
    FROM tableB
    WHERE tableB.colonne4 = tableA.colonne5
    );

Si l'on prend la sous-requête toute seule, on ne pourra pas l'exécuter :

SELECT colonne3
FROM tableB
WHERE tableB.colonne4 = tableA.colonne5

En effet, seule la tableB est sélectionnée dans la clause FROM, il n'y a pas de jointure avec la tableA, et pourtant on utilise la tableA dans la condition.

Par contre, aucun problème pour l'utiliser comme sous-requête, puisque la clause FROM de la requête principale sélectionne la tableA. La sous-requête est donc corrélée à la requête principale.

Attention : si MySQL rencontre une table inconnue dans une sous-requête, elle va aller chercher dans les niveaux supérieurs uniquement si cette table existe. Donc, imaginons que l'on a une requête avec 3 niveaux : la requête principale (niveau 1), une ou plusieurs sous-requêtes (niveau 2) et une ou plusieurs sous-sous-requêtes, c'est-à-dire une sous-requête dans une sous-requête (niveau 3).

Si l'on prend le schéma suivant, on peut donc remonter l'arbre, mais jamais descendre d'un cran pour trouver les tables nécessaires.

Sous-requêtes corrélées
Sous-requêtes corrélées

Peuvent être corrélées à :

Le temps de vous expliquer le fonctionnement de EXISTS et NOT EXISTS, et nous verrons un exemple de sous-requête corrélée.

Conditions avec EXISTS et NOT EXISTS

Les conditions EXISTS et NOT EXISTS s'utilisent de la manière suivante :

SELECT * FROM nom_table
WHERE [NOT] EXISTS (sous-requête)

Une condition avec EXISTS sera vraie (et donc la requête renverra quelque chose) si la sous-requête correspondante renvoie au moins une ligne.
Une condition avec NOT EXISTS sera vraie si la sous-requête correspondante ne renvoie aucune ligne.

Exemple : on sélectionne les races s'il existe un animal qui s'appelle Balou.

SELECT id, nom, espece_id FROM Race 
WHERE EXISTS (SELECT * FROM Animal WHERE nom = 'Balou');

Vu qu'il existe bien un animal du nom de Balou dans notre table Animal, la condition est vraie, on sélectionne donc toutes les races. Si l'on avait utilisé un nom qui n'existe pas, la requête n'aurait renvoyé aucun résultat.

id

nom

espece_id

1

Berger allemand

1

2

Berger blanc suisse

1

3

Boxer

1

4

Bleu russe

2

5

Maine coon

2

6

Singapura

2

7

Sphynx

2

8

Nebelung

2

Vous conviendrez cependant qu'une telle requête n'a pas beaucoup de sens, c'était juste pour vous faire comprendre le principe. En général, on utilise WHERE [NOT] EXISTS avec des sous-requêtes corrélées.

Exemple : je veux sélectionner toutes les races dont on ne possède aucun animal.

SELECT * FROM Race
WHERE NOT EXISTS (SELECT * FROM Animal WHERE Animal.race_id = Race.id);

La sous-requête est bien corrélée à la requête principale, puisqu'elle utilise la table Race, qui n'est pas sélectionnée dans la sous-requête.

En résultat, on a bien le Sphynx, puisqu'on n'en possède aucun.

id

nom

espece_id

description

7

Sphynx

2

Chat sans poils.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Sous-requêtes dans les conditions Jointures et sous-requêtes : modification de données

Jointures et sous-requêtes : modification de données

Sous-requêtes corrélées Insertion

Voici un petit chapitre bonus sur les jointures et les sous-requêtes.
Vous allez apprendre ici à utiliser ces outils, non pas dans le cadre de la sélection de données comme on l'a fait jusqu'à présent, mais pour :

Insertion

Jointures et sous-requêtes : modification de données Modification

Insertion

Pour l'insertion nous n'allons nous servir que de sous-requêtes, pas de jointures. Quoique… La sous-requête pourrait très bien contenir une jointure !

Sous-requête pour l'insertion

Vous venez d'acquérir un magnifique Maine Coon chez un éleveur voisin. Vous vous apprêtez à l'insérer dans votre base de données, mais vous ne vous souvenez absolument pas de l'id de la race Maine Coon, ni de celui de l'espèce Chat. Du coup, deux possibilités s'offrent à vous.

Je ne sais pas ce que vous en pensez, mais moi je trouve la seconde option bien plus sexy !

Donc, allons-y. Qu'avons-nous comme information ?

De quoi avons-nous besoin en plus pour l'insérer dans notre table ? L'id de l'espèce et de la race.
Comment récupérer ces deux id ? Ça, vous savez faire : une simple requête suffit.

SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon';

race_id

espece_id

5

2

Bien, mais le but était de tout faire en une seule requête, pas d'insérer nous-mêmes 5 et 2 après les avoir récupérés.

C'est ici que les sous-requêtes interviennent. Nous allons utiliser une nouvelle syntaxe d'insertion.

INSERT INTO... SELECT

Cette syntaxe permet de sélectionner des éléments dans des tables, afin de les insérer directement dans une autre.

INSERT INTO nom_table
   [(colonne1, colonne2, ...)]
SELECT [colonne1, colonne2, ...]
FROM nom_table2
[WHERE ...]

Vous n'êtes bien sûr pas obligés de préciser dans quelles colonnes se fait l'insertion, si vous sélectionnez une valeur pour toutes les colonnes de la table. Ce sera cependant rarement le cas puisque nous avons des clés primaires auto-incrémentées.

Avec cette requête, il est absolument indispensable (sinon l'insertion ne se fera pas), d'avoir le même nombre de colonnes dans l'insertion et dans la sélection, et qu'elles soient dans le même ordre.
Si vous n'avez pas le même nombre de colonnes, cela déclenchera une erreur. De plus, si l'ordre n'est pas bon, vous aurez probablement une insertion erronée.

Nous allons donc insérer le résultat de notre requête qui sélectionne les id de l'espèce et la race directement dans notre table Animal.
Pas de souci, mais il faut également insérer le nom, le sexe, etc.

En effet ! Mais c'est très facile. Souvenez-vous, vous pouvez très bien faire des requêtes de ce type :

SELECT 'Yoda' AS nom;

Et donc, en combinant avec notre requête précédente :

SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon';

Vous pouvez obtenir très facilement, et en une seule requête, tous les renseignements indispensables à l'insertion de notre petit Yoda !

SELECT 'Yoda', 'M', '2010-11-09', id AS race_id, espece_id
FROM Race WHERE nom = 'Maine coon';

Si tout se passe bien, cette requête devrait vous donner ceci :

Yoda

M

2010-11-09

race_id

espece_id

Yoda

M

2010-11-09

5

2

Les noms qui sont donnés aux colonnes n'ont pas d'importance, mais vous pouvez les changer avec des alias si cela vous perturbe.

Venons-en maintenant à notre super insertion !

INSERT INTO Animal 
    (nom, sexe, date_naissance, race_id, espece_id)              -- Je précise les colonnes puisque je ne donne pas une valeur pour toutes.
SELECT  'Yoda', 'M', '2010-11-09', id AS race_id, espece_id     -- Attention à l'ordre !
FROM Race WHERE nom = 'Maine coon';

Sélectionnons maintenant les Maine coon de notre base, pour vérifier que l'insertion s'est faite correctement.

SELECT Animal.id, Animal.sexe, Animal.nom, Race.nom AS race, Espece.nom_courant as espece
FROM Animal
INNER JOIN Race ON Animal.race_id = Race.id
INNER JOIN Espece ON Race.espece_id = Espece.id
WHERE Race.nom = 'Maine coon';

Et qui voyons-nous apparaître dans les résultats ? Notre petit Yoda !

id

sexe

nom

race

espece

8

M

Bagherra

Maine coon

Chat

30

M

Zonko

Maine coon

Chat

32

M

Farceur

Maine coon

Chat

34

M

Capou

Maine coon

Chat

39

F

Zara

Maine coon

Chat

40

F

Milla

Maine coon

Chat

42

F

Bilba

Maine coon

Chat

43

F

Cracotte

Maine coon

Chat

61

F

Yoda

Maine coon

Chat

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Jointures et sous-requêtes : modification de données Modification

Modification

Insertion Suppression

Modification

Utilisation des sous-requêtes
Pour la sélection

Imaginez que pour une raison bizarre, vous vouliez que tous les perroquets aient en commentaire "Coco veut un gâteau !".

Si vous saviez que l'id de l'espèce est 4, ce serait facile :

UPDATE Animal SET commentaires = 'Coco veut un gâteau' WHERE espece_id = 4;

Seulement voilà, vous ne savez évidemment pas que l'id de cette espèce est 4. Sinon, ce n'est pas drôle ! Vous allez donc utiliser une magnifique sous-requête pour modifier ces bruyants volatiles !

UPDATE Animal SET commentaires = 'Coco veut un gâteau !' WHERE espece_id = 
    (SELECT id FROM Espece WHERE nom_courant LIKE 'Perroquet%');

Bien sûr, toutes les possibilités de conditions que l'on a vues pour la sélection sont encore valables pour les modifications. Après tout, une clause WHERE est une clause WHERE !

Pour l'élément à modifier

Ce matin, un client demande à voir vos chats Bleu Russe, car il compte en offrir un à sa fille. Ni une ni deux, vous vérifiez dans la base de données, puis allez chercher Schtroumpfette, Filou, Caribou, Raccou, Callune, Feta et Cawette.
Et là, horreur et damnation ! À l'instant où ses yeux se posent sur Cawette, le client devient vert de rage. Il prend à peine le temps de vous expliquer, outré, que Cawette n'est pas un Bleu Russe mais bien un Nebelung, à cause de ses poils longs, puis s'en va chercher un éleveur plus compétent.

Bon… L'erreur est humaine, mais autant la réparer rapidement. Vous insérez donc une nouvelle race dans la table ad hoc.

INSERT INTO Race (nom, espece_id, description)
VALUES ('Nebelung', 2, 'Chat bleu russe, mais avec des poils longs...');

Une fois cela fait, il vous reste encore à modifier la race de Cawette. Pour cela, vous avez besoin de l'id de la race Nebelung que vous venez d'ajouter.
Vous vous en doutez, il est tout à fait possible de le faire grâce à une sous-requête :

UPDATE Animal SET race_id = 
    (SELECT id FROM Race WHERE nom = 'Nebelung' AND espece_id = 2)
WHERE nom = 'Cawette';

Il est bien entendu indispensable que le résultat de la sous-requête soit une valeur !

Limitation des sous-requêtes dans un UPDATE

Une limitation importante des sous-requêtes est qu'on ne peut pas modifier un élément d'une table que l'on utilise dans une sous-requête.

Exemple : vous trouvez que Callune ressemble quand même fichtrement à Cawette, et ses poils sont aussi longs. Du coup, vous vous dites que vous auriez dû également modifier la race de Callune. Vous essayez donc la requête suivante :

UPDATE Animal SET race_id = 
    (SELECT race_id FROM Animal WHERE nom = 'Cawette' AND espece_id = 2)
WHERE nom = 'Callune';

Malheureusement :

ERROR 1093 (HY000): You can't specify target table 'Animal' for update in FROM clause

La sous-requête utilise la table Animal, or vous cherchez à modifier le contenu de celle-ci. C'est impossible !

Il vous faudra donc utiliser la même requête que pour Cawette, en changeant simplement le nom (je ne vous fais pas l'affront de vous l'écrire).

Modification avec jointure

Imaginons que vous vouliez que, pour les tortues et les perroquets, si un animal n'a pas de commentaire, on lui ajoute comme commentaire la description de l'espèce. Vous pourriez sélectionner les descriptions, les copier, retenir l'id de l'espèce, et ensuite faire un UPDATE pour les tortues et un autre pour les perroquets.
Ou alors, vous pourriez simplement faire un UPDATE avec jointure !

Voici la syntaxe que vous devriez utiliser pour le faire avec une jointure :

UPDATE Animal                                                             -- Classique !
INNER JOIN Espece                                                         -- Jointure.
    ON Animal.espece_id = Espece.id                                        -- Condition de la jointure.
SET Animal.commentaires = Espece.description                               -- Ensuite, la modification voulue.
WHERE Animal.commentaires IS NULL                                         -- Seulement s'il n'y a pas encore de commentaire.
AND Espece.nom_courant IN ('Perroquet amazone', 'Tortue d''Hermann');      -- Et seulement pour les perroquets et les tortues.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Insertion Suppression

Suppression

Modification Union de plusieurs requêtes

Suppression

Cette partie sera relativement courte, puisque l'utilisation des sous-requêtes et des jointures est assez ressemblante entre la suppression et la modification.
Simplement, pour la suppression, les sous-requêtes et jointures ne peuvent servir qu'à sélectionner les lignes à supprimer.

Utilisation des sous-requêtes

On peut, tout simplement, utiliser une sous-requête dans la clause WHERE. Par exemple, imaginez que nous ayons deux animaux dont le nom est "Carabistouille", un chat et un perroquet. Vous désirez supprimer Carabistouille-le-chat, mais garder Carabistouille-le-perroquet. Vous ne pouvez donc pas utiliser la requête suivante, qui supprimera les deux :

DELETE FROM Animal WHERE nom = 'Carabistouille';

Mais il suffit d'une sous-requête dans la clause WHERE pour sélectionner l'espèce, et le tour est joué !

DELETE FROM Animal
WHERE nom = 'Carabistouille' AND espece_id = 
    (SELECT id FROM Espece WHERE nom_courant = 'Chat');
Limitations

Les limitations sur DELETE sont les mêmes que pour UPDATE : on ne peut pas supprimer des lignes d'une table qui est utilisée dans une sous-requête.

Suppression avec jointure

Pour les jointures, c'est le même principe. Si je reprends le même problème que ci-dessus, voici comment supprimer la ligne voulue avec une jointure :

DELETE Animal                                        -- Je précise de quelles tables les données doivent être supprimées
FROM Animal                                          -- Table principale
INNER JOIN Espece ON Animal.espece_id = Espece.id    -- Jointure     
WHERE Animal.nom = 'Carabistouille' AND Espece.nom_courant = 'Chat';

Vous remarquez une petite différence avec la syntaxe "classique" de DELETE (sans jointure) : je précise le nom de la table dans laquelle les lignes doivent être supprimées juste après le DELETE. En effet, comme on utilise plusieurs tables, cette précision est obligatoire. Ici, on ne supprimera que les lignes d'Animal correspondantes.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Modification Union de plusieurs requêtes

Union de plusieurs requêtes

Suppression Syntaxe

Toujours dans l'optique de rassembler plusieurs requêtes en une seule, voici l'UNION.
Faire l'union de deux requêtes, cela veut simplement dire réunir les résultats de la première requête et les résultats de la seconde requête.
Voyons comment ça fonctionne !

Syntaxe

Union de plusieurs requêtes UNION ALL

Syntaxe

La syntaxe d'UNION est simplissime : vous avez deux requêtes SELECT dont vous voulez additionner les résultats ; il vous suffit d'ajouter UNION entre ces deux requêtes.

SELECT ...
UNION
SELECT ...

Le nombre de requêtes qu'il est possible d'unir est illimité. Si vous avez cinquante requêtes de sélection, placez un UNION entre les cinquante requêtes.

SELECT ...
UNION
SELECT ...
UNION
SELECT ...
....
UNION
SELECT ...

Par exemple, vous pouvez obtenir les chiens et les tortues de la manière suivante :

SELECT Animal.* FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id 
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.* FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id 
WHERE Espece.nom_courant = 'Tortue d''Hermann';
Les règles

Bien sûr, ce n'est pas si simple que ça, il faut respecter certaines contraintes.

Nombre de colonnes

Il est absolument indispensable que toutes les requêtes unies renvoient le même nombre de colonnes.

Dans la requête que l'on a écrite ci-dessus, aucun problème puisque l'on sélectionne Animal.* dans les deux requêtes. Mais il ne serait donc pas possible de sélectionner un nombre de colonnes différent dans chaque requête intermédiaire.

Par exemple, la requête ci-dessous renverra une erreur :

-- Pas le même nombre de colonnes --
------------------------------------

SELECT Animal.id, Animal.nom, Espece.nom_courant                    -- 3 colonnes sélectionnées
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.id, Animal.nom, Espece.nom_courant, Animal.espece_id -- 4 colonnes sélectionnées
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann';
ERROR 1222 (21000): The used SELECT statements have a different number of columns
Type et ordre des colonnes

En ce qui concerne le type des colonnes, je pense vous avoir déjà signalé que MySQL est très (vraiment très) permissif. Par conséquent, si vous sélectionnez des colonnes de différents types, vous n'aurez pas d'erreurs, mais vous aurez des résultats un peu… spéciaux. o_O

Prenons la requête suivante :

SELECT Animal.id, Animal.nom, Espece.nom_courant    -- 3e colonne : nom_courant VARCHAR
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.id, Animal.nom, Espece.id             -- 3e colonne : id SMALLINT
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%';

Vous aurez bien un résultat :

id

nom

nom_courant

5

Choupi

Chat

33

Caribou

Chat

34

Capou

Chat

37

Callune

Chat

43

Cracotte

Chat

44

Cawette

Chat

50

Cheli

3

51

Chicaca

3

Mais avouez que ce n'est pas un résultat très cohérent. MySQL va simplement convertir tout en chaînes de caractères pour ne pas avoir de problème. Soyez donc très prudents !

Et enfin, pour l'ordre des colonnes, à nouveau vous n'aurez pas d'erreur tant que vous avez le même nombre de colonnes dans chaque requête, mais vous pouvez avoir des résultats bizarres. En effet, MySQL n'analyse pas les noms des colonnes pour trouver une quelconque correspondance d'une requête à l'autre ; tout se fait sur la base de la position de la colonne dans la requête.

SELECT Animal.id, Animal.nom, Espece.nom_courant FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.nom, Animal.id, Espece.nom_courant FROM Animal   -- 1e et 2e colonnes inversées par rapport à la première requête
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%';

id

nom

nom_courant

5

Choupi

Chat

33

Caribou

Chat

34

Capou

Chat

37

Callune

Chat

43

Cracotte

Chat

44

Cawette

Chat

Cheli

50

Tortue d'Hermann

Chicaca

51

Tortue d'Hermann

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Union de plusieurs requêtes UNION ALL

UNION ALL

Syntaxe LIMIT et ORDER BY

UNION ALL

Exécutez la requête suivante :

SELECT * FROM Espece
UNION
SELECT * FROM Espece;

Non, non, je ne me suis pas trompée, je vous demande bien d'unir deux requêtes qui sont exactement les mêmes.

Résultat :

id

nom_courant

nom_latin

description

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

Chaque résultat n’apparaît qu'une seule fois. Pour la simple et bonne raison que lorsque vous faites UNION, les doublons sont effacés. En fait, UNION est équivalent à UNION DISTINCT. Si vous voulez conserver les doublons, vous devez utiliser UNION ALL.

SELECT * FROM Espece
UNION ALL
SELECT * FROM Espece;

id

nom_courant

nom_latin

description

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

1

Chien

Canis canis

Bestiole à quatre pattes qui aime les caresses et tire souvent la langue

2

Chat

Felis silvestris

Bestiole à quatre pattes qui saute très haut et grimpe aux arbres

3

Tortue d'Hermann

Testudo hermanni

Bestiole avec une carapace très dure

4

Perroquet amazone

Alipiopsitta xanthops

Joli oiseau parleur vert et jaune

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe LIMIT et ORDER BY

LIMIT et ORDER BY

UNION ALL Options des clés étrangères

LIMIT et ORDER BY

LIMIT

Il est possible de restreindre les résultats avec LIMIT au niveau de la requête entière, ou au niveau des différentes requêtes unies. L'important est que ce soit clair. Par exemple, vous unissez deux requêtes et vous voulez limiter les résultats de la première. La requête suivante est parfaite pour ça :

SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 3
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece;

Vous avez bien trois noms de races, suivis de toutes les espèces.

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

3

Boxer

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

2

Felis silvestris

Espèce

3

Testudo hermanni

Espèce

Par contre, si vous voulez limiter les résultats de la seconde requête, comment faire ? Essayons la requête suivante.

SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 2;

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

Visiblement, ce n'est pas ce que nous voulions… En fait, LIMIT a été appliqué à l'ensemble des résultats, après UNION. Par conséquent, si l'on veut que LIMIT ne porte que sur la dernière requête, il faut le préciser. Pour ça, il suffit d'utiliser des parenthèses.

SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 2);

id

nom

table_origine

1

Berger allemand

Race

2

Berger blanc suisse

Race

3

Boxer

Race

4

Bleu russe

Race

5

Maine coon

Race

6

Singapura

Race

7

Sphynx

Race

8

Nebelung

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

Et voilà le travail !

ORDER BY

Par contre, s'il est possible de trier le résultat final d'une requête avec UNION, on ne peut pas trier les résultats des requêtes intermédiaires. Par exemple, cette requête trie bien les résultats par ordre anti-alphabétique du nom :

SELECT id, nom, 'Race' AS table_origine FROM Race
UNION
SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece
ORDER BY nom DESC;

id

nom

table_origine

3

Testudo hermanni

Espèce

7

Sphynx

Race

6

Singapura

Race

8

Nebelung

Race

5

Maine coon

Race

2

Felis silvestris

Espèce

1

Canis canis

Espèce

3

Boxer

Race

4

Bleu russe

Race

2

Berger blanc suisse

Race

1

Berger allemand

Race

4

Alipiopsitta xanthops

Espèce

Vous pouvez bien sûr combiner LIMIT et ORDER BY.

(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3)
ORDER BY nom LIMIT 5;

id

nom

table_origine

4

Alipiopsitta xanthops

Espèce

1

Berger allemand

Race

2

Berger blanc suisse

Race

4

Bleu russe

Race

3

Boxer

Race

Exception pour les tris sur les requêtes intermédiaires

Je vous ai dit qu'il n'était pas possible de trier les résultats d'une requête intermédiaire. En réalité, c'est plus subtil que ça.
Dans une requête intermédiaire, il est possible d'utiliser un ORDER BY mais uniquement combiné à un LIMIT. Cela permettra de restreindre les résultats voulus (les X premiers dans l'ordre défini par l'ORDER BY par exemple).

Prenons la requête suivante :

(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3);

Cela vous renvoie bien 6 races et 3 espèces, mais imaginons que vous ne vouliez pas n'importe quelles races, mais les 6 dernières par ordre alphabétique du nom. Dans ce cas-là, vous pouvez utiliser ORDER BY en combinaison avec LIMIT dans la requête intermédiaire :

(SELECT id, nom, 'Race' AS table_origine FROM Race ORDER BY nom DESC LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espèce' AS table_origine FROM Espece LIMIT 3);

id

nom

table_origine

7

Sphynx

Race

6

Singapura

Race

8

Nebelung

Race

5

Maine coon

Race

3

Boxer

Race

4

Bleu russe

Race

4

Alipiopsitta xanthops

Espèce

1

Canis canis

Espèce

2

Felis silvestris

Espèce

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

UNION ALL Options des clés étrangères

Options des clés étrangères

LIMIT et ORDER BY Option sur suppression des clés étrangères

Lorsque je vous ai parlé des clés étrangères, et que je vous ai donné la syntaxe pour les créer, j'ai omis de vous parler des deux options fort utiles :

Nous allons maintenant examiner ces options.

Option sur suppression des clés étrangères

Options des clés étrangères Option sur modification des clés étrangères

Option sur suppression des clés étrangères

Petits rappels
La syntaxe

Voici comment on ajoute une clé étrangère à une table déjà existante :

ALTER TABLE nom_table
ADD [CONSTRAINT fk_col_ref]         -- On donne un nom à la clé (facultatif)
    FOREIGN KEY colonne             -- La colonne sur laquelle on ajoute la clé
    REFERENCES table_ref(col_ref);  -- La table et la colonne de référence
Le principe

Dans notre table Animal, nous avons par exemple mis une clé étrangère sur la colonne race_id, référençant la colonne id de la table Race. Cela implique que chaque fois qu'une valeur est insérée dans cette colonne (soit en ajoutant une ligne, soit en modifiant une ligne existante), MySQL va vérifier que cette valeur existe bien dans la colonne id de la table Race.
Aucun animal ne pourra donc avoir un race_id qui ne correspond à rien dans notre base.

Suppression d'une référence

Que se passe-t-il si l'on supprime la race des Boxers ? Certains animaux référencent cette espèce dans leur colonne race_id. On risque donc d'avoir des données incohérentes. Or, éviter cela est précisément la raison d'être de notre clé étrangère.

Essayons :

DELETE FROM Race WHERE nom = 'Boxer';
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`elevage`.`animal`, CONSTRAINT `fk_race_id` FOREIGN KEY (`race_id`) REFERENCES `race` (`id`))

Ouf ! Visiblement, MySQL vérifie la contrainte de clé étrangère lors d'une suppression aussi, et empêche de supprimer une ligne si elle contient une référence utilisée ailleurs dans la base (ici, l'id de la ligne est donc utilisé par certaines lignes de la table Animal).

Mais ça veut donc dire que chaque fois qu'on veut supprimer des lignes de la table Race, il faut d'abord supprimer toutes les références à ces races. Dans notre base, ça va encore, il n'y a pas énormément de clés étrangères, mais imaginez si l'id de Race servait de référence à des clés étrangères dans ne serait-ce que cinq ou six tables. Pour supprimer une seule race, il faudrait faire jusqu'à six ou sept requêtes.

C'est donc ici qu'intervient notre option ON DELETE, qui permet de changer la manière dont la clé étrangère gère la suppression d'une référence.

Syntaxe

Voici comment on ajoute cette option à la clé étrangère :

ALTER TABLE nom_table
ADD [CONSTRAINT fk_col_ref]         
    FOREIGN KEY (colonne)            
    REFERENCES table_ref(col_ref)
    ON DELETE {RESTRICT | NO ACTION | SET NULL | CASCADE};  -- <-- Nouvelle option !

Il y a donc quatre comportements possibles, que je vais vous détailler tout de suite (bien que leurs noms soient plutôt clairs) : RESTRICT, NO ACTION, SET NULL et CASCADE.

RESTRICT ou NO ACTION

RESTRICT est le comportement par défaut. Si l'on essaye de supprimer une valeur référencée par une clé étrangère, l'action est avortée et on obtient une erreur. NO ACTION a exactement le même effet.

SET NULL

Si on choisit SET NULL, alors tout simplement, NULL est substitué aux valeurs dont la référence est supprimée.
Pour reprendre notre exemple, en supprimant la race des Boxers, tous les animaux auxquels on a attribué cette race verront la valeur de leur race_id passer à NULL.

D'ailleurs, ça me semble plutôt intéressant comme comportement dans cette situation ! Nous allons donc modifier notre clé étrangère fk_race_id. C'est-à-dire que nous allons supprimer la clé, puis la recréer avec le bon comportement :

ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id) ON DELETE SET NULL;

Dorénavant, si vous supprimez une race, tous les animaux auxquels vous avez attribué cette race auparavant auront NULL comme race_id.

Vérifions en supprimant les Boxers, depuis le temps qu'on essaye !

-- Affichons d'abord tous les animaux, avec leur race --
-- -----------------------------------------------------
SELECT Animal.nom, Animal.race_id, Race.nom as race FROM Animal
LEFT JOIN Race ON Animal.race_id = Race.id
ORDER BY race;

-- Supprimons ensuite la race 'Boxer' --
-- -------------------------------------
DELETE FROM Race WHERE nom = 'Boxer';

-- Réaffichons les animaux --
-- --------------------------
SELECT Animal.nom, Animal.race_id, Race.nom as race FROM Animal
LEFT JOIN Race ON Animal.race_id = Race.id
ORDER BY race;

Les ex-boxers existent toujours dans la table Animal, mais ils n'appartiennent plus à aucune race.

CASCADE

Ce dernier comportement est le plus risqué (et le plus violent ! :pirate: ). En effet, cela supprime purement et simplement toutes les lignes qui référençaient la valeur supprimée !
Donc, si on choisit ce comportement pour la clé étrangère sur la colonne espece_id de la table Animal, vous supprimez l'espèce "Perroquet amazone" et POUF !, quatre lignes de votre table Animal (les quatre perroquets) sont supprimées en même temps.
Il faut donc être bien sûr de ce que l'on fait si l'on choisit ON DELETE CASCADE. Il y a cependant de nombreuses situations dans lesquelles c'est utile. Prenez par exemple un forum sur un site internet. Vous avez une table Sujet, et une table Message, avec une colonne sujet_id. Avec ON DELETE CASCADE, il vous suffit de supprimer un sujet pour que tous les messages de ce sujet soient également supprimés. Plutôt pratique non ?

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Options des clés étrangères Option sur modification des clés étrangères

Option sur modification des clés étrangères

Option sur suppression des clés étrangères Utilisation de ces options dans notre base

Option sur modification des clés étrangères

On peut également rencontrer des problèmes de cohérence des données en cas de modification. En effet, si l'on change par exemple l'id de la race "Singapura", tous les animaux qui ont l'ancien id dans leur colonne race_id référenceront une ligne qui n'existe plus. Les modifications de références de clés étrangères sont donc soumises aux mêmes restrictions que la suppression.

Exemple : essayons de modifier l'id de la race "Singapura".

UPDATE Race SET id = 3 WHERE nom = 'Singapura';
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`elevage`.`animal`, CONSTRAINT `fk_race_id` FOREIGN KEY (`race_id`) REFERENCES `race` (`id`) ON DELETE SET NULL)

L'option permettant de définir le comportement en cas de modification est donc ON UPDATE {RESTRICT | NO ACTION | SET NULL | CASCADE}. Les quatre comportements possibles sont exactement les mêmes que pour la suppression.

Petite explication à propos de CASCADE

CASCADE signifie que l'événement est répété sur les tables qui référencent la valeur. Pensez à des "réactions en cascade". Ainsi, une suppression provoquera d'autres suppressions, tandis qu'une modification provoquera d'autres… modifications ! :soleil:

Modifions par exemple la clé étrangère sur Animal.race_id, avant de modifier l'id de la race "Singapura" (jetez d'abord un œil aux données des tables Race et Animal, afin de voir les différences).

-- Suppression de la clé --
-- ------------------------
ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

-- Recréation de la clé avec les bonnes options --
-- -----------------------------------------------
ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id)
    ON DELETE SET NULL   -- N'oublions pas de remettre le ON DELETE !
    ON UPDATE CASCADE;   

-- Modification de l'id des Singapura --
-- -------------------------------------
UPDATE Race SET id = 3 WHERE nom = 'Singapura';

Les animaux notés comme étant des "Singapura" ont désormais leur race_id à 3. Parfait ! :magicien:

Modifions une dernière fois cette clé étrangère pour remettre l'option ON UPDATE par défaut.

ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id) ON DELETE SET NULL;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Option sur suppression des clés étrangères Utilisation de ces options dans notre base

Utilisation de ces options dans notre base

Option sur modification des clés étrangères Violation de contrainte d'unicité

Utilisation de ces options dans notre base

Appliquons maintenant ce que nous avons appris à notre base de données elevage. Celle-ci comporte 5 clés étrangères :

Sur la table Animal
Sur la table Race
Modifications

Pour les modifications, le mieux ici est de laisser le comportement par défaut (RESTRICT) pour toutes nos clés étrangères. Les colonnes référencées sont chaque fois des colonnes auto-incrémentées, on ne devrait donc pas modifier leurs valeurs.

Suppressions

Le problème est plus délicat pour les suppressions. On a déjà défini ON DELETE SET NULL pour la clé sur Animal.race_id. Prenons les autres clés une à une.

Clé sur Animal.espece_id

Si l'on supprime une espèce de la base de données, c'est qu'on ne l'utilise plus dans notre élevage, donc a priori, on n'a plus besoin non plus des animaux de cette espèce. Sachant cela, on serait sans doute tentés de mettre ON DELETE CASCADE. Ainsi, en une seule requête, tout est fait.
Cependant, les animaux sont quand même le point central de notre base de données. Cela me paraît donc un peu violent de les supprimer automatiquement de cette manière, en cas de suppression d'espèce. Par conséquent, je vous propose plutôt de laisser le ON DELETE RESTRICT. Supprimer une espèce n'est pas anodin, et supprimer de nombreux animaux d'un coup non plus. En empêchant la suppression des espèces tant qu'il existe des animaux de celle-ci, on oblige l'utilisateur à supprimer d'abord tous ces animaux. Pas de risque de fausse manœuvre donc.
Attention au fait que le ON DELETE SET NULL n'est bien sûr pas envisageable, puisque la colonne espece_id de la table Animal ne peut pas être NULL.
Pas de changement pour cette clé étrangère !

Clés sur Animal.mere_id et Animal.pere_id

Ce n'est pas parce qu'on supprime un animal que tous ses enfants doivent être supprimés également. Par contre, mettre à NULL semble une bonne idée. ON DELETE SET NULL donc !

Clé sur Race.espece_id

Si une espèce est finalement supprimée, et donc que tous les animaux de cette espèce ont également été supprimés auparavant (puisqu'on a laissé ON DELETE RESTRICT pour la clé sur Animal.espece_id), alors les races de cette espèce deviennent caduques. On peut donc utiliser un ON DELETE CASCADE ici.

Les requêtes

Vous avez toutes les informations nécessaires pour écrire ces requêtes, je vous encourage donc à les écrire vous-mêmes avant de regarder mon code.

-- Animal.mere_id --
-- -----------------
ALTER TABLE Animal DROP FOREIGN KEY fk_mere_id;

ALTER TABLE Animal
ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal(id) ON DELETE SET NULL;

-- Animal.pere_id --
-- -----------------
ALTER TABLE Animal DROP FOREIGN KEY fk_pere_id;

ALTER TABLE Animal
ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal(id) ON DELETE SET NULL;

-- Race.espece_id --
-- -----------------
ALTER TABLE Race DROP FOREIGN KEY fk_race_espece_id;

ALTER TABLE Race
ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece(id) ON DELETE CASCADE;
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Option sur modification des clés étrangères Violation de contrainte d'unicité

Violation de contrainte d'unicité

Utilisation de ces options dans notre base Ignorer les erreurs

Lorsque vous insérez ou modifiez une ligne dans une table, différents événements en relation avec les clés et les index peuvent se produire.

Nous allons ici nous intéresser à la troisième possibilité : ce qui arrive en cas de non-respect d'une contrainte d'unicité. Pour l'instant, dans ce cas-là, une erreur est déclenchée. À la fin de ce chapitre, vous serez capables de modifier ce comportement :

Ignorer les erreurs

Violation de contrainte d'unicité Remplacer l'ancienne ligne

Ignorer les erreurs

Les commandes d'insertion et modification possèdent une option : IGNORE, qui permet d'ignorer (tiens donc ! o_O ) l'insertion ou la modification si elle viole une contrainte d'unicité.

Insertion

Nous avons mis une contrainte d'unicité (sous la forme d'un index UNIQUE) sur la colonne nom_latin de la table Espece. Donc, si l'on essaye d'insérer la ligne suivante, une erreur sera déclenchée puisqu'il existe déjà une espèce dont le nom latin est "Canis canis".

INSERT INTO Espece (nom_courant, nom_latin, description)
VALUES ('Chien en peluche', 'Canis canis', 'Tout doux, propre et  silencieux');
ERROR 1062 (23000): Duplicate entry 'Canis canis' for key 'nom_latin'

Par contre, si l'on utilise le mot-clé IGNORE :

INSERT IGNORE INTO Espece (nom_courant, nom_latin, description)
VALUES ('Chien en peluche', 'Canis canis', 'Tout doux, propre et  silencieux');
Query OK, 0 rows affected (0.01 sec)

Plus d'erreur, la ligne n'a simplement pas été insérée.

Modification

Si l'on essaye de modifier l'espèce des chats, pour lui donner comme nom latin Canis canis, une erreur sera déclenchée, sauf si l'on ajoute l'option IGNORE.

UPDATE Espece SET nom_latin = 'Canis canis' WHERE nom_courant = 'Chat';
ERROR 1062 (23000): Duplicate entry 'Canis canis' for key 'nom_latin'
UPDATE IGNORE Espece SET nom_latin = 'Canis canis' WHERE nom_courant = 'Chat';
Query OK, 0 rows affected (0.01 sec)

Les chats sont toujours des "Felix silvestris" !

LOAD DATA INFILE

La même option est disponible avec la commande LOAD DATA INFILE, ce qui est plutôt pratique si vous voulez éviter de devoir traficoter votre fichier suite à une insertion partielle due à une ligne qui ne respecte pas une contrainte d'unicité.

Syntaxe
LOAD DATA [LOCAL] INFILE 'nom_fichier' IGNORE    -- IGNORE se place juste avant INTO, comme dans INSERT
INTO TABLE nom_table
[FIELDS
    [TERMINATED BY '\t']
    [ENCLOSED BY '']
    [ESCAPED BY '\\' ]
]
[LINES 
    [STARTING BY '']    
    [TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Violation de contrainte d'unicité Remplacer l'ancienne ligne

Remplacer l'ancienne ligne

Ignorer les erreurs Modifier l'ancienne ligne

Remplacer l'ancienne ligne

Lorsque vous voulez insérer une ligne dans une table, vous pouvez utiliser la commande bien connue INSERT INTO, ou vous pouvez utiliser REPLACE INTO. La différence entre ces deux requêtes est la façon qu'elles ont de gérer les contraintes d'unicité (ça tombe bien, c'est le sujet de ce chapitre !).

Dans le cas d'une insertion qui enfreint une contrainte d'unicité, REPLACE ne va ni renvoyer une erreur, ni ignorer l'insertion (comme INSERT INTO [IGNORE]). REPLACE va, purement, simplement et violemment, remplacer l'ancienne ligne par la nouvelle.

Mais que veut dire "remplacer l'ancienne ligne par la nouvelle" ?

Prenons par exemple Spoutnik la tortue, qui se trouve dans notre table Animal.

SELECT id, sexe, date_naissance, nom, espece_id FROM Animal WHERE nom = 'Spoutnik';

id

sexe

date_naissance

nom

espece_id

53

M

2007-04-02 01:45:00

Spoutnik

3

Étant donné que nous avons mis un index UNIQUE sur (nom, espece_id), il est absolument impossible d'avoir une autre tortue du nom de Spoutnik dans notre table. La requête suivante va donc échouer lamentablement.

INSERT INTO Animal (sexe, nom, date_naissance, espece_id)
VALUES ('F', 'Spoutnik', '2010-08-06 15:05:00', 3);
ERROR 1062 (23000): Duplicate entry 'Spoutnik-3' for key 'ind_uni_nom_espece_id'

Par contre, si on utilise REPLACE au lieu de INSERT :

REPLACE INTO Animal (sexe, nom, date_naissance, espece_id)
VALUES ('F', 'Spoutnik', '2010-08-06 15:05:00', 3);
Query OK, 2 rows affected (0.06 sec)

Pas d'erreur, mais vous pouvez voir que deux lignes ont été affectées par la commande. En effet, Spoutnik est mort, vive Spoutnik !

SELECT id, sexe, date_naissance, nom, espece_id FROM Animal WHERE nom = 'Spoutnik';

id

sexe

date_naissance

nom

espece_id

63

F

2010-08-06 15:05:00

Spoutnik

3

Comme vous voyez, nous n'avons toujours qu'une seule tortue du nom de Spoutnik, mais il ne s'agit plus du mâle né le 2 avril 2007 que nous avions précédemment, mais bien de la femelle née le 6 août 2010 que nous venons d'insérer avec REPLACE INTO. L'autre Spoutnik a été purement et simplement remplacé.

Attention cependant, quand je dis que l'ancien Spoutnik a été remplacé, j'utilise le terme "remplacé", car il s'agit de la traduction de REPLACE. Il s'agit cependant d'un abus de langage. En réalité, la ligne de l'ancien Spoutnik a été supprimée, et ensuite seulement, le nouveau Spoutnik a été inséré. C'est d'ailleurs pour cela que les deux Spoutnik n'ont pas du tout le même id.

Remplacement de plusieurs lignes

Pourquoi ai-je bien précisé qu'il ne s'agissait pas vraiment d'un remplacement, mais d'une suppression suivie d'une insertion ? Parce que ce comportement a des conséquences qu'il ne faut pas négliger !

Prenons par exemple une table sur laquelle existent plusieurs contraintes d'unicité. C'est le cas d'Animal, puisqu'on a cet index UNIQUE (nom, espece_id), ainsi que la clé primaire (l'id doit donc être unique aussi).

Nous allons insérer avec REPLACE une ligne qui viole les deux contraintes d'unicité :

REPLACE INTO Animal (id, sexe, nom, date_naissance, espece_id) -- Je donne moi-même un id, qui existe déjà !
VALUES (32, 'M', 'Spoutnik', '2009-07-26 11:52:00', 3);        -- Et Spoutnik est mon souffre-douleur du jour.
Query OK, 3 rows affected (0.05 sec)

Cette fois-ci, trois lignes ont été affectées. Trois !
Tout simplement, les deux lignes qui empêchaient l'insertion à cause des contraintes d'unicité ont été supprimées. La ligne qui avait l'id 32, ainsi que l'ancien Spoutnik ont été supprimés. Le nouveau Spoutnik a ensuite été inséré.

LOAD DATA INFILE

REPLACE est également disponible avec LOAD DATA INFILE. Le comportement est exactement le même.

Bien entendu, IGNORE et REPLACE ne peuvent pas être utilisés en même temps. C'est l'un ou l'autre.

Syntaxe
LOAD DATA [LOCAL] INFILE 'nom_fichier' REPLACE   -- se place au même endroit que IGNORE
INTO TABLE nom_table
[FIELDS
    [TERMINATED BY '\t']
    [ENCLOSED BY '']
    [ESCAPED BY '\\' ]
]
[LINES 
    [STARTING BY '']    
    [TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Ignorer les erreurs Modifier l'ancienne ligne

Modifier l'ancienne ligne

Remplacer l'ancienne ligne Rappels et introduction

Modifier l'ancienne ligne

REPLACE supprime l'ancienne ligne (ou les anciennes lignes), puis insère la nouvelle. Mais parfois, ce qu'on veut, c'est bien modifier la ligne déjà existante. C'est possible grâce à la clause ON DUPLICATE KEY UPDATE de la commande INSERT.

Syntaxe

Voici donc la syntaxe de INSERT INTO avec cette fameuse clause :

INSERT INTO nom_table [(colonne1, colonne2, colonne3)]
VALUES (valeur1, valeur2, valeur3)
ON DUPLICATE KEY UPDATE colonne2 = valeur2 [, colonne3 = valeur3];

Donc, si une contrainte d'unicité est violée par la requête d'insertion, la clause ON DUPLICATE KEY va aller modifier les colonnes spécifiées dans la ligne déjà existante.

Exemple : revoici notre petit Spoutnik !

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id 
FROM Animal 
WHERE nom = 'Spoutnik';

id

sexe

date_naissance

nom

espece_id

mere_id

pere_id

32

M

2009-07-26 11:52:00

Spoutnik

3

NULL

NULL

Essayons d'insérer une autre tortue du nom de Spoutnik, mais cette fois-ci avec la nouvelle clause.

INSERT INTO Animal (sexe, date_naissance, espece_id, nom, mere_id)
VALUES ('M', '2010-05-27 11:38:00', 3, 'Spoutnik', 52) -- date_naissance et mere_id sont différents du Spoutnik existant
ON DUPLICATE KEY UPDATE mere_id = 52;

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id 
FROM Animal 
WHERE nom = 'Spoutnik';

id

sexe

date_naissance

nom

espece_id

mere_id

pere_id

32

M

2009-07-26 11:52:00

Spoutnik

3

52

NULL

Spoutnik est toujours là, mais il a désormais une mère ! Et son id n'a pas été modifié. Il s'agit donc bien d'une modification de la ligne, et non d'une suppression suivie d'une insertion. De même, sa date de naissance est restée la même puisque l'UPDATE ne portait que sur mere_id.

En fait, ce que nous avons fait était équivalent à la requête de modification suivante :

UPDATE Animal
SET mere_id = 52
WHERE nom = 'Spoutnik'
AND espece_id = 3;
Attention : plusieurs contraintes d'unicité sur la même table

Souvenez-vous, avec REPLACE, nous avons vu que s'il existait plusieurs contraintes d'unicité sur la même table, et que plusieurs lignes faisaient échouer l'insertion à cause de ces contraintes, REPLACE supprimait autant de lignes que nécessaire pour faire l'insertion.

Le comportement de ON DUPLICATE KEY est différent ! Dans le cas où plusieurs lignes seraient impliquées, seule une de ces lignes est modifiée (et impossible de prédire laquelle). Il faut donc à tout prix éviter d'utiliser cette clause quand plusieurs contraintes d'unicité pourraient être violées par l'insertion.

En résumé

On commence à faire des choses plutôt sympathiques avec nos données, n'est-ce pas ? Cette partie a pu vous paraître un peu plus compliquée. Du coup, pour les prochaines, je vous prépare quelque chose de simple, et pourtant extrêmement utile !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Remplacer l'ancienne ligne Rappels et introduction

Rappels et introduction

Modifier l'ancienne ligne Rappels et manipulation simple de nombres

Pour commencer en douceur, voici un chapitre d'introduction. On commence avec quelques rappels et astuces. Ensuite, on entre dans le vif du sujet avec la définition d'une fonction, et la différence entre une fonction scalaire et une fonction d’agrégation. Et pour finir, nous verrons quelques fonctions permettant d'obtenir des renseignements sur votre environnement, sur la dernière requête effectuée, et permettant de convertir des valeurs.

Que du bonheur, que du facile, que du super utile !

Etat actuel de la base de données
Note : les tables de test ne sont pas reprises

SET NAMES utf8;

DROP TABLE IF EXISTS Animal;
DROP TABLE IF EXISTS Race;
DROP TABLE IF EXISTS Espece;


CREATE TABLE Espece (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom_courant varchar(40) NOT NULL,
  nom_latin varchar(40) NOT NULL,
  description text,
  PRIMARY KEY (id),
  UNIQUE KEY nom_latin (nom_latin)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

LOCK TABLES Espece WRITE;
INSERT INTO Espece VALUES (1,'Chien','Canis canis','Bestiole à quatre pattes qui aime les caresses et tire souvent la langue'),(2,'Chat','Felis silvestris','Bestiole à quatre pattes qui saute très haut et grimpe aux arbres'),(3,'Tortue d''Hermann','Testudo hermanni','Bestiole avec une carapace très dure'),
(4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert et jaune');
UNLOCK TABLES;


CREATE TABLE Race (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom varchar(40) NOT NULL,
  espece_id smallint(6) unsigned NOT NULL,
  description text,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

LOCK TABLES Race WRITE;
INSERT INTO Race VALUES (1,'Berger allemand',1,'Chien sportif et élégant au pelage dense, noir-marron-fauve, noir ou gris.'),(2,'Berger blanc suisse',1,'Petit chien au corps compact, avec des pattes courtes mais bien proportionnées et au pelage tricolore ou bicolore.'),
(3,'Singapura',2,'Chat de petite taille aux grands yeux en amandes.'),(4,'Bleu russe',2,'Chat aux yeux verts et à la robe épaisse et argentée.'),(5,'Maine coon',2,'Chat de grande taille, à poils mi-longs.'),
(7,'Sphynx',2,'Chat sans poils.'),(8,'Nebelung',2,'Chat bleu russe, mais avec des poils longs...');
UNLOCK TABLES;


CREATE TABLE Animal (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  sexe char(1) DEFAULT NULL,
  date_naissance datetime NOT NULL,
  nom varchar(30) DEFAULT NULL,
  commentaires text,
  espece_id smallint(6) unsigned NOT NULL,
  race_id smallint(6) unsigned DEFAULT NULL,
  mere_id smallint(6) unsigned DEFAULT NULL,
  pere_id smallint(6) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id)
) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8;

LOCK TABLES Animal WRITE;
INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille beaucoup',1,1,18,22),
(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F','2010-09-13 15:02:00','Schtroumpfette',NULL,2,4,41,31),(4,'F','2009-08-03 05:12:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(5,NULL,'2010-10-03 16:44:00','Choupi','Né sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace bizarre',3,NULL,NULL,NULL),(7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NULL),
(8,'M','2008-09-11 15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(10,'M','2010-07-21 15:41:00','Bobo',NULL,1,NULL,7,21),
(11,'F','2008-02-20 15:45:00','Canaille',NULL,1,NULL,NULL,NULL),(12,'F','2009-05-26 08:54:00','Cali',NULL,1,2,NULL,NULL),(13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL),
(14,'F','2009-05-26 08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL,NULL,NULL),(16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL),
(17,'F','2008-03-10 13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,1,NULL,NULL),(19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL),
(20,'M','2007-04-24 12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,NULL,NULL,NULL),(22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL),
(24,'M','2007-04-12 05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14 15:50:00','Zambo',NULL,1,1,NULL,NULL),(26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL),
(27,'M','2008-03-10 13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,1,NULL,NULL),(29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL),
(30,'M','2007-03-12 12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,NULL,NULL),(32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL),
(33,'M','2006-05-19 16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,5,NULL,NULL),(35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NULL),
(36,'M','2009-05-14 06:42:00','Boucan',NULL,2,3,NULL,NULL),(37,'F','2006-05-19 16:06:00','Callune',NULL,2,8,NULL,NULL),(38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL),
(39,'F','2008-04-20 03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,NULL,NULL),(41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL),
(42,'F','2008-04-20 03:20:00','Bilba','Sourde de l''oreille droite à 80%',2,5,NULL,NULL),(43,'F','2007-03-12 11:54:00','Cracotte',NULL,2,5,NULL,NULL),(44,'F','2006-05-19 16:16:00','Cawette',NULL,2,8,NULL,NULL),
(45,'F','2007-04-01 18:17:00','Nikki','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(46,'F','2009-03-24 08:23:00','Tortilla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(47,'F','2009-03-26 01:24:00','Scroupy','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(48,'F','2006-03-15 14:56:00','Lulla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(50,'F','2009-05-25 19:57:00','Cheli','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(51,'F','2007-04-01 03:54:00','Chicaca','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','Insomniaque',3,NULL,NULL,NULL),(54,'M','2008-03-16 08:20:00','Bubulle','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NULL),(56,'M','2009-05-25 18:54:00','Bulbizard','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(57,'M','2007-03-04 19:36:00','Safran','Coco veut un gâteau !',4,NULL,NULL,NULL),
(58,'M','2008-02-20 02:50:00','Gingko','Coco veut un gâteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26 08:28:00','Bavard','Coco veut un gâteau !',4,NULL,NULL,NULL),(60,'F','2009-03-26 07:55:00','Parlotte','Coco veut un gâteau !',4,NULL,NULL,NULL),
(61,'M','2010-11-09 00:00:00','Yoda',NULL,2,5,NULL,NULL);
UNLOCK TABLES;


ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id) ON DELETE CASCADE;

ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id);
ALTER TABLE Animal ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id) ON DELETE SET NULL;

Rappels et manipulation simple de nombres

Rappels et introduction Définition d'une fonction

Rappels et manipulation simple de nombres

Rappels

Nous avons vu qu'il était possible d'afficher des nombres ou des chaînes de caractères avec un simple SELECT :

Exemple : affichage simple de nombres et de chaînes de caractères.

SELECT 3, 'Bonjour !';

3

Bonjour !

3

Bonjour !

Vous savez également qu'il est possible de faire diverses opérations mathématiques de la même manière, et que la priorité des opérations est respectée :

Exemple : quelques opérations mathématiques.

SELECT 3+5, 8/3, 10+2/2, (10+2)/2;

3+5

8/3

10+2/2

(10+2)/2

8

2.6667

11.0000

6.0000

Opérateurs mathématiques

Six opérateurs mathématiques sont utilisables avec MySQL.

Symbole

Opération

+

addition

-

soustraction

*

multiplication

/

division

DIV

division entière

% (ou MOD)

modulo

Pour ceux qui ne le sauraient pas, le modulo de a par b est le reste de la division entière de a par b (par exemple, le modulo de 13 par 10 vaut 3).

Exemple : les six opérateurs mathématiques.

SELECT 1+1, 4-2, 3*6, 5/2, 5 DIV 2, 5 % 2, 5 MOD 2;

1+1

4-2

3*6

5/2

5 DIV 2

5 % 2

5 MOD 2

2

2

18

2.5000

2

1

1

Combiner les données avec des opérations mathématiques

Jusqu'ici, rien de plus simple. Cependant, si l'on veut juste faire 3+6, pas besoin de MySQL, une calculette (ou un cerveau) suffit.
Après tout, dans une base de données, l'important, ce sont les données. Et, il est évidemment possible de faire des opérations mathématiques (et bien d'autres choses) sur les données.

Modification de notre base de données

Mais avant de rentrer dans le vif du sujet, je vais vous demander d'ajouter quelque chose à notre base de données. En effet, pour l'instant nous n'avons pas de données numériques, si ce n'est les différents id. Or, il n'est pas très intéressant (et ça peut même être dangereux) de manipuler les id.
Par conséquent, nous allons ajouter une colonne prix à nos tables Espece et Race, qui contiendra donc le prix à payer pour adopter un animal de telle espèce ou telle race. Cette colonne sera du type DECIMAL, avec deux chiffres après la virgule. Elle ne pourra pas contenir de nombres négatifs, et sera donc également UNSIGNED.

Voici donc les commandes à exécuter :

ALTER TABLE Race 
ADD COLUMN prix DECIMAL(7,2) UNSIGNED;

ALTER TABLE Espece 
ADD COLUMN prix DECIMAL(7,2) UNSIGNED;

-- Remplissage des colonnes "prix"
UPDATE Espece SET prix = 200 WHERE id = 1;
UPDATE Espece SET prix = 150 WHERE id = 2;
UPDATE Espece SET prix = 140 WHERE id = 3;
UPDATE Espece SET prix = 700 WHERE id = 4;
UPDATE Espece SET prix = 10 WHERE id = 5;
UPDATE Espece SET prix = 75 WHERE id = 6;

UPDATE Race SET prix = 450 WHERE id = 1;
UPDATE Race SET prix = 900 WHERE id = 2;
UPDATE Race SET prix = 950 WHERE id = 3;
UPDATE Race SET prix = 800 WHERE id = 4;
UPDATE Race SET prix = 700 WHERE id = 5;
UPDATE Race SET prix = 1200 WHERE id = 7;
UPDATE Race SET prix = 950 WHERE id = 8;
UPDATE Race SET prix = 600 WHERE id = 9;

Le prix de la race sera prioritaire sur le prix de l'espèce. Donc pour un Berger allemand, on ira chercher le prix dans la table Race, tandis que pour un bâtard ou une tortue d'Hermann, on prendra le prix de la table Espece.

Opérations sur données sélectionnées

Exemple 1 : Imaginons que nous voulions savoir le prix à payer pour acheter 3 individus de la même espèce (sans considérer la race). Facile, il suffit de multiplier le prix de chaque espèce par 3.

SELECT nom_courant, prix*3 AS prix_trio
FROM Espece;

nom_courant

prix_trio

Chien

600.00

Chat

450.00

Tortue d'Hermann

420.00

Perroquet amazone

2100.00

Exemple 2 : toutes les opérations courantes peuvent bien entendu être utilisées.

SELECT nom_courant, prix, 
       prix+100 AS addition, prix/2 AS division,
       prix-50.5 AS soustraction, prix%3 AS modulo
FROM Espece;

nom_courant

prix

addition

division

soustraction

modulo

Chien

200.00

300.00

100.000000

149.50

2.00

Chat

150.00

250.00

75.000000

99.50

0.00

Tortue d'Hermann

140.00

240.00

70.000000

89.50

2.00

Perroquet amazone

700.00

800.00

350.000000

649.50

1.00

Modification de données grâce à des opérations mathématiques

Il est tout à fait possible, et souvent fort utile, de modifier des données grâce à des opérations.

Exemple : la commande suivante va augmenter le prix de toutes les races de 35 :

UPDATE Race 
SET prix = prix + 35;

C'est quand même plus élégant que de devoir faire une requête SELECT pour savoir le prix actuel des races, calculer ces prix +35, puis faire un UPDATE pour chaque race avec le résultat.

Bien entendu, on peut faire ce genre de manipulation également dans une requête de type INSERT INTO ... SELECT par exemple. En fait, on peut faire ce genre de manipulation partout.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Rappels et introduction Définition d'une fonction

Définition d'une fonction

Rappels et manipulation simple de nombres Quelques fonctions générales

Définition d'une fonction

Nous avons déjà utilisé quelques fonctions dans ce cours. Par exemple, dans le chapitre sur les sous-requêtes, je vous proposais la requête suivante :

SELECT MIN(date_naissance)     -- On utilise ici une fonction !
FROM (
    SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id
    FROM Animal
    INNER JOIN Espece
        ON Espece.id = Animal.espece_id
    WHERE Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets;

Lorsque l'on fait MIN(date_naissance), on appelle la fonction MIN(), en lui donnant en paramètre la colonne date_naissance (ou plus précisément, les lignes de la colonne date_naissance sélectionnées par la requête).

Détaillons un peu tout ça !

Une fonction

Une fonction est un code qui effectue une série d'instructions bien précises (dans le cas de MIN(), ces instructions visent donc à chercher la valeur minimale), et renvoie le résultat de ces instructions (la valeur minimale en question).
Une fonction est définie par son nom (exemple : MIN) et ses paramètres.

Un paramètre

Un paramètre de fonction est une donnée (ou un ensemble de données) que l'on fournit à la fonction afin qu'elle puisse effectuer son action. Par exemple, pour MIN(), il faut passer un paramètre : les données parmi lesquelles on souhaite récupérer la valeur minimale. Une fonction peut avoir un ou plusieurs paramètres, ou n'en avoir aucun. Dans le cas d'une fonction ayant plusieurs paramètres, l'ordre dans lequel on donne ces paramètres est très important.

On parle aussi des arguments d'une fonction.

Appeler une fonction

Lorsque l'on utilise une fonction, on dit qu'on fait appel à celle-ci. Pour appeler une fonction, il suffit donc de donner son nom, suivi des paramètres éventuels entre parenthèses (lesquelles sont obligatoires, même s'il n'y a aucun paramètre).

Exemples
-- Fonction sans paramètre
SELECT PI();    -- renvoie le nombre Pi, avec 5 décimales

-- Fonction avec un paramètre
SELECT MIN(prix) AS minimum  -- il est bien sûr possible d'utiliser les alias !
FROM Espece;

-- Fonction avec plusieurs paramètres
SELECT REPEAT('fort ! Trop ', 4);  -- répète une chaîne (ici : 'fort ! Trop ', répété 4 fois)

-- Même chose qu'au-dessus, mais avec les paramètres dans le mauvais ordre
SELECT REPEAT(4, 'fort ! Trop '); -- la chaîne de caractères 'fort ! Trop ' va être convertie en entier par MySQL, ce qui donne 0. "4" va donc être répété zéro fois...

PI()

3.141593

minimum

140.00

REPEAT('fort ! Trop ', 4)

fort ! Trop fort ! Trop fort ! Trop fort ! Trop

REPEAT(4, 'fort ! Trop ')

Fonctions scalaires vs fonctions d'agrégation

On peut distinguer deux types de fonctions : les fonctions scalaires et les fonctions d'agrégation (ou fonctions de groupement). Les fonctions scalaires s'appliquent à chaque ligne indépendamment, tandis que les fonctions d'agrégation regroupent les lignes (par défaut, elles regroupent toutes les lignes en une seule). Un petit exemple rendra cette explication lumineuse.

Fonction scalaire

La fonction ROUND(X) arrondit X à l'entier le plus proche. Il s'agit d'une fonction scalaire.

SELECT nom, prix, ROUND(prix)
FROM Race;

nom

prix

ROUND(prix)

Berger allemand

485.00

485

Berger blanc suisse

935.00

935

Singapura

985.00

985

Bleu russe

835.00

835

Maine coon

735.00

735

Sphynx

1235.00

1235

Nebelung

985.00

985

Il y a sept races dans ma table, et lorsqu'on applique la fonction ROUND(X) à la colonne prix, on récupère bien sept lignes.

Fonction d'agrégation

La fonction MIN(X) par contre, est une fonction d'agrégation.

SELECT MIN(prix)
FROM Race;

MIN(prix)

485.00

On ne récupère qu'une seule ligne de résultat. Les sept races ont été regroupées (ça n'aurait d'ailleurs pas de sens d'avoir une ligne par race).
Cette particularité des fonctions d'agrégation les rend un peu plus délicates à utiliser, mais offre des possibilités vraiment intéressantes. Nous commencerons donc en douceur avec les fonctions scalaires pour consacrer ensuite plusieurs chapitres à l'utilisation des fonctions de groupement.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Rappels et manipulation simple de nombres Quelques fonctions générales

Quelques fonctions générales

Définition d'une fonction Fonctions scalaires

Quelques fonctions générales

Petite mise au point

Le but de cette partie n'est évidemment pas de référencer toutes les fonctions existantes. De même, les fonctions présentées seront décrites avec des exemples, mais nous ne verrons pas les petits cas particuliers, les exceptions, ni les éventuels comportements étranges et imprévisibles. Pour ça, la doc est, et restera, votre meilleur compagnon. Le but ici est de vous montrer un certain nombre de fonctions que, selon mon expérience, je juge utile que vous connaissiez. Par conséquent, et on ne le répétera jamais assez, n'hésitez pas à faire un tour sur la documentation officielle de MySQL si vous ne trouvez pas votre bonheur parmi les fonctions citées ici.

Informations sur l'environnement actuel
Version de MySQL

La fonction classique parmi les classiques : VERSION() vous permettra de savoir sous quelle version de MySQL tourne votre serveur.

SELECT VERSION();

VERSION()

5.5.16

Où suis-je ? Qui suis-je ?

Vous avez créé plusieurs utilisateurs différents pour gérer votre base de données, et présentement vous ne savez plus avec lequel vous êtes connectés ? Pas de panique, il existe les fonctions CURRENT_USER() et USER(). Ces deux fonctions ne font pas exactement la même chose. Par conséquent, il n'est pas impossible qu'elles vous renvoient deux résultats différents.

Mais comment cela pourrait-il être différent ? Tout simplement, parce que si vous vous connectez avec un utilisateur qui n'a aucun droit (droits que l'on donne avec la commande GRANT, mais nous verrons ça dans une prochaine partie), vous arriverez à vous connecter, mais le serveur vous identifiera avec un "utilisateur anonyme". USER() vous renverra alors votre utilisateur sans droit, tandis que CURRENT_USER() vous donnera l'utilisateur anonyme.

Dans notre cas, l’utilisateur "sdz" (ou n'importe quel user que vous avez créé) ayant des droits, les deux fonctions renverront exactement la même chose.

SELECT CURRENT_USER(), USER();

CURRENT_USER()

USER()

sdz@localhost

sdz@localhost

Informations sur la dernière requête
Dernier ID généré par auto-incrémentation

Dans une base de données relationnelle, il arrive très souvent que vous deviez insérer plusieurs lignes en une fois dans la base de données, et que certaines de ces nouvelles lignes doivent contenir une référence à d'autres nouvelles lignes. Par exemple, vous voulez ajouter Pipo le rottweiller dans votre base. Pour ce faire, vous devez insérer une nouvelle race (rottweiller), et un nouvel animal (Pipo) pour lequel vous avez besoin de l'id de la nouvelle race.
Plutôt que de faire un SELECT sur la table Race une fois la nouvelle race insérée, il est possible d'utiliser LAST_INSERT_ID().
Cette fonction renvoie le dernier id créé par auto-incrémentation, pour la connexion utilisée (donc si quelqu'un se connecte au même serveur, avec un autre client, il n'influera pas sur le LAST_INSERT_ID() que vous recevez).

INSERT INTO Race (nom, espece_id, description, prix)
VALUES ('Rottweiller', 1, 'Chien d''apparence solide, bien musclé, à la robe noire avec des taches feu bien délimitées.', 600.00);

INSERT INTO Animal (sexe, date_naissance, nom, espece_id, race_id)
VALUES ('M', '2010-11-05', 'Pipo', 1, LAST_INSERT_ID());  -- LAST_INSERT_ID() renverra ici l'id de la race Rottweiller
Nombre de lignes renvoyées par la requête

La fonction FOUND_ROWS() vous permet d'afficher le nombre de lignes que votre dernière requête a ramenées.

SELECT id, nom, espece_id, prix 
FROM Race;

SELECT FOUND_ROWS();

id

nom

espece_id

prix

1

Berger allemand

1

485.00

2

Berger blanc suisse

1

935.00

3

Singapura

2

985.00

4

Bleu russe

2

835.00

5

Maine coon

2

735.00

7

Sphynx

2

1235.00

8

Nebelung

2

985.00

9

Rottweiller

1

600.00

FOUND_ROWS()

8

Jusque-là, rien de bien extraordinaire. Cependant, utilisée avec LIMIT, FOUND_ROWS() peut avoir un comportement très intéressant. En effet, moyennant l'ajout d'une option dans la requête SELECT d'origine, FOUND_ROWS() nous donnera le nombre de lignes que la requête aurait ramenées en l'absence de LIMIT. L'option à ajouter dans le SELECT est SQL_CALC_FOUND_ROWS, et se place juste après le mot-clé SELECT.

SELECT id, nom, espece_id, prix                       -- Sans option
FROM Race 
LIMIT 3;              
         
SELECT FOUND_ROWS() AS sans_option;

SELECT SQL_CALC_FOUND_ROWS id, nom, espece_id, prix   -- Avec option
FROM Race 
LIMIT 3; 

SELECT FOUND_ROWS() AS avec_option;

sans_option

3

avec_option

8

Convertir le type de données

Dans certaines situations, vous allez vouloir convertir le type de votre donnée (une chaîne de caractères '45' en entier 45 par exemple). Il faut savoir que dans la majorité de ces situations, MySQL est assez souple et permissif pour faire la conversion lui-même, automatiquement, et sans que vous ne vous en rendiez vraiment compte (attention, ce n'est pas le cas de la plupart des SGBDR).

Exemple : conversions automatiques

SELECT *
FROM Espece
WHERE id = '3';

INSERT INTO Espece (nom_latin, nom_courant, description, prix)
VALUES ('Rattus norvegicus', 'Rat brun', 'Petite bestiole avec de longues moustaches et une longue queue sans poils', '10.00');

La colonne id est de type INT, pourtant la comparaison avec une chaîne de caractères renvoie bien un résultat. De même, la colonne prix est de type DECIMAL, mais l'insertion d'une valeur sous forme de chaîne de caractères n'a posé aucun problème. MySQL a converti automatiquement.

Dans les cas où la conversion automatique n'est pas possible, vous pouvez utiliser la fonction CAST(expr AS type). expr représente la donnée que vous voulez convertir, et type est bien sûr le type vers lequel vous voulez convertir votre donnée.

Ce type peut être : BINARY, CHAR, DATE, DATETIME, TIME, UNSIGNED (sous-entendu INT), SIGNED (sous-entendu INT), DECIMAL.

Exemple : conversion d'une chaîne de caractère en date

SELECT CAST('870303' AS DATE);

CAST('870303' AS DATE)

1987-03-03

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Définition d'une fonction Fonctions scalaires

Fonctions scalaires

Quelques fonctions générales Manipulation de nombres

Comme prévu, ce chapitre sera consacré aux fonctions scalaires permettant la manipulation de nombres et de chaînes de caractères.
Nous verrons entre autres comment arrondir un nombre ou tirer un nombre au hasard, et comment connaître la longueur d'un texte ou en extraire une partie.
La fin de ce chapitre est constituée d'exercices afin de pouvoir mettre les fonctions en pratique.

À nouveau, si vous ne trouvez pas votre bonheur parmi les fonctions présentées ici, n'hésitez surtout pas à faire un tour sur la documentation officielle.
N'essayez pas de retenir par cœur toutes ces fonctions bien sûr. Si vous savez qu'elles existent, il vous sera facile de retrouver leur syntaxe exacte en cas de besoin, que ce soit ici ou dans la documentation.

Manipulation de nombres

Fonctions scalaires Manipulation de chaînes de caractères

Manipulation de nombres

Voici donc quelques fonctions scalaires qui vont vous permettre de manipuler les nombres : faire des calculs, des arrondis, prendre un nombre au hasard, etc.

Arrondis

Arrondir un nombre, c'est trouver une valeur proche de ce nombre avec une précision donnée et selon certains critères.
La précision est en général représentée par le nombre de décimales désirées. Par exemple, pour un prix, on travaille rarement avec plus de deux décimales. Pour un âge, on préférera généralement un nombre entier (c'est-à-dire aucune décimale).
Quant aux critères, il s'agit de décider si l'on veut arrondir au plus proche (ex. : 4,3 arrondi à l'entier le plus proche vaut 4), arrondir au supérieur (ex. : 4,3 arrondi à l'entier supérieur vaut 5) ou arrondir à l'inférieur (ex. : 4,3 arrondi à l'entier inférieur vaut 4).

Voici quatre fonctions permettant d'arrondir les nombres, selon ces différents critères.
Pour les paramètres, n représente le nombre à arrondir, d le nombre de décimales désirées.

CEIL()

CEIL(n) ou CEILING(n) arrondit au nombre entier supérieur.

SELECT CEIL(3.2), CEIL(3.7);

CEIL(3.2)

CEIL(3.7)

4

4

FLOOR()

FLOOR(n) arrondit au nombre entier inférieur.

SELECT FLOOR(3.2), FLOOR(3.7);

FLOOR(3.2)

FLOOR(3.7)

3

3

ROUND()

ROUND(n, d) arrondit au nombre à d décimales le plus proche. ROUND(n) équivaut à écrire ROUND(n, 0), donc arrondit à l'entier le plus proche.

SELECT ROUND(3.22, 1), ROUND(3.55, 1), ROUND(3.77, 1);

SELECT ROUND(3.2), ROUND(3.5), ROUND(3.7);

ROUND(3.22, 1)

ROUND(3.55, 1)

ROUND(3.77, 1)

3.2

3.6

3.8

ROUND(3.2)

ROUND(3.5)

ROUND(3.7)

3

4

4

Si le nombre se trouve juste entre l'arrondi supérieur et l'arrondi inférieur (par exemple si on arrondit 3,5 à un nombre entier), certaines implémentations vont arrondir vers le haut, d'autres vers le bas. Testez donc pour savoir dans quel cas est votre serveur (comme vous voyez, ici, c'est vers le haut).

TRUNCATE()

TRUNCATE(n, d) arrondit en enlevant purement et simplement les décimales en trop (donc arrondi à l'inférieur pour les nombres positifs, au supérieur pour les nombres négatifs).

SELECT TRUNCATE(3.2, 0), TRUNCATE(3.5, 0), TRUNCATE(3.7, 0);

SELECT TRUNCATE(3.22, 1), TRUNCATE(3.55, 1), TRUNCATE(3.77, 1);

TRUNCATE(3.2, 0)

TRUNCATE(3.5, 0)

TRUNCATE(3.7, 0)

3

3

3

TRUNCATE(3.22, 1)

TRUNCATE(3.55, 1)

TRUNCATE(3.77, 1)

3.2

3.5

3.7

Exposants et racines
Exposants

POWER(n, e) (ou POW(n, e)) retourne le résultat de n exposant e (n^e).

Pour rappel, n exposant e (n^e) veut dire que l'on multiplie n par lui-même, e fois. Donc par exemple, 2^3 = 2 imes2 imes2 = 8

SELECT POW(2, 5), POWER(5, 2);

POW(2, 5)

POWER(5, 2)

32

25

Racines

Prendre la racine nième d'un nombre x (\sqrt[n]{x}), c'est trouver le (ou les) nombre(s) y qui répond(ent) à la condition suivante : y^n = x. Donc la racine cinquième de 32 (\sqrt[5]{32}) vaut 2, puisque 2^5 = 2 imes2 imes2 imes2 imes2 = 32

SQRT(n) donne la racine carrée positive de n (\sqrt{n} = \sqrt[2]{n}).

SELECT SQRT(4);

SQRT(4)

2

Il n'existe pas de fonction particulière pour obtenir la racine nième d'un nombre pour n > 2. Cependant, pour ceux qui ne le sauraient pas : \sqrt[n]{x} = x^{\frac{1}{n}}. Donc, pour obtenir par exemple la racine cinquième de 32, il suffit de faire :

SELECT POW(32, 1/5);

POW(32, 1/5)

2

Hasard

Le "vrai" hasard n'existe pas en informatique. Il est cependant possible de simuler le hasard, avec par exemple un générateur de nombres aléatoires. MySQL implémente un générateur de nombres aléatoires auquel vous pouvez faire appel en utilisant la fonction RAND(), qui retourne un nombre "aléatoire" entre 0 et 1.

SELECT RAND();

RAND()

0.08678611469155748

Cette fonction peut par exemple être utile pour trier des résultats de manière aléatoire. Un nombre différent va en effet être généré pour chaque ligne de résultat. Le tri se fera alors sur ce nombre généré "au hasard".
Notez que ce n'est pas très bon en termes de performances, étant donné qu'un nombre doit être généré pour chaque ligne.

SELECT * 
FROM Race 
ORDER BY RAND();
Divers
SIGN()

SIGN(n) renvoie le signe du nombre n. Ou plus exactement, SIGN(n) renvoie -1 si n est négatif, 0 si n vaut 0, et 1 si n est positif.

SELECT SIGN(-43), SIGN(0), SIGN(37);

SIGN(-43)

SIGN(0)

SIGN(37)

-1

0

1

ABS()

ABS(n) retourne la valeur absolue de n, donc sa valeur sans le signe.

SELECT ABS(-43), ABS(0), ABS(37);

ABS(-43)

ABS(0)

ABS(37)

43

0

37

MOD()

La fonction MOD(n, div) retourne le modulo, donc le reste de la division entière de n par div (comme l'opérateur % ou MOD)

SELECT MOD(56, 10);

MOD(56, 10)

6

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Fonctions scalaires Manipulation de chaînes de caractères

Manipulation de chaînes de caractères

Manipulation de nombres Exemples d'application et exercices

Manipulation de chaînes de caractères

Nous allons maintenant manipuler, triturer, examiner des chaînes de caractères. À toutes fins utiles, je rappelle en passant qu'en SQL, les chaînes de caractères sont entourées de guillemets (simples, mais les guillemets doubles fonctionnent avec MySQL également).

Longueur et comparaison
Connaître la longueur d'une chaîne

Trois fonctions permettent d'avoir des informations sur la longueur d'une chaîne de caractères. Chaque fonction calcule la longueur d'une manière particulière.

SELECT BIT_LENGTH('élevage'), 
       CHAR_LENGTH('élevage'), 
       LENGTH('élevage'); -- Les caractères accentués sont codés sur 2 octets en UTF-8

BIT_LENGTH('élevage')

CHAR_LENGTH('élevage')

LENGTH('élevage')

64

7

8

A priori, dans neuf cas sur dix au moins, la fonction qui vous sera utile est donc CHAR_LENGTH().

Comparer deux chaînes

La fonction STRCMP(chaine1, chaine2) compare les deux chaînes passées en paramètres et retourne 0 si les chaînes sont les mêmes, -1 si la première chaîne est classée avant dans l'ordre alphabétique et 1 dans le cas contraire.

SELECT STRCMP('texte', 'texte') AS 'texte=texte', 
       STRCMP('texte','texte2') AS 'texte<texte2', 
       STRCMP('chaine','texte') AS 'chaine<texte', 
       STRCMP('texte', 'chaine') AS 'texte>chaine',
       STRCMP('texte3','texte24') AS 'texte3>texte24'; -- 3 est après 24 dans l'ordre alphabétique

texte=texte

texte<texte2

chaine<texte

texte>chaine

texte3>texte24

0

-1

-1

1

1

Retrait et ajout de caractères
Répéter une chaîne

REPEAT(c, n) retourne le texte c, n fois.

SELECT REPEAT('Ok ', 3);

REPEAT('Ok ', 3)

Ok Ok Ok

Compléter/réduire une chaîne

Les fonctions LPAD() et RPAD() appliquées à une chaîne de caractères retournent cette chaîne en lui donnant une longueur particulière, donnée en paramètre. Si la chaîne de départ est trop longue, elle sera raccourcie, si elle est trop courte, des caractères seront ajoutés, à gauche de la chaîne pour LPAD(), à droite pour RPAD().
Ces fonctions nécessitent trois paramètres : la chaîne à transformer (texte), la longueur désirée (long), et le caractère à ajouter si la chaîne est trop courte (caract).

LPAD(texte, long, caract)
RPAD(texte, long, caract)
SELECT LPAD('texte', 3, '@') AS '3_gauche_@', 
       LPAD('texte', 7, '$') AS '7_gauche_$', 
       RPAD('texte', 5, 'u') AS '5_droite_u', 
       RPAD('texte', 7, '*') AS '7_droite_*', 
       RPAD('texte', 3, '-') AS '3_droite_-';

3_gauche_@

7_gauche_$

5_droite_u

7_droite_*

3_droite_-

tex

$$texte

texte

texte**

tex

Ôter les caractères inutiles

Il peut arriver que certaines de vos données aient des caractères inutiles ajoutés avant et/ou après le texte intéressant. Dans ce cas, il vous est possible d'utiliser la fonction TRIM(), qui va supprimer tous ces caractères. Cette fonction a une syntaxe un peu particulière que voici :

TRIM([[BOTH | LEADING | TRAILING] [caract] FROM] texte);

Je rappelle que ce qui est entre crochets est facultatif.
On peut donc choisir entre trois options : BOTH, LEADING et TRAILING.

Si aucune option n'est précisée, c'est BOTH qui est utilisé par défaut.

caract est la chaîne de caractères (ou le caractère unique) à éliminer en début et/ou fin de chaîne. Ce paramètre est facultatif : par défaut les espaces blancs seront supprimées. Et texte est bien sûr la chaîne de caractères à traiter.

SELECT TRIM('   Tralala  ') AS both_espace, 
       TRIM(LEADING FROM '   Tralala  ') AS lead_espace, 
       TRIM(TRAILING FROM '   Tralala  ') AS trail_espace,

       TRIM('e' FROM 'eeeBouHeee') AS both_e,
       TRIM(LEADING 'e' FROM 'eeeBouHeee') AS lead_e,
       TRIM(BOTH 'e' FROM 'eeeBouHeee') AS both_e,

       TRIM('123' FROM '1234ABCD4321') AS both_123;

both_espace

lead_espace

trail_espace

both_e

lead_e

both_e

both_123

Tralala

Tralala

Tralala

BouH

BouHeee

BouH

4ABCD4321

Récupérer une sous-chaîne

La fonction SUBSTRING() retourne une partie d'une chaîne de caractères. Cette partie est définie par un ou deux paramètres : pos (obligatoire), qui donne la position de début de la sous-chaîne, et long (facultatif) qui donne la longueur de la sous-chaîne désirée (si ce paramètre n'est pas précisé, toute la fin de la chaîne est prise).
Quatre syntaxes sont possibles :

SELECT SUBSTRING('texte', 2) AS from2,
        SUBSTRING('texte' FROM 3) AS from3,
        SUBSTRING('texte', 2, 3) AS from2long3, 
        SUBSTRING('texte' FROM 3 FOR 1) AS from3long1;

from2

from3

from2long3

from3long1

exte

xte

ext

x

Recherche et remplacement
Rechercher une chaîne de caractères

INSTR(), LOCATE() et POSITION() retournent la position de la première occurrence d'une chaîne de caractères rech dans une chaîne de caractères chaine. Ces trois fonctions ont chacune une syntaxe particulière :

Si la chaîne de caractères rech n'est pas trouvée dans chaine, ces fonctions retournent 0. Par conséquent, la première lettre d'une chaîne de caractères est à la position 1 (alors que dans beaucoup de langages de programmation, on commence toujours à la position 0).

LOCATE() peut aussi accepter un paramètre supplémentaire : pos, qui définit la position dans la chaîne à partir de laquelle il faut rechercher rech : LOCATE(rech, chaine, pos).

SELECT INSTR('tralala', 'la') AS fct_INSTR,
       POSITION('la' IN 'tralala') AS fct_POSITION,
       LOCATE('la', 'tralala') AS fct_LOCATE,
       LOCATE('la', 'tralala', 5) AS fct_LOCATE2;

fct_INSTR

fct_POSITION

fct_LOCATE

fct_LOCATE2

4

4

4

6

Changer la casse des chaînes

Les fonctions LOWER(chaine) et LCASE(chaine) mettent toutes les lettres de chaine en minuscules, tandis que UPPER(chaine) et UCASE(chaine) mettent toutes les lettres en majuscules.

SELECT LOWER('AhAh') AS minuscule, 
        LCASE('AhAh') AS minuscule2, 
        UPPER('AhAh') AS majuscule,
        UCASE('AhAh') AS majuscule2;

minuscule

minuscule2

majuscule

majuscule2

ahah

ahah

AHAH

AHAH

Récupérer la partie gauche ou droite

LEFT(chaine, long) retourne les long premiers caractères de chaine en partant de la gauche, et RIGHT(chaine, long) fait la même chose en partant de la droite.

SELECT LEFT('123456789', 5), RIGHT('123456789', 5);

LEFT('123456789', 5)

RIGHT('123456789', 5)

12345

56789

Inverser une chaîne

REVERSE(chaine) renvoie chaine en inversant les caractères.

SELECT REVERSE('abcde');

REVERSE('abcde')

edcba

Remplacer une partie par autre chose

Deux fonctions permettent de remplacer une partie d'une chaîne de caractères : INSERT() et REPLACE().

SELECT INSERT('texte', 3, 2, 'blabla') AS fct_INSERT, 
        REPLACE('texte', 'e', 'a') AS fct_REPLACE, 
        REPLACE('texte', 'ex', 'ou') AS fct_REPLACE2;

fct_INSERT

fct_REPLACE

fct_REPLACE2

teblablae

taxta

toute

Concaténation

Concaténer deux chaînes de caractères signifie les mettre bout à bout pour n'en faire qu'une seule. Deux fonctions scalaires permettent la concaténation : CONCAT() et CONCAT_WS(). Ces deux fonctions permettent de concaténer autant de chaînes que vous voulez, il suffit de toutes les passer en paramètres. Par conséquent, ces deux fonctions n'ont pas un nombre de paramètres défini.

SELECT CONCAT('My', 'SQL', '!'), CONCAT_WS('-', 'My', 'SQL', '!');

CONCAT('My', 'SQL', '!')

CONCAT_WS('-', 'My', 'SQL', '!')

MySQL!

My-SQL-!

FIELD(), une fonction bien utile pour le tri

La fonction FIELD(rech, chaine1, chaine2, chaine3,…) recherche le premier argument (rech) parmi les arguments suivants (chaine1, chaine2, chaine3,…) et retourne l'index auquel rech est trouvée (1 si rech = chaine1, 2 si rech = chaine2,…). Si rech n'est pas trouvée parmi les arguments, 0 est renvoyé.

SELECT FIELD('Bonjour', 'Bonjour !', 'Au revoir', 'Bonjour', 'Au revoir !') AS field_bonjour;

field_bonjour

3

Par conséquent, FIELD peut être utilisée pour définir un ordre arbitraire dans une clause ORDER BY.

Exemple : ordonnons les espèces selon un ordre arbitraire. La fonction FIELD() dans la clause SELECT n'est là que pour illustrer la façon dont ce tri fonctionne.

SELECT nom_courant, nom_latin, FIELD(nom_courant, 'Rat brun', 'Chat', 'Tortue d''Hermann', 'Chien', 'Perroquet amazone') AS resultat_field
FROM Espece
ORDER BY FIELD(nom_courant, 'Rat brun', 'Chat', 'Tortue d''Hermann', 'Chien', 'Perroquet amazone');

nom_courant

nom_latin

resultat_field

Rat brun

Rattus norvegicus

1

Chat

Felis silvestris

2

Tortue d'Hermann

Testudo hermanni

3

Chien

Canis canis

4

Perroquet amazone

Alipiopsitta xanthops

5

Code ASCII

Les deux dernières fonctions que nous allons voir sont ASCII() et CHAR(), qui sont complémentaires. ASCII(chaine) renvoie le code ASCII du premier caractère de la chaîne passée en paramètre, tandis que CHAR(ascii1, ascii2,…) retourne les caractères correspondant aux codes ASCII passés en paramètres (autant de paramètres qu'on veut). Les arguments passés à CHAR() seront convertis en entiers par MySQL.

SELECT ASCII('T'), CHAR(84), CHAR('84', 84+32, 84.2);

ASCII('T')

CHAR(84)

CHAR('84', 84+32, 84.2)

84

T

TtT

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Manipulation de nombres Exemples d'application et exercices

Exemples d'application et exercices

Manipulation de chaînes de caractères Fonctions d'agrégation

Exemples d'application et exercices

Ce chapitre a été fort théorique jusqu'à maintenant. Donc pour changer un peu, et vous réveiller, je vous propose de passer à la pratique, en utilisant les données de notre base elevage. Ces quelques exercices sont faisables en utilisant uniquement les fonctions et opérateurs mathématiques que je vous ai décrits dans ce chapitre.

On commence par du facile
1. Afficher une phrase donnant le prix de l'espèce, pour chaque espèce

Par exemple, afficher "Un chat coûte 100 euros.", ou une autre phrase du genre, et ce pour les cinq espèces enregistrées.

SELECT CONCAT('Un(e) ', nom_courant, ' coûte ', prix, ' euros.') AS Solution
FROM Espece;

-- OU

SELECT CONCAT_WS(' ','Un(e)', nom_courant, 'coûte', prix, 'euros.') AS Solution
FROM Espece;
2. Afficher les chats dont la deuxième lettre du nom est un "a"
SELECT Animal.nom, Espece.nom_courant 
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat'
AND SUBSTRING(nom, 2, 1) = 'a';
Puis on corse un peu
1. Afficher les noms des perroquets en remplaçant les "a" par "@" et les "e" par "3" pour en faire des perroquets Kikoolol
SELECT REPLACE(REPLACE(nom, 'a', '@'), 'e', '3') AS Solution
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant LIKE 'Perroquet%';

Une petite explication s'impose avant de vous laisser continuer. Comme vous voyez, il est tout à fait possible d'imbriquer plusieurs fonctions. Le tout est de le faire correctement, et pour cela, il faut procéder par étape. Ici, vous voulez faire deux remplacements successifs dans une chaîne de caractères (en l’occurrence, le nom des perroquets). Donc, vous effectuez un premier remplacement, en changeant les "a" par les "@" : REPLACE(nom, 'a', '@'). Ensuite, sur la chaîne résultant de ce premier remplacement, vous effectuez le second : REPLACE(REPLACE(nom, 'a', '@'), 'e', '3'). Logique, non ?

2. Afficher les chiens dont le nom a un nombre pair de lettres
SELECT nom, nom_courant
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE nom_courant = 'Chien' 
AND CHAR_LENGTH(nom)%2 = 0;

-- OU

SELECT nom, nom_courant
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE nom_courant = 'Chien' 
AND CHAR_LENGTH(nom) MOD 2 = 0;

-- OU

SELECT nom, nom_courant
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE nom_courant = 'Chien' 
AND MOD(CHAR_LENGTH(nom),2) = 0;

Le nombre de lettres, c'était facile, il suffisait d'utiliser CHAR_LENGTH().
Pour savoir si un nombre est pair, il faut utiliser les modulos : lorsqu'un nombre est pair, le reste d'une division entière de ce nombre par 2 est 0, donc ce nombre modulo 2 vaut 0.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Manipulation de chaînes de caractères Fonctions d'agrégation

Fonctions d'agrégation

Exemples d'application et exercices Fonctions statistiques

Les fonctions d'agrégation, ou de groupement, sont des fonctions qui vont regrouper les lignes. Elles agissent sur une colonne, et renvoient un résultat unique pour toutes les lignes sélectionnées (ou pour chaque groupe de lignes, mais nous verrons cela plus tard).

Elles servent majoritairement à faire des statistiques, comme nous allons le voir dans la première partie de ce chapitre (compter des lignes, connaître une moyenne, trouver la valeur maximale d'une colonne,…).
Nous verrons ensuite la fonction GROUP_CONCAT() qui, comme son nom l'indique, est une fonction de groupement qui sert à concaténer des valeurs.

Fonctions statistiques

Fonctions d'agrégation Concaténation

Fonctions statistiques

La plupart des fonctions d'agrégation vont vous permettre de faire des statistiques sur vos données.

Nombre de lignes

La fonction COUNT() permet de savoir combien de lignes sont sélectionnées par la requête.

-- Combien de races avons-nous ? --
-- ---------------------------------
SELECT COUNT(*) AS nb_races
FROM Race;

-- Combien de chiens avons-nous ? --
-- ---------------------------------
SELECT COUNT(*) AS nb_chiens
FROM Animal 
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant = 'Chien';

nb_races

8

nb_chiens

21

COUNT(*) ou COUNT(colonne)

Vous l'avez vu, j'ai utilisé COUNT(*) dans les exemples ci-dessus. Cela signifie que l'on compte tout simplement les lignes, sans se soucier de ce qu'elles contiennent.

Par contre, si on utilise COUNT(colonne), seules les lignes dont la valeur de colonne n'est pas NULL seront prises en compte.

Exemple : comptons les lignes de la table Animal, avec COUNT(*) et COUNT(race_id).

SELECT COUNT(race_id), COUNT(*)
FROM Animal;

COUNT(race_id)

COUNT(*)

31

60

Il n'y a donc que 31 animaux sur nos 60 pour lesquels la race est définie.

Doublons

Comme dans une requête SELECT tout à fait banale, il est possible d'utiliser le mot-clé DISTINCT pour ne pas prendre en compte les doublons.

Exemple : comptons le nombre de races distinctes définies dans la table Animal.

SELECT COUNT(DISTINCT race_id)
FROM Animal;

COUNT(DISTINCT race_id)

7

Parmi nos 31 animaux dont la race est définie, on trouve donc 7 races différentes.

Minimum et maximum

Nous avons déjà eu l'occasion de rencontrer la fonction MIN(x), qui retourne la plus petite valeur de x. Il existe également une fonction MAX(x), qui renvoie la plus grande valeur de x.

SELECT MIN(prix), MAX(prix)
FROM Race;

MIN(prix)

MAX(prix)

485.00

1235.00

Notez que MIN() et MAX() ne s'utilisent pas uniquement sur des données numériques. Si vous lui passez des chaînes de caractères, MIN() récupérera la première chaîne dans l'ordre alphabétique, MAX() la dernière ; avec des dates, MIN() renverra la plus vieille et MAX() la plus récente.

Exemple :

SELECT MIN(nom), MAX(nom), MIN(date_naissance), MAX(date_naissance)
FROM Animal;

MIN(nom)

MAX(nom)

MIN(date_naissance)

MAX(date_naissance)

Anya

Zonko

2006-03-15 14:26:00

2010-11-09 00:00:00

Somme et moyenne
Somme

La fonction SUM(x) renvoie la somme de x.

SELECT SUM(prix)
FROM Espece;

SUM(prix)

1200.00

Moyenne

La fonction AVG(x) (du mot anglais average) renvoie la valeur moyenne de x.

SELECT AVG(prix)
FROM Espece;

AVG(prix)

240.000000

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Fonctions d'agrégation Concaténation

Concaténation

Fonctions statistiques Regroupement

Concaténation

Principe

Avec les fonctions d'agrégation, on regroupe plusieurs lignes. Les fonctions statistiques nous permettent d'avoir des informations fort utiles sur le résultat d'une requête, mais parfois, il est intéressant d'avoir également les valeurs concernées.
Ceci est faisable avec GROUP_CONCAT(nom_colonne). Cette fonction concatène les valeurs de nom_colonne pour chaque groupement réalisé.

Exemple : on récupère la somme des prix de chaque espèce, et on affiche les espèces concernées par la même occasion.

SELECT SUM(prix), GROUP_CONCAT(nom_courant)
FROM Espece;

SUM(prix)

GROUP_CONCAT(nom_courant)

1200.00

Chien,Chat,Tortue d'Hermann,Perroquet amazone,Rat brun

Syntaxe

Voici la syntaxe de cette fonction :

GROUP_CONCAT(
              [DISTINCT] col1 [, col2, ...]
              [ORDER BY col [ASC | DESC]]
              [SEPARATOR sep]
            )
Exemples
-- --------------------------------------
-- CONCATENATION DE PLUSIEURS COLONNES --
-- --------------------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, Espece.nom_courant)
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ---------------------------------------------------
-- CONCATENATION DE PLUSIEURS COLONNES EN PLUS JOLI --
-- ---------------------------------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')')
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ---------------------------
-- ELIMINATION DES DOUBLONS --
-- ---------------------------
SELECT SUM(Espece.prix), GROUP_CONCAT(DISTINCT Espece.nom_courant) -- Essayez sans le DISTINCT pour voir
FROM Espece
INNER JOIN Race ON Race.espece_id = Espece.id;

-- --------------------------
-- UTILISATION DE ORDER BY --
-- --------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')' ORDER BY Race.nom DESC)
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ----------------------------
-- CHANGEMENT DE SEPARATEUR  --
-- ----------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')' SEPARATOR ' - ')
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;
En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Fonctions statistiques Regroupement

Regroupement

Concaténation Regroupement sur un critère

Vous savez donc que les fonctions d'agrégation groupent plusieurs lignes ensemble. Jusqu'à maintenant, toutes les lignes étaient chaque fois regroupées en une seule. Mais ce qui est particulièrement intéressant avec ces fonctions, c'est qu'il est possible de regrouper les lignes en fonction d'un critère, et d'avoir ainsi plusieurs groupes distincts.
Par exemple, avec la fonction COUNT(*), vous pouvez compter le nombre de lignes que vous avez dans la table Animal. Mais que diriez-vous de faire des groupes par espèces, et donc de savoir en une seule requête combien vous avez de chats, chiens, etc. ? Un simple groupement, et c'est fait !

Au programme de ce chapitre :

Regroupement sur un critère

Regroupement Regroupement sur plusieurs critères

Regroupement sur un critère

Pour regrouper les lignes selon un critère, il faut utiliser GROUP BY, qui se place après l'éventuelle clause WHERE (sinon directement après FROM), suivi du nom de la colonne à utiliser comme critère de regroupement.

SELECT ...
FROM nom_table
[WHERE condition]
GROUP BY nom_colonne;

Exemple 1 : comptons les lignes dans la table Animal, en regroupant sur le critère de l'espèce (donc avec la colonne espece_id).

SELECT COUNT(*) AS nb_animaux
FROM Animal
GROUP BY espece_id;

nb_animaux

21

20

15

4

Exemple 2 : Même chose, mais on ne prend que les mâles cette fois-ci.

SELECT COUNT(*) AS nb_males
FROM Animal
WHERE sexe = 'M'
GROUP BY espece_id;

nb_males

10

9

4

3

C'est déjà intéressant, mais nous n'allons pas en rester là. En effet, il serait quand même mieux de savoir à quelle espèce correspond quel nombre !

Voir d'autres colonnes

Pour savoir à quoi correspond chaque nombre, il suffit d'afficher également le critère qui a permis de regrouper les lignes. Dans notre cas, espece_id.

SELECT espece_id, COUNT(*) AS nb_animaux
FROM Animal
GROUP BY espece_id;

espece_id

nb_animaux

1

21

2

20

3

15

4

4

C'est déjà mieux, mais l'idéal serait d'avoir le nom des espèces directement. Qu'à cela ne tienne, il suffit de faire une jointure ! Sans oublier de changer le critère et de mettre nom_courant à la place de espece_id.

SELECT nom_courant, COUNT(*) AS nb_animaux
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

nom_courant

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Tortue d'Hermann

15

Colonnes sélectionnées
La règle SQL

Lorsque l'on fait un groupement dans une requête, avec GROUP BY, on ne peut sélectionner que deux types d'éléments dans la clause SELECT :

Cette règle est d'ailleurs logique. Imaginez la requête suivante :

SELECT nom_courant, COUNT(*) AS nb_animaux, date_naissance
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

Que vient faire la date de naissance dans cette histoire ? Et surtout, quelle date de naissance espère-t-on sélectionner ? Chaque ligne représente une espèce puisque l'on a regroupé les lignes sur la base de Espece.nom_courant. Donc date_naissance n'a aucun sens par rapport aux groupes formés, une espèce n'ayant pas de date de naissance. Il en est de même pour les colonnes sexe ou commentaires par exemple.

Qu'en est-il des colonnes Espece.id, Animal.espece_id, ou Espece.nom_latin ? En groupant sur le nom courant de l'espèce, ces différentes colonnes ont un sens, et pourraient donc être utiles. Il a cependant été décidé que par sécurité, la sélection de colonnes n'étant pas dans les critères de groupement serait interdite. Cela afin d'éviter les situations comme au-dessus, où les colonnes sélectionnées n'ont aucun sens par rapport au groupement fait.
Par conséquent, si vous voulez afficher également l'id de l'espèce et son nom latin, il vous faudra grouper sur les trois colonnes : Espece.nom_latin, Espece.nom_courant et Espece.id. Les groupes créés seront les mêmes qu'en groupant uniquement sur Espece.nom_courant, mais votre requête respectera les standards SQL.

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant, Espece.id, nom_latin;

id

nom_courant

nom_latin

nb_animaux

2

Chat

Felis silvestris

20

1

Chien

Canis canis

21

4

Perroquet amazone

Alipiopsitta xanthops

4

3

Tortue d'Hermann

Testudo hermanni

15

Le cas MySQL

On ne le répétera jamais assez : MySQL est un SGBD extrêmement permissif. Dans certains cas, c'est bien pratique, mais c'est toujours dangereux.
Et notamment en ce qui concerne GROUP BY, MySQL ne sera pas perturbé pour un sou si vous sélectionnez une colonne qui n'est pas dans les critères de regroupement. Reprenons la requête qui sélectionne la colonne date_naissance alors que le regroupement se fait sur la base de l'espece_id. J'insiste, cette requête ne respecte pas la norme SQL, et n'a aucun sens. La plupart des SGBD vous renverront une erreur si vous tentez de l'exécuter.

SELECT nom_courant, COUNT(*) AS nb_animaux, date_naissance
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY espece_id;

Pourtant, loin de rouspéter, MySQL donne le résultat suivant :

nom_courant

nb_animaux

date_naissance

Chat

20

2010-03-24 02:23:00

Chien

21

2010-04-05 13:43:00

Perroquet amazone

4

2007-03-04 19:36:00

Tortue d'Hermann

15

2009-08-03 05:12:00

MySQL a tout simplement pris n'importe quelle valeur parmi celles du groupe pour la date de naissance. D'ailleurs, il est tout à fait possible que vous ayez obtenu des valeurs différentes des miennes.
Soyez donc très prudents lorsque vous utilisez GROUP BY. Vous faites peut-être des requêtes qui n'ont aucun sens, et MySQL ne vous en avertira pas !

Tri des données

Par défaut dans MySQL, les données sont triées sur la base du critère de regroupement. C'est la raison pour laquelle, dans la requête précédente, la colonne nom_courant est triée par ordre alphabétique : c'est le premier critère de regroupement. MySQL permet d'utiliser les mots-clés ASC et DESC dans une clause GROUP BY pour choisir un tri ascendant (par défaut) ou descendant.

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant DESC, Espece.id, nom_latin;  -- On trie par ordre anti-alphabétique sur le nom_courant

id

nom_courant

nom_latin

nb_animaux

3

Tortue d'Hermann

Testudo hermanni

15

4

Perroquet amazone

Alipiopsitta xanthops

4

1

Chien

Canis canis

21

2

Chat

Felis silvestris

20

Mais rien n'empêche d'utiliser une clause ORDER BY classique, après la clause GROUP BY. L'ORDER BY sera prioritaire sur l'ordre défini par la clause de regroupement.

Dans le même ordre d'idées, il n'est possible de faire un tri des données qu'à partir d'une colonne qui fait partie des critères de regroupement, ou à partir d'une fonction d'agrégation. Ça n'a pas plus de sens de trier les espèces par date de naissance que de sélectionner une date de naissance par espèce.

Vous pouvez par contre parfaitement écrire ceci :

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant, Espece.id, nom_latin
ORDER BY nb_animaux;

id

nom_courant

nom_latin

nb_animaux

4

Perroquet amazone

Alipiopsitta xanthops

4

3

Tortue d'Hermann

Testudo hermanni

15

2

Chat

Felis silvestris

20

1

Chien

Canis canis

21

Notez que la norme SQL veut que l'on n'utilise pas d'expressions (fonction, opération mathématique,…) dans GROUP BY ou ORDER BY. C'est la raison pour laquelle j'ai mis ORDER BY nb_animaux et non pas ORDER BY COUNT(*), bien qu'avec MySQL les deux fonctionnent. Pensez donc à utiliser des alias pour ces situations.

Et les autres espèces ?

La requête suivante nous donne le nombre d'animaux qu'on possède pour chaque espèce dont on possède au moins un animal. Comment peut-on faire pour afficher également les autres espèces ?

SELECT Espece.nom_courant, COUNT(*) AS nb_animaux
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

Essayons donc avec une jointure externe, puisqu'il faut tenir compte de toutes les espèces, même celles qui n'ont pas de correspondance dans la table Animal.

SELECT Espece.nom_courant, COUNT(*) AS nb_animaux
FROM Animal
RIGHT JOIN Espece ON Animal.espece_id = Espece.id -- RIGHT puisque la table Espece est à droite.
GROUP BY nom_courant;

nom_courant

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Rat brun

1

Tortue d'Hermann

15

Les rats bruns apparaissent bien. En revanche, ce n'est pas 1 qu'on attend, mais 0, puisqu'on n'a pas de rats dans notre élevage. Cela dit, ce résultat est logique : avec la jointure externe, on aura une ligne correspondant aux rats bruns, avec NULL dans toutes les colonnes de la table Animal. Donc ce qu'il faudrait, c'est avoir les cinq espèces, mais ne compter que lorsqu'il y a un animal correspondant. Pour ce faire, il suffit de faire COUNT(Animal.espece_id) par exemple.

SELECT Espece.nom_courant, COUNT(Animal.espece_id) AS nb_animaux
FROM Animal
RIGHT JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

nom_courant

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Rat brun

0

Tortue d'Hermann

15

C'est pas magique ça ? :magicien:

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Regroupement Regroupement sur plusieurs critères

Regroupement sur plusieurs critères

Regroupement sur un critère Super-agrégats

Regroupement sur plusieurs critères

J'ai mentionné le fait qu'il était possible de grouper sur plusieurs colonnes, mais jusqu'à maintenant, cela n'a servi qu'à pouvoir afficher correctement les colonnes voulues, sans que ça n'influe sur les groupes. On n'avait donc en fait qu'un seul critère, représenté par plusieurs colonnes. Voyons maintenant un exemple avec deux critères différents (qui ne créent pas les mêmes groupes).

Les deux requêtes suivantes permettent de savoir combien d'animaux de chaque espèce vous avez dans la table Animal, ainsi que combien de mâles et de femelles, toutes espèces confondues.

SELECT nom_courant, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant;

SELECT sexe, COUNT(*) as nb_animaux
FROM Animal
GROUP BY sexe;

nom_courant

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Tortue d'Hermann

15

sexe

nb_animaux

NULL

3

F

31

M

26

En faisant un regroupement multicritère, il est possible de savoir facilement combien de mâles et de femelles par espèce il y a dans la table Animal. Notez que l'ordre des critères a son importance.

Exemple 1 : on regroupe d'abord sur l'espèce, puis sur le sexe.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant, sexe;

nom_courant

sexe

nb_animaux

Chat

NULL

2

Chat

F

9

Chat

M

9

Chien

F

11

Chien

M

10

Perroquet amazone

F

1

Perroquet amazone

M

3

Tortue d'Hermann

NULL

1

Tortue d'Hermann

F

10

Tortue d'Hermann

M

4

Exemple 2 : on regroupe d'abord sur le sexe, puis sur l'espèce.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY sexe,nom_courant;

nom_courant

sexe

nb_animaux

Chat

NULL

2

Tortue d'Hermann

NULL

1

Chat

F

9

Chien

F

11

Perroquet amazone

F

1

Tortue d'Hermann

F

10

Chat

M

9

Chien

M

10

Perroquet amazone

M

3

Tortue d'Hermann

M

4

Étant donné que le regroupement par sexe donnait trois groupes différents, et le regroupement par espèce donnait quatre groupes différents, il peut y avoir jusqu'à douze (3 x 4) groupes lorsque l'on regroupe en se basant sur les deux critères. Ici, il y en aura moins puisque le sexe de tous les chiens et de tous les perroquets est défini (pas de NULL).

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Regroupement sur un critère Super-agrégats

Super-agrégats

Regroupement sur plusieurs critères Conditions sur les fonctions d'agrégation

Super-agrégats

Parlons maintenant de l'option WITH ROLLUP de GROUP BY. Cette option va afficher des lignes supplémentaires dans la table de résultats. Ces lignes représenteront des "super-groupes" (ou super-agrégats), donc des "groupes de groupes". Deux petits exemples, et vous aurez compris !

Exemple avec un critère de regroupement
SELECT nom_courant, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant WITH ROLLUP;

nom_courant

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Tortue d'Hermann

15

NULL

60

Nous avons donc 20 chats, 21 chiens, 4 perroquets et 15 tortues. Et combien font 20 + 21 + 4 + 15 ? 60 ! Exactement. La ligne supplémentaire représente donc le regroupement de nos quatre groupes basé sur le critère GROUP BY nom_courant.

Exemple avec deux critères de regroupement
SELECT nom_courant, sexe, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE sexe IS NOT NULL                            
GROUP BY nom_courant, sexe WITH ROLLUP;

nom_courant

sexe

nb_animaux

Chat

F

9

Chat

M

9

Chat

NULL

18

Chien

F

11

Chien

M

10

Chien

NULL

21

Perroquet amazone

F

1

Perroquet amazone

M

3

Perroquet amazone

NULL

4

Tortue d'Hermann

F

10

Tortue d'Hermann

M

4

Tortue d'Hermann

NULL

14

NULL

NULL

57

Les deux premières lignes correspondent aux nombres de chats mâles et femelles. Jusque-là, rien de nouveau. Par contre, la troisième ligne est une ligne insérée par WITH ROLLUP, et contient le nombre de chats (mâles et femelles). Nous avons fait des groupes en séparant les espèces et les sexes, et WITH ROLLUP a créé des "super-groupes" en regroupant les sexes mais gardant les espèces séparées.
Nous avons donc également le nombre de chiens à la sixième ligne, de perroquets à la neuvième, et de tortues à la douzième. Quant à la toute dernière ligne, c'est un "super-super-groupe" qui réunit tous les groupes ensemble.

C'est en utilisant WITH ROLLUP qu'on se rend compte que l'ordre des critères est vraiment important. En effet, voyons ce qui se passe si on échange les deux critères nom_courant et sexe.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE sexe IS NOT NULL
GROUP BY sexe, nom_courant WITH ROLLUP;

nom_courant

sexe

nb_animaux

Chat

F

9

Chien

F

11

Perroquet amazone

F

1

Tortue d'Hermann

F

10

NULL

F

31

Chat

M

9

Chien

M

10

Perroquet amazone

M

3

Tortue d'Hermann

M

4

NULL

M

26

NULL

NULL

57

Cette fois-ci, les super-groupes ne correspondent pas aux espèces, mais aux sexes, c'est-à-dire au premier critère. Le regroupement se fait bien dans l'ordre donné par les critères.

NULL, c'est pas joli

Il est possible d'éviter d'avoir ces NULL dans les lignes des super-groupes. Pour cela, on peut utiliser la fonction COALESCE().
Cette fonction prend autant de paramètres que l'on veut, et renvoie le premier paramètre non NULL.

Exemples :

SELECT COALESCE(1, NULL, 3, 4); -- 1
SELECT COALESCE(NULL, 2);       -- 2
SELECT COALESCE(NULL, NULL, 3); -- 3

Voici comment l'utiliser dans le cas des super-agrégats.

SELECT COALESCE(nom_courant, 'Total'), COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant WITH ROLLUP;

COALESCE(nom_courant, 'Total')

nb_animaux

Chat

20

Chien

21

Perroquet amazone

4

Tortue d'Hermann

15

Total

60

Tant qu'il s'agit de simples groupes, nom_courant contient bien le nom de l'espèce. COALESCE() renvoie donc celui-ci. Par contre, quand il s'agit du super-groupe, la colonne nom_courant du résultat contient NULL, et donc COALESCE() va renvoyer "Total".

Exemple : groupons sur le sexe, sans éliminer les lignes pour lesquelles le sexe n'est pas défini.

SELECT COALESCE(sexe, 'Total'), COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY sexe WITH ROLLUP;

COALESCE(sexe, 'Total')

nb_animaux

Total

3

F

31

M

26

Total

60

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Regroupement sur plusieurs critères Conditions sur les fonctions d'agrégation

Conditions sur les fonctions d'agrégation

Super-agrégats Exercices sur les agrégats

Conditions sur les fonctions d'agrégation

Il n'est pas possible d'utiliser la clause WHERE pour faire des conditions sur une fonction d'agrégation. Donc, si l'on veut afficher les espèces dont on possède plus de 15 individus, la requête suivante ne fonctionnera pas.

SELECT nom_courant, COUNT(*)
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE COUNT(*) > 15
GROUP BY nom_courant;
ERROR 1111 (HY000): Invalid use of group function

Il faut utiliser une clause spéciale : HAVING. Cette clause se place juste après le GROUP BY.

SELECT nom_courant, COUNT(*)
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING COUNT(*) > 15;

nom_courant

COUNT(*)

Chat

20

Chien

21

Il est également possible d'utiliser un alias dans une condition HAVING :

SELECT nom_courant, COUNT(*) as nombre
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING nombre > 15;

nom_courant

nombre

Chat

20

Chien

21

Optimisation

Les conditions données dans la clause HAVING ne doivent pas nécessairement comporter une fonction d'agrégation. Les deux requêtes suivantes donneront par exemple des résultats équivalents :

SELECT nom_courant, COUNT(*) as nombre
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING nombre > 6 AND SUBSTRING(nom_courant, 1, 1) = 'C'; -- Deux conditions dans HAVING

SELECT nom_courant, COUNT(*) as nombre
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE SUBSTRING(nom_courant, 1, 1) = 'C'                  -- Une condition dans WHERE
GROUP BY nom_courant
HAVING nombre > 6;                                        -- Et une dans HAVING

Il est cependant préférable, et de loin, d'utiliser la clause WHERE autant que possible, c'est-à-dire pour toutes les conditions, sauf celles utilisant une fonction d'agrégation. En effet, les conditions HAVING ne sont absolument pas optimisées, au contraire des conditions WHERE.

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Super-agrégats Exercices sur les agrégats

Exercices sur les agrégats

Conditions sur les fonctions d'agrégation Du simple…

Jusqu'à maintenant, tout a été très théorique. Or, la meilleure façon d'apprendre, c'est la pratique. Voici donc quelques exercices que je vous conseille de faire.
S'il vaut mieux que vous essayiez par vous-mêmes avant de regarder la solution, ne restez cependant pas bloqués trop longtemps sur un exercice, et prenez toujours le temps de bien comprendre la solution.

Du simple…

Exercices sur les agrégats …Vers le complexe

Du simple…

1. Combien de races avons-nous dans la table Race ?
SELECT COUNT(*) 
FROM Race;

Simple échauffement.

2. De combien de chiens connaissons-nous le père ?
SELECT COUNT(pere_id)
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant = 'Chien';

L'astuce ici était de ne pas oublier de donner la colonne pere_id en paramètre à COUNT(), pour ne compter que les lignes où pere_id est non NULL. Si vous avez fait directement WHERE espece_id = 1 au lieu d'utiliser une jointure pour sélectionner les chiens, ce n'est pas bien grave.

3. Quelle est la date de naissance de notre plus jeune femelle ?
SELECT MAX(date_naissance) 
FROM Animal
WHERE sexe = 'F';
4. En moyenne, quel est le prix d'un chien ou d'un chat de race, par espèce, et en général ?
SELECT nom_courant AS Espece, AVG(Race.prix) AS prix_moyen
FROM Race
INNER JOIN Espece ON Race.espece_id = Espece.id
WHERE Espece.nom_courant IN ('Chat', 'Chien')
GROUP BY Espece.nom_courant WITH ROLLUP;

Ne pas oublier WITH ROLLUP pour avoir le résultat général.

5. Combien avons-nous de perroquets mâles et femelles, et quels sont leurs noms (en une seule requête bien sûr) ?
SELECT sexe, COUNT(*), GROUP_CONCAT(nom SEPARATOR ', ')
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE nom_courant = 'Perroquet amazone'
GROUP BY sexe;

Il suffisait de se souvenir de la méthode GROUP_CONCAT() pour pouvoir réaliser simplement cette requête. Peut-être avez-vous groupé sur l'espèce aussi (avec nom_courant ou autre). Ce n'était pas nécessaire puisqu'on avait restreint à une seule espèce avec la clause WHERE. Cependant, cela n'influe pas sur le résultat, mais sur la rapidité de la requête.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Exercices sur les agrégats …Vers le complexe

…Vers le complexe

Du simple… Obtenir la date/l'heure actuelle

…Vers le complexe

1. Quelles sont les races dont nous ne possédons aucun individu ?
SELECT Race.nom, COUNT(Animal.race_id) AS nombre
FROM Race
LEFT JOIN Animal ON Animal.race_id = Race.id
GROUP BY Race.nom
HAVING nombre = 0;

Il fallait ici ne pas oublier de faire une jointure externe (LEFT ou RIGHT, selon votre requête), ainsi que de mettre la colonne Animal.race_id (ou Animal.id, ou Animal.espece_id mais c'est moins intuitif) en paramètre de la fonction COUNT().

2. Quelles sont les espèces (triées par ordre alphabétique du nom latin) dont nous possédons moins de cinq mâles ?
SELECT Espece.nom_latin, COUNT(espece_id) AS nombre
FROM Espece
LEFT JOIN Animal ON Animal.espece_id = Espece.id
WHERE sexe = 'M' OR Animal.id IS NULL
GROUP BY Espece.nom_latin
HAVING nombre < 5;

À nouveau, une jointure externe et espece_id en argument de COUNT(), mais il y avait ici une petite subtilité en plus.
Puisqu'on demandait des informations sur les mâles uniquement, il fallait une condition WHERE sexe = 'M'. Mais cette condition fait que les lignes de la jointure provenant de la table Espece n'ayant aucune correspondance dans la table Animal sont éliminées également (puisque forcément, toutes les colonnes de la table Animal, dont sexe, seront à NULL pour ces lignes). Par conséquent, il fallait ajouter une condition permettant de garder ces fameuses lignes (les espèces pour lesquelles on n'a aucun individu, donc aucun mâle). Il fallait donc ajouter OR Animal.id IS NULL, ou faire cette condition sur toute autre colonne d'Animal ayant la contrainte NOT NULL, et qui donc ne sera NULL que lors d'une jointure externe, en cas de non-correspondance avec l'autre table.
Il n'y a plus alors qu'à ajouter la clause HAVING pour sélectionner les espèces ayant moins de cinq mâles.

3. Combien de mâles et de femelles de chaque race avons-nous, avec un compte total intermédiaire pour les races (mâles et femelles confondues) et pour les espèces ? Afficher le nom de la race, et le nom courant de l'espèce.
SELECT Animal.sexe, Race.nom, Espece.nom_courant, COUNT(*) AS nombre
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
INNER JOIN Race ON Animal.race_id = Race.id
WHERE Animal.sexe IS NOT NULL
GROUP BY Espece.nom_courant, Race.nom, sexe WITH ROLLUP;

Deux jointures sont nécessaires pour pouvoir afficher les noms des races et des espèces. Il suffit alors de ne pas oublier l'option WITH ROLLUP et de mettre les critères de regroupement dans le bon ordre pour avoir les super-agrégats voulus.

4. Quel serait le coût, par espèce et au total, de l'adoption de Parlotte, Spoutnik, Caribou, Cartouche, Cali, Canaille, Yoda, Zambo et Lulla ?
SELECT Espece.nom_courant, SUM(COALESCE(Race.prix, Espece.prix)) AS somme
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.nom IN ('Parlotte', 'Spoutnik', 'Caribou', 'Cartouche', 'Cali', 'Canaille', 'Yoda', 'Zambo', 'Lulla')
GROUP BY Espece.nom_courant WITH ROLLUP;

C'est ici la fonction SUM() qu'il fallait utiliser, puisqu'on veut le prix total par groupe. Sans oublier le WITH ROLLUP pour avoir également le prix total tous groupes confondus.
Quant au prix de chaque animal, c'est typiquement une situation où l'on peut utiliser COALESCE() !

Et voilà pour les nombres et les chaînes de caractères ! Notez que les fonctions d'agrégat sont parmi les plus utilisées donc soyez bien sûrs d'avoir compris comment elles fonctionnent, couplées à GROUP BY ou non.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Du simple… Obtenir la date/l'heure actuelle

Obtenir la date/l'heure actuelle

…Vers le complexe Rappels

Avant de plonger tête la première dans les fonctions temporelles, il convient de faire un bref rappel sur les différents types de données temporelles, qui sont au nombre de cinq : DATE, TIME, DATETIME, TIMESTAMP et YEAR.
Ensuite, nous verrons avec quelles fonctions il est possible d'obtenir la date actuelle, l'heure actuelle, ou les deux.

Etat actuel de la base de données
Note : les tables de test ne sont pas reprises.

SET NAMES utf8;


DROP TABLE IF EXISTS Animal;
DROP TABLE IF EXISTS Race;
DROP TABLE IF EXISTS Espece;


CREATE TABLE Espece (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom_courant varchar(40) NOT NULL,
  nom_latin varchar(40) NOT NULL,
  description text,
  prix decimal(7,2) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY nom_latin (nom_latin)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

LOCK TABLES Espece WRITE;
INSERT INTO Espece VALUES (1,'Chien','Canis canis','Bestiole à quatre pattes qui aime les caresses et tire souvent la langue',200.00),(2,'Chat','Felis silvestris','Bestiole à quatre pattes qui saute très haut et grimpe aux arbres',150.00),(3,'Tortue d''Hermann','Testudo hermanni','Bestiole avec une carapace très dure',140.00),
(4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert et jaune',700.00),(5,'Rat brun','Rattus norvegicus','Petite bestiole avec de longues moustaches et une longue queue sans poils',10.00);
UNLOCK TABLES;


CREATE TABLE Race (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom varchar(40) NOT NULL,
  espece_id smallint(6) unsigned NOT NULL,
  description text,
  prix decimal(7,2) unsigned DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;

LOCK TABLES Race WRITE;
INSERT INTO Race VALUES (1,'Berger allemand',1,'Chien sportif et élégant au pelage dense, noir-marron-fauve, noir ou gris.',485.00),(2,'Berger blanc suisse',1,'Petit chien au corps compact, avec des pattes courtes mais bien proportionnées et au pelage tricolore ou bicolore.',935.00),(3,'Singapura',2,'Chat de petite taille aux grands yeux en amandes.',985.00),
(4,'Bleu russe',2,'Chat aux yeux verts et à la robe épaisse et argentée.',835.00),(5,'Maine coon',2,'Chat de grande taille, à poils mi-longs.',735.00),(7,'Sphynx',2,'Chat sans poils.',1235.00),
(8,'Nebelung',2,'Chat bleu russe, mais avec des poils longs...',985.00),(9,'Rottweiller',1,'Chien d''apparence solide, bien musclé, à la robe noire avec des taches feu bien délimitées.',600.00);
UNLOCK TABLES;


CREATE TABLE Animal (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  sexe char(1) DEFAULT NULL,
  date_naissance datetime NOT NULL,
  nom varchar(30) DEFAULT NULL,
  commentaires text,
  espece_id smallint(6) unsigned NOT NULL,
  race_id smallint(6) unsigned DEFAULT NULL,
  mere_id smallint(6) unsigned DEFAULT NULL,
  pere_id smallint(6) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id)
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8;

LOCK TABLES Animal WRITE;
INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille beaucoup',1,1,18,22),(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F','2010-09-13 15:02:00','Schtroumpfette',NULL,2,4,41,31),
(4,'F','2009-08-03 05:12:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(5,NULL,'2010-10-03 16:44:00','Choupi','Né sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace bizarre',3,NULL,NULL,NULL),
(7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NULL),(8,'M','2008-09-11 15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(10,'M','2010-07-21 15:41:00','Bobo',NULL,1,NULL,7,21),(11,'F','2008-02-20 15:45:00','Canaille',NULL,1,NULL,NULL,NULL),(12,'F','2009-05-26 08:54:00','Cali',NULL,1,2,NULL,NULL),
(13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL),(14,'F','2009-05-26 08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL,NULL,NULL),
(16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL),(17,'F','2008-03-10 13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,1,NULL,NULL),
(19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL),(20,'M','2007-04-24 12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,NULL,NULL,NULL),
(22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL),(24,'M','2007-04-12 05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14 15:50:00','Zambo',NULL,1,1,NULL,NULL),
(26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL),(27,'M','2008-03-10 13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,1,NULL,NULL),
(29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL),(30,'M','2007-03-12 12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,NULL,NULL),
(32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL),(33,'M','2006-05-19 16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,5,NULL,NULL),
(35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NULL),(36,'M','2009-05-14 06:42:00','Boucan',NULL,2,3,NULL,NULL),(37,'F','2006-05-19 16:06:00','Callune',NULL,2,8,NULL,NULL),
(38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL),(39,'F','2008-04-20 03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,NULL,NULL),
(41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL),(42,'F','2008-04-20 03:20:00','Bilba','Sourde de l''oreille droite à 80%',2,5,NULL,NULL),(43,'F','2007-03-12 11:54:00','Cracotte',NULL,2,5,NULL,NULL),
(44,'F','2006-05-19 16:16:00','Cawette',NULL,2,8,NULL,NULL),(45,'F','2007-04-01 18:17:00','Nikki','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(46,'F','2009-03-24 08:23:00','Tortilla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(47,'F','2009-03-26 01:24:00','Scroupy','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(48,'F','2006-03-15 14:56:00','Lulla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(50,'F','2009-05-25 19:57:00','Cheli','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(51,'F','2007-04-01 03:54:00','Chicaca','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','Insomniaque',3,NULL,NULL,NULL),
(54,'M','2008-03-16 08:20:00','Bubulle','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NULL),(56,'M','2009-05-25 18:54:00','Bulbizard','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(57,'M','2007-03-04 19:36:00','Safran','Coco veut un gâteau !',4,NULL,NULL,NULL),(58,'M','2008-02-20 02:50:00','Gingko','Coco veut un gâteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26 08:28:00','Bavard','Coco veut un gâteau !',4,NULL,NULL,NULL),
(60,'F','2009-03-26 07:55:00','Parlotte','Coco veut un gâteau !',4,NULL,NULL,NULL),(61,'M','2010-11-09 00:00:00','Yoda',NULL,2,5,NULL,NULL),(62,'M','2010-11-05 00:00:00','Pipo',NULL,1,9,NULL,NULL);
UNLOCK TABLES;


ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id) ON DELETE CASCADE;

ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id);
ALTER TABLE Animal ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal (id) ON DELETE SET NULL;

Rappels

Obtenir la date/l'heure actuelle Date actuelle

Rappels

Nous allons rapidement revoir les cinq types de données temporelles disponibles pour MySQL. Pour plus de détails, je vous renvoie au chapitre consacré à ceux-ci, ou à la documentation officielle.

Date

On peut manipuler une date (jour, mois, année) avec le type DATE. Ce type représente la date sous forme de chaîne de caractères 'AAAA-MM-JJ' (A = année, M = mois, J = jour). Par exemple : le 21 octobre 2011 sera représenté '2011-10-21'.

Lorsque l'on crée une donnée de type DATE, on peut le faire avec une multitude de formats différents, que MySQL convertira automatiquement. Il suffit de donner l'année (en deux ou quatre chiffres), suivie du mois (deux chiffres) puis du jour (deux chiffres). Avec une chaîne de caractères, n'importe quel caractère de ponctuation (ou aucun caractère) peut être utilisé pour séparer l'année du mois et le mois du jour. On peut aussi utiliser un nombre entier pour initialiser une date (pour autant qu'il ait du sens en tant que date bien sûr).

MySQL supporte des DATE allant de '1001-01-01' à '9999-12-31'.

Heure

Pour une heure, ou une durée, on utilise le type TIME, qui utilise également une chaîne de caractères pour représenter l'heure : '[H]HH:MM:SS' (H = heures, M = minutes, S = secondes).

MySQL supporte des TIME allant de '-838:59:59' à '838:59:59'. Ce n'est en effet pas limité à 24h, puisqu'il est possible de stocker des durées.

Pour créer un TIME, on donne d'abord les heures, puis les minutes, puis les secondes, avec : entre chaque donnée. On peut éventuellement aussi spécifier un nombre de jours avant les heures (suivi cette fois d'une espace, et non d'un :) : 'J HH:MM:SS'.

Date et heure

Sans surprise, DATETIME est le type de données représentant une date et une heure, toujours stockées sous forme de chaîne de caractères : 'AAAA-MM-JJ HH:MM:SS'. Les heures doivent ici être comprises entre 00 et 23, puisqu'il ne peut plus s'agir d'une durée.

Comme pour DATE, l'important dans DATETIME est l'ordre des données : année, mois, jour, heures, minutes, secondes ; chacune avec deux chiffres, sauf l'année pour laquelle on peut aussi donner quatre chiffres. Cela peut être un nombre entier, ou une chaîne de caractères, auquel cas les signes de ponctuation entre chaque partie du DATETIME importent peu.

MySQL supporte des DATETIME allant de '1001-01-01 00:00:00' à '9999-12-31 23:59:59'.

Timestamp

Le timestamp d'une date est le nombre de secondes écoulées depuis le 1er janvier 1970, 0h0min0s (TUC) et la date en question. Mais attention, ce qui est stocké par MySQL dans une donnée de type TIMESTAMP n'est pas ce nombre de secondes, mais bien la date, sous format numérique : AAAAMMJJHHMMSS (contrairement à DATE, TIME et DATETIME qui utilisent des chaînes de caractères).

Un timestamp est limité aux dates allant du 1er janvier 1970 00h00min00s au 19 janvier 2038 03h14min07s.

Année

Le dernier type temporel est YEAR, qui stocke une année sous forme d'entier. Nous n'en parlerons pas beaucoup dans cette partie.

YEAR peut contenir des années comprises entre 1901 et 2155.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Obtenir la date/l'heure actuelle Date actuelle

Date actuelle

Rappels Heure actuelle

Date actuelle

Il existe deux fonctions permettant d'avoir la date actuelle (juste la date, sans l'heure, donc au format DATE) :

SELECT CURDATE(), CURRENT_DATE(), CURRENT_DATE;

CURDATE()

CURRENT_DATE()

CURRENT_DATE

2011-10-25

2011-10-25

2011-10-25

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Rappels Heure actuelle

Heure actuelle

Date actuelle Date et heure actuelles

Heure actuelle

À nouveau, deux fonctions existent, extrêmement similaires aux fonctions permettant d'avoir la date actuelle. Il suffit en effet de remplacer DATE par TIME dans le nom de la fonction.

SELECT CURTIME(), CURRENT_TIME(), CURRENT_TIME;

CURTIME()

CURRENT_TIME()

CURRENT_TIME

18:04:20

18:04:20

18:04:20

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Date actuelle Date et heure actuelles

Date et heure actuelles

Heure actuelle Formater une donnée temporelle

Date et heure actuelles

Les fonctions

Pour obtenir la date et l'heure actuelles (format DATETIME), c'est Byzance : vous avez le choix entre cinq fonctions différentes !

NOW() et SYSDATE()

NOW() est sans doute la fonction MySQL la plus utilisée pour obtenir la date du jour. C'est aussi la plus facile à retenir (bien que les noms des fonctions soient souvent explicites en SQL), puisque now veut dire "maintenant" en anglais. SYSDATE() ("system date") est aussi pas mal utilisée.

SELECT NOW(), SYSDATE();

NOW()

SYSDATE()

2011-10-26 09:40:18

2011-10-26 09:40:18

Et les autres

Les trois autres fonctions peuvent s'utiliser avec ou sans parenthèses.

SELECT LOCALTIME, CURRENT_TIMESTAMP(), LOCALTIMESTAMP;

LOCALTIME

CURRENT_TIMESTAMP()

LOCALTIMESTAMP

2011-10-26 10:02:31

2011-10-26 10:02:31

2011-10-26 10:02:31

Qui peut le plus, peut le moins

Il est tout à fait possible d'utiliser une des fonctions donnant l'heure et la date pour remplir une colonne de type DATE, ou de type TIME. MySQL convertira simplement le DATETIME en DATE, ou en TIME, en supprimant la partie inutile.

Exemple

Créons une table de test simple, avec juste trois colonnes. Une de type DATE, une de type TIME, une de type DATETIME. On peut voir que l'insertion d'une ligne en utilisant NOW() pour les trois colonnes donne le résultat attendu.

-- Création d'une table de test toute simple
CREATE TABLE testDate (
    dateActu DATE, 
    timeActu TIME, 
    datetimeActu DATETIME
);

INSERT INTO testDate VALUES (NOW(), NOW(), NOW());

SELECT *
FROM testDate;

dateActu

timeActu

datetimeActu

2011-10-26

11:22:10

2011-10-26 11:22:10

Timestamp Unix

Il existe encore une fonction qui peut donner des informations sur la date et l'heure actuelle, sous forme de timestamp Unix ; ce qui est donc le nombre de secondes écoulées depuis le premier janvier 1970, à 00:00:00. Il s'agit de UNIX_TIMESTAMP().

Je vous la donne au cas où, mais j'espère que vous ne vous en servirez pas pour stocker vos dates sous forme d'INT avec un timestamp Unix !

SELECT UNIX_TIMESTAMP();

UNIX_TIMESTAMP()

1319621754

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Heure actuelle Formater une donnée temporelle

Formater une donnée temporelle

Date et heure actuelles Extraire une information précise

Lorsque l'on tombe sur quelqu'un qui a fait le (mauvais) choix de stocker ses dates sous forme de chaînes de caractères, et qu'on lui demande les raisons de son choix, celle qui revient le plus souvent est qu'il ne veut pas afficher ses dates sous la forme 'AAAA-MM-JJ'. Donc il les stocke sous forme de CHAR ou VARCHAR'JJ/MM/AAAA' par exemple, ou n'importe quel format de son choix.
Malheureusement, en faisant ça, il se prive des nombreux avantages des formats temporels SQL (en particulier toutes les fonctions que nous voyons dans cette partie), et cela pour rien, car SQL dispose de puissantes fonctions permettant de formater une donnée temporelle.
C'est donc ce que nous allons voir dans ce chapitre.

Extraire une information précise

Formater une donnée temporelle Formater une date facilement

Extraire une information précise

Commençons en douceur avec des fonctions permettant d'extraire une information d'une donnée temporelle. Par exemple, le jour de la semaine, le nom du mois, l'année, etc.

Informations sur la date
Extraire la partie DATE

La fonction DATE(datetime) permet d'extraire la partie DATE d'une donnée de type DATETIME (ou DATE mais c'est moins utile…).

SELECT nom, date_naissance, 
        DATE(date_naissance) AS uniquementDate
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

uniquementDate

Safran

2007-03-04 19:36:00

2007-03-04

Gingko

2008-02-20 02:50:00

2008-02-20

Bavard

2009-03-26 08:28:00

2009-03-26

Parlotte

2009-03-26 07:55:00

2009-03-26

Le jour

Les fonctions suivantes donnent des informations sur le jour :

Exemples :

SELECT nom, DATE(date_naissance) AS date_naiss, 
        DAY(date_naissance) AS jour, 
        DAYOFMONTH(date_naissance) AS jour, 
        DAYOFWEEK(date_naissance) AS jour_sem,
        WEEKDAY(date_naissance) AS jour_sem2,
        DAYNAME(date_naissance) AS nom_jour, 
        DAYOFYEAR(date_naissance) AS jour_annee
FROM Animal
WHERE espece_id = 4;

nom

date_naiss

jour

jour

jour_sem

jour_sem2

nom_jour

jour_annee

Safran

2007-03-04

4

4

1

6

Sunday

63

Gingko

2008-02-20

20

20

4

2

Wednesday

51

Bavard

2009-03-26

26

26

5

3

Thursday

85

Parlotte

2009-03-26

26

26

5

3

Thursday

85

Tout ça fonctionne très bien, mais ce serait encore mieux si l'on pouvait avoir le nom des jours en français plutôt qu'en anglais. Aucun problème, il suffit de le demander, en exécutant la requête suivante :

SET lc_time_names = 'fr_FR';

Et voilà le travail :

SELECT nom, date_naissance, 
        DAYNAME(date_naissance) AS jour_semaine 
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

jour_semaine

Safran

2007-03-04 19:36:00

dimanche

Gingko

2008-02-20 02:50:00

mercredi

Bavard

2009-03-26 08:28:00

jeudi

Parlotte

2009-03-26 07:55:00

jeudi

La semaine

À partir d'une date, il est possible de calculer la semaine à laquelle correspond celle-ci. S'agit-il de la première semaine de l'année ? De la quinzième ? Ceci peut être obtenu grâce à trois fonctions : WEEK(date), WEEKOFYEAR(date) et YEARWEEK(date).

SELECT nom, date_naissance, WEEK(date_naissance) AS semaine, WEEKOFYEAR(date_naissance) AS semaine2, YEARWEEK(date_naissance) AS semaine_annee
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

semaine

semaine2

semaine_annee

Safran

2007-03-04 19:36:00

9

9

200709

Gingko

2008-02-20 02:50:00

7

8

200807

Bavard

2009-03-26 08:28:00

12

13

200912

Parlotte

2009-03-26 07:55:00

12

13

200912

WEEK() et YEARWEEK() peuvent également accepter un deuxième argument, qui sert à spécifier si la semaine doit commencer le lundi ou le dimanche, et ce qu'on considère comme la première semaine de l'année. Selon l'option utilisée par WEEK(), le résultat de cette fonction peut donc différer de celui de WEEKOFYEAR(). Si ces options vous intéressent, je vous invite à aller vous renseigner dans la documentation officielle.

Le mois

Pour le mois, il existe deux fonctions : MONTH(date) qui donne le numéro du mois (nombre de 1 à 12) et MONTHNAME(date) qui donne le nom du mois.

SELECT nom, date_naissance, MONTH(date_naissance) AS numero_mois, MONTHNAME(date_naissance) AS nom_mois
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

numero_mois

nom_mois

Safran

2007-03-04 19:36:00

3

mars

Gingko

2008-02-20 02:50:00

2

février

Bavard

2009-03-26 08:28:00

3

mars

Parlotte

2009-03-26 07:55:00

3

mars

L'année

Enfin, la fonction YEAR(date) extrait l'année.

SELECT nom, date_naissance, YEAR(date_naissance)
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

YEAR(date_naissance)

Safran

2007-03-04 19:36:00

2007

Gingko

2008-02-20 02:50:00

2008

Bavard

2009-03-26 08:28:00

2009

Parlotte

2009-03-26 07:55:00

2009

Informations sur l'heure

En ce qui concerne l'heure, voici quatre fonctions intéressantes (et faciles à retenir) qui s'appliquent à une donnée de type DATETIME ou TIME:

SELECT nom, date_naissance, 
       TIME(date_naissance) AS time_complet, 
       HOUR(date_naissance) AS heure, 
       MINUTE(date_naissance) AS minutes, 
       SECOND(date_naissance) AS secondes
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

time_complet

heure

minutes

secondes

Safran

2007-03-04 19:36:00

19:36:00

19

36

0

Gingko

2008-02-20 02:50:00

02:50:00

2

50

0

Bavard

2009-03-26 08:28:00

08:28:00

8

28

0

Parlotte

2009-03-26 07:55:00

07:55:00

7

55

0

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Formater une donnée temporelle Formater une date facilement

Formater une date facilement

Extraire une information précise Créer une date à partir d'une chaîne de caractères

Formater une date facilement

Avec les fonctions que nous venons de voir, vous êtes maintenant capables d'afficher une date dans un joli format, par exemple "le lundi 8 novembre 1987".

SELECT nom, date_naissance, CONCAT_WS(' ', 'le', DAYNAME(date_naissance), DAY(date_naissance), MONTHNAME(date_naissance), YEAR(date_naissance)) AS jolie_date
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

jolie_date

Safran

2007-03-04 19:36:00

le dimanche 4 mars 2007

Gingko

2008-02-20 02:50:00

le mercredi 20 février 2008

Bavard

2009-03-26 08:28:00

le jeudi 26 mars 2009

Parlotte

2009-03-26 07:55:00

le jeudi 26 mars 2009

Cependant, il faut bien avouer que c'est un peu long à écrire. Heureusement, il existe une fonction qui va nous permettre de faire la même chose, en bien plus court : DATE_FORMAT(date, format).

Cette fonction DATE_FORMAT() a donc deux paramètres :

Format

Le format à utiliser doit être donné sous forme de chaîne de caractères. Cette chaîne peut contenir un ou plusieurs spécificateurs dont les plus courants sont listés dans le tableau suivant.

Spécificateur

Description

%d

Jour du mois (nombre à deux chiffres, de 00 à 31)

%e

Jour du mois (nombre à un ou deux chiffres, de 0 à 31)

%D

Jour du mois, avec suffixe (1rst, 2nd,…, 31th) en anglais

%w

Numéro du jour de la semaine (dimanche = 0,…, samedi = 6)

%W

Nom du jour de la semaine

%a

Nom du jour de la semaine en abrégé

%m

Mois (nombre de deux chiffres, de 00 à 12)

%c

Mois (nombre de un ou deux chiffres, de 0 à 12)

%M

Nom du mois

%b

Nom du mois en abrégé

%y

Année, sur deux chiffres

%Y

Année, sur quatre chiffres

%r

Heure complète, format 12h (hh:mm:ss AM/PM)

%T

Heure complète, format 24h (hh:mm:ss)

%h

Heure sur deux chiffres et sur 12 heures (de 00 à 12)

%H

Heure sur deux chiffres et sur 24 heures (de 00 à 23)

%l

Heure sur un ou deux chiffres et sur 12 heures (de 0 à 12)

%k

Heure sur un ou deux chiffres et sur 24 heures (de 0 à 23)

%i

Minutes (de 00 à 59)

%s ou %S

Secondes (de 00 à 59)

%p

AM/PM

Tous les caractères ne faisant pas partie d'un spécificateur sont simplement recopiés tels quels.

Exemples
Même résultat que précédemment…

…Avec une requête bien plus courte :

SELECT nom, date_naissance, DATE_FORMAT(date_naissance, 'le %W %e %M %Y') AS jolie_date
FROM Animal
WHERE espece_id = 4;

nom

date_naissance

jolie_date

Safran

2007-03-04 19:36:00

le dimanche 4 mars 2007

Gingko

2008-02-20 02:50:00

le mercredi 20 février 2008

Bavard

2009-03-26 08:28:00

le jeudi 26 mars 2009

Parlotte

2009-03-26 07:55:00

le jeudi 26 mars 2009

Autres exemples
SELECT DATE_FORMAT(NOW(), 'Nous sommes aujourd''hui le %d %M de l''année %Y. Il est actuellement %l heures et %i minutes.') AS Top_date_longue;

SELECT DATE_FORMAT(NOW(), '%d %b. %y - %r') AS Top_date_courte;

Top_date_longue

Nous sommes aujourd'hui le 27 octobre de l'année 2011. Il est actuellement 3 heures et 17 minutes.

Top_date_courte

27 oct. 11 - 03:34:40 PM

Fonction supplémentaire pour l'heure

DATE_FORMAT() peut s'utiliser sur des données de type DATE, TIME ou DATETIME. Mais il existe également une fonction TIME_FORMAT(heure, format), qui ne sert qu'à formater les heures (et ne doit donc pas s'utiliser sur une DATE). Elle s'utilise exactement de la même manière, simplement il faut y utiliser des spécificateurs ayant du sens pour une donnée TIME, sinon NULL ou 0 est renvoyé.
Si un mauvais format de TIME ou DATETIME est donné à TIME_FORMAT() (par exemple, si on lui donne une DATE), MySQL va tenter d'interpréter la donnée et renverra bien un résultat, mais celui-ci n'aura peut-être pas beaucoup de sens (pour vous du moins).

Exemples :

-- Sur une DATETIME
SELECT TIME_FORMAT(NOW(), '%r') AS sur_datetime, 
       TIME_FORMAT(CURTIME(), '%r') AS sur_time, 
       TIME_FORMAT(NOW(), '%M %r') AS mauvais_specificateur, 
       TIME_FORMAT(CURDATE(), '%r') AS sur_date;

sur_datetime

sur_time

mauvais_specificateur

sur_date

10:58:47 AM

10:58:47 AM

NULL

12:20:12 AM

L'application de TIME_FORMAT() sur CURDATE() a renvoyé un avertissement :

Level

Code

Message

Warning

1292

Truncated incorrect time value: '2012-04-30'

Formats standards

Il existe un certain nombre de formats de date et d'heure standards, prédéfinis, que l'on peut utiliser dans la fonction DATE_FORMAT(). Pour obtenir ces formats, il faut appeler la fonction GET_FORMAT(type, standard).

Le paramètre type doit être choisi entre les trois types de données : DATE, TIME et DATETIME.

Il existe cinq formats standards :

Voici un tableau reprenant les différentes possibilités :

Fonction

Format

Exemple

GET_FORMAT(DATE,'USA')

'%m.%d.%Y'

10.30.1988

GET_FORMAT(DATE,'JIS')

'%Y-%m-%d'

1988-10-30

GET_FORMAT(DATE,'ISO')

'%Y-%m-%d'

1988-10-30

GET_FORMAT(DATE,'EUR')

'%d.%m.%Y'

30.10.1988

GET_FORMAT(DATE,'INTERNAL')

'%Y%m%d'

19881031

GET_FORMAT(DATETIME,'USA')

'%Y-%m-%d %H.%i.%s'

1988-10-30 13.44.33

GET_FORMAT(DATETIME,'JIS')

'%Y-%m-%d %H:%i:%s'

1988-10-30 13:44:33

GET_FORMAT(DATETIME,'ISO')

'%Y-%m-%d %H:%i:%s'

1988-10-30 13:44:33

GET_FORMAT(DATETIME,'EUR')

'%Y-%m-%d %H.%i.%s'

1988-10-30 13.44.33

GET_FORMAT(DATETIME,'INTERNAL')

'%Y%m%d%H%i%s'

19881030134433

GET_FORMAT(TIME,'USA')

'%h:%i:%s %p'

1:44:33 PM

GET_FORMAT(TIME,'JIS')

'%H:%i:%s'

13:44:33

GET_FORMAT(TIME,'ISO')

'%H:%i:%s'

13:44:33

GET_FORMAT(TIME,'EUR')

'%H.%i.%S'

13.44.33

GET_FORMAT(TIME,'INTERNAL')

'%H%i%s'

134433

Exemples :

SELECT DATE_FORMAT(NOW(), GET_FORMAT(DATE, 'EUR')) AS date_eur,
       DATE_FORMAT(NOW(), GET_FORMAT(TIME, 'JIS')) AS heure_jis,
       DATE_FORMAT(NOW(), GET_FORMAT(DATETIME, 'USA')) AS date_heure_usa;

date_eur

heure_jis

date_heure_usa

29.04.2012

11:20:55

2012-04-29 11.20.55

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Extraire une information précise Créer une date à partir d'une chaîne de caractères

Créer une date à partir d'une chaîne de caractères

Formater une date facilement Calculs sur les données temporelles

Créer une date à partir d'une chaîne de caractères

Voici une dernière fonction ayant trait au format des dates : STR_TO_DATE(date, format). Cette fonction est l'exact contraire de DATE_FORMAT() : elle prend une chaîne de caractères représentant une date suivant le format donné, et renvoie la DATETIME correspondante.

Exemples :

SELECT STR_TO_DATE('03/04/2011 à 09h17', '%d/%m/%Y à %Hh%i') AS StrDate,
       STR_TO_DATE('15blabla', '%Hblabla') StrTime;

StrDate

StrTime

2011-04-03 09:17:00

15:00:00

Il est bien sûr possible d'utiliser GET_FORMAT() aussi avec STR_TO_DATE().

SELECT STR_TO_DATE('11.21.2011', GET_FORMAT(DATE, 'USA')) AS date_usa,
       STR_TO_DATE('12.34.45', GET_FORMAT(TIME, 'EUR')) AS heure_eur,
       STR_TO_DATE('20111027133056', GET_FORMAT(TIMESTAMP, 'INTERNAL')) AS date_heure_int;

date_usa

heure_eur

date_heure_int

2011-11-21

12:34:45

2011-10-27 13:30:56

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Formater une date facilement Calculs sur les données temporelles

Calculs sur les données temporelles

Créer une date à partir d'une chaîne de caractères Différence entre deux dates/heures

Il est fréquent de vouloir faire des calculs sur des données temporelles. Par exemple, pour calculer le nombre de jours ou d'heures entre deux dates, pour ajouter une certaine durée à une donnée en cas de calcul d'échéance, etc.
Pour ce faire, on peut soit se lancer dans des calculs compliqués en convertissant des jours en heures, des heures en jours, des minutes en secondes ; soit utiliser les fonctions SQL prévues à cet effet. Je vous laisse deviner quelle solution est la meilleure…

Nous allons donc voir dans ce chapitre comment :

Différence entre deux dates/heures

Calculs sur les données temporelles Ajout et retrait d'un intervalle de temps

Différence entre deux dates/heures

Trois fonctions permettent de calculer le temps écoulé entre deux données temporelles :

DATEDIFF()

DATEDIFF(date1, date2) peut s'utiliser avec des données de type DATE ou DATETIME (dans ce dernier cas, seule la partie date est utilisée).

Les trois requêtes suivantes donnent donc le même résultat.

SELECT DATEDIFF('2011-12-25','2011-11-10') AS nb_jours;
SELECT DATEDIFF('2011-12-25 22:12:18','2011-11-10 12:15:41') AS nb_jours;
SELECT DATEDIFF('2011-12-25 22:12:18','2011-11-10') AS nb_jours;

nb_jours

45

TIMEDIFF()

La fonction TIMEDIFF(expr1, expr2) calcule la durée entre expr1 et expr2. Les deux arguments doivent être de même type, soit TIME, soit DATETIME.

-- Avec des DATETIME
SELECT '2011-10-08 12:35:45' AS datetime1, '2011-10-07 16:00:25' AS datetime2, TIMEDIFF('2011-10-08 12:35:45', '2011-10-07 16:00:25') as difference;

-- Avec des TIME
SELECT '12:35:45' AS time1, '00:00:25' AS time2, TIMEDIFF('12:35:45', '00:00:25') as difference;

datetime1

datetime2

difference

2011-10-08 12:35:45

2011-10-07 16:00:25

20:35:20

time1

time2

difference

12:35:45

00:00:25

12:35:20

TIMESTAMPDIFF()

La fonction TIMESTAMPDIFF() prend, quant à elle, un paramètre supplémentaire : l'unité de temps désirée pour le résultat. Les unités autorisées comprennent : SECOND (secondes), MINUTE (minutes), HOUR (heures), DAY (jours), WEEK (semaines), MONTH (mois), QUARTER (trimestres) et YEAR (années).

TIMESTAMPDIFF(unite, date1, date2) s'utilise également avec des données de type DATE ou DATETIME. Si vous demandez un résultat comprenant une unité inférieure au jour (heure ou minute par exemple) et que l'une de vos données est de type DATE, MySQL complétera cette date avec l'heure par défaut '00:00:00'.

SELECT TIMESTAMPDIFF(DAY, '2011-11-10', '2011-12-25') AS nb_jours,
       TIMESTAMPDIFF(HOUR,'2011-11-10', '2011-12-25 22:00:00') AS nb_heures_def, 
       TIMESTAMPDIFF(HOUR,'2011-11-10 14:00:00', '2011-12-25 22:00:00') AS nb_heures,
       TIMESTAMPDIFF(QUARTER,'2011-11-10 14:00:00', '2012-08-25 22:00:00') AS nb_trimestres;

nb_jours

nb_heures_def

nb_heures

nb_trimestres

45

1102

1088

3

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Calculs sur les données temporelles Ajout et retrait d'un intervalle de temps

Ajout et retrait d'un intervalle de temps

Différence entre deux dates/heures Divers

Ajout et retrait d'un intervalle de temps

Intervalle

Certaines des fonctions et opérations suivantes utilisent le mot-clé INTERVAL, permettant de définir un intervalle de temps à ajouter ou à soustraire d'une date.

Un intervalle de temps est défini par une quantité et une unité ("3 jours" par exemple, "3" étant la quantité, "jour" l'unité). En MySQL, il existe une vingtaine d'unités possibles pour un INTERVAL, dont une partie est listée dans le tableau ci-dessous.

Unité

Format

SECOND

-

MINUTE

-

HOUR

-

DAY

-

WEEK

-

MONTH

-

YEAR

-

MINUTE_SECOND

'm:S'

HOUR_SECOND

'HH:mm:SS'

HOUR_MINUTE

'HH:mm'

DAY_SECOND

'J HH:mm:SS'

DAY_MINUTE

'J HH:mm'

DAY_HOUR

'J HH'

YEAR_MONTH

'A-M'

Notez que, comme pour DATE, DATETIME et TIME, les signes de ponctuation séparant les différentes parties d'un intervalle ne doivent pas nécessairement être '-' pour la partie date et ':' pour la partie heure. Il ne s'agit que de suggestions. N'importe quel signe de ponctuation (ou aucun) sera accepté.

Ajout d'un intervalle de temps

Trois fonctions permettent d'ajouter un intervalle de temps à une date (de type DATE ou DATETIME) :

ADDDATE()

Cette fonction peut s'utiliser de deux manières : soit en précisant un intervalle de temps avec le mot-clé INTERVAL, soit en donnant un nombre de jours à ajouter à la date.

Exemples :

SELECT ADDDATE('2011-05-21', INTERVAL 3 MONTH) AS date_interval,                              -- Avec DATE et INTERVAL
       ADDDATE('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS datetime_interval, -- Avec DATETIME et INTERVAL
       ADDDATE('2011-05-21', 12) AS date_nombre_jours,                                        -- Avec DATE et nombre de jours
       ADDDATE('2011-05-21 12:15:56', 42) AS datetime_nombre_jours;                           -- Avec DATETIME et nombre de jours

date_interval

datetime_interval

date_nombre_jours

datetime_nombre_jours

2011-08-21

2011-05-24 14:26:28

2011-06-02

2011-07-02 12:15:56

DATE_ADD()

DATE_ADD(date, INTERVAL quantite unite) s'utilise exactement de la même manière que ADDDATE(date, INTERVAL quantite unite).

Exemples :

SELECT DATE_ADD('2011-05-21', INTERVAL 3 MONTH) AS avec_date,                               -- Avec DATE
       DATE_ADD('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS avec_datetime;  -- Avec DATETIME

avec_date

avec_datetime

2011-08-21

2011-05-24 14:26:28

Opérateur +

Il est également possible d'ajouter un intervalle de temps à une date en utilisant simplement l'opérateur + et un INTERVAL. L'intervalle peut se trouver à droite ou à gauche du signe +.

Exemples :

SELECT '2011-05-21' + INTERVAL 5 DAY AS droite,                    -- Avec DATE et intervalle à droite
       INTERVAL '3 12' DAY_HOUR + '2011-05-21 12:15:56' AS gauche; -- Avec DATETIME et intervalle à gauche

droite

gauche

2011-05-26

2011-05-25 00:15:56

TIMESTAMPADD()

TIMESTAMPADD(unite, quantite, date) est un peu plus restreint que DATE_ADD() et ADDDATE(). En effet, cette fonction n'utilise pas d'INTERVAL. Il faut cependant définir une unité parmi les suivantes : FRAC_SECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, et YEAR.

Exemple :

SELECT TIMESTAMPADD(DAY, 5, '2011-05-21') AS avec_date,                   -- Avec DATE
       TIMESTAMPADD(MINUTE, 34, '2011-05-21 12:15:56') AS avec_datetime;  -- Avec DATETIME

avec_date

avec_datetime

2011-05-26

2011-05-21 12:49:56

ADDTIME()

La fonction ADDTIME(expr1, expr2) permet d'ajouter expr2 (de type TIME) à expr1 (de type DATETIME ou TIME). Le résultat sera du même type que expr1.

Exemples :

SELECT NOW() AS Maintenant, ADDTIME(NOW(), '01:00:00') AS DansUneHeure,                  -- Avec un DATETIME
       CURRENT_TIME() AS HeureCourante, ADDTIME(CURRENT_TIME(), '03:20:02') AS PlusTard; -- Avec un TIME

Maintenant

DansUneHeure

HeureCourante

PlusTard

2012-04-29 12:08:33

2012-04-29 13:08:33

12:08:33

15:28:35

Soustraction d'un intervalle de temps
SUBDATE(), DATE_SUB() et SUBTIME()

SUBDATE(), DATE_SUB() et SUBTIME() sont les équivalents de ADDDATE(), DATE_ADD() et ADDTIME() pour la soustraction. Elles s'utilisent exactement de la même manière.

Exemples :

SELECT SUBDATE('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS SUBDATE1, 
       SUBDATE('2011-05-21', 12) AS SUBDATE2,
       DATE_SUB('2011-05-21', INTERVAL 3 MONTH) AS DATE_SUB;

SELECT SUBTIME('2011-05-21 12:15:56', '18:35:15') AS SUBTIME1,
       SUBTIME('12:15:56', '8:35:15') AS SUBTIME2;

SUBDATE1

SUBDATE2

DATE_SUB

2011-05-18 10:05:24

2011-05-09

2011-02-21

SUBTIME1

SUBTIME2

2011-05-20 17:40:41

03:40:41

Opérateur -

Tout comme l'opérateur + peut s'utiliser pour ajouter un intervalle de temps, il est possible d'utiliser l'opérateur - pour en soustraire un. Cependant, pour la soustraction, la date doit impérativement se trouver à gauche du signe -, et l'intervalle à droite. Il n'est en effet pas possible de soustraire une date d'un intervalle.

Exemple :

SELECT '2011-05-21' - INTERVAL 5 DAY;

'2011-05-21' - INTERVAL 5 DAY

2011-05-16

Soustraire, c'est ajouter un négatif

Un INTERVAL peut être défini avec une quantité négative, et ajouter un intervalle négatif, c'est soustraire un intervalle positif. De même soustraire un intervalle négatif revient à ajouter un intervalle positif.

Par conséquent, dans les requêtes suivantes, les deux parties du SELECT sont équivalentes.

SELECT ADDDATE(NOW(), INTERVAL -3 MONTH) AS ajout_negatif, SUBDATE(NOW(), INTERVAL 3 MONTH) AS retrait_positif;
SELECT DATE_ADD(NOW(), INTERVAL 4 HOUR) AS ajout_positif, DATE_SUB(NOW(), INTERVAL -4 HOUR) AS retrait_negatif;
SELECT NOW() + INTERVAL -15 MINUTE AS ajout_negatif, NOW() - INTERVAL 15 MINUTE AS retrait_positif;

ajout_negatif

retrait_positif

2011-09-01 16:04:26

2011-09-01 16:04:26

ajout_positif

retrait_negatif

2011-12-01 20:04:26

2011-12-01 20:04:26

ajout_negatif

retrait_positif

2011-12-01 15:49:28

2011-12-01 15:49:28

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Différence entre deux dates/heures Divers

Divers

Ajout et retrait d'un intervalle de temps Exercices

Divers

Créer une date/heure à partir d'autres informations
À partir d'un timestamp Unix

La fonction FROM_UNIXTIME(ts) renvoie un DATETIME à partir du timestamp Unix ts.

SELECT FROM_UNIXTIME(1325595287);

FROM_UNIXTIME(1325595287)

2012-01-03 13:54:47

Notez que la fonction UNIX_TIMESTAMP(), que nous avons vue lors d'un chapitre précédent et qui donne le timestamp actuel, peut également s'utiliser avec un DATETIME en paramètre ; dans ce cas, elle fait l'inverse de la fonction FROM_UNIXTIME(ts) : elle renvoie le timestamp Unix du DATETIME passé en paramètre.

SELECT UNIX_TIMESTAMP('2012-01-03 13:54:47');

UNIX_TIMESTAMP('2012-01-03 13:54:47')

1325595287

À partir de différents éléments d'une date/heure

La fonction MAKEDATE() crée une DATE à partir d'une année et d'un numéro de jour (1 étant le premier janvier, 32 le premier février, etc.). Quant à la fonction MAKETIME(), elle crée un TIME à partir d'une heure et d'un nombre de minutes et de secondes.

SELECT MAKEDATE(2012, 60) AS 60eJour2012, MAKETIME(3, 45, 34) AS heureCree;

60eJour2012

heureCree

2012-02-29

03:45:34

Convertir un TIME en secondes, et vice versa

Il est toujours utile de connaître la fonction SEC_TO_TIME()qui convertit un nombre de secondes en une donnée de type TIME, et son opposé TIME_TO_SEC()qui convertit un TIME en un nombre de secondes.

Exemples :

SELECT SEC_TO_TIME(102569), TIME_TO_SEC('01:00:30');

SEC_TO_TIME(102569)

TIME_TO_SEC('01:00:30')

28:29:29

3630

Dernier jour du mois

Enfin, voici la dernière fonction que nous verrons dans ce chapitre : LAST_DAY(date). Cette fonction donne le dernier jour du mois de la date passée en paramètre. Cela permet par exemple de voir que 2012 est une année bissextile, contrairement à l'an 2100.

SELECT LAST_DAY('2012-02-03') AS fevrier2012, LAST_DAY('2100-02-03') AS fevrier2100;

fevrier2012

fevrier2100

2012-02-29

2100-02-28

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Ajout et retrait d'un intervalle de temps Exercices

Exercices

Divers Commençons par le format

Nous avons maintenant vu une bonne partie des fonctions MySQL relatives aux données temporelles. N'oubliez pas de consulter la documentation officielle au besoin, car celle-ci contient plus de détails et d'autres fonctions.
Je vous propose maintenant quelques exercices pour passer de la théorie à la pratique. Cela va vous permettre de retenir déjà une partie des fonctions, mais surtout, cela va replacer ces fonctions dans un véritable contexte puisque nous allons bien sûr travailler sur notre table Animal.
Bien entendu, certaines questions ont plusieurs réponses possibles, mais je ne donnerai qu'une seule solution. Ne soyez donc pas étonnés d'avoir trouvé une requête faisant ce qui est demandé mais différente de la réponse indiquée.

Commençons par le format

Exercices Passons aux calculs

Commençons par le format

1. Sélectionner tous les animaux nés en juin.
SELECT id, date_naissance, nom
FROM Animal 
WHERE MONTH(date_naissance) = 6;
2. Sélectionner tous les animaux nés dans les huit premières semaines d'une année.
SELECT id, date_naissance, nom
FROM Animal 
WHERE WEEKOFYEAR(date_naissance) < 9;
3. Afficher le jour (en chiffres) et le mois de naissance (en toutes lettres) des tortues et des chats nés avant 2007 (en deux colonnes).
SELECT DAY(date_naissance), MONTHNAME(date_naissance) 
FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant IN ('Chat', 'Tortue d''Hermann')
AND YEAR(date_naissance) < 2007;
4. Même chose qu'à la question précédente, mais en une seule colonne.
SELECT DATE_FORMAT(date_naissance, '%e %M') 
FROM Animal 
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant IN ('Chat', 'Tortue d''Hermann')
AND YEAR(date_naissance) < 2007;
5. Sélectionner tous les animaux nés en avril, mais pas un 24 avril, triés par heure de naissance décroissante (heure dans le sens commun du terme, donc heure, minutes, secondes) et afficher leur date de naissance suivant le même format que l'exemple

Format : 8 janvier, à 6h30PM, en l'an 2010 après J.C.

SELECT DATE_FORMAT(date_naissance, '%e %M, à %lh%i%p, en l''an %Y après J.C.') AS jolie_date
FROM Animal
WHERE MONTH(date_naissance) = 4
AND DAY(date_naissance) <> 24
ORDER BY TIME(date_naissance) DESC;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Exercices Passons aux calculs

Passons aux calculs

Commençons par le format Et pour finir, mélangeons le tout

Passons aux calculs

1. Moka était censé naître le 27 février 2008. Calculer le nombre de jours de retard de sa naissance.
SELECT DATEDIFF(date_naissance, '2008-02-27')  AS retard
FROM Animal 
WHERE nom = 'Moka';
2. Afficher la date à laquelle chaque perroquet (espece_id = 4) fêtera son 25e anniversaire.
SELECT DATE(ADDDATE(date_naissance, INTERVAL 25 YEAR)) AS Anniversaire
FROM Animal 
WHERE espece_id = 4;

On ne demandait que la date (on fête rarement son anniversaire à l'heure pile de sa naissance), d'où l'utilisation de la fonction DATE().

3. Sélectionner les animaux nés dans un mois contenant exactement 29 jours.
SELECT id, date_naissance, nom
FROM Animal 
WHERE DAY(LAST_DAY(date_naissance)) = 29;
4. Après douze semaines, un chaton est sevré (sauf exception bien sûr). Afficher la date à partir de laquelle les chats (espece_id = 2) de l'élevage peuvent être adoptés (qu'il s'agisse d'une date dans le passé ou dans le futur).
SELECT id, nom, DATE(DATE_ADD(date_naissance, INTERVAL 12 WEEK)) AS sevrage
FROM Animal 
WHERE espece_id = 2;
5. Rouquine, Zira, Bouli et Balou (id 13, 18, 20 et 22 respectivement) font partie de la même portée. Calculer combien de temps, en minutes, Balou est né avant Zira.
SELECT TIMESTAMPDIFF(MINUTE, 
					(SELECT date_naissance 
					FROM Animal 
					WHERE nom = 'Balou'), 
					(SELECT date_naissance 
					FROM Animal 
					WHERE nom = 'Zira')) 
		AS nb_minutes;

Il fallait ici penser aux sous-requêtes, afin d'obtenir les dates de naissance de Balou et Zira pour les utiliser dans la même fonction TIMESTAMPDIFF().

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Commençons par le format Et pour finir, mélangeons le tout

Et pour finir, mélangeons le tout

Passons aux calculs Transactions

Et pour finir, mélangeons le tout

Et quand on dit "tout", c'est tout ! Par conséquent, il est possible (et même fort probable) que vous ayez besoin de notions et fonctions vues dans les chapitres précédents (regroupements, sous-requêtes, etc.) pour résoudre ces exercices.

1. Rouquine, Zira, Bouli et Balou (id 13, 18, 20 et 22 respectivement) font partie de la même portée. Calculer combien de temps, en minutes, s'est écoulé entre le premier né et le dernier né de la portée.
SELECT TIMESTAMPDIFF(MINUTE, 
		      (
		        SELECT MIN(date_naissance) 
			FROM Animal 
			WHERE id IN (13, 18, 20, 22)
		      ), 
		      (
			SELECT MAX(date_naissance) 
			FROM Animal 
			WHERE id IN (13, 18, 20, 22)
		      )
		    ) AS nb_minutes;

Presque le même exercice qu'au-dessus, à ceci près qu'il fallait utiliser les fonctions d’agrégation.

2. Calculer combien d'animaux sont nés durant un mois pendant lequel les moules sont les plus consommables (c'est-à-dire les mois finissant en "bre").
SELECT COUNT(*)
FROM Animal
WHERE MONTHNAME(date_naissance) LIKE '%bre';

Il faut bien sûr avoir préalablement défini que le nom des mois et des jours doit être exprimé en français. Je rappelle la requête à utiliser : SET lc_time_names = 'fr_FR';

3. Pour les chiens et les chats (espece_id = 1 et espece_id = 2 respectivement), afficher les différentes dates de naissance des portées d'au moins deux individus (format JJ/MM/AAAA), ainsi que le nombre d'individus pour chacune de ces portées. Attenti
SELECT DATE_FORMAT(date_naissance, '%d/%m/%Y'), COUNT(*) as nb_individus
FROM Animal
WHERE espece_id IN (1, 2)
GROUP BY DATE(date_naissance), espece_id
HAVING nb_individus > 1;

Il faut regrouper sur la date (et uniquement la date, pas l'heure) puis sur l'espèce pour éviter de mélanger chiens et chats. Une simple clause HAVING permet ensuite de sélectionner les portées de deux individus ou plus.

4. Calculer combien de chiens (espece_id = 1) sont nés en moyenne chaque année entre 2006 et 2010 (sachant qu'on a eu au moins une naissance chaque année).
SELECT AVG(nb)
FROM (
	SELECT COUNT(*) AS nb
	FROM Animal
	WHERE espece_id = 1
	AND YEAR(date_naissance) >= 2006
	AND YEAR(date_naissance) <= 2010
	GROUP BY YEAR(date_naissance)
) AS tableIntermedaire;

Ici, il fallait penser à faire une sous-requête dans la clause FROM. Si vous n'avez pas trouvé, rappelez-vous qu'il faut penser par étapes. Vous voulez la moyenne du nombre de chiens nés chaque année ? Commencez par obtenir le nombre de chiens nés chaque année, puis seulement demandez-vous comment faire la moyenne.

5. Afficher la date au format ISO du 5e anniversaire des animaux dont on connaît soit le père, soit la mère.
SELECT DATE_FORMAT(DATE_ADD(date_naissance, INTERVAL 5 YEAR), GET_FORMAT(DATE, 'ISO')) AS dateIso
FROM Animal
WHERE pere_id IS NOT NULL
OR mere_id IS NOT NULL;

Pour cette dernière question, il fallait juste imbriquer plusieurs fonctions différentes. À nouveau, si vous n'avez pas réussi, c'est sans doute parce que vous ne décomposez pas le problème correctement.

À partir de maintenant, si j'en vois encore un stocker ses dates sous forme de chaîne de caractères, ou sous forme de INT pour stocker un timestamp Unix, je le mords !
Avec cette partie sur les dates s'achèvent les parties "basiques" de ce tutoriel. Vous devriez maintenant avoir les connaissances suffisantes pour gérer la base de données d'un petit site web ou d'une application toute simple, sans toutefois exploiter vraiment les possibilités de MySQL. Les parties suivantes aborderont des notions un peu plus avancées.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Passons aux calculs Transactions

Transactions

Et pour finir, mélangeons le tout Principe

Pour commencer cette partie, nous allons voir ce que sont les transactions, à quoi elles servent exactement, et comment les utiliser avec MySQL.

Les transactions sont une fonctionnalité absolument indispensable, permettant de sécuriser une application utilisant une base de données. Sans transactions, certaines opérations risqueraient d'être à moitié réalisées, et la moindre erreur, la moindre interruption pourrait avoir des conséquences énormes. En effet, les transactions permettent de regrouper des requêtes dans des blocs, et de faire en sorte que tout le bloc soit exécuté en une seule fois, cela afin de préserver l'intégrité des données de la base.

Les transactions ont été implémentées assez tard dans MySQL, et qui plus est, elles ne sont pas utilisables pour tous les types de tables. C'est d'ailleurs un des principaux arguments des détracteurs de MySQL.

Etat actuel de la base de données
Note : les tables de test ne sont pas reprises.

SET NAMES utf8;


DROP TABLE IF EXISTS Animal;
DROP TABLE IF EXISTS Race;
DROP TABLE IF EXISTS Espece;


CREATE TABLE Espece (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom_courant varchar(40) NOT NULL,
  nom_latin varchar(40) NOT NULL,
  description text,
  prix decimal(7,2) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY nom_latin (nom_latin)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

LOCK TABLES Espece WRITE;
INSERT INTO Espece VALUES (1,'Chien','Canis canis','Bestiole à quatre pattes qui aime les caresses et tire souvent la langue',200.00),(2,'Chat','Felis silvestris','Bestiole à quatre pattes qui saute très haut et grimpe aux arbres',150.00),(3,'Tortue d''Hermann','Testudo hermanni','Bestiole avec une carapace très dure',140.00),
(4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert et jaune',700.00),(5,'Rat brun','Rattus norvegicus','Petite bestiole avec de longues moustaches et une longue queue sans poils',10.00);
UNLOCK TABLES;


CREATE TABLE Race (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  nom varchar(40) NOT NULL,
  espece_id smallint(6) unsigned NOT NULL,
  description text,
  prix decimal(7,2) unsigned DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;

LOCK TABLES Race WRITE;
INSERT INTO Race VALUES (1,'Berger allemand',1,'Chien sportif et élégant au pelage dense, noir-marron-fauve, noir ou gris.',485.00),(2,'Berger blanc suisse',1,'Petit chien au corps compact, avec des pattes courtes mais bien proportionnées et au pelage tricolore ou bicolore.',935.00),(3,'Singapura',2,'Chat de petite taille aux grands yeux en amandes.',985.00),
(4,'Bleu russe',2,'Chat aux yeux verts et à la robe épaisse et argentée.',835.00),(5,'Maine coon',2,'Chat de grande taille, à poils mi-longs.',735.00),(7,'Sphynx',2,'Chat sans poils.',1235.00),
(8,'Nebelung',2,'Chat bleu russe, mais avec des poils longs...',985.00),(9,'Rottweiller',1,'Chien d''apparence solide, bien musclé, à la robe noire avec des taches feu bien délimitées.',600.00);
UNLOCK TABLES;


CREATE TABLE Animal (
  id smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  sexe char(1) DEFAULT NULL,
  date_naissance datetime NOT NULL,
  nom varchar(30) DEFAULT NULL,
  commentaires text,
  espece_id smallint(6) unsigned NOT NULL,
  race_id smallint(6) unsigned DEFAULT NULL,
  mere_id smallint(6) unsigned DEFAULT NULL,
  pere_id smallint(6) unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id)
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8;

LOCK TABLES Animal WRITE;
INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille beaucoup',1,1,18,22),(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F','2010-09-13 15:02:00','Schtroumpfette',NULL,2,4,41,31),
(4,'F','2009-08-03 05:12:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(5,NULL,'2010-10-03 16:44:00','Choupi','Né sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace bizarre',3,NULL,NULL,NULL),
(7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NULL),(8,'M','2008-09-11 15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(10,'M','2010-07-21 15:41:00','Bobo',NULL,1,NULL,7,21),(11,'F','2008-02-20 15:45:00','Canaille',NULL,1,NULL,NULL,NULL),(12,'F','2009-05-26 08:54:00','Cali',NULL,1,2,NULL,NULL),
(13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL),(14,'F','2009-05-26 08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL,NULL,NULL),
(16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL),(17,'F','2008-03-10 13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,1,NULL,NULL),
(19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL),(20,'M','2007-04-24 12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,NULL,NULL,NULL),
(22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL),(24,'M','2007-04-12 05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14 15:50:00','Zambo',NULL,1,1,NULL,NULL),
(26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL),(27,'M','2008-03-10 13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,1,NULL,NULL),
(29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL),(30,'M','2007-03-12 12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,NULL,NULL),
(32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL),(33,'M','2006-05-19 16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,5,NULL,NULL),
(35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NULL),(36,'M','2009-05-14 06:42:00','Boucan',NULL,2,3,NULL,NULL),(37,'F','2006-05-19 16:06:00','Callune',NULL,2,8,NULL,NULL),
(38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL),(39,'F','2008-04-20 03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,NULL,NULL),
(41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL),(42,'F','2008-04-20 03:20:00','Bilba','Sourde de l''oreille droite à 80%',2,5,NULL,NULL),(43,'F','2007-03-12 11:54:00','Cracotte',NULL,2,5,NULL,NULL),
(44,'F','2006-05-19 16:16:00','Cawette',NULL,2,8,NULL,NULL),(45,'F','2007-04-01 18:17:00','Nikki','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(46,'F','2009-03-24 08:23:00','Tortilla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(47,'F','2009-03-26 01:24:00','Scroupy','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(48,'F','2006-03-15 14:56:00','Lulla','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(50,'F','2009-05-25 19:57:00','Cheli','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(51,'F','2007-04-01 03:54:00','Chicaca','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','Insomniaque',3,NULL,NULL,NULL),
(54,'M','2008-03-16 08:20:00','Bubulle','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),(55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NULL),(56,'M','2009-05-25 18:54:00','Bulbizard','Bestiole avec une carapace très dure',3,NULL,NULL,NULL),
(57,'M','2007-03-04 19:36:00','Safran','Coco veut un gâteau !',4,NULL,NULL,NULL),(58,'M','2008-02-20 02:50:00','Gingko','Coco veut un gâteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26 08:28:00','Bavard','Coco veut un gâteau !',4,NULL,NULL,NULL),
(60,'F','2009-03-26 07:55:00','Parlotte','Coco veut un gâteau !',4,NULL,NULL,NULL),(61,'M','2010-11-09 00:00:00','Yoda',NULL,2,5,NULL,NULL),(62,'M','2010-11-05 00:00:00','Pipo',NULL,1,9,NULL,NULL);
UNLOCK TABLES;


ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id) ON DELETE CASCADE;

ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES Espece (id);
ALTER TABLE Animal ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (id) ON DELETE SET NULL;
ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal (id) ON DELETE SET NULL;

Principe

Transactions Syntaxe et utilisation

Principe

Une transaction, c'est un ensemble de requêtes qui sont exécutées en un seul bloc. Ainsi, si une des requêtes du bloc échoue, on peut décider d'annuler tout le bloc de requêtes (ou de quand même valider les requêtes qui ont réussi).

À quoi ça sert ?

Imaginez que Monsieur Durant fasse un virement de 300 euros à Monsieur Dupont via sa banque en ligne. Il remplit toutes les petites cases du virement, puis valide. L'application de la banque commence à traiter le virement quand soudain, une violente panne de courant provoque l'arrêt des serveurs de la banque.

Deux jours plus tard, Monsieur Durant reçoit un coup de fil de Monsieur Dupont, très énervé, qui lui demande pourquoi le paiement convenu n'a toujours pas été fait. Intrigué, Monsieur Durant va vérifier son compte, et constate qu'il a bien été débité de 300 euros.

Mais que s'est-il donc passé ?

Normalement, le traitement d'un virement est plutôt simple, deux étapes suffisent :

Seulement voilà, pas de chance pour Monsieur Durant, la panne de courant qui a éteint les serveurs est survenue pile entre l'étape 1 et l'étape 2. Du coup, son compte a été débité, mais le compte de Monsieur Dupont n'a jamais été crédité.

La banque de Monsieur Durant n'utilisait pas les transactions. Si c'était le cas, la seconde requête du traitement n'ayant jamais été exécutée, la première requête n'aurait jamais été validée.

Comment se déroule une transaction ?

Voici un schéma qui devrait vous éclairer sur le principe des transactions.

Schéma d'une transaction
Support des transactions

Il n'est pas possible d'utiliser les transactions sur n'importe quelle table. Pour les supporter, une table doit être transactionnelle, ce qui, avec MySQL, est défini par le moteur de stockage utilisé pour la table.
Rappelez-vous, nous avons vu dans le chapitre sur la création des tables qu'il existait différents moteurs de stockage possibles avec MySQL, dont les plus connus sont MyISAM et InnoDB.
MyISAM ne supportant pas les contraintes de clés étrangères, nos tables ont été créées avec le moteur InnoDB, ce qui tombe plutôt bien pour la suite de ce chapitre. En effet :

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Transactions Syntaxe et utilisation

Syntaxe et utilisation

Principe Validation implicite et commandes non-annulables

Syntaxe et utilisation

Vocabulaire

Lorsque l'on valide les requêtes d'une transaction, on dit aussi que l'on commite les changements. À l'inverse, l'annulation des requêtes s'appelle un rollback.

Comportement par défaut

Vous l'aurez compris, par défaut MySQL ne travaille pas avec les transactions. Chaque requête effectuée est directement commitée (validée). On ne peut pas revenir en arrière. On peut donc en fait considérer que chaque requête constitue une transaction, qui est automatiquement commitée. Par défaut, MySQL est donc en mode "autocommit".

Pour quitter ce mode, il suffit de lancer la requête suivante :

SET autocommit=0;

Une fois que vous n'êtes plus en autocommit, chaque modification de donnée devra être commitée pour prendre effet. Tant que vos modifications ne sont pas validées, vous pouvez à tout moment les annuler (faire un rollback).

Valider/annuler les changements

Les commandes pour commiter et faire un rollback sont relativement faciles à retenir :

COMMIT;   -- pour valider les requêtes
ROLLBACK; -- pour annuler les requêtes
Exemples de transactions en mode non-autocommit

Si ce n'est pas déjà fait, changez le mode par défaut de MySQL grâce à la commande que nous venons de voir.

Première expérience : annulation des requêtes.

Exécutez ces quelques requêtes :

INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Baba', 5, '2012-02-13 15:45:00', 'F'); 
INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Bibo', 5, '2012-02-13 15:48:00', 'M');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Buba', 5, '2012-02-13 18:32:00', 'F'); -- Insertion de 3 rats bruns

UPDATE Espece
SET prix = 20
WHERE id = 5;  -- Les rats bruns coûtent maintenant 20 euros au lieu de 10

Faites maintenant un SELECT sur les tables Espece et Animal.

SELECT * 
FROM Animal
WHERE espece_id = 5;

SELECT * 
FROM Espece 
WHERE id = 5;

Les changements faits sont bien visibles. Les rats bruns valent maintenant 20 euros, et nos trois nouvelles bestioles ont bien été insérées. Cependant, un simple rollback va annuler ces changements.

ROLLBACK;

Nos rats coûtent à nouveau 10 euros et Baba, Bibo et Buba ont disparu.

Deuxième expérience : Interruption de la transaction.

Exécutez à nouveau les trois requêtes INSERT et la requête UPDATE.
Ensuite, quittez votre client MySQL (fermez simplement la fenêtre, ou tapez quit ou exit).

Reconnectez-vous et vérifiez vos données : les rats valent 10 euros, et Baba, Bibo et Buba n'existent pas. Les changements n'ont pas été commités, c'est comme s'il ne s'était rien passé !

Troisième expérience : validation et annulation.

Exécutez la séquence de requêtes suivante :

INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Baba', 5, '2012-02-13 15:45:00', 'F'); 
INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Bibo', 5, '2012-02-13 15:48:00', 'M');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Buba', 5, '2012-02-13 18:32:00', 'F'); -- Insertion de 3 rats bruns

COMMIT;

UPDATE Espece
SET prix = 20
WHERE id = 5;  -- Les rats valent 20 euros

ROLLBACK;

Si vous n'avez pas oublié de réactiver le mode non-autocommit, vous avez maintenant trois nouveaux rats bruns (les requêtes d'insertion ayant été validées), et ils ne valent toujours que 10 euros chacun (la modification de l'espèce ayant été annulée).

Quatrième expérience : visibilité des changements non-commités.

Exécutez la requête suivante :

UPDATE Animal 
SET commentaires = 'Queue coupée'
WHERE nom = 'Bibo' AND espece_id = 5;

Ensuite, tout en laissant ce client MySQL ouvert, ouvrez-en un deuxième. Connectez-vous comme d'habitude à la base de données elevage. Vous avez maintenant deux sessions ouvertes, connectées à votre base de données.
Sélectionnez les rats bruns.

SELECT id, sexe, nom, commentaires, espece_id, race_id 
FROM Animal
WHERE espece_id = 5;

id

sexe

nom

commentaires

espece_id

race_id

69

F

Baba

NULL

5

NULL

70

M

Bibo

NULL

5

NULL

71

F

Buba

NULL

5

NULL

Les commentaires de Bibo sont toujours vides. Les changements non-commités ne sont donc pas visibles à l'extérieur de la transaction qui les a faits. En particulier, une autre session n'a pas accès à ces changements.

Annulez la modification de Bibo dans la première session avec un ROLLBACK. Vous pouvez fermer la seconde session.

Démarrer explicitement une transaction

En désactivant le mode autocommit, en réalité, on démarre une transaction. Et chaque fois que l'on fait un rollback ou un commit (ce qui met fin à la transaction), une nouvelle transaction est créée automatiquement, et ce tant que la session est ouverte.

Il est également possible de démarrer explicitement une transaction, auquel cas on peut laisser le mode autocommit activé, et décider au cas par cas des requêtes qui doivent être faites dans une transaction.

Repassons donc en mode autocommit :

SET autocommit=1;

Pour démarrer une transaction, il suffit de lancer la commande suivante :

START TRANSACTION;

Une fois la transaction ouverte, les requêtes devront être validées pour prendre effet. Attention au fait qu'un COMMIT ou un ROLLBACK met fin automatiquement à la transaction, donc les commandes suivantes seront à nouveau commitées automatiquement si une nouvelle transaction n'est pas ouverte.

Exemples de transactions en mode autocommit
-- Insertion d'un nouveau rat brun, plus vieux
INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Momy', 5, '2008-02-01 02:25:00', 'F');

-- Ouverture d'une transaction
START TRANSACTION;

-- La nouvelle rate est la mère de Buba et Baba
UPDATE Animal 
SET mere_id = LAST_INSERT_ID()
WHERE espece_id = 5
AND nom IN ('Baba', 'Buba');

-- On annule les requêtes de la transaction, ce qui termine celle-ci
ROLLBACK;

-- La nouvelle rate est la mère de Bibo
UPDATE Animal 
SET mere_id = LAST_INSERT_ID()
WHERE espece_id = 5
AND nom = 'Bibo';

-- Nouvelle transaction
START TRANSACTION;

-- Suppression de Buba
DELETE FROM Animal 
WHERE espece_id = 5
AND nom = 'Buba';

-- On valide les requêtes de la transaction, ce qui termine celle-ci
COMMIT;

Si vous avez bien suivi, vous devriez savoir les changements qui ont été faits.

SELECT id, nom, espece_id, mere_id 
FROM Animal
WHERE espece_id = 5;

id

nom

espece_id

mere_id

69

Baba

5

NULL

70

Bibo

5

72

72

Momy

5

NULL

Jalon de transaction

Lorsque l'on travaille dans une transaction, et que l'on constate que certaines requêtes posent problème, on n'a pas toujours envie de faire un rollback depuis le début de la transaction, annulant toutes les requêtes alors qu'une partie aurait pu être validée.
Il n'est pas possible de démarrer une transaction à l'intérieur d'une transaction. Par contre, on peut poser des jalons de transaction. Il s'agit de points de repère, qui permettent d'annuler toutes les requêtes exécutées depuis ce jalon, et non toutes les requêtes de la transaction.

Syntaxe

Trois nouvelles commandes suffisent pour pouvoir utiliser pleinement les jalons :

SAVEPOINT nom_jalon; -- Crée un jalon avec comme nom "nom_jalon"

ROLLBACK [WORK] TO [SAVEPOINT] nom_jalon; -- Annule les requêtes exécutées depuis le jalon "nom_jalon", WORK et SAVEPOINT ne sont pas obligatoires

RELEASE SAVEPOINT nom_jalon; -- Retire le jalon "nom_jalon" (sans annuler, ni valider les requêtes faites depuis)

Exemple : exécutez les requêtes suivantes.

START TRANSACTION;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Popi', 5, '2007-03-11 12:45:00', 'M');

SAVEPOINT jalon1;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Momo', 5, '2007-03-12 05:23:00', 'M');

ROLLBACK TO SAVEPOINT jalon1;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe) 
VALUES ('Mimi', 5, '2007-03-12 22:03:00', 'F');

COMMIT;

On n'utilise qu'une seule transaction, on valide à la fin, et pourtant la seconde insertion n'a pas été faite au final, puisqu'elle a été annulée grâce au jalon. Seuls Popi et Mimi existent.

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id
FROM Animal
WHERE espece_id = 5;

id

sexe

date_naissance

nom

espece_id

mere_id

pere_id

69

F

2012-02-13 15:45:00

Baba

5

NULL

NULL

70

M

2012-02-13 15:48:00

Bibo

5

72

NULL

72

F

2008-02-01 02:25:00

Momy

5

NULL

NULL

73

M

2007-03-11 12:45:00

Popi

5

NULL

NULL

75

F

2007-03-12 22:03:00

Mimi

5

NULL

NULL

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Principe Validation implicite et commandes non-annulables

Validation implicite et commandes non-annulables

Syntaxe et utilisation ACID

Validation implicite et commandes non-annulables

Vous savez déjà que pour terminer une transaction, il faut utiliser les commandes COMMIT ou ROLLBACK, selon que l'on veut valider les requêtes ou les annuler.

Ça, c'est la manière classique et recommandée. Mais il faut savoir qu'un certain nombre d'autres commandes auront aussi pour effet de clôturer une transaction. Et pas seulement la clôturer, mais également valider toutes les requêtes qui ont été faites dans cette transaction. Exactement comme si vous utilisiez COMMIT.

Par ailleurs, ces commandes ne peuvent pas être annulées par un ROLLBACK.

Commandes DDL

Toutes les commandes qui créent, modifient, suppriment des objets dans la base de données valident implicitement les transactions.

Cela comprend donc :

De manière générale, tout ce qui influe sur la structure de la base de données, et non sur les données elles-mêmes.

Utilisateurs

La création, la modification et la suppression d'utilisateurs (voir partie 7) provoquent aussi une validation implicite.

Transactions et verrous

Je vous ai signalé qu'il n'était pas possible d'imbriquer des transactions, donc d'avoir une transaction à l'intérieur d'une transaction. En fait, la commande START TRANSACTION provoque également une validation implicite si elle est exécutée à l'intérieur d'une transaction.
Le fait d'activer le mode autocommit (s'il n'était pas déjà activé) a le même effet.

La création et suppression de verrous de table clôturent aussi une transaction en la validant implicitement (voir chapitre suivant).

Chargements de données

Enfin, le chargement de données avec LOAD DATA provoque également une validation implicite.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Syntaxe et utilisation ACID

ACID

Validation implicite et commandes non-annulables Verrous

ACID

Derrière ce titre mystérieux se cache un concept très important !

Quels sont les critères qu'un système utilisant les transactions doit respecter pour être fiable ?

Il a été défini que ces critères sont au nombre de quatre : Atomicité, Cohérence, Isolation et Durabilité. Soit, si on prend la première lettre de chaque critère : ACID.
Voyons donc en détail ces quatre critères.

A pour Atomicité

Atome signifie étymologiquement "qui ne peut être divisé".
Une transaction doit être atomique, c'est-à-dire qu'elle doit former une entité complète et indivisible. Chaque élément de la transaction, chaque requête effectuée, ne peut exister que dans la transaction.

Si l'on reprend l'exemple du virement bancaire, en utilisant les transactions, les deux étapes (débit du compte donneur d'ordre, crédit du compte bénéficiaire) ne peuvent exister indépendamment l'une de l'autre. Si l'une est exécutée, l'autre doit l'être également. Il s'agit d'un tout.

Peut-on dire que nos transactions sont atomiques ?

Oui. Si une transaction en cours est interrompue, aucune des requêtes exécutées ne sera validée. De même, en cas d'erreur, il suffit de faire un ROLLBACK pour annuler toute la transaction. Et si tout se passe bien, un COMMIT validera l'intégralité de la transaction en une fois.

C pour cohérence

Les données doivent rester cohérentes dans tous les cas : que la transaction se termine sans encombre, qu'une erreur survienne, ou que la transaction soit interrompue. Un virement dont seule l'étape de débit du donneur d'ordre est exécutée produit des données incohérentes (la disparition de 300 euros jamais arrivés chez le bénéficiaire). Avec une transaction, cette incohérence n'apparaît jamais. Tant que la totalité des étapes n'a pas été réalisée avec succès, les données restent dans leur état initial.

Nos transactions permettent-elles d'assurer la cohérence des données ?

Oui, les changements de données ne sont validés qu'une fois que toutes les étapes ont été exécutées. De l'extérieur de la transaction, le moment entre les deux étapes d'un virement n'est jamais visible.

I pour Isolation

Chaque transaction doit être isolée, donc ne pas interagir avec une autre transaction.

Nos transactions sont-elles isolées ?

Test

Dans votre client MySQL, exécutez les requêtes suivantes (ne commitez pas) pour modifier le pere_id du rat Bibo :

START TRANSACTION; -- On ouvre une transaction

UPDATE Animal     -- On modifie Bibo
SET pere_id = 73
WHERE espece_id = 5 AND nom = 'Bibo';

SELECT id, nom, commentaires, pere_id, mere_id
FROM Animal
WHERE espece_id = 5;

À nouveau, ouvrez une deuxième session, tout en laissant la première ouverte (démarrez un deuxième client SQL et connectez-vous à votre base de données).
Exécutez les requêtes suivantes, pour modifier les commentaires de Bibo.

START TRANSACTION;   -- On ouvre une transaction

SELECT id, nom, commentaires, pere_id, mere_id
FROM Animal
WHERE espece_id = 5;

UPDATE Animal       -- On modifie la perruche Bibo
SET commentaires = 'Agressif'
WHERE espece_id = 5 AND nom = 'Bibo';

SELECT id, nom, commentaires, pere_id, mere_id
FROM Animal
WHERE espece_id = 5;

Le résultat n'est pas du tout le même dans les deux sessions. En effet, dans la première, on a la confirmation que la requête UPDATE a été effectuée :

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Et le SELECT renvoie bien les données modifiées (pere_id n'est plus NULL pour Bibo) :

id

nom

commentaires

pere_id

mere_id

69

Baba

NULL

NULL

NULL

70

Bibo

NULL

73

72

72

Momy

NULL

NULL

NULL

73

Popi

NULL

NULL

NULL

75

Mimi

NULL

NULL

NULL

Par contre, dans la deuxième session, on a d'abord fait un SELECT, et Bibo n'a toujours pas de père (puisque ça n'a pas été commité dans la première session). Donc on s'attendrait à ce que la requête UPDATE laisse pere_id à NULL et modifie commentaires.

id

nom

commentaires

pere_id

mere_id

69

Baba

NULL

NULL

NULL

70

Bibo

NULL

NULL

72

72

Momy

NULL

NULL

NULL

73

Popi

NULL

NULL

NULL

75

Mimi

NULL

NULL

NULL

Seulement voilà, la requête UPDATE ne fait rien ! La session semble bloquée : pas de message de confirmation après la requête UPDATE, et le second SELECT n'a pas été effectué.

mysql>
mysql> UPDATE Animal       -- On modifie Bibo
    -> SET commentaires = 'Agressif'
    -> WHERE espece_id = 5 AND nom = 'Bibo';
_

Commitez maintenant les changements dans la première session (celle qui n'est pas bloquée). Retournez voir dans la seconde session : elle s'est débloquée et indique maintenant un message de confirmation aussi :

Query OK, 1 row affected (5.17 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Qui plus est, le SELECT a été exécuté (vous devrez peut-être appuyer sur Entrée pour que ce soit envoyé au serveur) et les modifications ayant été faites par la session 1 ont été prises en compte : commentaires vaut 'Agressif' et pere_id vaut 73 !

id

nom

commentaires

pere_id

mere_id

69

Baba

NULL

NULL

NULL

70

Bibo

Agressif

73

72

72

Momy

NULL

NULL

NULL

73

Popi

NULL

NULL

NULL

75

Mimi

NULL

NULL

NULL

Il n'y a plus qu'à commiter les changements faits par la deuxième session, et c'est terminé ! Si vous ne commitez pas, commentaires restera NULL. Par contre, pere_id vaudra toujours 73 puisque ce changement-là a été commité par la première session.

Conclusion

La deuxième session n'a pas interagi avec les changements faits par la première session, chaque transaction est bien isolée.

Et la première session qui bloque la seconde, ce n'est pas une interaction ça ?

Pas dans le cadre des critères ACID. Oui, la première session provoque un retard dans l'exécution des requêtes de la deuxième session, mais les critères de fiabilité que nous examinons ici concernent les données impactées par les transactions, et non le déroulement de celles-ci (qui importe peu finalement).
Ce blocage a pour effet d'empêcher la deuxième session d'écraser un changement fait par la première. Donc, ce blocage a bien pour effet l'isolation des transactions.

Verrous

Le blocage de la deuxième session vient en fait de ce que la première session, en faisant sa requête UPDATE, a automatiquement posé un verrou sur la ligne contenant Bobi le rat, empêchant toute modification tant que la transaction était en cours. Les verrous faisant l'objet du prochain chapitre, je n'en dis pas plus pour l'instant.

Utilité

Je vous l'accorde, vous n'allez pas vous amuser tous les jours à ouvrir deux sessions MySQL. Par contre, pour une application pouvant être utilisée par plusieurs personnes en même temps (qui toutes travaillent sur la même base de données), il est impératif que ce critère soit respecté.
Prenons l'exemple simple d'un jeu par navigateur : de nombreux joueurs peuvent être connectés en même temps, et effectuer des opérations différentes. Si les transactions ne sont pas isolées, une partie des actions des joueurs risquerait de se voir annulées. On isole donc les transactions grâce aux verrous (qui sont ici automatiquement posés mais ce n'est pas toujours le cas).

D pour Durabilité

Une fois la transaction terminée, les données résultant de cette transaction doivent être stockées de manière durable, et pouvoir être récupérées, en cas de crash du serveur par exemple.

Nos transactions modifient-elles les données de manière durable ?

Oui, une fois les changements commités, ils sont stockés définitivement (jusqu'à modification par une nouvelle transaction).

En résumé
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Validation implicite et commandes non-annulables Verrous

Verrous

ACID Principe

Complément indispensable des transactions, les verrous permettent de sécuriser les requêtes en bloquant ponctuellement et partiellement l'accès aux données.

Il s'agit d'un gros chapitre, avec beaucoup d'informations. Il y a par conséquent un maximum d'exemples pour vous aider à comprendre le comportement des verrous selon les situations.

Au sommaire de ce chapitre :

Principe

Verrous Syntaxe et utilisation : verrous de table

Principe

Lorsqu'une session MySQL pose un verrou sur un élément de la base de données, cela veut dire qu'il restreint, voire interdit, l'accès à cet élément aux autres sessions MySQL qui voudraient y accéder.

Verrous de table et verrous de ligne

Il est possible de poser un verrou sur une table entière, ou seulement sur une ou plusieurs lignes d'une table. Étant donné qu'un verrou empêche l'accès d'autres sessions, il est en général plus intéressant de poser un verrou sur la plus petite partie de la base possible.

Par exemple, si l'on travaille avec les chiens de la table Animal.

Cette notion d'accès simultané aux données par plusieurs sessions différentes s'appelle la concurrence. Plus la concurrence est possible, donc plus le nombre de sessions pouvant accéder aux données simultanément est grand, mieux c'est.
En effet, prenons l'exemple d'un site web. En général, on préfère permettre à plusieurs utilisateurs de surfer en même temps, sans devoir attendre entre chaque action de pouvoir accéder aux informations chacun à son tour. Or, chaque utilisateur crée une session chaque fois qu'il se connecte à la base de données (pour lire les informations ou les modifier).
Préférez donc (autant que possible) les verrous de ligne aux verrous de table !

Avertissements

Les informations données dans ce chapitre concernent exclusivement MySQL, et en particulier les tables utilisant les moteurs MyISAM ou InnoDB (selon le type de verrou utilisé). En effet, les verrous sont implémentés différemment selon les SGDB, et même selon le moteur de table en ce qui concerne MySQL. Si le principe général reste toujours le même, certains comportements et certaines options peuvent différer d'une implémentation à l'autre. N'hésitez pas à vous renseigner plus avant.

Par ailleurs, je vous présente ici les principes généraux et les principales options, mais il faut savoir qu'il y a énormément à dire sur les verrous, et que j'ai donc dû faire un sérieux tri des informations avant de rédiger ce chapitre. À nouveau, en cas de doute, ou si vous avez besoin d'informations précises, je vous conseille vraiment de consulter la documentation officielle (si possible en anglais, car elle est infiniment plus complète qu'en français).

Enfin, dernier avertissement : de nombreux changements dans l'implémentation des verrous sont advenus lors du développement des dernières versions de MySQL. Aussi, la différence entre les verrous dans la version 5.0 et la version 5.5 est assez importante. Tout ce que je présente dans ce chapitre concerne la version 5.5. Vérifiez bien votre version, et si vous consultez la documentation officielle, prenez bien celle qui concerne votre propre version.

Modification de notre base de données

Nous allons ajouter deux tables à notre base de données, afin d'illustrer au mieux l'intérêt et l'utilisation des verrous : une table Client, qui contiendra les coordonnées des clients de notre élevage, et une table Adoption, qui contiendra les renseignements concernant les adoptions faites par nos clients.
Dorénavant, certains animaux présents dans notre table Animal ne seront plus disponibles, car ils auront été adoptés. Nous les garderons cependant dans notre base de données. Avant toute adoption, il nous faudra donc vérifier la disponibilité de l'animal.

Voici les requêtes à effectuer pour faire ces changements.

-- Table Client
CREATE TABLE Client (
	id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL,
	nom VARCHAR(100) NOT NULL,
	prenom VARCHAR(60) NOT NULL,
	adresse VARCHAR(200),
	code_postal VARCHAR(6),
	ville VARCHAR(60),
	pays VARCHAR(60),
	email VARBINARY(100),
	PRIMARY KEY (id),
	UNIQUE INDEX ind_uni_email (email)
) ENGINE = InnoDB;

-- Table Adoption
CREATE TABLE Adoption (
	client_id SMALLINT UNSIGNED NOT NULL,
	animal_id SMALLINT UNSIGNED NOT NULL,
	date_reservation DATE NOT NULL,
	date_adoption DATE,
        prix DECIMAL(7,2) UNSIGNED NOT NULL,
	paye TINYINT(1) NOT NULL DEFAULT 0,
	PRIMARY KEY (client_id, animal_id),
	CONSTRAINT fk_client_id FOREIGN KEY (client_id) REFERENCES Client(id),
	CONSTRAINT fk_adoption_animal_id FOREIGN KEY (animal_id) REFERENCES Animal(id),
	UNIQUE INDEX ind_uni_animal_id (animal_id)
) ENGINE = InnoDB;

-- Insertion de quelques clients
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Jean', 'Dupont', 'Rue du Centre, 5', '45810', 'Houtsiplou', 'France', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Marie', 'Boudur', 'Place de la Gare, 2', '35840', 'Troudumonde', 'France', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Fleur', 'Trachon', 'Rue haute, 54b', '3250', 'Belville', 'Belgique', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Julien', 'Van Piperseel',  NULL, NULL, NULL, NULL, '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Johan', 'Nouvel',  NULL, NULL, NULL, NULL, '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Frank', 'Germain',  NULL, NULL, NULL, NULL, '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Maximilien', 'Antoine', 'Rue Moineau, 123', '4580', 'Trocoul', 'Belgique', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Hector', 'Di Paolo', NULL, NULL, NULL, NULL, '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Anaelle', 'Corduro',  NULL, NULL, NULL, NULL, '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Eline', 'Faluche', 'Avenue circulaire, 7', '45870', 'Garduche', 'France', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Carine', 'Penni', 'Boulevard Haussman, 85', '1514', 'Plasse', 'Suisse', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Virginie', 'Broussaille', 'Rue du Fleuve, 18', '45810', 'Houtsiplou', 'France', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Hannah', 'Durant', 'Rue des Pendus, 66', '1514', 'Plasse', 'Suisse', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Elodie', 'Delfour', 'Rue de Flore, 1', '3250', 'Belville', 'Belgique', '[email protected]');
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Joel', 'Kestau', NULL, NULL, NULL, NULL, '[email protected]');

-- Insertion de quelques adoptions
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (1, 39, '2008-08-17', '2008-08-17', 735.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (1, 40, '2008-08-17', '2008-08-17', 735.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (2, 18, '2008-06-04', '2008-06-04', 485.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (3, 27, '2009-11-17', '2009-11-17', 200.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (4, 26, '2007-02-21', '2007-02-21', 485.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (4, 41, '2007-02-21', '2007-02-21', 835.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (5, 21, '2009-03-08', '2009-03-08', 200.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (6, 16, '2010-01-27', '2010-01-27', 200.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (7, 5, '2011-04-05', '2011-04-05', 150.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (8, 42, '2008-08-16', '2008-08-16', 735.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (9, 55, '2011-02-13', '2011-02-13', 140.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (9, 54, '2011-02-13', '2011-02-13', 140.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (10, 49, '2010-08-17', '2010-08-17', 140.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (11, 62, '2011-03-01', '2011-03-01', 630.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (12, 69, '2007-09-20', '2007-09-20', 10.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (13, 57, '2012-01-10', '2012-01-10', 700.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (14, 58, '2012-02-25', '2012-02-25', 700.00,  1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (15, 30, '2008-08-17', '2008-08-17', 735.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (11, 32, '2008-08-17', '2010-03-09', 140.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (9, 33, '2007-02-11', '2007-02-11', 835.00, 1);
INSERT INTO Adoption (client_id, animal_id, date_reservation, date_adoption, prix, paye) VALUES (2, 3, '2011-03-12', '2011-03-12', 835.00, 1);

La table Adoption ne contient pas de colonne id auto-incrémentée. Par contre, on a bien défini une clé primaire, mais une clé primaire composite (sur plusieurs colonnes). En effet, une adoption est définie par un client adoptant un animal. Il n'est pas nécessaire d'ajouter une colonne supplémentaire pour définir individuellement chaque ligne ; le couple (client_id, animal_id) fait très bien l'affaire (il est composé de deux SMALLINT, donc les recherches sur cette clé seront rapides).
Notez que nous définissons également un index UNIQUE sur la colonne animal_id. Par conséquent, on aurait même pu définir directement animal_id comme étant la clé primaire. Je trouvais cependant plus logique d'inclure le client dans la définition d'une adoption. C'est un choix plutôt arbitraire, qui a surtout comme avantage de vous montrer un exemple de clé composite.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Verrous Syntaxe et utilisation : verrous de table

Syntaxe et utilisation : verrous de table

Principe Syntaxe et utilisation : verrous de ligne

Syntaxe et utilisation : verrous de table

Les verrous de table sont les seuls supportés par MyISAM. Ils sont d'ailleurs principalement utilisés pour pallier en partie l'absence de transactions dans MyISAM.
Les tables InnoDB peuvent également utiliser ce type de verrou.

Pour verrouiller une table, il faut utiliser la commande LOCK TABLES :

LOCK TABLES nom_table [AS alias_table] [READ | WRITE] [, ...];

Pour déverrouiller les tables, on utilise UNLOCK TABLES. Cela déverrouille toutes les tables verrouillées. Il n'est pas possible de préciser les tables à déverrouiller. Tous les verrous de table d'une session sont relâchés en même temps.

Session ayant obtenu le verrou

Lorsqu'une session acquiert un ou plusieurs verrous de table, cela a plusieurs conséquences pour cette session :

Exemples : on pose deux verrous, l'un READ, l'autre WRITE, l'un en donnant un alias au nom de la table, l'autre sans.

LOCK TABLES Espece READ,              -- On pose un verrou de lecture sur Espece
            Adoption AS adopt WRITE;  -- et un verrou d'écriture sur Adoption avec l'alias adopt

Voyons maintenant le résultat de ces différentes requêtes.

1. Sélection dans Espece, sans alias.

SELECT id, nom_courant FROM Espece;

id

nom_courant

1

Chien

2

Chat

3

Tortue d'Hermann

4

Perroquet amazone

5

Rat brun

Pas de problème, on a bien un verrou sur Espece, sans alias.

2. Sélection dans Espece, avec alias.

SELECT id, nom_courant 
FROM Espece AS table_espece;
ERROR 1100 (HY000): Table 'table_espece' was not locked with LOCK TABLES

Par contre, si l'on essaye d'utiliser un alias, cela ne fonctionne pas. Le verrou est posé sur Espece, pas sur EspeceAStable_espece.

3. Modification dans Espece, sans alias.

UPDATE Espece
SET description = 'Petit piaf bruyant' 
WHERE id = 4;
ERROR 1099 (HY000): Table 'Espece' was locked with a READ lock and can't be updated

Avec ou sans alias, impossible de modifier la table Espece, puisque le verrou que l'on possède dessus est un verrou de lecture.

4. Sélection dans Adoption, sans alias.

SELECT client_id, animal_id 
FROM Adoption;
ERROR 1100 (HY000): Table 'Adoption' was not locked with LOCK TABLES

Cette fois, c'est le contraire, sans alias, ça ne passe pas.

5. Sélection dans Adoption, avec alias.

SELECT client_id, animal_id 
FROM Adoption AS adopt
WHERE client_id = 4;

client_id

animal_id

4

26

4

41

6. Modification dans Adoption, sans alias.

UPDATE Adoption
SET paye = 0 
WHERE client_id = 10 AND animal_id = 49;
ERROR 1100 (HY000): Table 'Adoption' was not locked with LOCK TABLES

Idem pour la modification, l'alias est indispensable.

7. Modification dans Adoption, avec alias.

UPDATE Adoption AS adopt
SET paye = 0
WHERE client_id = 10 AND animal_id = 49;
Query OK, 1 row affected (0.03 sec)

Il faut donc penser à acquérir tous les verrous nécessaires aux requêtes à exécuter. De plus, il faut les obtenir en une seule requêteLOCK TABLES. En effet, LOCK TABLES commence par enlever tous les verrous de table de la session avant d'en acquérir de nouveaux.
Il est bien entendu possible de poser plusieurs verrous sur la même table en une seule requête afin de verrouiller son nom ainsi qu'un ou plusieurs alias.

Exemples : on pose un verrou de lecture sur Adoption, puis avec une seconde requête, on pose deux verrous de lecture sur la table Espece, l'un avec alias, l'autre sans.

UNLOCK TABLES; -- On relâche d'abord les deux verrous précédents

LOCK TABLES Adoption READ;
LOCK TABLES Espece READ, Espece AS table_espece READ;

Une fois ces deux requêtes effectuées, nous aurons donc bien deux verrous de lecture sur la table Espece : un avec son nom, l'autre avec un alias. Par contre, le verrou sur Adoption n'existera plus puisqu'il aura été relâché par l'exécution de la seconde requête LOCK TABLES.

1. Sélection dans Espece, sans alias.

SELECT id, nom_courant FROM Espece;

id

nom_courant

1

Chien

2

Chat

3

Tortue d'Hermann

4

Perroquet amazone

5

Rat brun

2. Sélection dans Espece, avec alias.

SELECT id, nom_courant FROM Espece AS table_espece;

id

nom_courant

1

Chien

2

Chat

3

Tortue d'Hermann

4

Perroquet amazone

5

Rat brun

Avec ou sans alias, on peut sélectionner les données de la table Espece, puisque l'on a un verrou sur Espece et sur EspeceAStable_espece.

3. Sélection dans Espece, avec mauvais alias.

SELECT id, nom_courant FROM Espece AS table_esp;
ERROR 1100 (HY000): Table 'table_esp' was not locked with LOCK TABLES

Bien entendu, cela ne fonctionne que pour l'alias que l'on a donné lors du verrouillage.

4. Sélection dans Adoption, sans alias.

SELECT * FROM Adoption;
ERROR 1100 (HY000): Table 'Adoption' was not locked with LOCK TABLES

Le verrou sur Adoption a été relâché lorsque l'on a posé les verrous sur Espece. On ne peut donc pas lire les données d'Adoption (avec ou sans alias).

Conséquences pour les autres sessions

Si une session a obtenu un verrou de lecture sur une table, les autres sessions :

Si par contre une session a obtenu un verrou d'écriture, les autres sessions ne peuvent absolument pas accéder à cette table tant que ce verrou existe.

Exemples : ouvrez un deuxième client MySQL et connectez-vous à votre base de données, afin d'avoir deux sessions ouvertes.

1. Sélection sur des tables verrouillées à partir d'une autre session.

Session 1 :

LOCK TABLES Client READ,        -- Verrou de lecture sur Client 
            Adoption WRITE;     -- Verrou d'écriture sur Adoption

Session 2 :

SELECT id, nom, prenom, ville, email 
FROM Client 
WHERE ville = 'Houtsiplou';

id

nom

prenom

ville

email

1

Dupont

Jean

Houtsiplou

[email protected]

12

Broussaille

Virginie

Houtsiplou

[email protected]

La sélection sur Client se fait sans problème.

Session 2 :

SELECT * 
FROM Adoption 
WHERE client_id = 4;

Par contre, la sélection sur Adoption ne passe pas. La session se bloque, jusqu'à ce que la session 1 déverrouille les tables avec UNLOCK TABLES.

2. Modification sur des tables verrouillées à partir d'une autre session.

Reverrouillez les tables avec la session 1 :

LOCK TABLES Client READ,       -- Verrou de lecture sur Client 
            Adoption WRITE;    -- Verrou d'écriture sur Adoption

Session 2 :

UPDATE Client
SET pays = 'Suisse'
WHERE id = 5;

La modification sur Client, contrairement à la sélection, est bloquée jusqu'au déverrouillage. Déverrouillez puis verrouillez à nouveau avec la session 1.

Session 2:

UPDATE Adoption
SET paye = 1
WHERE client_id = 3;

Bien entendu, la modification sur la table Adoption attend également que les verrous soient relâchés par la session 1.

En ce qui concerne la pose de verrous de table par les autres sessions, faites vos propres tests, mais simplement : si une session peut lire les données d'une table, elle peut également poser un verrou de lecture. Si une session peut modifier les données d'une table, elle peut également poser un verrou d'écriture.

Interaction avec les transactions

Si l'on utilise des tables MyISAM, il n'y a évidemment aucune précaution particulière à prendre par rapport aux transactions lorsqu'on utilise des verrous de table (les tables MyISAM étant non-transactionnelles). Par contre, si on utilise des tables InnoDB, il convient d'être prudent. En effet :

Pour utiliser à la fois les transactions et les verrous de table, il faut renoncer à démarrer explicitement les transactions, et donc utiliser le mode non-autocommit. Lorsque l'on est dans ce mode, il est facile de contourner la validation implicite provoquée par LOCK TABLES et UNLOCK TABLES : il suffit d'appeler LOCK TABLES avant toute modification de données, et de commiter/annuler les modifications avant d'exécuter UNLOCK TABLES.

Exemple :

SET autocommit = 0;
LOCK TABLES Adoption WRITE; -- La validation implicite ne commite rien puisque aucun changement n'a été fait

UPDATE Adoption SET date_adoption = NOW() WHERE client_id = 9 AND animal_id = 54;
SELECT client_id, animal_id, date_adoption FROM Adoption WHERE client_id = 9;

ROLLBACK;
UNLOCK TABLES; -- On a annulé les changements juste avant donc la validation implicite n'a aucune conséquence
SELECT client_id, animal_id, date_adoption FROM Adoption WHERE client_id = 9;
SET autocommit = 1;
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Principe Syntaxe et utilisation : verrous de ligne

Syntaxe et utilisation : verrous de ligne

Syntaxe et utilisation : verrous de table Niveaux d'isolation

Syntaxe et utilisation : verrous de ligne

Comme les verrous de table, les verrous de ligne peuvent être de deux types :

Requêtes de modification, insertion et suppression
Requêtes de sélection

Les requêtes de sélection, par défaut, ne posent pas de verrous. Il faut donc en poser explicitement au besoin.

Verrou partagé

Pour poser un verrou partagé, on utilise LOCK IN SHARE MODE à la fin de la requête SELECT.

SELECT * FROM Animal WHERE espece_id = 5 LOCK IN SHARE MODE;

Cette requête pose donc un verrou partagé sur les lignes de la table Animal pour lesquelles espece_id vaut 5.

Ce verrou signifie en fait, pour les autres sessions : "Je suis en train de lire ces données. Vous pouvez venir les lire aussi, mais pas les modifier tant que je n'ai pas terminé.".

Verrou exclusif

Pour poser un verrou exclusif, on utilise FOR UPDATE à la fin de la requête SELECT.

SELECT * FROM Animal WHERE espece_id = 5 FOR UPDATE;

Ce verrou signifie aux autres sessions : "Je suis en train de lire ces données dans le but probable de faire une modification. Ne les lisez pas avant que j'aie fini (et bien sûr, ne les modifiez pas).".

Transactions et fin d'un verrou de ligne

Les verrous de ligne ne sont donc pas posés par des commandes spécifiques, mais par des requêtes de sélection, insertion ou modification.
Ces verrous existent donc uniquement tant que la requête qui les a posés interagit avec les données.

Par conséquent, ce type de verrou s'utilise en conjonction avec les transactions. En effet, hors transaction, dès qu'une requête est lancée, elle est effectuée et les éventuelles modifications des données sont immédiatement validées.
Par contre, dans le cas d'une requête faite dans une transaction, les changements ne sont pas validés tant que la transaction n'a pas été commitée. Donc, à partir du moment où une requête a été exécutée dans une transaction, et jusqu'à la fin de la transaction (COMMIT ou ROLLBACK), la requête a potentiellement un effet sur les données.
C'est à ce moment-là (quand une requête a été exécutée mais pas validée ou annulée) qu'il est intéressant de verrouiller les données qui vont potentiellement être modifiées (ou supprimées) par la transaction.

Un verrou de ligne est donc lié à la transaction dans laquelle il est posé. Dès que l'on fait un COMMIT ou un ROLLBACK de la transaction, le verrou est levé.

Exemples
Verrou posé par une requête de modification

Session 1 :

START TRANSACTION;

UPDATE Client SET pays = 'Suisse'
WHERE id = 8;           -- un verrou exclusif sera posé sur la ligne avec id = 8

Session 2:

START TRANSACTION;

SELECT * FROM Client
WHERE id = 8;      -- pas de verrou

SELECT * FROM Client 
WHERE id = 8 
LOCK IN SHARE MODE; -- on essaye de poser un verrou partagé

La première session a donc posé un verrou exclusif automatiquement en faisant un UPDATE.

La seconde session fait d'abord une simple sélection, sans poser de verrou. Pas de problème, la requête passe.

Ah ? La requête passe ? Et c'est normal ? Et le verrou exclusif alors ?

Oui, c'est normal et c'est important de comprendre pourquoi.
En fait, lorsqu'une session démarre une transaction, elle prend en quelque sorte une photo des tables dans leur état actuel (les modifications non commitées n'étant pas visibles). La transaction va alors travailler sur la base de cette photo, tant qu'on ne lui demande pas d'aller vérifier que les données n'ont pas changé. Donc le SELECT ne voit pas les changements, et ne se heurte pas au verrou, puisque celui-ci est posé sur les lignes de la table, et non pas sur la photo de cette table que détient la session.

Et comment fait-on pour demander à la session d'actualiser sa photo ?

On lui demande de poser un verrou ! Lorsqu'une session pose un verrou sur une table, elle est obligée de travailler vraiment avec la table, et pas sur sa photo. Elle va donc aller chercher les dernières infos disponibles, et actualiser sa photo par la même occasion.
On le voit bien avec la seconde requête, qui tente de poser un verrou partagé (qui vise donc uniquement la lecture). Elle va d'abord chercher les lignes les plus à jour et tombe sur le verrou posé par la première session ; elle se retrouve alors bloquée jusqu'à ce que la première session ôte le verrou exclusif.

Session 1 :

COMMIT;

En committant les changements de la session 1, le verrou exclusif posé par la requête de modification est relâché. La session 2 est donc libre de poser à son tour un verrou partagé.

On peut également essayer la même manœuvre, avec cette fois-ci un UPDATE plutôt qu'un SELECT ... LOCK IN SHARE MODE (donc une requête qui va tenter de poser un verrou exclusif plutôt qu'un verrou partagé).

Session 1 :

START TRANSACTION;

UPDATE Adoption SET paye = 0
WHERE client_id = 11;

Session 2 :

START TRANSACTION;

UPDATE Adoption SET paye = 1
WHERE animal_id = 32; -- l'animal 32 a été adopté par le client 11

Comme prévu, la seconde session est bloquée, jusqu'à ce que la première session termine sa transaction. Validez la transaction de la première session, puis de la seconde. Le comportement sera le même si la deuxième session fait un DELETE sur les lignes verrouillées, ou un SELECT ... FOR UPDATE.

Verrou posé par une requête d'insertion

Session 1 :

START TRANSACTION;

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix)
VALUES (12, 75, NOW(), 10.00);

Session 2 :

SELECT * FROM Adoption
WHERE client_id > 13
LOCK IN SHARE MODE;

SELECT * FROM Adoption
WHERE client_id < 13
LOCK IN SHARE MODE;

La première session insère une adoption pour le client 12 et pose un verrou exclusif sur cette ligne.
La seconde session fait deux requêtes SELECT en posant un verrou partagé : l'une qui sélectionne les adoptions des clients avec un id supérieur à 13 ; l'autre qui sélectionne les adoptions des clients avec un id inférieur à 13.
Seule la seconde requête SELECT se heurte au verrou posé par la première session, puisqu'elle tente de récupérer notamment les adoptions du client 12, dont une est verrouillée.

Dès que la session 1 commite l'insertion, la sélection se fait dans la session 2.

Session 1 :

COMMIT;
Verrou posé par une requête de sélection

Voyons d'abord le comportement d'un verrou partagé, posé par SELECT ... LOCK IN SHARE MODE.

Session 1 :

START TRANSACTION;

SELECT * FROM Client
WHERE id < 5
LOCK IN SHARE MODE;

Session 2 :

START TRANSACTION;

SELECT * FROM Client 
WHERE id BETWEEN 3 AND 8;

SELECT * FROM Client
WHERE id BETWEEN 3 AND 8
LOCK IN SHARE MODE;

SELECT * FROM Client
WHERE id BETWEEN 3 AND 8
FOR UPDATE;

La première session pose un verrou partagé sur les clients 1, 2, 3 et 4.
La seconde session fait trois requêtes de sélection. Toutes les trois concernent les clients 3 à 8 (dont les deux premiers sont verrouillés).

Bien entendu, des requêtes UPDATE ou DELETE (posant des verrous exclusifs) faites par la deuxième session se verraient, elles aussi, bloquées.

Terminez les transactions des deux sessions (par un rollback ou un commit).

Quant aux requêtes SELECT ... FOR UPDATE posant un verrou exclusif, elles provoqueront exactement les mêmes effets qu'une requête UPDATE ou DELETE (après tout, un verrou exclusif, c'est un verrou exclusif).

Session 1 :

START TRANSACTION;

SELECT * FROM Client
WHERE id < 5
FOR UPDATE;

Session 2 :

START TRANSACTION;

SELECT * FROM Client 
WHERE id BETWEEN 3 AND 8;

SELECT * FROM Client
WHERE id BETWEEN 3 AND 8
LOCK IN SHARE MODE;

Cette fois-ci, même la requête SELECT ... LOCK IN SHARE MODE de la seconde session est bloquée (comme le serait une requête SELECT ... FOR UPDATE, ou une requête UPDATE, ou une requête DELETE).

En résumé
Rôle des index

Tentons une nouvelle expérience.

Session 1 :

START TRANSACTION;
UPDATE Animal 
SET commentaires = CONCAT_WS(' ', 'Animal fondateur.', commentaires) -- On ajoute une phrase de commentaire
WHERE date_naissance < '2007-01-01';                                 -- à tous les animaux nés avant 2007

Session 2 :

START TRANSACTION;
UPDATE Animal 
SET commentaires = 'Aveugle'                  -- On modifie les commentaires
WHERE date_naissance = '2008-03-10 13:40:00'; -- De l'animal né le 10 mars 2008 à 13h40

Dans la session 1, on fait un UPDATE sur les animaux nés avant 2007. On s'attend donc à pouvoir utiliser les animaux nés après dans une autre session, puisque InnoDB pose des verrous sur les lignes et pas sur toute la table.
Pourtant, la session 2 semble bloquée lorsque l'on fait un UPDATE sur un animal né en 2008.
Faites un rollback sur la session 1 ; ceci débloque la session 2. Annulez également la requête de cette session.

Ce comportement est donc en contradiction avec ce qu'on obtenait précédemment. Quelle est la différence ?

Le sous-titre vous a évidemment soufflé la réponse : la différence se trouve au niveau des index. Voyons donc ça !
Voici une commande qui va vous afficher les index présents sur la table Animal :

SHOW INDEX FROM Animal;

Table

Non_unique

Key_name

Column_name

Null

Animal

0

PRIMARY

id

Animal

0

ind_uni_nom_espece_id

nom

YES

Animal

0

ind_uni_nom_espece_id

espece_id

Animal

1

fk_race_id

race_id

YES

Animal

1

fk_espece_id

espece_id

Animal

1

fk_mere_id

mere_id

YES

Animal

1

fk_pere_id

pere_id

YES

Nous avons donc des index sur les colonnes suivantes : id, nom, mere_id, pere_id, espece_id et race_id.
Mais aucun index sur la colonne date_naissance.

Il semblerait donc que lorsque l'on pose un verrou, avec dans la clause WHERE de la requête une colonne indexée (espece_id), le verrou est bien posé uniquement sur les lignes pour lesquelles espece_id vaut la valeur recherchée.
Par contre, si dans la clause WHERE on utilise une colonne non-indexée (date_naissance), MySQL n'est pas capable de déterminer quelles lignes doivent être bloquées, donc on se retrouve avec toutes les lignes bloquées.

Pourquoi faut-il un index pour pouvoir poser un verrou efficacement ?

C'est très simple ! Vous savez que lorsqu'une colonne est indexée (que ce soit un index simple, unique, ou une clé primaire ou étrangère), MySQL stocke les valeurs de cette colonne en les triant. Du coup, lors d'une recherche sur l'index, pas besoin de parcourir toutes les lignes, il peut utiliser des algorithmes de re