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 :
Commandes pour l'insertion, la suppression ou la modification de données.
Possibilité arithmétique : en SQL il est possible d'ajouter des opérations arithmétiques aussi bien que des comparaisons, ex. A < B + 3. Notez que "+" ou d'autres opérateurs arithmétiques n'apparaissent ni dans l'algèbre ni dans le calcul relationnels.
Les commandes d'allocation et d'impression : il est possible d'imprimer une relation construite par une requête et d'affecter un nom de relation à une relation calculée.
Fonctions d'agrégation : les opérations comme average, sum, max, etc. peuvent être appliquées aux colonnes d'une relation pour obtenir une quantité unique.
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.
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; |
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; |
PNAME | PRICE --------+-------- Bolt | 15 Cam | 25 |
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); |
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; |
PNAME | DOUBLE --------+--------- Screw | 20 Nut | 16 Bolt | 30 |
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; |
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.
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; |
COUNT ------- 4 |
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; |
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.
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; |
SNO | SNAME | COUNT -----+-------+------- 1 | Smith | 2 3 | Adams | 2 4 | Blake | 3 |
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.
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'; |
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; |
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; |
SNO | SNAME | CITY -----+-------+-------- 2 | Jones | Paris 3 | Adams | Vienna |
Le langage SQL intègre un ensemble de commandes utilisées pour la définition des données.
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); |
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 ≥ p ≥ qq ≥ 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.
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.
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 |
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'; |
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.
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; |
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); |
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.
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'; |
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.
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 :
Certaines requêtes ne peuvent être formulées en utilisant le pur SQL (par exemple les requêtes récursives). Pour pouvoir les formuler nous avons besoin d'un langage offrant une plus grande puissance d'expression que SQL.
Nous voulons accéder à une base depuis une application développée dans un langage donné (ex. un système de réservation de tickets avec une interface utilisateur graphique est écrite en C et l'information sur les tickets encore disponibles est stockée dans une base qui peut être accessible en utilisant le SQL intégré).
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)