La commande SELECT constitue, à elle seule, le langage permettant d’interroger une base de données. Elle permet de :
Une requête (i.e. une interrogation) est une combinaison d’opérations portant sur des tables (relations) et dont le résultat est lui-même une table dont l’existence est éphémère (le temps de la requête).
Une requête se présente généralement sous la forme :
SELECT [ ALL | DISTINCT ] { * | attribut [, ...] }
FROM nom_table [, ...]
[ WHERE condition ]
Par exemple, pour afficher l’ensemble des n-uplets de la table film, vous pouvez utiliser la requête :
SELECT * FROM film
De manière synthétique, on peut dire que la clause SELECT permet de réaliser la projection, la clause FROM le produit cartésien et la clause WHERE la sélection (cf. section 4.5.2).
Pour spécifier littéralement une chaîne de caractères, il faut l’entourer d’apostrophes (i.e. guillemets simples). Par exemple, pour sélectionner les films policiers, on utilise la requête :
SELECT * FROM film WHERE genre='Policier'
Les date doivent également être entourée d’apostrophes (ex : ’01/01/2005’).
Comme l’apostrophe est utilisée pour délimiter les chaînes de caractères, pour la représenter dans une chaîne, il faut la dédoubler (exemple : ’l’’arbre’), ou la faire précéder d’un antislash (exemple : 'l\'arbre').
Lorsque le nom d’un élément d’une base de données (un nom de table ou de colonne par exemple) est identique à un mot clef du SQL, il convient de l’entourer d’apostrophes doubles. Par exemple, si la table achat possède un attribut date, on pourra écrire :
SELECT ''date'' FROM achat
Bien entendu, les mots réservés du SQL sont déconseillés pour nommer de tels objets. Les apostrophes doubles sont également nécessaires lorsque le nom (d’une colonne ou d’une table) est composé de caractères particuliers tels que les blancs ou autres, ce qui est évidemment déconseillé.
L’opérateur de projection Π(A1, … An)(relation) se traduit tout simplement en SQL par la requête :
SELECT DISTINCT A_1, ..., A_n FROM relation
DISTINCT permet de ne retenir qu’une occurrence de n-uplet dans le cas où une requête produit plusieurs n-uplets identiques (cf. section 4.5.4).
L’opérateur de sélection σ(prédicat)(relation) se traduit tout simplement en SQL par la requête :
SELECT * FROM relation WHERE prédicat
De manière simplifiée, un prédicat est une expression logique sur des comparaisons. Reportez-vous à la section 4.5.7 pour une description plus complète.
L’opérateur de produit cartésien relation1 × relation2 se traduit en SQL par la requête :
SELECT * FROM relation_1, relation_2
Nous reviendrons sur le produit cartésien dans les sections 4.5.5 et 4.7.1.
L’opérateur d’équi-jointure relation1 ▷◁A1,A2 relation2 se traduit en SQL par la requête :
SELECT * FROM relation_1, relation_2 WHERE relation_1.A_1 = relation_2.A_2
Nous reviendrons sur les différents types de jointure dans la section 4.7.1.
Voici la syntaxe générale d’une commande SELECT :
SELECT [ ALL | DISTINCT ] { * | expression [ AS nom_affiché ] } [, ...]
FROM nom_table [ [ AS ] alias ] [, ...]
[ WHERE prédicat ]
[ GROUP BY expression [, ...] ]
[ HAVING condition [, ...] ]
[ {UNION | INTERSECT | EXCEPT [ALL]} requête ]
[ ORDER BY expression [ ASC | DESC ] [, ...] ]
En fait l’ordre SQL SELECT est composé de 7 clauses dont 5 sont optionnelles :
Comme nous l’avons déjà dit, la clause SELECT permet de spécifier les attributs que l’on désire voir apparaître dans le résultat de la requête. Pour préciser explicitement les attributs que l’on désire conserver, il faut les lister en les séparant par une virgule. Cela revient en fait à opérer une projection de la table intermédiaire générée par le reste de la requête. Nous verrons dans cette section que la clause SELECT permet d’aller plus loin que la simple opération de projection. En effet, cette clause permet également de renommer des colonnes, voire d’en créer de nouvelles à partir des colonnes existantes.
Pour illustrer par des exemples les sections qui suivent, nous utiliserons une table dont le schéma est le suivant :
employee(id_employee, surname, name, salary)
Cette table contient respectivement l’identifiant, le nom, le prénom et le salaire mensuel des employés d’une compagnie.
Le caractère étoile (*) permet de récupérer automatiquement tous les attributs de la table générée par la clause FROM de la requête.
Pour afficher la table employee on peut utiliser la requête :
SELECT * FROM employee
Lorsque le SGBD construit la réponse d’une requête, il rapatrie toutes les lignes qui satisfont la requête, généralement dans l’ordre ou il les trouve, même si ces dernières sont en double (comportement ALL par défaut). C’est pourquoi il est souvent nécessaire d’utiliser le mot clef DISTINCT qui permet d’éliminer les doublons dans la réponse.
Par exemple, pour afficher la liste des prénoms, sans doublon, des employés de la compagnie, il faut utiliser la requête :
SELECT DISTINCT name FROM employee
Il est possible d’utiliser les opérateurs mathématiques de base (i.e. +, -, * et /) pour générer de nouvelles colonnes à partir, en générale, d’une ou plusieurs colonnes existantes.
Pour afficher le nom, le prénom et le salaire annuel des employés, on peut utiliser la requête :
SELECT surname, name, salary*12 FROM employee
Le mot clef AS permet de renommer une colonne, ou de nommer une colonne créée dans la requête.
Pour afficher le nom, le prénom et le salaire annuel des employés, on peut utiliser la requête :
SELECT surname AS nom, name AS prénom, salary*12 AS salaire FROM employee
L’opérateur || (double barre verticale) permet de concaténer des champs de type caractères.
Pour afficher le nom et le prénom sur une colonne, puis le salaire annuel des employés, on peut utiliser la requête :
SELECT surname || ' ' || name AS nom, salary*12 AS salaire FROM employee
Comme nous l’avons déjà dit, la clause FROM spécifie les tables sur lesquelles porte la requête. Plus exactement, cette clause construit la table intermédiaire (i.e. virtuelle), à partir d’une ou de plusieurs tables, sur laquelle des modifications seront apportées par les clauses SELECT, WHERE, GROUP BY et HAVING pour générer la table finale résultat de la requête. Quand plusieurs tables, séparées par des virgules, sont énumérées dans la clause FROM, la table intermédiaire est le résultat du produit cartésien de toutes les tables énumérées.
Le mot clef AS permet de renommer une table, ou de nommer une table créée dans la requête (c’est à dire une sous-requête) afin de pouvoir ensuite y faire référence. Le renommage du nom d’une table se fait de l’une des deux manières suivantes :
FROM nom_de_table AS nouveau_nom FROM nom_de_table nouveau_nom
Une application typique du renommage de table est de simplifier les noms trop long :
SELECT * FROM nom_de_table_1 AS t1, nom_de_table_1 AS t2 WHERE t1.A_1 = t2.A_2
Attention, le nouveau nom remplace complètement l’ancien nom de la table dans la requête. Ainsi, quand une table a été renommée, il n’est plus possible d’y faire référence en utilisant son ancien nom. La requête suivante n’est donc pas valide :
SELECT * FROM nom_table AS t WHERE nom_table.a > 5
Les tables mentionnées dans la clause FROM peuvent très bien correspondre à des tables résultant d’une requête, spécifiée entre parenthèses, plutôt qu’à des tables existantes dans la base de données. Il faut toujours nommer les tables correspondant à des sous-requêtes en utilisant l’opérateur AS.
Par exemple, les deux requêtes suivantes sont équivalentes :
SELECT * FROM table_1, table_2 SELECT * FROM (SELECT * FROM table_1) AS t1, table_2
Nous traiterons cet aspect de la clause FROM dans la section 4.7.1.
Comme nous l’avons déjà dit, la clause ORDER BY permet de trier les n-uplets du résultat et sa syntaxe est la suivante :
ORDER BY expression [ ASC | DESC ] [, ...]
expression désigne soit une colonne, soit une opération mathématique de base (nous avons abordé ce type d’opérations dans la section 4.5.4 sur « La clause SELECT ») sur les colonnes.
ASC spécifie l’ordre ascendant et DESC l’ordre descendant du tri. En l’absence de précision ASC ou DESC, c’est l’ordre ascendant qui est utilisé par défaut.
Quand plusieurs expressions, ou colonnes sont mentionnées, le tri se fait d’abord selon les premières, puis suivant les suivantes pour les n-uplet qui sont égaux selon les premières.
Le tri est un tri interne sur le résultat final de la requête, il ne faut donc placer dans cette clause que les noms des colonnes mentionnés dans la clause SELECT.
La clause ORDER BY permet de trier le résultat final de la requête, elle est donc la dernière clause de tout ordre SQL et ne doit figurer qu’une seule fois dans le SELECT, même s’il existe des requêtes imbriquées ou un jeu de requêtes ensemblistes (cf. section 4.7.3).
En l’absence de clause ORDER BY, l’ordre des n-uplet est aléatoire et non garanti. Souvent, le fait de placer le mot clef DISTINCT suffit à établir un tri puisque le SGBD doit se livrer à une comparaison des lignes, mais ce mécanisme n’est pas garanti car ce tri s’effectue dans un ordre non contrôlable qui peut varier d’un serveur à l’autre.
Comme nous l’avons déjà dit, la clause WHERE permet de filtrer les n-uplets en imposant une condition à remplir pour qu’ils soient présents dans le résultat de la requête ; sa syntaxe est la suivante :
WHERE prédicat
Concrètement, après que la table intermédiaire (i.e. virtuelle) de la clause FROM a été construite, chaque ligne de la table est confrontée au prédicat prédicat afin de vérifier si la ligne satisfait (i.e. le prédicat est vrai pour cette ligne) ou ne satisfait pas (i.e. le prédicat est faux ou NULL pour cette ligne) le prédicat. Les lignes qui ne satisfont pas le prédicat sont supprimées de la table intermédiaire.
Le prédicat n’est rien d’autre qu’une expression logique. En principe, celle-ci fait intervenir une ou plusieurs lignes de la table générée par la clause FROM, cela n’est pas impératif mais, dans le cas contraire, l’utilité de la clause WHERE serait nulle.
Une expression simple peut être une variable désignée par un nom de colonne ou une constante. Si la variable désigne un nom de colonne, la valeur de la variable sera la valeur située dans la table à l’intersection de la colonne et de la ligne dont le SGBD cherche à vérifier si elle satisfait le prédicat de la clause WHERE.
Les expressions simples peuvent être de trois types : numérique, chaîne de caractères ou date.
Une expression simple peut également être le résultat d’une sous-requête, spécifiée entre parenthèses, qui retourne une table ne contenant qu’une seule ligne et qu’une seule colonne (i.e. une sous-requête retournant une valeur unique).
Un prédicat simple peut être le résultat de la comparaison de deux expressions simples au moyen de l’un des opérateurs suivants :
| = | égal |
| != | différent |
| < | strictement inférieur |
| <= | inférieur ou égal |
| > | strictement supérieur |
| >= | supérieur ou égal |
Dans ce cas, les trois types d’expressions (numérique, chaîne de caractères et date) peuvent être comparés. Pour les types date, la relation d’ordre est l’ordre chronologique. Pour les caractères, la relation d’ordre est l’ordre lexicographique.
Un prédicat simple peut également correspondre à un test de description d’une chaîne de caractères par une expression régulière :
~ | décrit par l’expression régulière |
~* | comme LIKE mais sans tenir compte de la casse |
!~ | non décrit par l’expression régulière |
!~* | comme NOT LIKE mais sans tenir compte de la casse |
Dans ce cas, la chaîne de caractères faisant l’objet du test est à gauche et correspond à une expression simple du type chaîne de caractères, il s’agit généralement d’un nom de colonne. L’expression régulière, qui s’écrit entre apostrophe simple, comme une chaîne de caractères, est située à droite de l’opérateur. La section 4.5.8 donne une description détaillée du formalisme des expressions régulières.
Un prédicat simple peut enfin correspondre à l’un des tests suivants :
| expr IS NULL | test sur l’indétermination de expr |
| expr IN (expr_1 [, ...]) | comparaison de expr à une liste de valeurs |
| expr NOT IN (expr_1 [, ...]) | test d’absence d’une liste de valeurs |
| expr IN (requête) | même chose, mais la liste de valeurs est le résultat d’une |
| expr NOT IN (requête) | sous-requête qui doit impérativement retourner une table |
| ne contenant qu’une colonne | |
| EXIST (requête) | vraie si la sous-requête retourne au moins un n-uplet |
| vraie si au moins un n-uplet de la sous-requête vérifie la | |
| expr operateur ANY (requête) | comparaison « expr opérateur n-uplet » ; la sous-requête |
| doit impérativement retourner une table ne contenant | |
| qu’une colonne ; IN est équivalent à = ANY | |
| vraie si tous les n-uplets de la sous-requête vérifient la | |
| expr operateur ALL (requête) | comparaison « expr opérateur n-uplet » ; la sous-requête |
| doit impérativement retourner une table ne contenant | |
| qu’une colonne |
Dans ce tableau, expr désigne une expression simple et requête une sous-requête.
Les prédicats simples peuvent être combinés au sein d’expression logiques en utilisant les opérateurs logiques AND (et logique), OR (ou logique) et NOT (négation logique).
Le terme expression régulière est issu de la théorie informatique et fait référence à un ensemble de règles permettant de définir un ensemble de chaînes de caractères.
Une expression régulière constitue donc une manière compacte de définir un ensemble de chaînes de caractères. Nous dirons qu’une chaîne de caractères est décrite par une expression régulière si cette chaîne est un élément de l’ensemble de chaînes de caractères défini par l’expression régulière.
PostgreSQL dispose de trois opérateurs de description par une expression régulière :
~~
~
La syntaxe et le pouvoir expressif des expressions régulières diffèrent pour ces trois opérateurs. Nous ne décrirons ici que la syntaxe du formalisme le plus standard et le plus puissant, celui que l’on retrouve sous Unix avec les commandes egrep, sed et awk. Ce formalisme est celui associé à l’opérateur ~.
Avec PostgreSQL, le test d’égalité avec une chaîne de caractères s’écrit :
expression='chaine'
De manière équivalente, le test de description par une expression régulière s’écrit :
expression~'expression_régulière'
L’opérateur de description ~ est sensible à la casse, l’opérateur de description insensible à la casse est ~*. L’opérateur de non description sensible à la casse est !~, son équivalent insensible à la casse se note !~*.
Comme nous allons le voir, dans une expression régulière, certains symboles ont une signification spéciale. Dans ce qui suit, expreg, expreg_1, expreg_2 désignent des expressions régulières, caractère un caractère quelconque et liste_de_caractères une liste de caractères quelconque.
un caractère est une expression régulière qui désigne le caractère lui-même, excepté pour les caractères
., ?, +, *, {, |, (, ), ^, $,
\, [, ]. Ces derniers sont des méta-caractères et ont une signification spéciale. Pour
désigner ces méta-caractères, il faut les faire précéder d’un antislash (\., \?,
\+, \*, \{, \|, \(, \), \^, \$, \\,
\[, \]).
^, il faut le mettre n’importe où sauf en tête de liste, enfin un - se place à
la fin de la liste.[[:alnum:]] est équivalent à [0-9A-Za-z]) ; sur le même principe, on a également
[:alpha:], [:cntrl:], [:digit:], [:graph:], [:lower:],
[:print:], [:punct:], [:space:], [:upper:] et
[:xdigit:].Un caractère, qui n’est pas un méta-caractère, se décrit lui-même. Ce qui signifie que si vous
cherchez une chaîne qui contient « voiture », vous devez utiliser l’expression régulière 'voiture'.
Si vous ne cherchez que les motifs situés en début de ligne, utilisez le symbole ^. Pour
chercher toutes les chaînes qui commencent par « voiture », utilisez '^voiture'.
Le signe $ (dollar) indique que vous souhaitez trouver les motifs en fin de ligne. Ainsi : 'voiture$' permet de trouver toutes les chaînes finissant par « voiture ».
Le symbole . (point) remplace n’importe quel caractère. Pour trouver toutes les occurrences du motif composé des lettres vo, de trois lettres quelconques, et de la lettre e, utilisez : 'vo...e'. Cette commande permet de trouver des chaînes comme : voyagent, voyage, voyager, voyageur, vous_e.
Vous pouvez aussi définir un ensemble de lettres en les insérant entre crochets [ ]. Pour
chercher toutes les chaînes qui contiennent les lettres P ou p suivies de
rince, utilisez :'[Pp]rince'.
Si vous voulez spécifier un intervalle de caractères, servez-vous d’un trait d’union pour délimiter
le début et la fin de l’intervalle. Vous pouvez aussi définir plusieurs intervalles simultanément. Par exemple
[A-Za-z] désigne toutes les lettres de l’alphabet, hormis les caractères accentués, quelque soit la casse. Notez bien qu’un
intervalle ne correspond qu’à un caractère dans le texte.
Le symbole * est utilisé pour définir zéro ou plusieurs occurrences du motif précédent. Par exemple, l’expression régulière '^Pa(pa)*$' décrit les chaînes : Pa, Papa, Papapa, Papapapapapapa, …
Si vous souhaitez qu’un symbole soit interprété littéralement, il faut le préfixer par un \.
Pour trouver toutes les lignes qui contiennent le symbole $, utilisez : \$