Le langage SQL

Comme c'est le cas dans la plupart des langages relationnels modernes, SQL est fondé sur le calcul relationnel de tuple. Chaque requête qui peut être formulée en l'utilisant (ou, de façon équivalente, par l'algèbre relationnelle), peut aussi être formulée en SQL. Ce dernier offre toutefois des possibilités dépassant celles de l'algèbre et du calcul relationnels. Voici une liste de certaines possibilités supplémentaires fournies par SQL :

Select

La commande la plus utilisée en SQL est la clause SELECT, qui permet de retrouver des données. La syntaxe est :
   SELECT [ALL|DISTINCT] 
          { * | expr_1 [AS c_alias_1] [, ... 
                [, expr_k [AS c_alias_k]]]}
   FROM table_name_1 [t_alias_1] 
        [, ... [, table_name_n [t_alias_n]]]
   [WHERE condition]
   [GROUP BY name_of_attr_i 
             [,... [, name_of_attr_j]] [HAVING condition]]
   [{UNION [ALL] | INTERSECT | EXCEPT} SELECT ...]
   [ORDER BY name_of_attr_i [ASC|DESC] 
             [, ... [, name_of_attr_j [ASC|DESC]]]];
     

Nous allons illustrer la syntaxe complexe du mot-clé SELECT grâce à divers exemples.

Selects simples

Voici quelques exemples simples utilisant le mot-clé SELECT :

Exemple 2-4. Requête simple avec qualification

Pour retrouver tous les tuples de la table PART dont l'attribut PRICE est supérieur à 10 nous formulons la requête suivante :
   SELECT * FROM PART
     WHERE PRICE > 10;
	
et nous obtenons la table :
                   PNO |  PNAME  |  PRICE
                  -----+---------+--------
                    3  |  Bolt   |   15
                    4  |  Cam    |   25
	

L'utilisation de * dans le mot-clé SELECT fournira tous les attributs de la table. Si nous voulons retrouver seulement les attributs PNAME et PRICE de la table PART nous utiliserons la clause :
   SELECT PNAME, PRICE 
   FROM PART
   WHERE PRICE > 10;
	
dans ce cas le résultat est :
                      PNAME  |  PRICE
                     --------+--------
                      Bolt   |   15
                      Cam    |   25
	
Notez que le SELECT de SQL correspond au "projection" en algèbre relationnelle mais pas au "sélection" (lire la section Algèbre relationnelle).

Les qualifications dans la clause WHERE peuvent aussi être logiquement connectées en utilisant les mots-clé OR, AND, et NOT.
   SELECT PNAME, PRICE 
   FROM PART
   WHERE PNAME = 'Bolt' AND
         (PRICE = 0 OR PRICE < 15);
	
qui conduit au résultat :
                      PNAME  |  PRICE
                     --------+--------
                      Bolt   |   15
	

Les opérations arithmétiques peuvent être utilisées dans la liste cible et dans la clause WHERE. Par exemple si nous voulons connaître combien coûteront deux exemplaires d'une pièce acquise sans que le montant total de la facture atteigne 50 F nous utilisons la requête suivante :
   SELECT PNAME, PRICE * 2 AS DOUBLE
   FROM PART
   WHERE PRICE * 2 < 50;
	
et obtenons :
                      PNAME  |  DOUBLE
                     --------+---------
                      Screw  |    20
                      Nut    |    16
                      Bolt   |    30
	
Notez que le mot DOUBLE après le mot-clé AS est le nouveau titre de la seconde colonne. Cette technique peut être utilisée pour chaque élément de la liste cible afin de donner un nouveau titre à la colonne qui en résultera. Ce nouveau titre est souvent utilisé comme alias. Ce dernier ne peut pas être utilisé dans le reste de la requête.

Jointures

L'exemple suivant montre comment les jointures sont réalisées en SQL.

Pour joindre les trois tables SUPPLIER, PART, et SELLS sur leurs attributs communs nous formulons la clause suivante :
   SELECT S.SNAME, P.PNAME
   FROM SUPPLIER S, PART P, SELLS SE
   WHERE S.SNO = SE.SNO AND
         P.PNO = SE.PNO;
      
et obtenons comme résultat la table :
                       SNAME | PNAME
                      -------+-------
                       Smith | Screw
                       Smith | Nut
                       Jones | Cam
                       Adams | Screw
                       Adams | Bolt
                       Blake | Nut
                       Blake | Bolt
                       Blake | Cam
      

Dans la clause FROM nous avons introduit un nom alias pour chaque relation parce qu'elles ont un nom d'attribut commun (SNO et PNO). Nous pouvons donc distinguer les attributs communs en préfixant simplement le nom attribut avec le nom alias suivi par un point. En premier lieu le produit cartésien SUPPLIER × PART × SELLS est engendré. Puis, seuls les tuples satisfaisant les conditions données dans la clause WHERE (leurs noms d'attributs communs doivent être égaux) sont sélectionnés. Ce SELECT ne restitue que les attributs S.SNAME et P.PNAME.

Opérateurs d'agrégation

SQL fournit des opérateurs d'agrégation (ex. AVG, COUNT, SUM, MIN, MAX) qui prennent le nom d'un attribut comme argument. Une seule valeur en résultant, elle est calculée avec toutes les valeurs de l'attribut (colonne) spécifié. Si des groupes sont spécifiés dans la requête le calcul est fait seulement sur les valeurs d'un groupe (voir section suivante).

Exemple 2-5. Aggrégats

Si nous voulons connaître le coût moyen (AVeraGe) de toutes les pièces de la table PART nous utilisons la requête suivante :
   SELECT AVG(PRICE) AS AVG_PRICE
   FROM PART;
	

le résultat est :
                         AVG_PRICE
                        -----------
                           14.5
	

Si nous voulons savoir combien de pièces sont stockées dans la table PART nous utilisons la clause :
   SELECT COUNT(PNO)
   FROM PART;
	
et obtenons :
                           COUNT
                          -------
                             4
	

Aggrégation par groupes

SQL admet la partition des tuples d'une table dans des groupes. L'un des opérateurs d'agrégation décrit ci-dessus peut alors leur être appliqué, ce qui signifie que la valeur de l'aggrégat n'est plus calculée sur toutes les valeurs de la colonne spécifiée mais sur toutes les valeurs d'un groupe. L'opérateur aggrégat est ainsi évalué pour chaque groupe.

Le partitionnement des tuples dans les groupes est réalisé en utilisant le mot-clé GROUP BY suivi d'une liste d'attributs qui les définissent. Par un GROUP BY A1, ⃛, Ak nous partitionnons la relation dans les groupes : deux tuples seront dans le même groupe si leurs attributs A1, ⃛, Ak contiennent les mêmes valeurs.

Exemple 2-6. Aggrégats

Pour connaître combien de pièces sont vendues par chaque fournisseur nous formulons la requête suivante :
   SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
   FROM SUPPLIER S, SELLS SE
   WHERE S.SNO = SE.SNO
   GROUP BY S.SNO, S.SNAME;
	
et obtenons :
                     SNO | SNAME | COUNT
                    -----+-------+-------
                      1  | Smith |   2
                      2  | Jones |   1
                      3  | Adams |   2
                      4  | Blake |   3
	

Voici comment ce résultat est calculé. La jointure des tables SUPPLIER et SELLS est tout d'abord engendrée :
                  S.SNO | S.SNAME | SE.PNO
                 -------+---------+--------
                    1   |  Smith  |   1
                    1   |  Smith  |   2
                    2   |  Jones  |   4
                    3   |  Adams  |   1
                    3   |  Adams  |   3
                    4   |  Blake  |   2
                    4   |  Blake  |   3
                    4   |  Blake  |   4
	

Puis les tuples sont répartis dans les groupes. Tous ceux qui contiennent les mêmes valeurs pour les attributs S.SNO et S.SNAME appartiendront au même groupe.
                  S.SNO | S.SNAME | SE.PNO
                 -------+---------+--------
                    1   |  Smith  |   1
                                  |   2
                 --------------------------
                    2   |  Jones  |   4
                 --------------------------
                    3   |  Adams  |   1
                                  |   3
                 --------------------------
                    4   |  Blake  |   2
                                  |   3
                                  |   4
	

Dans notre exemple nous avons obtenu quatre groupes et pouvons à présent appliquer l'opérateur COUNT pour chaque groupe afin d'obtenir le résultat de la requête.

Notez que pour que le résultat d'une requête utilisant GROUP BY et les opérateurs d'agrégation ait un sens les attributs traités par GROUP BY doivent aussi apparaître dans la liste cible. Tous les attributs supplémentaires qui n'apparaissent pas dans la clause GROUP BY, ne peuvent être sélectionnés qu'en utilisant une fonction d'agrégation. En d'autres termes, vous ne pouvez pas utiliser des fonctions d'agrégation sur des attributs apparaissant dans une clause GROUP BY.

Clause HAVING

La clause HAVING est plus efficace que WHERE et est utilisée pour considérer seulement les groupes satisfaisant à des critères donnés. Les expressions admises dans la clause HAVING doivent comporter des fonctions d'agrégation. Chaque expression utilisant seulement des attributs pleins appartient à la clause WHERE. Autrement dit chaque expression comportant une fonction d'agrégation doit être placée dans la clause HAVING.

Exemple 2-7. Having

La requête suivante produit la liste des fournisseurs vendant plus d'une pièce :
   SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
   FROM SUPPLIER S, SELLS SE
   WHERE S.SNO = SE.SNO
   GROUP BY S.SNO, S.SNAME
   HAVING COUNT(SE.PNO) > 1;
	
et obtenons :
                     SNO | SNAME | COUNT
                    -----+-------+-------
                      1  | Smith |   2
                      3  | Adams |   2
                      4  | Blake |   3
	

Sous-requêtes

Dans les clauses WHERE et HAVING l'utilisation de sous-requêtes (subselects) est admise chaque fois qu'une valeur est attendue. Dans ce cas la (ou les) valeur(s) est obtenue par le serveur en évaluant la sous-requête en premier. L'usage de sous-requêtes augmente la puissance d'expression de SQL.

Exemple 2-8. Sous-requête

Nous voulons connaître toutes les pièces dont la valeur est plus grande que la pièce nommée 'Screw', nous nous servons de la requête :
   SELECT * 
   FROM PART 
   WHERE PRICE > (SELECT PRICE FROM PART
                  WHERE PNAME='Screw');
	

le résultat est :
                   PNO |  PNAME  |  PRICE
                  -----+---------+--------
                    3  |  Bolt   |   15
                    4  |  Cam    |   25
	

La requête ci-dessus contient deux SELECT. Le premier, placé au tout début de la requête (ici appelé «SELECT externe») et l'autre («SELECT interne») dans la clause WHERE qui commence une requête emboîtée. Pour chaque tuple du SELECT externe, le SELECT interne a été évalué. Après chaque évaluation nous connaissons le prix du tuple nommé 'Screw' et pouvons savoir si le prix du tuple actuel est plus grand.

Nous voulons connaître tous les fournisseurs qui ne vendent aucune pièce (afin, par exemple, de les supprimer de la base), utilisons :
   SELECT * 
   FROM SUPPLIER S
   WHERE NOT EXISTS
             (SELECT * FROM SELLS SE
              WHERE SE.SNO = S.SNO);
	

Dans notre exemple le résultat sera vide parce que chaque fournisseur vend au moins une pièce. Notez que nous utilisons le S.SNO acquis grâce au SELECT externe dans la clause WHERE du SELECT interne. Comme décrit ci-dessus la sous-requête est évaluée pour chaque tuple obtenu par la requête externe, i.e. la valeur pour S.SNO est toujours prise depuis le tuple du SELECT externe considéré sur le moment.

Union, Intersection, Exception

Il est possible d'effectuer, sur des tuples produits par deux sous-requêtes, diverses opérations formalisées par la théorie des ensembles : l'union, l'intersection et la différence.

Exemple 2-9. Union, Intersection, Exception

cet exemple de requête UNION :
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNAME = 'Jones'
   UNION
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNAME = 'Adams';    
	
donne le résultat :
                     SNO | SNAME |  CITY
                    -----+-------+--------
                      2  | Jones | Paris
                      3  | Adams | Vienna
	

ici, un exemple pour INTERSECT :
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNO > 1
   INTERSECT
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNO > 2;
	
donne le résultat :
                     SNO | SNAME |  CITY
                    -----+-------+--------
                      2  | Jones | Paris

Le seul tuple retourné par les deux parties de la requête est celui ayant
SNO=2.
	

Enfin un exemple pour EXCEPT :
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNO > 1
   EXCEPT
   SELECT S.SNO, S.SNAME, S.CITY
   FROM SUPPLIER S
   WHERE S.SNO > 3;
	
donne le résultat :
                     SNO | SNAME |  CITY
                    -----+-------+--------
                      2  | Jones | Paris
                      3  | Adams | Vienna
	

Définition de données

Le langage SQL intègre un ensemble de commandes utilisées pour la définition des données.

Create table

La commande la plus fondamentale pour la définition de données est celle qui crée une nouvelle relation (une nouvelle table) : CREATE TABLE. La syntaxe de la commande est :
   CREATE TABLE table_name
                (name_of_attr_1 type_of_attr_1
                 [, name_of_attr_2 type_of_attr_2 
                 [, ...]]);
      

Exemple 2-10. Création de table

Pour créer les tables de notre exemple les clauses SQL suivantes sont utilisées :
   CREATE TABLE SUPPLIER
                (SNO   INTEGER,
                 SNAME VARCHAR(20),
                 CITY  VARCHAR(20));
	
   CREATE TABLE PART
                (PNO   INTEGER,
                 PNAME VARCHAR(20),
                 PRICE DECIMAL(4 , 2));
	
   CREATE TABLE SELLS
                (SNO INTEGER,
                 PNO INTEGER);
	

Types de données en SQL

Voici une liste de certains types de données gérés par le SQL :

  • INTEGER: entier signé exprimé sur 32 bits (1 bit de signe, précision : 31 bits).

  • SMALLINT: entier signé exprimé sur 16 bits (1 bit de signe, précision : 15 bits).

  • DECIMAL (p[,q]): nombre décimal signé à la précision donnée. p q (15 ≥ pqq ≥ 0). Si q est omis, il est supposé nul.

  • FLOAT: nombre à virgule flottante signé, exprimé sur 64 bits.

  • CHAR(n): chaîne de caractères de longueur fixe n.

  • VARCHAR(n): chaîne de caractères de longueur variable avec maxima n.

Création d'index

Les index sont utilisés pour accélérer les accès à une relation.

Pour créer un index en SQL, la commande CREATE INDEX est utilisée. Sa syntaxe est :
   CREATE INDEX index_name 
   ON table_name ( name_of_attribute );
      

Exemple 2-11. Create index

Pour créer un index nommé I sur l'attribut SNAME de la relation SUPPLIER, nous utilisons la clause suivante :
   CREATE INDEX I
   ON SUPPLIER (SNAME);
      

L'index créé est maintenu automatiquement, donc lors de chaque insertion d'un nouveau tuple dans la relation SUPPLIER l'index I est adapté. Notez que les seuls changements que peut percevoir un utilisateur quand un index est créé est un accroissement des performances de certaines opérations de sélection.

Create view

Une vue peut être considérée comme une table virtuelle, sorte de table qui n'existe pas physiquement dans la base mais qui existe pour l'utilisateur. Par contraste, quand nous parlons de table de la base il y a réellement stockage physique de chaque ligne de la table quelque part (souvent sur un disque).

Une vue n'abrite pas ses propres copies des données, physiquement séparées et stockées de façon distincte. Le système stocke en effet dans le catalogue du système la définition de la vue (i.e. les règles d'accès physique aux tables de la base stockée) de façon à matérialiser la vue.

En SQL la commande CREATE VIEW est utilisée pour définir une vue. La syntaxe est :
   CREATE VIEW view_name
   AS select_stmt
      
select_stmt est une clause select valide. Notez que select_stmt n'est pas exécutée quand la vue est créée mais simplement stockée dans le catalogue système puis exécutée lors de chaque requête sur la vue.

Voici une définition de vue (d'après les exemples précédents) :
   CREATE VIEW London_Suppliers
      AS SELECT S.SNAME, P.PNAME
         FROM SUPPLIER S, PART P, SELLS SE
         WHERE S.SNO = SE.SNO AND
               P.PNO = SE.PNO AND
               S.CITY = 'London';
      

Nous pouvons maintenant utiliser cette relation virtuelle appelée London_Suppliers comme s'il s'agissait d'une table :
   SELECT *
   FROM London_Suppliers
   WHERE P.PNAME = 'Screw';
      
qui renverra la table suivante :
                       SNAME | PNAME
                      -------+-------
                       Smith | Screw                 
      

Pour calculer ce résultat, le système a fait un accès caché aux tables SUPPLIER, SELLS, et PART par l'exécution de la requête donnée dans la définition de la vue sur ces tables. Les qualifications supplémentaires (données dans la requête sur la vue) sont ensuite appliquées afin d'obtenir la table résultante.

Drop Table, Drop Index, Drop View

Pour détruire une table (y compris tous les tuples qu'elle abrite), la commande DROP TABLE est utilisée :
   DROP TABLE table_name;
       

Pour détruire la table SUPPLIER utilisez la commande :
   DROP TABLE SUPPLIER;
      

La commande DROP INDEX est utilisée pour détruire un index :
   DROP INDEX index_name;
      

Enfin, pour détruire une vue utilisez la commande DROP VIEW :
   DROP VIEW view_name;
      

Manipulation des données

Insert Into

Dès qu'une table est créée elle peut être remplie avec des tuples grâce à la commande INSERT INTO. La syntaxe est :
   INSERT INTO table_name (name_of_attr_1 
                             [, name_of_attr_2 [,...]])
   VALUES (val_attr_1 
           [, val_attr_2 [, ...]]);
      

Pour insérer le premier tuple dans la relation SUPPLIER nous utilisons la clause suivante :
   INSERT INTO SUPPLIER (SNO, SNAME, CITY)
   VALUES (1, 'Smith', 'London');
      

Pour insérer le premier tuple dans la relation SELLS nous utilisons :
   INSERT INTO SELLS (SNO, PNO)
   VALUES (1, 1);
      

Update

Pour modifier une ou plusieurs valeurs d'attributs de tuples dans une relation, la commande UPDATE est utilisée. La syntaxe est :
   UPDATE table_name
   SET name_of_attr_1 = value_1 
       [, ... [, name_of_attr_k = value_k]]
   WHERE condition;
      

Pour modifier la valeur de l'attribut PRICE de la pièce 'Screw' dans la relation PART nous utilisons :
   UPDATE PART
   SET PRICE = 15
   WHERE PNAME = 'Screw';
      

La nouvelle valeur de l'attribut PRICE du tuple 'Screw' est maintenant 15.

Delete

Pour supprimer un tuple d'une table utilisez la commande DELETE FROM. La syntaxe est :
   DELETE FROM table_name
   WHERE condition;
      

Pour supprimer le fournisseur appelé 'Smith' de la table SUPPLIER la clause suivante est utilisée :
   DELETE FROM SUPPLIER
   WHERE SNAME = 'Smith';
      

Catalogues système

Dans chaque système de bases de données SQL les catalogues système sont utilisés pour savoir comment les tables, vues, index, etc. y sont définis. Ils sont abrités dans des tables normales. Un catalogue est par exemple utilisé pour stocker la requête de la définition de la vue. Chaque fois qu'une requête sur une vue est menée le système recherche d'abord la requête de définition de vue dans le catalogue et matérialise la vue avant de satisfaire la requête utilisateur.

SQL intégré

Dans cette section nous verrons comment le SQL peut être exploité à partir d'un langage de programmation classique (par ex. le C). Il existe deux principales raisons d'utiliser ainsi SQL :

Un programme utilisant le SQL se compose de clauses du langage et de clauses SQL intégré (ESQL, Embedded SQL). Chaque clause ESQL débute avec le mot-clé EXEC SQL. Les clauses ESQL sont transformées en clauses du langage hôte par un pré-compilateur (lequel insère habituellement les appels aux routines qui transmettent les requêtes SQL).

Quand nous examinons les exemples précédents, nous constatons que le résultat des requêtes est très souvent un ensemble de tuples. La plupart des langages hôtes ne sont pas destinés a opérer sur des ensembles, aussi nous avons besoin d'un mécanisme d'accès à chaque tuple de l'ensemble de tuples retourné par une clause SELECT. Ce mécanisme peut être fourni par la déclaration d'un curseur. Après ça nous pouvons utiliser la commande FETCH pour retrouver un tuple et placer le curseur sur le prochain tuple.

Pour plus de détails sur le SQL intégré voir :

A Guide to the SQL Standard (C. J. Date and Hugh Darwen, ISBN: 0-201-96426-0, 1997, Addison-Wesley)

An Introduction to Database Systems (C. J. Date, 1994, Addison-Wesley)

Principles of Database and Knowledge (Jeffrey D. Ullman, Computer Science Press, 1988)