Présentation du langage SQL

Le langage SQL
Les origines 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
Contrairement aux langages classiques (Cobol, C, Pascal) qui sont des langages procéduraux, le SQL est un langage déclaratif. En SQL on ne va pas décrire pas à pas tous les traitements que la machine aura à réaliser pour fournir le résultat attendu. On déclare simplement ce que l'on souhaite obtenir et c'est la machine qui décide comment elle va faire pour fournir le résultat.

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.VILLE
Tbl_CLIENT.VILLE
Une 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.
Exemple :
T01VILLE
T02VILLE
Dans 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.

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_DEPARTEMENT
WHERE Tbl_CLIENT.ville = Tbl_DEPARTEMENT.ville;
On 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.

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 b
WHERE a.ville = b.ville;
Le résultat sera identique à la requête précédente mais l'écriture est plus concise.

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 Nom
,rue 
FROM tbl_CLIENT a
,tbl_CLIENT b
WHERE  a.ville = b.ville
AND  b.nom = 'DURAND';
Mais il est également possible de formuler cette requête en utilisant un SELECT imbriqué :
SELECT Nom
,rue
FROM tbl_CLIENT
WHERE  ville = ( SELECT ville FROM tbl_CLIENT
WHERE nom = 'DURAND');
C'est avec cette formulation que l'opérateur 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, ... 
FROM table1 a
JOIN table2 b
ON a.colonne = b.colonne;
Il est bien entendu possible d'ajouter une clause WHERE pour effectuer un filtrage des résultats. En voici un exemple :
SELECT colonne1, colonne2, ... 
FROM table1 a
JOIN table2 b
ON a.colonne = b.colonne
WHERE a.colonne3 > 25;
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.

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, ... 
FROM table1 a
INNER JOIN table2 b
ON a.colonne = b.colonne
WHERE ....;
Le mot INNER est facultatif. Par défaut, une jointure JOIN est de type INNER JOIN.

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.ville 
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept;
Cette 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.

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 client
NULL nom du client ville du client
département NULL NULL
Le premier type de résultat correspond aux clients français.
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.dept 
FROM tbl_DEPARTEMENT a
WHERE a.dept NOT IN (SELECT DISTINCT b.dept FROM tbl_CLIENT b WHERE a.dept = b.dept);
On peut l'écrire plus simplement en utilisant une jointure externe :
SELECT a.dept 
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept
WHERE b.dept IS NULL;
Ceci 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.

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.*) 
FROM tbl_DEPARTEMENT a
LEFT OUTER JOIN tbl_CLIENT b
ON a.dept = b.dept
GROUP BY a.dept
ORDER BY a.dept;
La clause HAVING sert à sélectionner des groupes.
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 ....
UNION
SELECT .... ;
De même, l'intersection est possible avec la clause INTERSECT.

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.

Glossaire informatique... Cette page est listée dans le Glossaire Informatique      Plus...