Présentation du langage SQL
SQL est un acronyme qui signifie « Structured Query Language », c'est-à-dire Langage d'Interrogation
Structuré.
En réalité le SQL ne se limite pas aux
recherches de données dans une base mais constitue un langage
complet de gestion de bases de données relationnelles.
Il a
été conçu par IBM dans les années 70, sur
les bases de recherches du Docteur Ted Codd, un chercheur d'IBM
à San José. A l'origine il avait été nommé SEQUEL pour Structured English QUEry Language. Il est devenu le langage standard des
systèmes de gestion de bases de données (SGBD)
relationnelles (SGBDR).
Le SQL permet la définition, la manipulation et l'interrogation des données.
Le SQL englobe à la fois :
- un langage de manipulation des données (LMD)
- un langage d'interrogation de données (ordre SELECT)
- un langage de manipulation des données (ordres INSERT, UPDATE, DELETE) - un langage de définition des structures de données
(LDD)
- ordres CREATE, ALTER, DROP - un langage de contrôle de l'accès aux données
(LCD)
- ordres GRANT, REVOKE
C'est sans doute cette approche qui a rendu le SQL populaire auprès de populations qui n'a pas de formations techniques en informatique. Je pense à particulier aux utilisateurs des services MOA ou aux membres des domaines métier. Coder un ordre SQL d'interrogation d'une base de données ne nécessite pas de connaitre les instructions d'un langage informatique mais seulement de connaître la structure de la base.
Le langage SQL est aujourd'hui utilisé par les principaux SGBDR du marché : DB2, Oracle, Ingres, RDB, MySQL, Postgre, Microsoft SQL Server, etc. Chacun de ces SGBDR a cependant sa propre variante du langage. Cette page vous présente les bases communes au langage SQL. Pour les spécificités de chaque SGBD il vous faudra consulter la documentation propre au produit que vous utilisez.
Les normes du SQL
Le SQL a été normalisé dès 1986 mais les premières normes n'ont pas été suivies par les éditeurs de SGBD car elles étaient incomplètes. Il faut attendre 1992 pour qu'apparaisse la norme SQL-2. Elle a été accepté partout mais quelques grands SGBD ne la respectent pas entièrement. Cette norme est appelée bien souvent SQL-92.
Que définie la norme SQL-2 ?
Le SQL est découpé en trois niveaux :
- Full SQL (ensemble de la norme)
- Intermediate SQL
- Entry Level SQL (ensemble minimum à respecter pour être aux normes SQL-2)
Les objets manipulés
Le SQL utilise des identificateurs pour désigner les objets
qu'il manipule. Ces objets peuvent être des tables, des vues, des
colonnes, des indexes, des constantes, des variables, etc...
Pour nommer un objet, l'identificateur ne doit pas contenir de
caractères accentués et certains mots
réservés du langage sont interdits. Certains symboles
sont utilisables : #, $ et _. Les chiffres peuvent également
être utilisés et SQL n'est pas sensible à la casse.
C'est au concepteur de la base de données de fixer ses propres
normes de nommage pour que l'utilisation du système de base de
données soit la plus claire pour tout le monde. Par exemple on
peut choisir de toujours nommer les tables en commençant par
table_ ou par un T. Une vue pourra être identifiée par
"vue_nomdelavue" ou par "Vnomdelavue". Un index sera
désigné par I_ ou devra commencer par I. L'important est
de s'y retrouver et de savoir quel est le type d'objet que l'on
manipule. SQL n'impose rien à ce sujet.
Les tables
Dans tout SGBD relationnel, l'objet de base est la table.
Une table est composée de lignes et de colonnes. Une ligne et
l'ensemble de ses colonnes est appelée un « tuple ».
Le SQL permet d'interroger des tables sur différents filtres et
d'effectuer les jointures entres plusieurs tables.
Les colonnes
Les données contenues dans une colonne doivent être toutes
d'un même type de donnée. Ce type est indiqué au
moment de la création de la table qui contient la colonne. Le
type de donnée peut correspondre à un entier, une
chaîne de caractère, un booléen, etc.
Chaque colonne est repérée par un identificateur unique
à l'intérieur de chaque table. Il ne peut y avoir deux
colonnes qui portent le même nom pour une même table. Mais
deux colonnes de deux tables différentes peuvent tout à
fait avoir le même identifiant. Il est une pratique courante de
donner le même nom aux colonnes qui correspondent à des
clés étrangères que celui donné à la
clé primaire de la table principale. Une autre pratique est de
préfixer les noms de colonnes par un préfixe propre
à chaque table. Ainsi on repère facilement les colonnes
qui sont des clés étrangères car leur
préfixe sera celui de la table principale et non celui de la
table contenant la clé étrangère.
Cette pratique de nommer les colonnes avec un préfixe rappelant
la table permet d'éviter toute ambiguïté. En effet,
si vous avez une table "Tbl_DEPARTEMENT" qui contient une colonne
"VILLE" et une autre table "Tbl_CLIENT" qui contient elle aussi une
colonne "VILLE", il y a ambiguïté. Quand on utilise la
colonne "VILLE" on ne sait pas s'il s'agit de la colonne "VILLE" de la
table des départements ou bien de la ville d'un client. SQL
permet d'éviter la confusion en préfixant les colonnes du
nom de la table suivit d'un point.
Exemple :
Tbl_DEPARTEMENT.VILLEUne bonne pratique est d'appeler toutes les colonnes de la Tbl_DEPARTEMENT en préfixant les identifiants par un mnémonique et d'utiliser un autre mnémonique pour les colonnes de la table des clients.
Tbl_CLIENT.VILLE
Exemple :
T01VILLEDans cet exemple on suppose que la Tbl_DEPARTEMENT aura tous ses identifiants de colonne commençant par T01 et que la Tbl_CLIENT aura ses propres identifiants de colonne commençant par T02.
T02VILLE
Les vues
Une vue est une extraction logique d'une table. Toutes les colonnes de
la table n'y sont pas forcément représentées. Une
vue peux contenir des colonnes provenant de plusieurs tables.
Les vues sont utiles pour restreindre des droits à certains
utilisateurs ou pour leur présenter les données de
façon plus claire et synthétique pour eux.
Les indexes
Pour accélérer les accès aux données, il
est possible de créer des indexes. Un index porte sur une et une
seule table et il peut être composé d'une ou plusieurs
colonnes.
Un index est dit « UNIQUE » quand il ne peut y avoir
plusieurs tuples de la table qui contiennent les mêmes valeurs
pour les colonnes appartenant à l'index.
Une bonne pratique est de suffixer le nom des indexes car il ne peut
pas y avoir deux indexes qui portent le même nom, même
s'ils concernent la même table. Pour la table Tbl_CLIENT on
pourra ainsi avoir les indexes suivants :
I_CLIENT01
I_CLIENT02
I_CLIENT03
Formation d'une requête SQL
On suppose que les tables ont déjà été
définies et en tant qu'utilisateurs nous souhaitons obtenir des
informations contenues dans ces tables. La manière la plus
simple est de faire une sélection de toutes les colonnes de la
table. Cette sélection s'effectue avec l'ordre SELECT qui doit
être suivi d'une liste de colonnes, du mot réservé
FROM et enfin du nom de la table. Un ordre SELECT se termine par un
point-virgule. L'étoile est un joker qui remplace l'ensemble des colonnes d'une table.
SELECT * FROM Tbl_CLIENT;Dans cet exemple, l'étoile remplace l'ensemble des colonnes de la table.
Pour obtenir la liste des codes postaux et des villes de tous les clients, l'ordre SELECT correspondant sera celui-ci:
SELECT code_postal, ville FROM Tbl_CLIENT;Dans ces deux exemples, l'ensemble de la table sera retourné. Il serait intéressant d'utiliser des filtres. L'introduction d'un filtre se fait par la clause WHERE :
SELECT code_postal, ville FROM Tbl_CLIENT WHERE numcli = 100;Ce nouvel ordre ne retournera qu'une seule ligne si on suppose que numcli est la clé primaire de la table client.
Des prédicats complexes
L'expression qui figure après la clause WHERE peut être
très complexe. Il est possible d'utiliser des expressions
arithmétiques avec les opérateurs +
-
*
/
. La
priorité des opérateurs est la même qu'en
arithmétique classique. L'utilisation de parenthèses est
possible pour forcer des calculs prioritairement aux autres.
Le SQL utilise beaucoup d'autres opérateurs et fonctions pour
manipuler les chaînes de caractères ou les dates.
L'opérateur ||
permet de concaténer deux chaînes de
caractères. L'opérateur -
donne la différence de
deux dates.
Les opérateurs peuvent s'utiliser à la fois dans le
prédicat de l'ordre SELECT, c'est-à-dire dans la clause
WHERE, mais également dans le résultat.
Les résultats d'une requête peuvent eux-même servir
de filtre pour une autre requête. Il est possible d'imbriquer
plusieurs ordres SELECT ou de les réunir selon la théorie
des ensembles.
Les différentes clauses d'un ordre SELECT
Nous avons déjà rencontré trois de ces clauses :
SELECT
, FROM
, WHERE
.
Mais il en existe au total 6, les quatre dernières étant
facultatives :
SELECT...
FROM...
WHERE...
GROUP BY...
HAVING...
ORDER BY...
Il est possible d'utiliser des modificateurs qui vont modifier le
comportement d'un ordre SQL.
DISTINCT
s'utilise pour éliminer les doublons. Il se place juste
après le mot SELECT :
SELECT DISTINCT * FROM ....
SELECT DISTINCT ville FROM Tbl_CLIENT;
Cette dernière requête retournera toutes les villes
différents où se trouvent les clients.
Le mot AS
permet de nommer une colonne résultante.
SELECT ville AS CLIVILLE FROM Tbl_CLIENT;
SELECT nom, fixe + variable AS Salaire FROM Tbl_Employe;
Le mot réservé FROM
Dans un SELECT, le mot FROM est obligatoire. Il sert à
préciser la table dans laquelle on doit sélectionner les
colonnes. Il est possible d'utiliser plusieurs tables après le
mot FROM.
SELECT nom, adresse, Tbl_CLIENT.ville FROM Tbl_CLIENT, Tbl_DEPARTEMENTOn suppose que pour chaque département il existe une seule ligne qui contient la colonne ville qui correspondra à la préfecture du département. Cette requête nous donnera la liste de tous les clients (nom, adresse et ville) qui habitent une préfecture.
WHERE Tbl_CLIENT.ville = Tbl_DEPARTEMENT.ville;
Pour éviter d'avoir à préciser le nom de la table pour chaque colonne on peut utiliser des synonymes. En général on utilise une lettre, comme ceci :
SELECT nom, adresse, a.ville FROM Tbl_CLIENT a, Tbl_DEPARTEMENT bLe résultat sera identique à la requête précédente mais l'écriture est plus concise.
WHERE a.ville = b.ville;
Notez que quand on utilise plusieurs tables après la clause FROM, mais sans utiliser de clause WHERE, on réalise un produit cartésien de toutes les tables. Quand la clause WHERE est précisée avec deux colonnes appartenant aux deux tables et une expression de comparaison, on effectue une jointure de ces deux tables sur cette colonne. En réalité, il ne s'agit pas d'une véritable jointure mais d'un simple filtrage du produit cartésien. Nous verrons plus loin comment faire une jointure dans les normes.
La clause WHERE
Le prédicat qui suit une clause WHERE s'applique comme un filtre. Il est possible de combiner plusieurs expressions entre-elles en utilisant des opérateurs.
Liste des opérateurs possibles en SQL
L'égalité : WHERE exp1 = exp2
La différence : WHERE exp1 <> exp2
L'infériorité : WHERE exp1 < exp2
La supériorité : WHERE exp1 > exp2
Inférieur ou égal : WHERE exp1 <= exp2
Supérieur ou égal : WHERE exp1 >= exp2
Entre deux limites (bornes incluses) : WHERE exp1 BETWEEN exp2 AND exp3
Commençant par : WHERE exp1 LIKE exp2
Ne commençant pas par : WHERE exp1 NOT LIKE exp2
Compris dans : WHERE exp1 IN (exp2, exp3, ...)
Non compris dans : WHERE exp1 NOT IN (exp2, exp3, ...)
Valeur nulle : WHERE exp IS NULL
Valeur non nulle : WHERE exp IS NOT NULL
Pour le prédicat LIKE il est possible d'utiliser le
caractère joker %
qui représente une chaine quelconque de
n'importe quelle longueur, même de longueur nulle. Le
caractère joker _
est utilisé pour
représenté un seul caractère quelconque.
Les opérateurs logiques
Les opérateurs logiques AND et OR peuvent être
utilisés pour combiner plusieurs prédicats.
L'opérateur AND est prioritaire par rapport à
l'opérateur OR. Des parenthèses peuvent être
utilisées pour imposer une priorité dans
l'évaluation du prédicat, ou simplement pour rendre plus
claire une expression logique complexe.
L'opérateur NOT placé devant un prédicat en
inverse le sens.
L'imbrication de clauses where
Une caractéristique puissante du SQL est la possibilité
d'imbriquer plusieurs requêtes. C'est-à-dire qu'un
critère de recherche est lui-même le résultat d'une
requête SQL.
Exemple :
Vous disposez d'une base tbl_CLIENT et vous voulez savoir quels sont
tous vos clients qui habitent la même ville que le client DURAND.
Vous pouvez écrire la requête de cette façon :
SELECT NomMais il est également possible de formuler cette requête en utilisant un SELECT imbriqué :
,rue
FROM tbl_CLIENT a
,tbl_CLIENT b
WHERE a.ville = b.ville
AND b.nom = 'DURAND';
SELECT NomC'est avec cette formulation que l'opérateur
,rue
FROM tbl_CLIENT
WHERE ville = ( SELECT ville FROM tbl_CLIENT
WHERE nom = 'DURAND');
IN
est souvent
utilisé à la place de =
quand le SELECT imbriqué peut
retourner une liste de valeurs.
Les jointures
La norme SQL-2 a introduit un nouveau formalisme pour coder les
jointures, en séparant les critères de filtrage des
conditions de jointure. Cette syntaxe se code ainsi :
SELECT colonne1, colonne2, ...Il est bien entendu possible d'ajouter une clause WHERE pour effectuer un filtrage des résultats. En voici un exemple :
FROM table1 a
JOIN table2 b
ON a.colonne = b.colonne;
SELECT colonne1, colonne2, ...Cette syntaxe du SQL-2 est beaucoup plus claire et compréhensible que l'ancienne norme de 1986. Je vous encourage à l'utiliser dès que l'ordre SQL devient un peu complexe, même si les jointures utilisant uniquement la clause WHERE sont toujours possibles.
FROM table1 a
JOIN table2 b
ON a.colonne = b.colonne
WHERE a.colonne3 > 25;
Le problème que vous allez rencontrer avec l'ancienne syntaxe sera lorsque vous souhaiterez faire des jointures un peu spéciales, conforme à la théorie des ensembles.
INNER JOIN
La jointure que nous venons de voir est une jointure interne. En SQL on l'appelle INNER JOIN.Si on représente les deux tables comme des ensembles, cette jointure retourne l'intersection qui existe entre les deux ensembles.
La valeur de la colonne doit se rencontrer à la fois dans la table 1 et dans la table 2. Si une valeur n'existe que dans l'une des deux tables, elle ne sera pas retournée.
Exemple : vous avez une table des départements et une table des clients. La table des départements contient tous les départements de France. Pour les clients étrangers on force par défaut le département "99" qui n'existe pas dans la table des départements. En faisant une jointure INNER JOIN de ces deux tables vous aurez la liste de tous les clients français. Mais il est possible que certains départements ne correspondent à aucun client. Ils ne feront pas partie du résultat.
La syntaxe d'une jointure INNER JOIN est la suivante :
SELECT colonne1, colonne2, ...Le mot INNER est facultatif. Par défaut, une jointure JOIN est de type INNER JOIN.
FROM table1 a
INNER JOIN table2 b
ON a.colonne = b.colonne
WHERE ....;
LEFT OUTER JOIN
Nous pourrions avoir besoin de retourner toutes les lignes de la table de gauche même s'ils n'ont aucune correspondance dans la table de droite. Pour cela il faut coder une jointure LEFT OUTER JOIN.Cette jointure correspond à l'union de la table de gauche avec l'intersection des deux tables. Pour les lignes de la table de gauche n'ayant pas de correspondance dans la table de droite, les colonnes de la table de droite sont valorisées à la valeur NULL.
SELECT a.dept, b.nom, b.villeCette jointure retourne la liste de tous les départements, qu'ils possèdent ou non des clients. Quand vous avez des clients dans un département vous aurez une ligne de résultat par client avec son nom et sa ville. Pour les départements sans client il n'y aura que le numéro de département avec le nom à NULL et la ville à NULL.
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept;
RIGHT OUTER JOIN
C'est exactement la même chose qu'un LEFT OUTER JOIN mais cette fois c'est l'ensemble des lignes de la table de droite qui seront retournées.Pour l'exemple précédent nous pouvons obtenir le même résultat en le codant comme ceci :
SELECT b.dept, a.nom, a.ville
FROM tbl_CLIENT a
RIGHT OUTER JOIN tbl_DEPARTEMENT b
ON a.dept = b.dept;
FULL OUTER JOIN
Ce type de jointure constitue une union des deux tables. Pour notre exemple avec les clients et les départements on aura les trois types de résultats suivants :département nom du client ville du clientLe premier type de résultat correspond aux clients français.
NULL nom du client ville du client
département NULL NULL
Le second va se produire pour les clients étrangers qui ne correspondent à aucun département de la table des départements.
Enfin, le dernier cas représente les départements pour lesquels il n'y a aucun client.
La syntaxe de l'ordre SQL pour obtenir ce résultat est la suivante :
SELECT b.dept, a.nom, a.ville
FROM tbl_CLIENT a
FULL OUTER JOIN tbl_DEPARTEMENT b
ON a.dept = b.dept;
Jointure sans l'intersection des deux tables
Supposons maintenant que je souhaite obtenir la liste de tous les
départements pour lesquels il n'y a pas encore de clients. Il
est possible d'utiliser des SELECT imbriqués :
SELECT a.deptOn peut l'écrire plus simplement en utilisant une jointure externe :
FROM tbl_DEPARTEMENT a
WHERE a.dept NOT IN (SELECT DISTINCT b.dept FROM tbl_CLIENT b WHERE a.dept = b.dept);
SELECT a.deptCeci est possible car, lors d'une jointure LEFT OUTER JOIN, quand il n'y a pas de correspondance entre la table de gauche et la table de droite, les valeurs de la table de droite sont remplacées par la valeur NULL. On obtient donc bien le résultat escompté, c'est à dire tous les départements sans clients.
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept
WHERE b.dept IS NULL;
Tri des résultats
Il peut être intéressant d'obtenir un résultat qui
soit trié. Pour ce faire le prédicat ORDER BY est votre
ami. Il s'utilise à la fin d'un ordre SQL et doit
être suivit d'une liste de colonnes qui seront les
critères de tri. Pour chaque colonne il est possible de
préciser le sens du tri :
- ASC pour un tri ascendant
- DESC pour un tri descendant
Par défaut, le tri se fait en ascendant.
Syntaxe :
ORDER BY exp1 [DESC], exp2 [DESC], ...Le tri se fait d'abord selon la première expression, puis les lignes ayant la même valeur pour la première expression sont triées selon la deuxième, etc.
Pour préciser lors d'un tri sur quelle expression va porter le tri, il est possible donner le rang relatif de la colonne dans la liste des colonnes du SELECT, plutôt que son nom. Il aussi possible de donner un nom d'en-tête de colonne du SELECT.
SELECT DEPT, NOM FROM tbl_DEPARTEMENT ORDER BY NOMD ASC;peut être remplacé par
SELECT DEPT, NOM FROM tbl_DEPARTEMENT ORDER BY 2;
En définissant des titres pour les colonnes du résultat ça donnerait :
SELECT DEPT "N°", NOM "Département" FROM tbl_DEPARTEMENT
ORDER BY 2;
La clause GROUP BY
Cette clause s'utilise souvent de pair avec la clause HAVING.
Il est possible de subdiviser la table en groupes, chaque groupe
étant un ensemble de lignes ayant une valeur commune.
Syntaxe :
GROUP BY exp1, exp2, ...Exemple : compter le nombre de clients par département
SELECT a.dept, count(b.*)La clause HAVING sert à sélectionner des groupes.
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept
GROUP BY a.dept
ORDER BY a.dept;
Exemple : liste tous les départements dans lesquels il y a plus de 50 clients.
SELECT a.dept, count(*)
FROM tbl_DEPARTEMENT a
JOIN tbl_CLIENT b
ON a.dept = b.dept
GROUP BY a.dept
HAVING count(*) > 50
ORDER BY a.dept;
Les fonctions ensemblistes du SQL
Le SQL s'inspire énormément de la théorie des
ensembles. Il est possible de réaliser des unions très
simplement :
SELECT ....De même, l'intersection est possible avec la clause INTERSECT.
UNION
SELECT .... ;
La différence s'effectue avec le mot clé EXCEPT.
Il faut noter qu'en SQL, l'union élimine automatiquement les doublons. Si on souhaite conserver les doublons il faut utiliser la clause UNION ALL.
Les fonctions INTERSECT et EXCEPT ne sont pas implémentées dans tous les systèmes SQL.
Une simple présentation, des utilisations étendues
Cet article ne prétend pas vous apprendre le SQL dans sa
globalité. Il s'agit d'une simple présentation en
s'attachant à l'ordre SELECT qui est celui que vous utiliserez
sans doute le plus. L'ordre SELECT ne nécessite pas d'être
informaticien pour interroger une base de données.
Mais le SQL permet également d'effectuer des insertions en
table, des mises à jour, des suppressions. Il s'utilise pour
définir les objets tels que les tables, les vues, les indexes,
... Le SQL est un langage très complet et puissant.
Du fait qu'il s'agit d'un langage déclaratif, il est souvent
utilisé à l'intérieur de programmes écrits
dans un langage procédural ou dans un langage orienté
objet. Ces langages utilisent le SQL par le biais d'une interface de
connexion au moteur SQL.
Pour traiter chaque ligne de résultat obtenu par un ordre
SELECT, il faut ouvrir ce qu'on appelle un « curseur ». Le
curseur retourne l'ensemble du résultat. Pour accéder
à chaque ligne il faut faire des « FETCH ». Les
fetch sont réalisés à l'intérieur d'une
boucle. Chaque ligne retournée par le fetch alimente des
variables qui sont alors utilisables comme n'importe quelles variable
du programme. C'est ainsi qu'un langage tel que le COBOL peut utiliser
le SQL pour accéder à des bases de données DB2. Le
PHP fonctionne sensiblement de la même façon avec la
notion des curseurs et des fetchs légèrement
différente qu'en DB2.