PG 11
PG 11
22
Legal Notice
PostgreSQL is Copyright (c) 1996-2023 by the PostgreSQL Global Development Group.
Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written
agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE
AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED
HEREUNDER IS ON AN « AS-IS » BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
Table des matières
Préface .................................................................................................................... xxxi
1. Définition de PostgreSQL ............................................................................... xxxi
2. Bref historique de PostgreSQL ........................................................................ xxxii
2.1. Le projet POSTGRES à Berkeley ......................................................... xxxii
2.2. Postgres95 ........................................................................................ xxxii
2.3. PostgreSQL ...................................................................................... xxxiii
3. Conventions ................................................................................................ xxxiii
4. Pour plus d'informations ............................................................................... xxxiii
5. Lignes de conduite pour les rapports de bogues ................................................. xxxiv
5.1. Identifier les bogues .......................................................................... xxxiv
5.2. Que rapporter ? .................................................................................. xxxv
5.3. Où rapporter des bogues ? .................................................................. xxxvii
I. Tutoriel .................................................................................................................... 1
1. Démarrage ........................................................................................................ 3
1.1. Installation ............................................................................................. 3
1.2. Concepts architecturaux de base ................................................................ 3
1.3. Création d'une base de données ................................................................. 4
1.4. Accéder à une base ................................................................................. 5
2. Le langage SQL ................................................................................................ 7
2.1. Introduction ............................................................................................ 7
2.2. Concepts ................................................................................................ 7
2.3. Créer une nouvelle table ........................................................................... 7
2.4. Remplir une table avec des lignes .............................................................. 8
2.5. Interroger une table ................................................................................. 9
2.6. Jointures entre les tables ......................................................................... 11
2.7. Fonctions d'agrégat ................................................................................ 13
2.8. Mises à jour ......................................................................................... 15
2.9. Suppressions ......................................................................................... 15
3. Fonctionnalités avancées ................................................................................... 16
3.1. Introduction .......................................................................................... 16
3.2. Vues .................................................................................................... 16
3.3. Clés étrangères ...................................................................................... 16
3.4. Transactions ......................................................................................... 17
3.5. Fonctions de fenêtrage ............................................................................ 19
3.6. Héritage ............................................................................................... 22
3.7. Conclusion ........................................................................................... 23
II. Langage SQL ......................................................................................................... 25
4. Syntaxe SQL ................................................................................................... 33
4.1. Structure lexicale ................................................................................... 33
4.2. Expressions de valeurs ........................................................................... 42
4.3. Fonctions appelantes .............................................................................. 57
5. Définition des données ...................................................................................... 60
5.1. Notions fondamentales sur les tables ......................................................... 60
5.2. Valeurs par défaut ................................................................................. 61
5.3. Contraintes ........................................................................................... 62
5.4. Colonnes système .................................................................................. 70
5.5. Modification des tables ........................................................................... 71
5.6. Droits .................................................................................................. 74
5.7. Politiques de sécurité niveau ligne ............................................................ 75
5.8. Schémas ............................................................................................... 82
5.9. L'héritage ............................................................................................. 86
5.10. Partitionnement de tables ...................................................................... 90
5.11. Données distantes ............................................................................... 104
5.12. Autres objets de la base de données ....................................................... 105
5.13. Gestion des dépendances ..................................................................... 105
iii
Documentation PostgreSQL 11.22
iv
Documentation PostgreSQL 11.22
v
Documentation PostgreSQL 11.22
vi
Documentation PostgreSQL 11.22
vii
Documentation PostgreSQL 11.22
viii
Documentation PostgreSQL 11.22
ix
Documentation PostgreSQL 11.22
x
Documentation PostgreSQL 11.22
xi
Documentation PostgreSQL 11.22
xii
Documentation PostgreSQL 11.22
xiii
Documentation PostgreSQL 11.22
xiv
Documentation PostgreSQL 11.22
xv
Documentation PostgreSQL 11.22
xvi
Documentation PostgreSQL 11.22
xvii
Documentation PostgreSQL 11.22
xviii
Documentation PostgreSQL 11.22
xix
Documentation PostgreSQL 11.22
xx
Documentation PostgreSQL 11.22
xxi
Liste des illustrations
60.1. Diagramme structuré d'un algorithme génétique ..................................................... 2333
xxii
Liste des tableaux
4.1. Séquences d'échappements avec antislash .................................................................. 36
4.2. Précédence des opérateurs (du plus haut vers le plus bas) ............................................. 41
8.1. Types de données ................................................................................................ 140
8.2. Types numériques ................................................................................................ 142
8.3. Types monétaires ................................................................................................. 147
8.4. Types caractère ................................................................................................... 147
8.5. Types caractères spéciaux ..................................................................................... 149
8.6. Types de données binaires ..................................................................................... 149
8.7. Octets littéraux bytea à échapper ......................................................................... 151
8.8. Octets échappés en sortie pour bytea .................................................................... 151
8.9. Types date et heure .............................................................................................. 152
8.10. Saisie de date .................................................................................................... 153
8.11. Saisie d'heure .................................................................................................... 154
8.12. Saisie des fuseaux horaires .................................................................................. 155
8.13. Saisie de dates/heures spéciales ............................................................................ 156
8.14. Styles d'affichage de date/heure ............................................................................ 157
8.15. Convention de présentation des dates ..................................................................... 157
8.16. Abréviations d'unités d'intervalle ISO 8601 ............................................................. 160
8.17. Saisie d'intervalle ............................................................................................... 161
8.18. Exemples de styles d'affichage d'intervalles ............................................................ 162
8.19. Type de données booléen .................................................................................... 162
8.20. Types géométriques ............................................................................................ 165
8.21. Types d'adresses réseau ....................................................................................... 168
8.22. Exemples de saisie de types cidr ........................................................................ 168
8.23. Types primitifs JSON et types PostgreSQL correspondants ........................................ 178
8.24. Types identifiant d'objet ...................................................................................... 208
8.25. Pseudo-Types .................................................................................................... 210
9.1. Opérateurs de comparaison .................................................................................... 211
9.2. Prédicats de comparaison ...................................................................................... 212
9.3. Fonctions de comparaison ..................................................................................... 214
9.4. Opérateurs mathématiques ..................................................................................... 214
9.5. Fonctions mathématiques ...................................................................................... 215
9.6. Fonctions de génération de nombres aléatoires .......................................................... 217
9.7. Fonctions trigonométriques .................................................................................... 217
9.8. Fonctions et opérateurs SQL pour le type chaîne ....................................................... 218
9.9. Autres fonctions de chaîne .................................................................................... 219
9.10. Conversions intégrées ......................................................................................... 226
9.11. Fonctions et opérateurs SQL pour chaînes binaires ................................................... 232
9.12. Autres fonctions sur les chaînes binaires ................................................................ 233
9.13. Opérateurs sur les chaînes de bits ......................................................................... 235
9.14. Opérateurs de correspondance des expressions rationnelles ........................................ 238
9.15. Atomes d'expressions rationnelles ......................................................................... 242
9.16. quantificateur d'expressions rationnelles ................................................................. 243
9.17. Contraintes des expressions rationnelles ................................................................. 244
9.18. Échappements de caractère dans les expressions rationnelles ...................................... 245
9.19. Échappement de raccourcis de classes dans les expressions rationnelles ........................ 246
9.20. Échappements de contrainte dans les expressions rationnelles ..................................... 246
9.21. Rétroréférences dans les expressions rationnelles ..................................................... 247
9.22. Lettres d'option intégrées à une ERA ..................................................................... 247
9.23. Fonctions de formatage ....................................................................................... 251
9.24. Modèles pour le formatage de champs de type date/heure .......................................... 252
9.25. Modificateurs de motifs pour le formatage des dates/heures ....................................... 254
9.26. Motifs de modèle pour le formatage de valeurs numériques ....................................... 256
9.27. Modifications de motifs pour le formatage numérique ............................................... 257
9.28. Exemples avec to_char .................................................................................... 257
xxiii
Documentation PostgreSQL 11.22
xxiv
Documentation PostgreSQL 11.22
xxv
Documentation PostgreSQL 11.22
xxvi
Documentation PostgreSQL 11.22
xxvii
Documentation PostgreSQL 11.22
xxviii
Documentation PostgreSQL 11.22
xxix
Liste des exemples
8.1. Utilisation des types caractère ................................................................................ 149
8.2. Utilisation du type boolean. ................................................................................ 163
8.3. Utiliser les types de chaînes de bits ......................................................................... 171
9.1. Feuille de style XSLT pour convertir du SQL/XML en HTML ..................................... 299
10.1. Résolution du type d'opérateur factoriel .................................................................. 384
10.2. Résolution de types pour les opérateurs de concaténation de chaînes ............................ 384
10.3. Résolution de types pour les opérateurs de valeur absolue et de négation ...................... 385
10.4. Résolution du type d'opérateur avec des inclusions de tableaux ................................... 385
10.5. Opérateur personnalisé sur un domaine .................................................................. 386
10.6. Résolution de types pour les arguments de la fonction arrondie ................................... 388
10.7. Résolution de fonction à arguments variables .......................................................... 389
10.8. Résolution de types pour les fonctions retournant un segment de chaîne ....................... 389
10.9. Conversion de types pour le stockage de character .............................................. 391
10.10. Résolution de types avec des types sous-spécifiés dans une union .............................. 392
10.11. Résolution de types dans une union simple ........................................................... 392
10.12. Résolution de types dans une union transposée ...................................................... 392
10.13. Résolution de type dans une union imbriquée ........................................................ 393
11.1. Mettre en place un index partiel pour exclure des valeurs courantes ............................. 401
11.2. Mettre en place un index partiel pour exclure les valeurs inintéressantes ....................... 402
11.3. Mettre en place un index d'unicité partiel ............................................................... 403
11.4. Ne pas utiliser les index partiels comme substitut au partitionnement ........................... 403
20.1. Exemple d'entrées de pg_hba.conf ................................................................... 647
20.2. Un exemple de fichier pg_ident.conf .............................................................. 650
34.1. Premier exemple de programme pour libpq ............................................................. 913
34.2. Deuxième exemple de programme pour libpq .......................................................... 916
34.3. Troisième exemple de programme pour libpq .......................................................... 919
35.1. Exemple de programme sur les objets larges avec libpq ............................................ 931
36.1. Programme de Démonstration SQLDA .................................................................. 987
36.2. Programme ECPG Accédant à un Large Object ..................................................... 1002
42.1. Installation manuelle de PL/Perl .......................................................................... 1249
43.1. Mettre entre guillemets des valeurs dans des requêtes dynamiques ............................. 1265
43.2. Exceptions avec UPDATE/INSERT ...................................................................... 1282
43.3. Une fonction trigger PL/pgSQL .......................................................................... 1296
43.4. Une fonction d'audit par trigger en PL/pgSQL ....................................................... 1297
43.5. Une fonction trigger en PL/pgSQL sur une vue pour un audit ................................... 1298
43.6. Une fonction trigger PL/pgSQL pour maintenir une table résumée ............................. 1299
43.7. Auditer avec les tables de transition ..................................................................... 1301
43.8. Une fonction PL/pgSQL pour un trigger d'événement .............................................. 1303
43.9. Portage d'une fonction simple de PL/SQL vers PL/pgSQL ....................................... 1311
43.10. Portage d'une fonction qui crée une autre fonction de PL/SQL vers PL/pgSQL ........... 1311
43.11. Portage d'une procédure avec manipulation de chaînes et paramètres OUT de PL/SQL
vers PL/pgSQL ........................................................................................................ 1313
43.12. Portage d'une procédure de PL/SQL vers PL/pgSQL ............................................. 1315
F.1. Créer une table distante pour les journaux applicatifs PostgreSQL au format CSV .......... 2669
xxx
Préface
Cet ouvrage représente l'adaptation française de la documentation officielle de PostgreSQL. Celle-ci a
été rédigée par les développeurs de PostgreSQL et quelques volontaires en parallèle du développement
du logiciel. Elle décrit toutes les fonctionnalités officiellement supportées par la dernière version de
PostgreSQL.
Afin de faciliter l'accès aux informations qu'il contient, cet ouvrage est organisé en plusieurs parties.
Chaque partie est destinée à une classe précise d'utilisateurs ou à des utilisateurs de niveaux d'expertise
différents :
• la Partie II présente l'environnement du langage de requêtes SQL, notamment les types de données,
les fonctions et les optimisations utilisateurs. Tout utilisateur de PostgreSQL devrait la lire ;
• la Partie V, destinée aux utilisateurs expérimentés, présente les éléments d'extension du serveur,
notamment les types de données et les fonctions utilisateurs ;
1. Définition de PostgreSQL
PostgreSQL est un système de gestion de bases de données relationnelles objet (ORDBMS) fondé sur
POSTGRES, Version 4.21. Ce dernier a été développé à l'université de Californie au département des
sciences informatiques de Berkeley. POSTGRES est à l'origine de nombreux concepts qui ne seront
rendus disponibles au sein de systèmes de gestion de bases de données commerciaux que bien plus tard.
PostgreSQL est un descendant libre du code original de Berkeley. Il supporte une grande partie du
standard SQL tout en offrant de nombreuses fonctionnalités modernes :
• requêtes complexes ;
• clés étrangères ;
• triggers ;
• vues modifiables ;
• intégrité transactionnelle ;
• contrôle des versions concurrentes (MVCC, acronyme de « MultiVersion Concurrency Control »).
De plus, PostgreSQL peut être étendu par l'utilisateur de multiples façons, en ajoutant, par exemple :
Et grâce à sa licence libérale, PostgreSQL peut être utilisé, modifié et distribué librement, quel que
soit le but visé, qu'il soit privé, commercial ou académique.
1
https://dsf.berkeley.edu/postgres.html
xxxi
Préface
Depuis, plusieurs versions majeures de POSTGRES ont vu le jour. La première « démo » devint
opérationnelle en 1987 et fut présentée en 1988 lors de la conférence ACM-SIGMOD. La version 1,
décrite dans [ston90a], fut livrée à quelques utilisateurs externes en juin 1989. Suite à la critique du
premier mécanisme de règles ([ston89]), celui-ci fut réécrit ([ston90b]) pour la version 2, présentée en
juin 1990. La version 3 apparut en 1991. Elle apporta le support de plusieurs gestionnaires de stockage,
un exécuteur de requêtes amélioré et une réécriture du gestionnaire de règles. La plupart des versions
qui suivirent, jusqu'à Postgres95 (voir plus loin), portèrent sur la portabilité et la fiabilité.
POSTGRES fut utilisé dans plusieurs applications, en recherche et en production. On peut citer, par
exemple : un système d'analyse de données financières, un programme de suivi des performances d'un
moteur à réaction, une base de données de suivi d'astéroïdes, une base de données médicale et plusieurs
systèmes d'informations géographiques. POSTGRES a aussi été utilisé comme support de formation
dans plusieurs universités. Illustra Information Technologies (devenu Informix2, maintenant détenu
par IBM3) a repris le code et l'a commercialisé. Fin 1992, POSTGRES est devenu le gestionnaire de
données principal du projet de calcul scientifique Sequoia 20004.
2.2. Postgres95
En 1994, Andrew Yu et Jolly Chen ajoutèrent un interpréteur de langage SQL à POSTGRES. Sous
le nouveau nom de Postgres95, le projet fut publié sur le Web comme descendant libre (OpenSource)
du code source initial de POSTGRES, version Berkeley.
Le code de Postgres95 était écrit en pur C ANSI et réduit de 25%. De nombreux changements internes
améliorèrent les performances et la maintenabilité. Les versions 1.0.x de Postgres95 passèrent le
Wisconsin Benchmark avec des performances meilleures de 30 à 50% par rapport à POSTGRES,
version 4.2. À part les correctifs de bogues, les principales améliorations furent les suivantes :
• le langage PostQUEL est remplacé par SQL (implanté sur le serveur). (La bibliothèque d'interface
libpq a été nommée à partir du langage PostQUEL.) Les requêtes imbriquées n'ont pas été supportées
avant PostgreSQL (voir plus loin), mais elles pouvaient être imitées dans Postgres95 à l'aide de
fonctions SQL utilisateur ; les agrégats furent reprogrammés, la clause GROUP BY ajoutée ;
• un nouveau programme, psql, qui utilise GNU Readline, permet l'exécution interactive de requêtes
SQL ; c'est la fin du programme monitor ;
2
https://www.ibm.com/analytics/informix
3
https://www.ibm.com/
4
http://meteora.ucsd.edu/s2k/s2k_home.html
xxxii
Préface
• une nouvelle bibliothèque cliente, libpgtcl, supporte les programmes écrits en Tcl ; un shell
exemple, pgtclsh, fournit de nouvelles commandes Tcl pour interfacer des programmes Tcl avec
Postgres95 ;
• l'interface de gestion des « Large Objects » est réécrite ; jusque-là, le seul mécanisme de stockage
de ces objets passait par le système de fichiers Inversion (« Inversion file system ») ; ce système
est abandonné ;
• le système de règles d'instance est supprimé ; les règles sont toujours disponibles en tant que règles
de réécriture ;
• un bref tutoriel présentant les possibilités du SQL ainsi que celles spécifiques à Postgres95 est
distribué avec les sources ;
• la version GNU de make est utilisée pour la construction à la place de la version BSD ; Postgres95
peut également être compilé avec un GCC sans correctif (l'alignement des doubles est corrigé).
2.3. PostgreSQL
En 1996, le nom « Postgres95 » commence à mal vieillir. Le nom choisi, PostgreSQL, souligne le lien
entre POSTGRES et les versions suivantes qui intègrent le SQL. En parallèle, la version est numérotée
6.0 pour reprendre la numérotation du projet POSTGRES de Berkeley.
Beaucoup de personnes font référence à PostgreSQL par « Postgres » (il est rare que le nom soit écrit
en capitales) par tradition ou parce que c'est plus simple à prononcer. Cet usage est accepté comme
alias ou pseudo.
Lors du développement de Postgres95, l'effort était axé sur l'identification et la compréhension des
problèmes dans le code. Avec PostgreSQL, l'accent est mis sur les nouvelles fonctionnalités, sans pour
autant abandonner les autres domaines.
3. Conventions
Les conventions suivantes sont utilisées dans le synopsis d'une commande : les crochets ([ et ])
indiquent des parties optionnelles. Les accolades ({ et }) et les barres verticales (|) indiquent un choix
entre plusieurs options. Les points de suspension (...) signifient que l'élément précédent peut être
répété. Tous les autres symboles, ceci incluant les parenthèses, devraient être acceptés directement.
Lorsque cela améliore la clarté, les commandes SQL sont précédées d'une invite =>, tandis que les
commandes shell le sont par $. Dans le cadre général, les invites ne sont pas indiquées.
Wiki
Le wiki5 de PostgreSQL contient la FAQ6 (liste des questions fréquemment posées), la liste
TODO7 et des informations détaillées sur de nombreux autres thèmes.
5
https://wiki.postgresql.org
xxxiii
Préface
Site web
Le site web8 de PostgreSQL contient des détails sur la dernière version, et bien d'autres
informations pour rendre un travail ou un investissement personnel avec PostgreSQL plus
productif.
Listes de discussion
Les listes de discussion constituent un bon endroit pour trouver des réponses à ses questions, pour
partager ses expériences avec celles d'autres utilisateurs et pour contacter les développeurs. La
consultation du site web de PostgreSQL fournit tous les détails.
Soi-même !
PostgreSQL est un projet Open Source. En tant que tel, le support dépend de la communauté des
utilisateurs. Lorsque l'on débute avec PostgreSQL, on est tributaire de l'aide des autres, soit au
travers de la documentation, soit par les listes de discussion. Il est important de faire partager à
son tour ses connaissances par la lecture des listes de discussion et les réponses aux questions.
Lorsque quelque chose est découvert qui ne figurait pas dans la documentation, pourquoi ne pas
en faire profiter les autres ? De même lors d'ajout de fonctionnalités au code.
Les suggestions suivantes ont pour but de vous former à la saisie d'un rapport de bogue qui pourra
ensuite être géré de façon efficace. Il n'est pas requis de les suivre, mais ce serait à l'avantage de tous.
Nous ne pouvons pas promettre de corriger tous les bogues immédiatement. Si le bogue est évident,
critique ou affecte un grand nombre d'utilisateurs, il y a de grandes chances pour que quelqu'un s'en
charge. Il se peut que nous vous demandions d'utiliser une version plus récente pour vérifier si le
bogue est toujours présent. Ou nous pourrions décider que le bogue ne peut être corrigé avant qu'une
réécriture massive, que nous avions planifiée, ne soit faite. Ou peut-être est-ce trop difficile et que
des choses plus importantes nous attendent. Si vous avez besoin d'aide immédiatement, envisagez
l'obtention d'un contrat de support commercial.
• Un programme refuse d'accepter une entrée valide (c'est-à-dire telle que définie dans la
documentation).
6
https://wiki.postgresql.org/wiki/Frequently_Asked_Questions
7
https://wiki.postgresql.org/wiki/Todo
8
https://www.postgresql.org
xxxiv
Préface
• Un programme accepte une entrée invalide sans information ou message d'erreur. Mais gardez en
tête que votre idée d'entrée invalide pourrait être notre idée d'une extension ou d'une compatibilité
avec les pratiques traditionnelles.
Une lenteur ou une absorption des ressources n'est pas nécessairement un bogue. Lisez la
documentation ou demandez sur une des listes de discussion de l'aide concernant l'optimisation de vos
applications. Ne pas se conformer au standard SQL n'est pas nécessairement un bogue, sauf si une
telle conformité est indiquée explicitement.
Avant de continuer, vérifiez sur la liste des choses à faire ainsi que dans la FAQ pour voir si votre
bogue n'est pas déjà connu. Si vous n'arrivez pas à décoder les informations sur la liste des choses à
faire, écrivez un rapport. Le minimum que nous puissions faire est de rendre cette liste plus claire.
Les éléments suivants devraient être fournis avec chaque rapport de bogue :
• La séquence exacte des étapes nécessaires pour reproduire le problème à partir du lancement du
programme. Ceci devrait se suffire ; il n'est pas suffisant d'envoyer une simple instruction SELECT
sans les commandes CREATE TABLE et INSERT qui ont précédé, si la sortie devait dépendre des
données contenues dans les tables. Nous n'avons pas le temps de comprendre le schéma de votre
base de données. Si nous sommes supposés créer nos propres données, nous allons probablement
ne pas voir le problème.
Le meilleur format pour un test suite à un problème relatif à SQL est un fichier qui peut être lancé
via l'interface psql et qui montrera le problème. (Assurez-vous de ne rien avoir dans votre fichier
de lancement ~/.psqlrc.) Un moyen facile pour créer ce fichier est d'utiliser pg_dump pour
récupérer les déclarations des tables ainsi que les données nécessaires pour mettre en place la scène.
Il ne reste plus qu'à ajouter la requête posant problème. Vous êtes encouragé à minimiser la taille
de votre exemple, mais ce n'est pas une obligation. Si le bogue est reproductible, nous le trouverons
de toute façon.
Si votre application utilise une autre interface client, telle que PHP, alors essayez d'isoler le
problème aux requêtes erronées. Nous n'allons certainement pas mettre en place un serveur web pour
reproduire votre problème. Dans tous les cas, rappelez-vous d'apporter les fichiers d'entrée exacts ;
n'essayez pas de deviner que le problème se pose pour les « gros fichiers », pour les « bases de
données de moyenne taille », etc., car cette information est trop inexacte, subjective pour être utile.
• La sortie que vous obtenez. Merci de ne pas dire que cela « ne fonctionne pas » ou s'est « arrêté
brutalement ». S'il existe un message d'erreur, montrez-le même si vous ne le comprenez pas. Si le
programme se termine avec une erreur du système d'exploitation, dites-le. Même si le résultat de
votre test est un arrêt brutal du programme ou un autre souci évident, il pourrait ne pas survenir sur
notre plate-forme. Le plus simple est de copier directement la sortie du terminal, si possible.
xxxv
Préface
Note
Si vous rapportez un message d'erreur, merci d'obtenir la forme la plus verbeuse de ce
message. Avec psql, exécutez \set VERBOSITY verbose avant tout. Si vous récupérez
le message des traces du serveur, initialisez la variable d'exécution log_error_verbosity avec
verbose pour que tous les détails soient tracés.
Note
Dans le cas d'erreurs fatales, le message d'erreur rapporté par le client pourrait ne pas
contenir toutes les informations disponibles. Jetez aussi un œil aux traces du serveur de la
base de données. Si vous ne conservez pas les traces de votre serveur, c'est le bon moment
pour commencer à le faire.
• Il est très important de préciser ce que vous attendez en sortie. Si vous écrivez uniquement « Cette
commande m'a donné cette réponse. » ou « Ce n'est pas ce que j'attendais. », nous pourrions le
lancer nous-mêmes, analyser la sortie et penser que tout est correct, car cela correspond exactement
à ce que nous attendions. Nous ne devrions pas avoir à passer du temps pour décoder la sémantique
exacte de vos commandes. Tout spécialement, ne vous contentez pas de dire que « Ce n'est pas
ce que SQL spécifie/Oracle fait. » Rechercher le comportement correct à partir de SQL n'est pas
amusant et nous ne connaissons pas le comportement de tous les autres serveurs de bases de données
relationnelles. (Si votre problème est un arrêt brutal du serveur, vous pouvez évidemment omettre
cet élément.)
• Toutes les options en ligne de commande ainsi que les autres options de lancement incluant les
variables d'environnement ou les fichiers de configuration que vous avez modifiées. Encore une
fois, soyez exact. Si vous utilisez une distribution prépackagée qui lance le serveur au démarrage,
vous devriez essayer de retrouver ce que cette distribution fait.
• Tout ce que vous avez fait de différent à partir des instructions d'installation.
• La version de PostgreSQL. Vous pouvez lancer la commande SELECT version(); pour trouver
la version du serveur sur lequel vous êtes connecté. La plupart des exécutables disposent aussi
d'une option --version ; postgres --version et psql --version devraient au moins
fonctionner. Si la fonction ou les options n'existent pas, alors votre version est bien trop ancienne
et vous devez mettre à jour. Si vous avez lancé une version préparée sous forme de paquets, tels
que les RPM, dites-le en incluant la sous-version que le paquet pourrait avoir. Si vous êtes sur une
version Git, mentionnez-le en indiquant le hachage du commit.
Si votre version est antérieure à la 11.22, nous allons certainement vous demander de mettre à jour.
Beaucoup de corrections de bogues et d'améliorations sont apportées dans chaque nouvelle version,
donc il est bien possible qu'un bogue rencontré dans une ancienne version de PostgreSQL soit
déjà corrigé. Nous ne fournissons qu'un support limité pour les sites utilisant d'anciennes versions
de PostgreSQL ; si vous avez besoin de plus de support que ce que nous fournissons, considérez
l'acquisition d'un contrat de support commercial.
N'ayez pas peur si votre rapport de bogue devient assez long. C'est un fait. Il est préférable de rapporter
tous les faits la première fois plutôt que nous ayons à vous tirer les vers du nez. D'un autre côté, si vos
xxxvi
Préface
fichiers d'entrée sont trop gros, il est préférable de demander si quelqu'un souhaite s'y plonger. Voici
un article9 qui donne quelques autres conseils sur les rapports de bogues.
Ne passez pas tout votre temps à vous demander quelles modifications apporter pour que le problème
s'en aille. Ceci ne nous aidera probablement pas à le résoudre. S'il arrive que le bogue ne puisse pas
être corrigé immédiatement, vous aurez toujours l'opportunité de chercher ceci et de partager vos
trouvailles. De même, encore une fois, ne perdez pas votre temps à deviner pourquoi le bogue existe.
Nous le trouverons assez rapidement.
Lors de la rédaction d'un rapport de bogue, merci de choisir une terminologie qui ne laisse pas place aux
confusions. Le paquet logiciel en totalité est appelé « PostgreSQL », quelquefois « Postgres » en court.
Si vous parlez spécifiquement du serveur, mentionnez-le, mais ne dites pas seulement « PostgreSQL a
planté ». Un arrêt brutal d'un seul processus serveur est assez différent de l'arrêt brutal du « postgres »
père ; merci de ne pas dire que « le serveur a planté » lorsque vous voulez dire qu'un seul processus
s'est arrêté, ni vice versa. De plus, les programmes clients tels que l'interface interactive « psql » sont
complètement séparés du moteur. Essayez d'être précis sur la provenance du problème : client ou
serveur.
Une autre méthode consiste à remplir le formulaire web disponible sur le site web10 du projet.
Saisir un rapport de bogue de cette façon fait que celui-ci est envoyé à la liste de discussion
<[email protected]>.
Si votre rapport de bogue a des implications sur la sécurité et que vous préféreriez
qu'il ne soit pas immédiatement visible dans les archives publiques, ne l'envoyez pas
sur pgsql-bugs. Les problèmes de sécurité peuvent être rapportés de façon privée sur
<[email protected]>.
N'envoyez pas de rapports de bogue aux listes de discussion des utilisateurs, comme
<[email protected]> ou
<[email protected]>. Ces listes de discussion servent à répondre
aux questions des utilisateurs et les abonnés ne souhaitent pas recevoir de rapports de bogue. Plus
important, ils ont peu de chance de les corriger.
De même, n'envoyez pas vos rapports de bogue à la liste de discussion des développeurs
<[email protected]>. Cette liste sert aux discussions concernant le
développement de PostgreSQL et il serait bon de conserver les rapports de bogue séparément. Nous
pourrions choisir de discuter de votre rapport de bogue sur pgsql-hackers si le problème nécessite
que plus de personnes s'en occupent.
Si vous avez un problème avec la documentation, le meilleur endroit pour le rapporter est la liste de
discussion pour la documentation <[email protected]>. Soyez précis sur
la partie de la documentation qui vous déplaît.
Si votre bogue concerne un problème de portabilité sur une plate-forme non supportée, envoyez
un courrier électronique à <[email protected]>, pour que nous
puissions travailler sur le portage de PostgreSQL sur votre plate-forme.
Note
Dû, malheureusement, au grand nombre de pourriels (spam), toutes les adresses de courrier
électronique ci-dessus sont modérées sauf si vous avez souscrit. Ceci signifie qu'il y aura un
9
https://www.chiark.greenend.org.uk/~sgtatham/bugs.html
10
https://www.postgresql.org/
xxxvii
Préface
certain délai avant que l'email ne soit délivré. Si vous souhaitez souscrire aux listes, merci de
visiter https://lists.postgresql.org/ pour les instructions.
xxxviii
Partie I. Tutoriel
Bienvenue dans le tutoriel de PostgreSQL. Les chapitres suivants présentent une courte introduction à PostgreSQL,
aux concepts des bases de données relationnelles et au langage SQL à ceux qui débutent dans l'un de ces
domaines. Seules sont nécessaires des connaissances générales sur l'utilisation des ordinateurs. Aucune expérience
particulière d'Unix ou de programmation n'est requise. Ce tutoriel a surtout pour but de faire acquérir une
expérience pratique des aspects importants du système PostgreSQL. Il n'est ni exhaustif ni complet, mais
introductif.
À la suite de ce tutoriel, la lecture de la Partie II permettra d'acquérir une connaissance plus complète du langage
SQL, celle de la Partie IV des informations sur le développement d'applications. La configuration et la gestion
sont détaillées dans la Partie III.
Table des matières
1. Démarrage ................................................................................................................ 3
1.1. Installation ..................................................................................................... 3
1.2. Concepts architecturaux de base ........................................................................ 3
1.3. Création d'une base de données ......................................................................... 4
1.4. Accéder à une base ......................................................................................... 5
2. Le langage SQL ........................................................................................................ 7
2.1. Introduction .................................................................................................... 7
2.2. Concepts ........................................................................................................ 7
2.3. Créer une nouvelle table ................................................................................... 7
2.4. Remplir une table avec des lignes ...................................................................... 8
2.5. Interroger une table ......................................................................................... 9
2.6. Jointures entre les tables ................................................................................. 11
2.7. Fonctions d'agrégat ........................................................................................ 13
2.8. Mises à jour ................................................................................................. 15
2.9. Suppressions ................................................................................................. 15
3. Fonctionnalités avancées ........................................................................................... 16
3.1. Introduction .................................................................................................. 16
3.2. Vues ............................................................................................................ 16
3.3. Clés étrangères .............................................................................................. 16
3.4. Transactions ................................................................................................. 17
3.5. Fonctions de fenêtrage ................................................................................... 19
3.6. Héritage ....................................................................................................... 22
3.7. Conclusion ................................................................................................... 23
2
Chapitre 1. Démarrage
1.1. Installation
Avant de pouvoir utiliser PostgreSQL, vous devez l'installer. Il est possible que PostgreSQL soit
déjà installé dans votre environnement, soit parce qu'il est inclus dans votre distribution, soit parce
que votre administrateur système s'en est chargé. Dans ce cas, vous devriez obtenir les informations
nécessaires pour accéder à PostgreSQL dans la documentation de votre distribution ou de la part de
votre administrateur.
Si vous n'êtes pas sûr que PostgreSQL soit déjà disponible ou que vous puissiez l'utiliser pour vos
tests, vous avez la possibilité de l'installer vous-même. Le faire n'est pas difficile et peut être un bon
exercice. PostgreSQL peut être installé par n'importe quel utilisateur sans droit particulier. Aucun
accès administrateur (root) n'est requis.
Si vous installez PostgreSQL vous-même, référez-vous au Chapitre 16, pour les instructions sur
l'installation, puis revenez à ce guide quand l'installation est terminée. Nous vous conseillons de suivre
attentivement la section sur la configuration des variables d'environnement appropriées.
Si votre administrateur n'a pas fait une installation par défaut, vous pouvez avoir à effectuer un
paramétrage supplémentaire. Par exemple, si le serveur de bases de données est une machine distante,
vous aurez besoin de configurer la variable d'environnement PGHOST avec le nom du serveur de bases
de données. Il sera aussi peut-être nécessaire de configurer la variable d'environnement PGPORT. La
démarche est la suivante : si vous essayez de démarrer un programme et qu'il se plaint de ne pas
pouvoir se connecter à la base de données, vous devez consulter votre administrateur ou, si c'est vous,
la documentation pour être sûr que votre environnement est correctement paramétré. Si vous n'avez
pas compris le paragraphe précédent, lisez donc la prochaine section.
Dans le jargon des bases de données, PostgreSQL utilise un modèle client/serveur. Une session
PostgreSQL est le résultat de la coopération des processus (programmes) suivants :
• Un processus serveur, qui gère les fichiers de la base de données, accepte les connexions à la base
de la part des applications clientes et effectue sur la base les actions des clients. Le programme
serveur est appelé postgres.
• L'application cliente (l'application de l'utilisateur), qui veut effectuer des opérations sur la base de
données. Les applications clientes peuvent être de natures très différentes : un client peut être un outil
texte, une application graphique, un serveur web qui accède à la base de données pour afficher des
pages web ou un outil spécialisé dans la maintenance de bases de données. Certaines applications
clientes sont fournies avec PostgreSQL ; la plupart sont développées par les utilisateurs.
Comme souvent avec les applications client/serveur, le client et le serveur peuvent être sur des hôtes
différents. Dans ce cas, ils communiquent à travers une connexion réseau TCP/IP. Vous devez garder
cela à l'esprit, car les fichiers qui sont accessibles sur la machine cliente peuvent ne pas l'être (ou l'être
seulement en utilisant des noms de fichiers différents) sur la machine exécutant le serveur de bases
de données.
Le serveur PostgreSQL peut traiter de multiples connexions simultanées depuis les clients. Dans ce
but, il démarre un nouveau processus pour chaque connexion. À ce moment, le client et le nouveau
processus serveur communiquent sans intervention de la part du processus postgres original. Ainsi,
le processus serveur maître s'exécute toujours, attendant de nouvelles connexions clientes, tandis
3
Démarrage
que le client et les processus serveur associés vont et viennent (bien sûr, tout ceci est invisible pour
l'utilisateur ; nous le mentionnons ici seulement par exhaustivité).
Il est possible que votre administrateur ait déjà créé une base pour vous. Dans ce cas, vous pouvez
omettre cette étape et aller directement à la prochaine section.
Pour créer une nouvelle base, nommée ma_base dans cet exemple, utilisez la commande suivante :
$ createdb ma_base
Si cette commande ne fournit aucune réponse, cette étape est réussie et vous pouvez sauter le reste
de cette section.
alors PostgreSQL n'a pas été installé correctement. Soit il n'a pas été installé du tout, soit le chemin
système n'a pas été configuré pour l'inclure. Essayez d'appeler la commande avec le chemin absolu :
$ /usr/local/pgsql/bin/createdb ma_base
Le chemin sur votre serveur peut être différent. Contactez votre administrateur ou vérifiez dans les
instructions d'installation pour corriger la commande.
Cela signifie que le serveur n'était pas démarré, ou qu'il n'était pas démarré là où createdb l'attendait.
Une fois encore, vérifiez les instructions d'installation ou consultez votre administrateur.
mais avec votre propre nom de connexion mentionné à la place de joe. Ceci survient si l'administrateur
n'a pas créé de compte utilisateur PostgreSQL pour vous (les comptes utilisateurs PostgreSQL sont
distincts de ceux du système d'exploitation). Si vous êtes l'administrateur, la lecture du Chapitre 21
vous expliquera comment créer de tels comptes. Vous aurez besoin de prendre l'identité de l'utilisateur
du système d'exploitation sous lequel PostgreSQL a été installé (généralement postgres) pour créer
le compte du premier utilisateur. Cela pourrait aussi signifier que vous avez un nom d'utilisateur
PostgreSQL qui est différent de celui de votre compte utilisateur du système d'exploitation. Dans ce
cas, vous avez besoin d'utiliser l'option -U ou de configurer la variable d'environnement PGUSER
pour spécifier votre nom d'utilisateur PostgreSQL.
Si vous n'avez pas les droits requis pour créer une base, vous verrez le message suivant :
4
Démarrage
Tous les utilisateurs n'ont pas l'autorisation de créer de nouvelles bases de données. Si PostgreSQL
refuse de créer des bases pour vous, alors il faut que l'administrateur vous accorde ce droit. Consultez
votre administrateur si cela arrive. Si vous avez installé vous-même l'instance PostgreSQL, alors vous
devez ouvrir une session sous le compte utilisateur que vous avez utilisé pour démarrer le serveur. 1
Vous pouvez aussi créer des bases de données avec d'autres noms. PostgreSQL vous permet de créer un
nombre quelconque de bases sur un site donné. Le nom des bases doit avoir comme premier caractère
un caractère alphabétique et est limité à 63 octets de longueur. Un choix pratique est de créer une base
avec le même nom que votre nom d'utilisateur courant. Beaucoup d'outils utilisent ce nom comme
nom par défaut pour la base : cela permet de gagner du temps en saisie. Pour créer cette base, tapez
simplement :
$ createdb
Si vous ne voulez plus utiliser votre base, vous pouvez la supprimer. Par exemple, si vous êtes
le propriétaire (créateur) de la base ma_base, vous pouvez la détruire en utilisant la commande
suivante :
$ dropdb ma_base
(Pour cette commande, le nom de la base n'est pas par défaut le nom du compte utilisateur. Vous devez
toujours en spécifier un.) Cette action supprime physiquement tous les fichiers associés avec la base
de données et elle ne peut pas être annulée, donc cela doit se faire avec beaucoup de prudence.
• Démarrez le programme en ligne de commande de PostgreSQL, appelé psql, qui vous permet de
saisir, d'éditer et d'exécuter de manière interactive des commandes SQL.
• Utilisez un outil existant avec une interface graphique comme pgAdmin ou une suite bureautique
avec un support ODBC ou JDBC pour créer et manipuler une base. Ces possibilités ne sont pas
couvertes dans ce tutoriel.
• Écrivez une application personnalisée en utilisant un des nombreux langages disponibles. Ces
possibilités sont davantage examinées dans la Partie IV.
Vous aurez probablement besoin de lancer psql pour essayer les exemples de ce tutoriel. Pour cela,
saisissez la commande suivante :
$ psql ma_base
Si vous n'indiquez pas le nom de la base, alors psql utilisera par défaut le nom de votre compte
utilisateur. Vous avez déjà découvert ce principe dans la section précédente en utilisant createdb.
psql (11.22)
Type "help" for help.
ma_base=>
5
Démarrage
ma_base=#
Cela veut dire que vous êtes le super-utilisateur de la base de données, ce qui est souvent le cas si vous
avez installé PostgreSQL vous-même. Être super-utilisateur ou administrateur signifie que vous n'êtes
pas sujet aux contrôles d'accès. Concernant ce tutoriel, cela n'a pas d'importance.
Si vous rencontrez des problèmes en exécutant psql, alors retournez à la section précédente. Les
diagnostics de psql et de createdb sont semblables. Si le dernier fonctionnait, alors le premier
devrait fonctionner également.
La dernière ligne affichée par psql est l'invite. Cela indique que psql est à l'écoute et que vous
pouvez saisir des requêtes SQL dans l'espace de travail maintenu par psql. Essayez ces commandes :
ma_base=> SELECT 2 + 2;
?column?
----------
4
(1 row)
Le programme psql dispose d'un certain nombre de commandes internes qui ne sont pas des
commandes SQL. Elles commencent avec le caractère antislash (une barre oblique inverse, « \ »). Par
exemple, vous pouvez obtenir de l'aide sur la syntaxe de nombreuses commandes SQL de PostgreSQL
en exécutant :
ma_base=> \h
ma_base=> \q
et psql se terminera et vous ramènera à votre shell. Pour plus de commandes internes, saisissez \?
à l'invite de psql. Les possibilités complètes de psql sont documentées dans psql. Dans ce tutoriel,
nous ne verrons pas ces caractéristiques explicitement, mais vous pouvez les utiliser vous-même quand
cela vous est utile.
6
Chapitre 2. Le langage SQL
2.1. Introduction
Ce chapitre fournit un panorama sur la façon d'utiliser SQL pour exécuter des opérations simples.
Ce tutoriel est seulement prévu pour vous donner une introduction et n'est, en aucun cas, un tutoriel
complet sur SQL. De nombreux livres ont été écrits sur SQL, incluant [melt93] et [date97]. Certaines
caractéristiques du langage de PostgreSQL sont des extensions de la norme.
Dans les exemples qui suivent, nous supposons que vous avez créé une base de données appelée
ma_base, comme cela a été décrit dans le chapitre précédent et que vous avez été capable de lancer
psql.
Les exemples dans ce manuel peuvent aussi être trouvés dans le répertoire src/tutorial/ de
la distribution source de PostgreSQL. (Les distributions binaires de PostgreSQL pourraient ne pas
fournir ces fichiers.) Pour utiliser ces fichiers, commencez par changer de répertoire et lancez make :
$ cd .../src/tutorial
$ make
Ceci crée les scripts et compile les fichiers C contenant des fonctions et types définis par l'utilisateur.
Puis, pour lancer le tutoriel, faites ce qui suit :
$ psql -s ma_base
...
ma_base=> \i basics.sql
La commande \i de psql lit les commandes depuis le fichier spécifié. L'option -s vous place dans
un mode pas à pas qui fait une pause avant d'envoyer chaque instruction au serveur. Les commandes
utilisées dans cette section sont dans le fichier basics.sql.
2.2. Concepts
PostgreSQL est un système de gestion de bases de données relationnelles (SGBDR). Cela signifie
que c'est un système pour gérer des données stockées dans des relations. Relation est essentiellement
un terme mathématique pour table. La notion de stockage de données dans des tables est si commune
aujourd'hui que cela peut sembler en soi évident, mais il y a de nombreuses autres manières d'organiser
des bases de données. Les fichiers et répertoires dans les systèmes d'exploitation de type Unix forment
un exemple de base de données hiérarchique. Un développement plus moderne est une base de données
orientée objet.
Chaque table est un ensemble de lignes. Chaque ligne d'une table donnée a le même ensemble de
colonnes et chaque colonne est d'un type de données particulier. Tandis que les colonnes ont un ordre
fixé dans chaque ligne, il est important de se rappeler que SQL ne garantit, d'aucune façon, l'ordre des
lignes à l'intérieur de la table (bien qu'elles puissent être explicitement triées pour l'affichage).
Les tables sont groupées dans des bases de données et un ensemble de bases gérées par une instance
unique du serveur PostgreSQL constitue une instance de bases (cluster en anglais).
7
Le langage SQL
Vous pouvez saisir cela dans psql avec les sauts de lignes. psql reconnaîtra que la commande n'est
pas terminée jusqu'à arriver à un point-virgule.
Les espaces blancs (c'est-à-dire les espaces, les tabulations et les retours à la ligne) peuvent être
librement utilisés dans les commandes SQL. Cela signifie que vous pouvez saisir la commande ci-
dessus alignée différemment ou même sur une seule ligne. Deux tirets (« -- ») introduisent des
commentaires. Ce qui les suit est ignoré jusqu'à la fin de la ligne. SQL est insensible à la casse pour les
mots-clés et les identifiants, excepté quand les identifiants sont entre doubles guillemets pour préserver
leur casse (non fait ci-dessus).
varchar(80) spécifie un type de données pouvant contenir une chaîne de caractères arbitraires
de 80 caractères au maximum. int est le type entier normal. real est un type pour les nombres
décimaux en simple précision. date devrait s'expliquer de lui-même (oui, la colonne de type date
est aussi nommée date ; cela peut être commode ou porter à confusion, à vous de choisir).
PostgreSQL prend en charge les types SQL standards int, smallint, real, double
precision, char(N), varchar(N), date, time, timestamp et interval, ainsi que
d'autres types d'utilité générale et un riche ensemble de types géométriques. PostgreSQL peut être
personnalisé avec un nombre arbitraire de types de données définis par l'utilisateur. En conséquence,
les noms des types ne sont pas des mots-clés dans la syntaxe sauf lorsqu'il est requis de supporter des
cas particuliers dans la norme SQL.
Pour finir, vous devez savoir que si vous n'avez plus besoin d'une table ou que vous voulez la recréer
différemment, vous pouvez la supprimer en utilisant la commande suivante :
Notez que tous les types utilisent des formats d'entrées plutôt évidents. Les constantes qui ne sont pas
des valeurs numériques simples doivent être habituellement entourées par des guillemets simples (')
comme dans l'exemple. Le type date est en réalité tout à fait flexible dans ce qu'il accepte, mais, pour
ce tutoriel, nous collerons au format non ambigu montré ici.
Le type point demande une paire de coordonnées en entrée, comme cela est montré ici :
La syntaxe utilisée jusqu'à maintenant nécessite de se rappeler l'ordre des colonnes. Une syntaxe
alternative vous autorise à lister les colonnes explicitement :
8
Le langage SQL
Vous pouvez lister les colonnes dans un ordre différent si vous le souhaitez ou même omettre certaines
colonnes ; par exemple, si la précipitation est inconnue :
De nombreux développeurs considèrent que le listage explicite des colonnes est un meilleur style que
de compter sur l'ordre implicite.
Merci d'exécuter toutes les commandes vues ci-dessus de façon à avoir des données sur lesquelles
travailler dans les prochaines sections.
Vous auriez pu aussi utiliser COPY pour charger de grandes quantités de données depuis des fichiers
texte. C'est habituellement plus rapide, car la commande COPY est optimisée pour cet emploi, mais
elle est moins flexible que INSERT. Par exemple :
où le nom du fichier source doit être disponible sur la machine qui exécute le processus serveur, car
le processus serveur lit le fichier directement. Vous avez plus d'informations sur la commande COPY
dans COPY.
Ici, * est un raccourci pour « toutes les colonnes ». 1 Donc, le même résultat pourrait être obtenu avec :
Vous pouvez écrire des expressions, pas seulement des références à de simples colonnes, dans la liste
de sélection. Par exemple, vous pouvez faire :
9
Le langage SQL
Notez comment la clause AS est utilisée pour renommer la sortie d'une colonne (cette clause AS est
optionnelle).
Une requête peut être « qualifiée » en ajoutant une clause WHERE qui spécifie les lignes souhaitées.
La clause WHERE contient une expression booléenne et seules les lignes pour lesquelles l'expression
booléenne est vraie sont renvoyées. Les opérateurs booléens habituels (AND, OR et NOT) sont autorisés
dans la qualification. Par exemple, ce qui suit recherche le temps à San Francisco les jours pluvieux :
Résultat :
Vous pouvez demander à ce que les résultats d'une requête soient renvoyés dans un ordre trié :
Dans cet exemple, l'ordre de tri n'est pas spécifié complètement, donc vous pouvez obtenir les lignes
San Francisco dans n'importe quel ordre. Mais, vous auriez toujours obtenu les résultats affichés ci-
dessus si vous aviez fait :
Vous pouvez demander que les lignes dupliquées soient supprimées du résultat d'une requête :
ville
---------------
Hayward
San Francisco
(2 rows)
De nouveau, l'ordre des lignes résultat pourrait varier. Vous pouvez vous assurer des résultats
cohérents en utilisant DISTINCT et ORDER BY ensemble : 2
10
Le langage SQL
ORDER BY ville;
Note
Ceci est uniquement un modèle conceptuel. La jointure est habituellement exécutée d'une
manière plus efficace que la comparaison de chaque paire de lignes, mais c'est invisible pour
l'utilisateur.
SELECT *
FROM temps, villes
WHERE ville = nom;
• Il n'y a pas de lignes pour la ville de Hayward dans le résultat. C'est parce qu'il n'y a aucune entrée
correspondante dans la table villes pour Hayward, donc la jointure ignore les lignes n'ayant pas
de correspondance avec la table temps. Nous verrons rapidement comment cela peut être résolu.
• Il y a deux colonnes contenant le nom des villes. C'est correct, car les listes des colonnes des tables
temps et villes sont concaténées. En pratique, ceci est indésirable, vous voudrez probablement
lister les colonnes explicitement plutôt que d'utiliser * :
Exercice : Essayez de déterminer la sémantique de cette requête quand la clause WHERE est omise.
Puisque toutes les colonnes ont un nom différent, l'analyseur a automatiquement trouvé à quelle table
elles appartiennent. Si des noms de colonnes sont communs entre les deux tables, vous aurez besoin
de qualifier les noms des colonnes pour préciser celles dont vous parlez. Par exemple :
11
Le langage SQL
La qualification des noms de colonnes dans une requête de jointure est fréquemment considérée
comme une bonne pratique. Cela évite l'échec de la requête si un nom de colonne dupliqué est ajouté
plus tard dans une des tables.
Les requêtes de jointure vues jusqu'ici peuvent aussi être écrites sous une autre forme :
SELECT *
FROM temps INNER JOIN villes ON (temps.ville = villes.nom);
Cette syntaxe n'est pas aussi couramment utilisée que les précédentes, mais nous la montrons ici pour
vous aider à comprendre les sujets suivants.
Maintenant, nous allons essayer de comprendre comment nous pouvons avoir les entrées de Hayward.
Nous voulons que la requête parcoure la table temps et que, pour chaque ligne, elle trouve la (ou les)
ligne(s) de villes correspondante(s). Si aucune ligne correspondante n'est trouvée, nous voulons
que les valeurs des colonnes de la table villes soient remplacées par des « valeurs vides ». Ce genre
de requêtes est appelé jointure externe (outer join). (Les jointures que nous avons vues jusqu'ici sont
des jointures internes -- inner joins). La commande ressemble à cela :
SELECT *
FROM temps LEFT OUTER JOIN villes ON (temps.ville =
villes.nom);
Cette requête est appelée une jointure externe à gauche (left outer join) parce que la table mentionnée
à la gauche de l'opérateur de jointure aura au moins une fois ses lignes dans le résultat, tandis que la
table sur la droite aura seulement les lignes qui correspondent à des lignes de la table de gauche. Lors
de l'affichage d'une ligne de la table de gauche pour laquelle il n'y a pas de correspondance dans la
table de droite, des valeurs vides (appelées NULL) sont utilisées pour les colonnes de la table de droite.
Exercice : Il existe aussi des jointures externes à droite et des jointures externes complètes. Essayez
de trouver ce qu'elles font.
Nous pouvons également joindre une table avec elle-même. Ceci est appelé une jointure réflexive.
Comme exemple, supposons que nous voulions trouver toutes les entrées de temps qui sont dans un
intervalle de températures d'autres entrées de temps. Nous avons donc besoin de comparer les colonnes
t_basse et t_haute de chaque ligne de temps aux colonnes t_basse et t_haute de toutes
les autres lignes de temps. Nous pouvons faire cela avec la requête suivante :
12
Le langage SQL
(2 rows)
Dans cet exemple, nous avons renommé la table temps en T1 et en T2 pour être capables de distinguer
respectivement le côté gauche et le côté droit de la jointure. Vous pouvez aussi utiliser ce genre d'alias
dans d'autres requêtes pour économiser de la frappe, c'est-à-dire :
SELECT *
FROM temps t, villes v
WHERE t.ville = v.nom;
Comme exemple, nous pouvons trouver la température la plus haute parmi les températures basses
avec :
max
-----
46
(1 row)
Si nous voulons connaître dans quelle ville (ou villes) ces lectures se sont produites, nous pouvons
essayer :
mais cela ne marchera pas puisque l'agrégat max ne peut pas être utilisé dans une clause WHERE (cette
restriction existe parce que la clause WHERE détermine les lignes qui seront traitées par l'agrégat ; donc
les lignes doivent être évaluées avant que les fonctions d'agrégat ne calculent leur résultat). Cependant,
comme cela est souvent le cas, la requête peut être répétée pour arriver au résultat attendu, ici en
utilisant une sous-requête :
ville
---------------
San Francisco
(1 row)
Ceci est correct, car la sous-requête est un calcul indépendant qui traite son propre agrégat séparément
à partir de ce qui se passe dans la requête externe.
Les agrégats sont également très utiles s'ils sont combinés avec les clauses GROUP BY. Par exemple,
nous pouvons obtenir le nombre de prises de température et la température la plus haute parmi les
températures basses observées dans chaque ville avec :
13
Le langage SQL
ce qui nous donne une ligne par ville dans le résultat. Chaque résultat d'agrégat est calculé avec
les lignes de la table correspondant à la ville. Nous pouvons filtrer ces lignes groupées en utilisant
HAVING :
ce qui nous donne le même résultat uniquement pour les villes qui ont toutes leurs valeurs de t_basse
en dessous de 40. Pour finir, si nous nous préoccupons seulement des villes dont le nom commence
par « S », nous pouvons faire :
1 L'opérateur LIKE fait la correspondance avec un motif ; cela est expliqué dans la Section 9.7.
Il est important de comprendre l'interaction entre les agrégats et les clauses SQL WHERE et HAVING.
La différence fondamentale entre WHERE et HAVING est que WHERE sélectionne les lignes en entrée
avant que les groupes et les agrégats ne soient traités (donc, cette clause contrôle les lignes qui se
retrouvent dans le calcul de l'agrégat), tandis que HAVING sélectionne les lignes groupées après que
les groupes et les agrégats ont été traités. Donc, la clause WHERE ne doit pas contenir de fonctions
d'agrégat ; cela n'a aucun sens d'essayer d'utiliser un agrégat pour déterminer les lignes en entrée des
agrégats. D'un autre côté, la clause HAVING contient toujours des fonctions d'agrégat (pour être précis,
vous êtes autorisés à écrire une clause HAVING qui n'utilise pas d'agrégat, mais c'est rarement utilisé.
La même condition pourra être utilisée plus efficacement par un WHERE).
Dans l'exemple précédent, nous pouvons appliquer la restriction sur le nom de la ville dans la clause
WHERE puisque cela ne nécessite aucun agrégat. C'est plus efficace que d'ajouter la restriction dans
HAVING parce que nous évitons le groupement et les calculs d'agrégat pour toutes les lignes qui ont
échoué lors du contrôle fait par WHERE.
Une autre façon de sélectionner les lignes qui vont dans le calcul d'un agrégat est d'utiliser la clause
FILTER, qui est une option par agrégat :
14
Le langage SQL
FILTER ressemble beaucoup à WHERE, sauf qu'elle supprime les lignes uniquement sur l'entrée de la
fonction d'agrégat à laquelle elle est attachée. Dans cet exemple, l'agrégat count compte seulement
les lignes pour lesquelles la colonne t_basse a une valeur inférieure à 45 alors que l'agrégat max
est toujours appliqué à toutes les lignes, donc il trouve toujours la valeur 46.
UPDATE temps
SET t_haute = t_haute - 2, t_basse = t_basse - 2
WHERE date > '1994-11-28';
2.9. Suppressions
Les lignes peuvent être supprimées de la table avec la commande DELETE. Supposez que vous ne
soyez plus intéressé par le temps de Hayward. Vous pouvez faire ce qui suit pour supprimer ses lignes
de la table :
Sans une qualification, DELETE supprimera toutes les lignes de la table donnée, la laissant vide. Le
système le fera sans demander de confirmation !
15
Chapitre 3. Fonctionnalités avancées
3.1. Introduction
Le chapitre précédent couvre les bases de l'utilisation de SQL pour le stockage et l'accès aux données
avec PostgreSQL. Il est temps d'aborder quelques fonctionnalités avancées du SQL qui simplifient
la gestion et empêchent la perte ou la corruption des données. Quelques extensions de PostgreSQL
sont également abordées.
Ce chapitre fait occasionnellement référence aux exemples disponibles dans le Chapitre 2 pour les
modifier ou les améliorer. Il est donc préférable d'avoir lu ce chapitre. Quelques exemples de ce
chapitre sont également disponibles dans advanced.sql situé dans le répertoire du tutoriel. De
plus, ce fichier contient quelques données à charger pour utiliser l'exemple. Cela n'est pas repris ici
(on peut se référer à la Section 2.1 pour savoir comment utiliser ce fichier).
3.2. Vues
Se référer aux requêtes de la Section 2.6. Si la liste des enregistrements du temps et des villes est d'un
intérêt particulier pour l'application considérée, mais qu'il devient contraignant de saisir la requête à
chaque utilisation, il est possible de créer une vue avec la requête. De ce fait, la requête est nommée
et il peut y être fait référence de la même façon qu'il est fait référence à une table :
L'utilisation des vues est un aspect clé d'une bonne conception des bases de données SQL. Les
vues permettent d'encapsuler les détails de la structure des tables. Celle-ci peut alors changer avec
l'évolution de l'application, tandis que l'interface reste constante.
Les vues peuvent être utilisées dans quasiment toutes les situations où une vraie table est utilisable.
De plus, il n'est pas inhabituel de construire des vues reposant sur d'autres vues.
16
Fonctionnalités avancées
t_haute int,
t_basse int,
prcp real,
date date
);
Le comportement des clés étrangères peut être adapté très finement à une application particulière. Ce
tutoriel ne va pas plus loin que cet exemple simple. De plus amples informations sont accessibles dans
le Chapitre 5. Une utilisation efficace des clés étrangères améliore la qualité des applications accédant
aux bases de données. Il est donc fortement conseillé d'apprendre à les utiliser.
3.4. Transactions
Les transactions sont un concept fondamental de tous les systèmes de bases de données. Une
transaction assemble plusieurs étapes en une seule opération tout ou rien. Les états intermédiaires
entre les étapes ne sont pas visibles par les transactions concurrentes. De plus, si un échec survient qui
empêche le succès de la transaction, alors aucune des étapes n'affecte la base de données.
Si l'on considère, par exemple, la base de données d'une banque qui contient le solde de différents
comptes clients et le solde total des dépôts par branches et que l'on veut enregistrer un virement de
100 euros du compte d'Alice vers celui de Bob, les commandes SQL peuvent ressembler à cela (après
simplification) :
Ce ne sont pas les détails des commandes qui importent ici ; le point important est la nécessité de
plusieurs mises à jour séparées pour accomplir cette opération assez simple. Les employés de la banque
veulent être assurés que, soit toutes les commandes sont effectuées, soit aucune ne l'est. Il n'est pas
envisageable que, suite à une erreur du système, Bob reçoive 100 euros qui n'ont pas été débités du
compte d'Alice. De la même façon, Alice ne restera pas longtemps une cliente fidèle si elle est débitée
du montant sans que celui-ci ne soit crédité sur le compte de Bob. Il est important de garantir que si
quelque chose se passe mal, aucune des étapes déjà exécutées n'est prise en compte. Le regroupement
des mises à jour au sein d'une transaction apporte cette garantie. Une transaction est dite atomique :
du point de vue des autres transactions, elle passe complètement ou pas du tout.
Il est également nécessaire de garantir qu'une fois la transaction terminée et validée par la base de
données, les transactions sont enregistrées définitivement et ne peuvent être perdues, même si une
panne survient peu après. Ainsi, si un retrait d'argent est effectué par Bob, il ne faut absolument pas
que le débit de son compte disparaisse suite à une panne survenant juste après son départ de la banque.
Une base de données transactionnelle garantit que toutes les mises à jour faites lors d'une transaction
sont stockées de manière persistante (c'est-à-dire sur disque) avant que la transaction ne soit déclarée
validée.
17
Fonctionnalités avancées
Une autre propriété importante des bases de données transactionnelles est en relation étroite avec
la notion de mises à jour atomiques : quand plusieurs transactions sont lancées en parallèle, aucune
d'entre elles ne doit être capable de voir les modifications incomplètes effectuées par les autres. Ainsi,
si une transaction calcule le total de toutes les branches, inclure le débit de la branche d'Alice sans
le crédit de la branche de Bob, ou vice-versa, est une véritable erreur. Les transactions doivent donc
être tout ou rien, non seulement pour leur effet persistant sur la base de données, mais aussi pour leur
visibilité au moment de leur exécution. Les mises à jour faites jusque-là par une transaction ouverte
sont invisibles aux autres transactions jusqu'à la fin de celle-ci. À ce moment, toutes les mises à jour
deviennent simultanément visibles.
Sous PostgreSQL, une transaction est déclarée en entourant les commandes SQL de la transaction par
les commandes BEGIN et COMMIT. La transaction bancaire ressemble alors à ceci :
BEGIN;
UPDATE comptes SET balance = balance - 100.00
WHERE nom = 'Alice';
-- etc etc
COMMIT;
Si, au cours de la transaction, il est décidé de ne pas valider (peut-être la banque s'aperçoit-elle que la
balance d'Alice passe en négatif), la commande ROLLBACK peut être utilisée à la place de COMMIT.
Toutes les mises à jour réalisées jusque-là sont alors annulées.
En fait, PostgreSQL traite chaque instruction SQL comme si elle était exécutée dans une transaction.
En l'absence de commande BEGIN explicite, chaque instruction individuelle se trouve implicitement
entourée d'un BEGIN et (en cas de succès) d'un COMMIT. Un groupe d'instructions entourées par
BEGIN et COMMIT est parfois appelé bloc transactionnel.
Note
Quelques bibliothèques clientes lancent les commandes BEGIN et COMMIT automatiquement.
L'utilisateur bénéficie alors des effets des blocs transactionnels sans les demander. Vérifiez la
documentation de l'interface que vous utilisez.
Il est possible d'augmenter la granularité du contrôle des instructions au sein d'une transaction
en utilisant des points de retournement (savepoint). Ceux-ci permettent d'annuler des parties de
la transaction tout en validant le reste. Après avoir défini un point de retournement à l'aide de
SAVEPOINT, les instructions exécutées depuis ce point peuvent, au besoin, être annulées avec
ROLLBACK TO. Toutes les modifications de la base de données effectuées par la transaction entre le
moment où le point de retournement a été défini et celui où l'annulation est demandée sont annulées,
mais les modifications antérieures à ce point sont conservées.
Le retour à un point de retournement ne l'annule pas. Il reste défini et peut donc être utilisé plusieurs
fois. À l'inverse, lorsqu'il n'est plus nécessaire de revenir à un point de retournement particulier, il peut
être relâché, ce qui permet de libérer des ressources système. Il faut savoir toutefois que relâcher un
point de retournement ou y revenir relâche tous les points de retournement qui ont été définis après.
Tout ceci survient à l'intérieur du bloc de transaction, et n'est donc pas visible par les autres sessions
en cours sur la base de données. Si le bloc est validé, et à ce moment-là seulement, toutes les actions
validées deviennent immédiatement visibles par les autres sessions, tandis que les actions annulées
ne le seront jamais.
Reconsidérant la base de données de la banque, on peut supposer vouloir débiter le compte d'Alice
de $100.00, somme à créditer sur le compte de Bob, mais considérer plus tard que c'est le compte de
Wally qu'il convient de créditer. À l'aide des points de retournement, cela peut se dérouler ainsi :
BEGIN;
18
Fonctionnalités avancées
Cet exemple est bien sûr très simplifié, mais de nombreux contrôles sont réalisables au sein d'un bloc
de transaction grâce à l'utilisation des points de retournement. Qui plus est, ROLLBACK TO est le seul
moyen de regagner le contrôle d'un bloc de transaction placé dans un état d'annulation par le système
du fait d'une erreur. C'est plus rapide que de tout annuler pour tout recommencer.
Voici un exemple permettant de comparer le salaire d'un employé avec le salaire moyen de sa division :
Les trois premières colonnes viennent directement de la table salaireemp, et il y a une ligne de
sortie pour chaque ligne de la table. La quatrième colonne représente une moyenne calculée sur tous
les enregistrements de la table qui ont la même valeur de nomdep que la ligne courante. (Il s'agit
effectivement de la même fonction que la fonction d'agrégat classique avg, mais la clause OVER
entraîne son exécution en tant que fonction de fenêtrage et son calcul sur la fenêtre.)
Un appel à une fonction de fenêtrage contient toujours une clause OVER qui suit immédiatement
le nom et les arguments de la fonction. C'est ce qui permet de la distinguer syntaxiquement d'une
fonction simple ou d'une fonction d'agrégat. La clause OVER détermine précisément comment les
lignes de la requête sont éclatées pour être traitées par la fonction de fenêtrage. La clause PARTITION
19
Fonctionnalités avancées
BY contenue dans OVER divise les enregistrements en groupes, ou partitions, qui partagent les
mêmes valeurs pour la (les) expression(s) contenue(s) dans la clause PARTITION BY. Pour chaque
enregistrement, la fonction de fenêtrage est calculée sur les enregistrements qui se retrouvent dans la
même partition que l'enregistrement courant.
Vous pouvez aussi contrôler l'ordre dans lequel les lignes sont traitées par les fonctions de fenêtrage en
utilisant la clause ORDER BY à l'intérieur de la clause OVER (la partition traitée par le ORDER BY n'a
de plus pas besoin de correspondre à l'ordre dans lequel les lignes seront affichées). Voici un exemple :
On remarque que la fonction rank produit un rang numérique pour chaque valeur ORDER BY distincte
dans la partition de la ligne courante, en utilisant l'ordre défini par la clause ORDER BY. rank n'a
pas besoin de paramètre explicite, puisque son comportement est entièrement déterminé par la clause
OVER.
Les lignes prises en compte par une fonction de fenêtrage sont celles de la table virtuelle produite par
la clause FROM de la requête filtrée par ses clauses WHERE, GROUP BY et HAVING, s'il y en a. Par
exemple, une ligne rejetée parce qu'elle ne satisfait pas à la condition WHERE n'est vue par aucune
fonction de fenêtrage. Une requête peut contenir plusieurs de ces fonctions de fenêtrage qui découpent
les données de façons différentes, par le biais de clauses OVER différentes, mais elles travaillent toutes
sur le même jeu d'enregistrements, défini par cette table virtuelle.
ORDER BY peut être omis lorsque l'ordre des enregistrements est sans importance. Il est aussi
possible d'omettre PARTITION BY, auquel cas il n'y a qu'une seule partition, contenant tous les
enregistrements.
Il y a un autre concept important associé aux fonctions de fenêtrage : pour chaque enregistrement, il
existe un jeu d'enregistrements dans sa partition appelé son window frame (cadre de fenêtre). Certaines
fonctions de fenêtrage travaillent uniquement sur les enregistrements du window frame, plutôt que
sur l'ensemble de la partition. Par défaut, si on a précisé une clause ORDER BY, la window frame
contient tous les enregistrements du début de la partition jusqu'à l'enregistrement courant, ainsi que
tous les enregistrements suivants qui sont égaux à l'enregistrement courant au sens de la clause ORDER
BY. Quand ORDER BY est omis, la window frame par défaut contient tous les enregistrements de la
partition. 1 Voici un exemple utilisant sum :
20
Fonctionnalités avancées
salaire| sum
--------+-------
5200 | 47100
5000 | 47100
3500 | 47100
4800 | 47100
3900 | 47100
4200 | 47100
4500 | 47100
4800 | 47100
6000 | 47100
5200 | 47100
(10 rows)
Dans l'exemple ci-dessus, puisqu'il n'y a pas d'ORDER BY dans la clause OVER, la window frame
est égale à la partition ; en d'autres termes, chaque somme est calculée sur toute la table, ce qui fait
qu'on a le même résultat pour chaque ligne du résultat. Mais si on ajoute une clause ORDER BY, on
a un résultat très différent :
salaire| sum
--------+-------
3500 | 3500
3900 | 7400
4200 | 11600
4500 | 16100
4800 | 25700
4800 | 25700
5000 | 30700
5200 | 41100
5200 | 41100
6000 | 47100
(10 rows)
Ici, sum est calculé à partir du premier salaire (c'est-à-dire le plus bas) jusqu'au salaire courant, en
incluant tous les doublons du salaire courant (remarquez les valeurs pour les salaires identiques).
Les fonctions window ne sont autorisées que dans la liste SELECT et la clause ORDER BY de la
requête. Elles sont interdites ailleurs, comme dans les clauses GROUP BY,HAVING et WHERE. La
raison en est qu'elles sont exécutées après le traitement de ces clauses. Par ailleurs, les fonctions de
fenêtrage s'exécutent après les fonctions d'agrégat classiques. Cela signifie qu'il est permis d'inclure
une fonction d'agrégat dans les arguments d'une fonction de fenêtrage, mais pas l'inverse.
S'il y a besoin de filtrer ou de grouper les enregistrements après le calcul des fonctions de fenêtrage,
une sous-requête peut être utilisée. Par exemple :
21
Fonctionnalités avancées
La requête ci-dessus n'affiche que les enregistrements de la requête interne ayant un rang inférieur à 3.
Quand une requête met en jeu plusieurs fonctions de fenêtrage, il est possible d'écrire chacune avec
une clause OVER différente, mais cela entraîne des duplications de code et augmente les risques
d'erreurs si on souhaite le même comportement pour plusieurs fonctions de fenêtrage. À la place,
chaque comportement de fenêtrage peut être associé à un nom dans une clause WINDOW et ensuite
être référencé dans OVER. Par exemple :
Plus de détails sur les fonctions de fenêtrage sont disponibles dans la Section 4.2.8, la Section 9.21,
la Section 7.2.5 et la page de référence SELECT.
3.6. Héritage
L'héritage est un concept issu des bases de données orientées objet. Il ouvre de nouvelles possibilités
intéressantes en conception de bases de données.
Soit deux tables : une table villes et une table capitales. Les capitales étant également des
villes, il est intéressant d'avoir la possibilité d'afficher implicitement les capitales lorsque les villes
sont listées. Un utilisateur particulièrement brillant peut écrire ceci
Cela fonctionne bien pour les requêtes, mais la mise à jour d'une même donnée sur plusieurs lignes
devient vite un horrible casse-tête.
22
Fonctionnalités avancées
population real,
elevation int -- (en pied)
);
Dans ce cas, une ligne de capitales hérite de toutes les colonnes (nom, population et
elevation) de son parent, villes. Le type de la colonne nom est text, un type natif de
PostgreSQL pour les chaînes de caractères à longueur variable. La table capitales a une colonne
supplémentaire, etat, qui affiche l'abréviation de cet état. Sous PostgreSQL, une table peut hériter
de zéro à plusieurs autres tables.
La requête qui suit fournit un exemple d'extraction des noms de toutes les villes, en incluant les
capitales des états, situées à une elevation de plus de 500 pieds :
ce qui renvoie :
nom | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
(3 rows)
À l'inverse, la requête qui suit récupère toutes les villes qui ne sont pas des capitales et qui sont situées
à une élévation d'au moins 500 pieds :
nom | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
(2 rows)
Ici, ONLY avant villes indique que la requête ne doit être exécutée que sur la table villes, et non
pas sur les tables en dessous de villes dans la hiérarchie des héritages. La plupart des commandes
déjà évoquées -- SELECT, UPDATE et DELETE -- supportent cette notation (ONLY).
Note
Bien que l'héritage soit fréquemment utile, il n'a pas été intégré avec les contraintes d'unicité
et les clés étrangères, ce qui limite son utilité. Voir la Section 5.9 pour plus de détails.
3.7. Conclusion
PostgreSQL dispose d'autres fonctionnalités non décrites dans ce tutoriel d'introduction orienté vers
les nouveaux utilisateurs de SQL. Ces fonctionnalités sont discutées plus en détail dans le reste de
ce livre.
23
Fonctionnalités avancées
Si une introduction plus approfondie est nécessaire, le lecteur peut visiter le site web2 de PostgreSQL
qui fournit des liens vers d'autres ressources.
2
https://www.postgresql.org
24
Partie II. Langage SQL
Cette partie présente l'utilisation du langage SQL au sein de PostgreSQL. La syntaxe générale de SQL y
est expliquée, ainsi que la création des structures de stockage des données, le peuplement de la base et son
interrogation. La partie centrale liste les types de données et les fonctions disponibles ainsi que leur utilisation dans
les requêtes SQL. Le reste traite de l'optimisation de la base de données en vue d'obtenir des performances idéales.
L'information dans cette partie est présentée pour qu'un utilisateur novice puisse la suivre du début à la fin et
obtenir ainsi une compréhension complète des sujets sans avoir à effectuer de fréquents sauts entre les chapitres.
Les chapitres sont indépendants. Un utilisateur plus expérimenté pourra, donc, ne consulter que les chapitres
l'intéressant. L'information est présentée dans un style narratif par unité thématique. Les lecteurs qui cherchent
une description complète d'une commande particulière peuvent se référer à la Partie VI.
Pour profiter pleinement de cette partie, il est nécessaire de savoir se connecter à une base PostgreSQL et d'y
exécuter des commandes SQL. Les lecteurs qui ne sont pas familiers avec ces prérequis sont encouragés à lire
préalablement la Partie I.
Les commandes SQL sont généralement saisies à partir du terminal interactif de PostgreSQL, psql. D'autres
programmes possédant des fonctionnalités similaires peuvent également être utilisés.
Table des matières
4. Syntaxe SQL ........................................................................................................... 33
4.1. Structure lexicale ........................................................................................... 33
4.1.1. identificateurs et mots-clés .................................................................... 33
4.1.2. Constantes ......................................................................................... 35
4.1.3. Opérateurs ......................................................................................... 40
4.1.4. Caractères spéciaux ............................................................................. 40
4.1.5. Commentaires .................................................................................... 41
4.1.6. Précédence d'opérateurs ........................................................................ 41
4.2. Expressions de valeurs ................................................................................... 42
4.2.1. Références de colonnes ........................................................................ 43
4.2.2. Paramètres de position ......................................................................... 43
4.2.3. Indices .............................................................................................. 44
4.2.4. Sélection de champs ............................................................................ 44
4.2.5. Appels d'opérateurs ............................................................................. 45
4.2.6. Appels de fonctions ............................................................................. 45
4.2.7. Expressions d'agrégat ........................................................................... 46
4.2.8. Appels de fonction de fenêtrage ............................................................. 48
4.2.9. Conversions de type ............................................................................ 51
4.2.10. Expressions de collationnement ............................................................ 51
4.2.11. Sous-requêtes scalaires ....................................................................... 52
4.2.12. Constructeurs de tableaux ................................................................... 52
4.2.13. Constructeurs de lignes ...................................................................... 54
4.2.14. Règles d'évaluation des expressions ...................................................... 55
4.3. Fonctions appelantes ...................................................................................... 57
4.3.1. En utilisant la notation par position ........................................................ 57
4.3.2. En utilisant la notation par nom ............................................................. 58
4.3.3. En utilisant la notation mixée ................................................................ 59
5. Définition des données .............................................................................................. 60
5.1. Notions fondamentales sur les tables ................................................................. 60
5.2. Valeurs par défaut ......................................................................................... 61
5.3. Contraintes ................................................................................................... 62
5.3.1. Contraintes de vérification .................................................................... 62
5.3.2. Contraintes de non-nullité (NOT NULL) ................................................. 64
5.3.3. Contraintes d'unicité ............................................................................ 65
5.3.4. Clés primaires .................................................................................... 66
5.3.5. Clés étrangères ................................................................................... 67
5.3.6. Contraintes d'exclusion ........................................................................ 70
5.4. Colonnes système .......................................................................................... 70
5.5. Modification des tables ................................................................................... 71
5.5.1. Ajouter une colonne ............................................................................ 72
5.5.2. Supprimer une colonne ........................................................................ 72
5.5.3. Ajouter une contrainte ......................................................................... 73
5.5.4. Supprimer une contrainte ...................................................................... 73
5.5.5. Modifier la valeur par défaut d'une colonne ............................................. 73
5.5.6. Modifier le type de données d'une colonne .............................................. 74
5.5.7. Renommer une colonne ........................................................................ 74
5.5.8. Renommer une table ............................................................................ 74
5.6. Droits .......................................................................................................... 74
5.7. Politiques de sécurité niveau ligne .................................................................... 75
5.8. Schémas ....................................................................................................... 82
5.8.1. Créer un schéma ................................................................................. 82
5.8.2. Le schéma public ................................................................................ 83
5.8.3. Chemin de parcours des schémas ........................................................... 83
5.8.4. Schémas et privilèges .......................................................................... 85
5.8.5. Le schéma du catalogue système ........................................................... 85
26
Langage SQL
27
Langage SQL
28
Langage SQL
29
Langage SQL
30
Langage SQL
31
Langage SQL
32
Chapitre 4. Syntaxe SQL
Ce chapitre décrit la syntaxe de SQL. Il donne les fondements pour comprendre les chapitres suivants
qui iront plus en détail sur la façon dont les commandes SQL sont appliquées pour définir et modifier
des données.
Nous avertissons aussi nos utilisateurs, déjà familiers avec le SQL, qu'ils doivent lire ce chapitre très
attentivement, car il existe plusieurs règles et concepts implémentés différemment suivant les bases
de données SQL ou spécifiques à PostgreSQL.
Un jeton peut être un mot-clé, un identificateur, un identificateur entre guillemets, une constante ou
un symbole de caractère spécial. Les jetons sont normalement séparés par des espaces blancs (espace,
tabulation, nouvelle ligne), mais n'ont pas besoin de l'être s'il n'y a pas d'ambiguïté (ce qui est seulement
le cas si un caractère spécial est adjacent à des jetons d'autres types).
Par exemple, ce qui suit est (syntaxiquement) valide pour une entrée SQL :
C'est une séquence de trois commandes, une par ligne (bien que cela ne soit pas requis , plusieurs
commandes peuvent se trouver sur une même ligne et une commande peut se répartir sur plusieurs
lignes).
De plus, des commentaires peuvent se trouver dans l'entrée SQL. Ce ne sont pas des jetons, ils sont
réellement équivalents à un espace blanc.
La syntaxe SQL n'est pas très cohérente en ce qui concerne les jetons identificateurs des commandes,
lesquels sont des opérandes ou des paramètres. Les premiers jetons sont généralement le nom de la
commande. Dans l'exemple ci-dessus, nous parlons d'une commande « SELECT », d'une commande
« UPDATE » et d'une commande « INSERT ». Mais en fait, la commande UPDATE requiert toujours
un jeton SET apparaissant à une certaine position, et cette variante particulière d'INSERT requiert
aussi un VALUES pour être complète. Les règles précises de syntaxe pour chaque commande sont
décrites dans la Partie VI.
Les identificateurs et les mots-clés SQL doivent commencer avec une lettre (a-z, mais aussi des lettres
de marques diacritiques différentes et des lettres non latines) ou un tiret bas (_). Les caractères suivants
dans un identificateur ou dans un mot-clé peuvent être des lettres, des tirets bas, des chiffres (0-9) ou
des signes dollar ($). Notez que les signes dollar ne sont pas autorisés en tant qu'identificateur d'après
le standard SQL, donc leur utilisation pourrait rendre les applications moins portables. Le standard
SQL ne définira pas un mot-clé contenant des chiffres ou commençant ou finissant par un tiret bas,
33
Syntaxe SQL
donc les identificateurs de cette forme sont sûrs de ne pas entrer en conflit avec les futures extensions
du standard.
Le système utilise au plus NAMEDATALEN-1 octets d'un identificateur ; les noms longs peuvent être
écrits dans des commandes, mais ils seront tronqués. Par défaut, NAMEDATALEN vaut 64. Du coup,
la taille maximale de l'identificateur est de 63 octets. Si cette limite est problématique, elle peut être
élevée en modifiant NAMEDATALEN dans src/include/pg_config_manual.h.
Les mots-clés et les identificateurs sans guillemets doubles sont insensibles à la casse. Du coup :
Une convention couramment utilisée revient à écrire les mots-clés en majuscule et les noms en
minuscule, c'est-à-dire :
Les identificateurs entre guillemets peuvent contenir tout caractère autre que celui de code 0. (Pour
inclure un guillemet double, écrivez deux guillemets doubles.) Ceci permet la construction de noms de
tables et de colonnes qui ne seraient pas possibles autrement, comme des noms contenant des espaces
ou des arobases. La limitation de la longueur s'applique toujours.
Une variante des identificateurs entre guillemets permet d'inclure des caractères Unicode échappés en
les identifiant par leur point de code. Cette variante commence par U& (U en majuscule ou minuscule
suivi par un « et commercial ») immédiatement suivis par un guillemet double d'ouverture, sans espace
entre eux. Par exemple U&"foo". (Notez que c'est source d'ambiguïté avec l'opérateur &. Utilisez
les espaces autour de l'opérateur pour éviter ce problème.) À l'intérieur des guillemets, les caractères
Unicode peuvent être indiqués dans une forme échappée en écrivant un antislash suivi par le code
hexadécimal sur quatre chiffres ou, autre possibilité, un antislash suivi du signe plus suivi d'un code
hexadécimal sur six chiffres. Par exemple, l'identificateur "data" peut être écrit ainsi :
U&"d\0061t\+000061"
L'exemple suivant, moins trivial, écrit le mot russe « slon » (éléphant) en lettres cyrilliques :
U&"\0441\043B\043E\043D"
Si un caractère d'échappement autre que l'antislash est désiré, il peut être indiqué en utilisant la clause
UESCAPE après la chaîne. Par exemple :
34
Syntaxe SQL
La chaîne d'échappement peut être tout caractère simple autre qu'un chiffre hexadécimal, le signe plus,
un guillemet simple ou double, ou un espace blanc. Notez que le caractère d'échappement est écrit
entre guillemets simples, pas entre guillemets doubles.
Pour inclure le caractère d'échappement dans l'identificateur sans interprétation, écrivez-le deux fois.
La syntaxe d'échappement Unicode fonctionne seulement quand l'encodage serveur est UTF8. Quand
d'autres encodages clients sont utilisés, seuls les codes dans l'échelle ASCII (jusqu'à \007F) peuvent
être utilisés. La forme sur quatre chiffres et la forme sur six chiffres peuvent être utilisées pour indiquer
des paires UTF-16, composant ainsi des caractères comprenant des points de code plus grands que U
+FFFF (et ce, bien que la disponibilité de la forme sur six chiffres ne le nécessite pas techniquement).
(Les paires de substitution ne sont pas stockées directement, mais combinées dans un point de code
seul qui est ensuite encodé en UTF-8.)
Mettre un identificateur entre guillemets le rend sensible à la casse alors que les noms sans guillemets
sont toujours convertis en minuscules. Par exemple, les identificateurs FOO, foo et "foo" sont
considérés identiques par PostgreSQL, mais "Foo" et "FOO" sont différents des trois autres et entre
eux. La mise en minuscule des noms sans guillemets avec PostgreSQL n'est pas compatible avec le
standard SQL qui indique que les noms sans guillemets devraient être mis en majuscule. Du coup,
foo devrait être équivalent à "FOO" et non pas à "foo" en respect avec le standard. Si vous voulez
écrire des applications portables, nous vous conseillons de toujours mettre entre guillemets un nom
particulier ou de ne jamais le mettre.
4.1.2. Constantes
Il existe trois types implicites de constantes dans PostgreSQL : les chaînes, les chaînes de bits et les
nombres. Les constantes peuvent aussi être spécifiées avec des types explicites, ce qui peut activer
des représentations plus précises et gérées plus efficacement par le système. Les constantes implicites
sont décrites ci-dessous ; ces constantes sont discutées dans les sous-sections suivantes.
Deux constantes de type chaîne séparées par un espace blanc avec au moins une nouvelle ligne sont
concaténées et traitées réellement comme si la chaîne avait été écrite dans une constante. Par exemple :
SELECT 'foo'
'bar';
est équivalent à :
SELECT 'foobar';
mais :
n'a pas une syntaxe valide (ce comportement légèrement bizarre est spécifié par le standard SQL ;
PostgreSQL suit le standard).
35
Syntaxe SQL
d'ouverture.) À l'intérieur d'une chaîne d'échappement, un caractère antislash (\) est géré comme
une séquence d'échappement avec antislash du langage C. La combinaison d'antislash et du (ou des)
caractère(s) suivant(s) représente une valeur spéciale, comme indiqué dans le Tableau 4.1.
Tout autre caractère suivi d'un antislash est pris littéralement. Du coup, pour inclure un caractère
antislash, écrivez deux antislashs (\\). De plus, un guillemet simple peut être inclus dans une chaîne
d'échappement en écrivant \', en plus de la façon normale ''.
Il est de votre responsabilité que les séquences d'octets que vous créez, tout spécialement lorsque
vous utilisez les échappements octaux et hexadécimaux, soient des caractères valides dans l'encodage
du jeu de caractères du serveur. Quand l'encodage est UTF-8, alors les échappements Unicode ou
l'autre syntaxe d'échappement Unicode, expliqués dans la Section 4.1.2.3, devraient être utilisés.
(L'alternative serait de réaliser l'encodage UTF-8 manuellement et d'écrire les octets, ce qui serait très
lourd.)
Attention
Si le paramètre de configuration standard_conforming_strings est désactivé (off), alors
PostgreSQL reconnaît les échappements antislashs dans les constantes traditionnelles de type
chaînes et celles échappées. Néanmoins, à partir de PostgreSQL 9.1, la valeur par défaut
est on, ce qui signifie que les échappements par antislash ne sont reconnus que dans les
constantes de chaînes d'échappement. Ce comportement est plus proche du standard SQL, mais
pourrait causer des problèmes aux applications qui se basent sur le comportement historique
où les échappements par antislash étaient toujours reconnus. Pour contourner ce problème,
vous pouvez configurer ce paramètre à off, bien qu'il soit préférable de ne plus utiliser
les échappements par antislash. Si vous avez besoin d'un échappement par antislash pour
représenter un caractère spécial, écrivez la chaîne fixe avec un E.
Le caractère de code zéro ne peut pas être placé dans une constante de type chaîne.
36
Syntaxe SQL
U&'d\0061t\+000061'
L'exemple suivant, moins trivial, écrit le mot russe « slon » (éléphant) en lettres cyrilliques :
U&'\0441\043B\043E\043D'
Si un caractère d'échappement autre que l'antislash est souhaité, il peut être indiqué en utilisant la
clause UESCAPE après la chaîne. Par exemple :
Le caractère d'échappement peut être tout caractère simple autre qu'un chiffre hexadécimal, le signe
plus, un guillemet simple ou double, ou un espace blanc.
La syntaxe d'échappement Unicode fonctionne seulement quand l'encodage du serveur est UTF8.
Quand d'autres encodages de serveur sont utilisés, seuls les codes dans l'échelle ASCII (jusqu'à
\007F) peuvent être utilisés. La forme sur quatre chiffres et la forme sur six chiffres peuvent être
utilisées pour indiquer des paires de substitution UTF-16, composant ainsi des caractères comprenant
des points de code plus grands que U+FFFF (et ce, bien que la disponibilité de la forme sur six chiffres
ne le nécessite pas techniquement). (Quand des paires de substitution sont utilisées avec un encodage
serveur UTF8, elles sont tout d'abord combinées en un seul point de code, qui est ensuite encodé en
UTF-8.)
De plus, la syntaxe d'échappement de l'Unicode pour les constantes de chaînes fonctionne seulement
quand le paramètre de configuration standard_conforming_strings est activé. Dans le cas contraire,
cette syntaxe est confuse pour les clients qui analysent les instructions SQL, au point que cela pourrait
amener des injections SQL et des problèmes de sécurité similaires. Si le paramètre est désactivé, cette
syntaxe sera rejetée avec un message d'erreur.
Pour inclure le caractère d'échappement littéralement dans la chaîne, écrivez-le deux fois.
37
Syntaxe SQL
Notez qu'à l'intérieur de la chaîne avec guillemet dollar, les guillemets simples peuvent être utilisés
sans devoir être échappés. En fait, aucun caractère à l'intérieur d'une chaîne avec guillemet dollar n'a
besoin d'être échappé : le contenu est toujours écrit littéralement. Les antislashs ne sont pas spéciaux,
pas plus que les signes dollar, sauf s'ils font partie d'une séquence correspondant à la balise ouvrante.
Il est possible d'imbriquer les constantes de chaînes avec guillemets dollar en utilisant différentes
balises pour chaque niveau d'imbrication. Ceci est habituellement utilisé lors de l'écriture de définition
de fonctions. Par exemple :
$fonction$
BEGIN
RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$fonction$
Dans cet exemple, la séquence $q$[\t\r\n\v\\]$q$ représente une chaîne constante avec
guillemet dollar [\t\r\n\v\\], qui sera reconnue quand le corps de la fonction est exécuté par
PostgreSQL. Mais comme la séquence ne correspond pas au délimiteur $fonction$, il s'agit juste
de quelques caractères à l'intérieur de la constante pour ce qu'en sait la chaîne externe.
La balise d'une chaîne avec guillemets dollar, si elle existe, suit les mêmes règles qu'un identificateur
sans guillemets, sauf qu'il ne peut pas contenir de signes dollar. Les balises sont sensibles à la casse, du
coup $balise$Contenu de la chaîne$balise$ est correct, mais $BALISE$Contenu
de la chaîne$balise$ ne l'est pas.
Une chaîne avec guillemets dollar suivant un mot clé ou un identificateur doit en être séparée par un
espace blanc ; sinon, le délimiteur du guillemet dollar serait pris comme faisant partie de l'identificateur
précédent.
Le guillemet dollar ne fait pas partie du standard SQL, mais c'est un moyen bien plus agréable pour
écrire des chaînes constantes que d'utiliser la syntaxe des guillemets simples, bien que compatible avec
le standard. Elle est particulièrement utile pour représenter des constantes de type chaîne à l'intérieur
d'autres constantes, comme cela est souvent le cas avec les définitions de fonctions. Avec la syntaxe
des guillemets simples, chaque antislash dans l'exemple précédent devrait avoir été écrit avec quatre
antislashs, ce qui sera réduit à deux antislashs dans l'analyse de la constante originale, puis à un lorsque
la constante interne est analysée de nouveau lors de l'exécution de la fonction.
Les constantes de chaînes de bits peuvent aussi être spécifiées en notation hexadécimale en utilisant un
X avant (minuscule ou majuscule), c'est-à-dire X'1FF'. Cette notation est équivalente à une constante
de chaîne de bits avec quatre chiffres binaires pour chaque chiffre hexadécimal.
Les deux formes de constantes de chaînes de bits peuvent être continuées sur plusieurs lignes de la
même façon que les constantes de chaînes habituelles. Le guillemet dollar ne peut pas être utilisé dans
une constante de chaîne de bits.
chiffres
chiffres.[chiffres][e[+-]chiffres]
38
Syntaxe SQL
[chiffres].chiffres[e[+-]chiffres]
chiffrese[+-]chiffres
où chiffres est un ou plusieurs chiffres décimaux (de 0 à 9). Au moins un chiffre doit être avant
ou après le point décimal, s'il est utilisé. Au moins un chiffre doit suivre l'indicateur d'exponentielle
(e), s'il est présent. Il ne peut pas y avoir d'espaces ou d'autres caractères imbriqués dans la constante.
Notez que tout signe plus ou moins en avant n'est pas considéré comme faisant part de la constante ;
il est un opérateur appliqué à la constante.
42
3.5
4.
.001
5e2
1.925e-3
Une constante numérique ne contenant ni un point décimal ni un exposant est tout d'abord présumée
du type integer si sa valeur est contenue dans le type integer (32 bits) ; sinon, il est présumé
de type bigint si sa valeur entre dans un type bigint (64 bits) ; sinon, il est pris pour un type
numeric. Les constantes contenant des points décimaux et/ou des exposants sont toujours présumées
de type numeric.
Le type de données affecté initialement à une constante numérique est seulement un point de départ
pour les algorithmes de résolution de types. Dans la plupart des cas, la constante sera automatiquement
convertie dans le type le plus approprié suivant le contexte. Si nécessaire, vous pouvez forcer
l'interprétation d'une valeur numérique sur un type de données spécifique en la convertissant. Par
exemple, vous pouvez forcer une valeur numérique à être traitée comme un type real (float4)
en écrivant :
Ce sont en fait des cas spéciaux des notations de conversion générales discutées après.
type 'chaîne'
'chaîne'::type
CAST ( 'chaîne' AS type )
Le texte de la chaîne constante est passé dans la routine de conversion pour le type appelé type. Le
résultat est une constante du type indiqué. La conversion explicite de type peut être omise s'il n'y a pas
d'ambiguïté sur le type de la constante (par exemple, lorsqu'elle est affectée directement à une colonne
de la table), auquel cas elle est convertie automatiquement.
La constante chaîne peut être écrite en utilisant soit la notation SQL standard soit les guillemets dollar.
Il est aussi possible de spécifier une conversion de type en utilisant une syntaxe style fonction :
nom_type ( 'chaîne' )
mais tous les noms de type ne peuvent pas être utilisés ainsi ; voir la Section 4.2.9 pour plus de détails.
Les syntaxes ::, CAST() et d'appels de fonctions sont aussi utilisables pour spécifier les conversions
de type à l'exécution d'expressions arbitraires, comme discuté dans la Section 4.2.9. Pour éviter une
39
Syntaxe SQL
ambiguïté syntaxique, la syntaxe type 'chaîne' peut seulement être utilisée pour spécifier le
type d'une constante. Une autre restriction sur la syntaxe type 'chaîne' est qu'elle ne fonctionne
pas pour les types de tableau ; utilisez :: ou CAST() pour spécifier le type d'une constante de type
tableau.
La syntaxe de CAST() est conforme au standard SQL. La syntaxe type 'chaine' est une
généralisation du standard : SQL spécifie cette syntaxe uniquement pour quelques types de données,
mais PostgreSQL l'autorise pour tous les types. La syntaxe :: est un usage historique dans
PostgreSQL, comme l'est la syntaxe d'appel de fonction.
4.1.3. Opérateurs
Un nom d'opérateur est une séquence d'au plus NAMEDATALEN-1 (63 par défaut) caractères provenant
de la liste suivante :
+-*/<>=~!@#%^&|`?
• -- et /* ne peuvent pas apparaître quelque part dans un nom d'opérateur, car ils seront pris pour
le début d'un commentaire.
• Un nom d'opérateur à plusieurs caractères ne peut pas finir avec + ou -, sauf si le nom contient
aussi un de ces caractères :
~!@#%^&|`?
Par exemple, @- est un nom d'opérateur autorisé, mais *- ne l'est pas. Cette restriction permet à
PostgreSQL d'analyser des requêtes compatibles avec SQL sans requérir des espaces entre les jetons.
Lors d'un travail avec des noms d'opérateurs ne faisant pas partie du standard SQL, vous aurez
habituellement besoin de séparer les opérateurs adjacents avec des espaces pour éviter toute ambiguïté.
Par exemple, si vous avez défini un opérateur unaire gauche nommé @, vous ne pouvez pas écrire
X*@Y ; vous devez écrire X* @Y pour vous assurer que PostgreSQL le lit comme deux noms
d'opérateurs, et non pas comme un seul.
• Un signe dollar ($) suivi de chiffres est utilisé pour représenter un paramètre de position dans le
corps de la définition d'une fonction ou d'une instruction préparée. Dans d'autres contextes, le signe
dollar pourrait faire partie d'un identificateur ou d'une constante de type chaîne utilisant le dollar
comme guillemet.
• Les parenthèses (()) ont leur signification habituelle pour grouper leurs expressions et renforcer la
précédence. Dans certains cas, les parenthèses sont requises, car faisant partie de la syntaxe d'une
commande SQL particulière.
• Les crochets ([]) sont utilisés pour sélectionner les éléments d'un tableau. Voir la Section 8.15
pour plus d'informations sur les tableaux.
• Les virgules (,) sont utilisées dans quelques constructions syntaxiques pour séparer les éléments
d'une liste.
• Le point-virgule (;) termine une commande SQL. Il ne peut pas apparaître quelque part dans une
commande, sauf à l'intérieur d'une constante de type chaîne ou d'un identificateur entre guillemets.
40
Syntaxe SQL
• Le caractère deux points (:) est utilisé pour sélectionner des « morceaux » de tableaux (voir la
Section 8.15). Dans certains dialectes SQL (tel que le SQL embarqué), il est utilisé pour préfixer
les noms de variables.
• L'astérisque (*) est utilisé dans certains contextes pour indiquer tous les champs de la ligne d'une
table ou d'une valeur composite. Elle a aussi une signification spéciale lorsqu'elle est utilisée comme
argument d'une fonction d'agrégat. Cela signifie que l'agrégat ne requiert pas de paramètre explicite.
• Le point (.) est utilisé dans les constantes numériques et pour séparer les noms de schéma, table
et colonne.
4.1.5. Commentaires
Un commentaire est une séquence de caractères commençant avec deux tirets et s'étendant jusqu'à la
fin de la ligne, par exemple :
/* commentaires multilignes
* et imbriqués: /* bloc de commentaire imbriqué */
*/
Un commentaire est supprimé du flux en entrée avant une analyse plus poussée de la syntaxe et est
remplacé par un espace blanc.
De même, vous aurez quelquefois besoin d'ajouter des parenthèses lors de l'utilisation de combinaisons
d'opérateurs binaires et unaires. Par exemple :
SELECT 5 ! - 6;
SELECT 5 ! (- 6);
parce que l'analyseur n'a aucune idée, jusqu'à ce qu'il ne soit trop tard, que ! est défini comme un
opérateur suffixe, et non pas préfixe. Pour obtenir le comportement désiré dans ce cas, vous devez
écrire :
SELECT (5 !) - 6;
Tableau 4.2. Précédence des opérateurs (du plus haut vers le plus bas)
Opérateur/Élément Associativité Description
. gauche séparateur de noms de table et de colonne
41
Syntaxe SQL
Notez que les règles de précédence des opérateurs s'appliquent aussi aux opérateurs définis par
l'utilisateur qui ont le même nom que les opérateurs internes mentionnés ici. Par exemple, si vous
définissez un opérateur « + » pour un type de données personnalisé, il aura la même précédence que
l'opérateur interne « + », peu importe ce que fait le vôtre.
Lorsqu'un nom d'opérateur qualifié par un schéma est utilisé dans la syntaxe OPERATOR, comme
dans :
SELECT 3 OPERATOR(pg_catalog.+) 4;
la construction OPERATOR est prise pour avoir la précédence par défaut affichée dans le Tableau 4.2
pour les opérateurs « autres ». Ceci est vrai, quel que soit le nom spécifique de l'opérateur apparaissant
à l'intérieur de OPERATOR().
Note
Les versions de PostgreSQL antérieures à la 9.5 utilisaient des règles de précédence différentes
pour les opérateurs. En particulier, <= >= et <> étaient traités comme des opérateurs
génériques ; les tests IS avaient une priorité supérieure ; NOT BETWEEN et les constructions
qui en découlent agissaient de façon incohérente, ayant dans certains cas la précédence de
NOT plutôt que de BETWEEN. Ces règles étaient modifiées pour un meilleur accord avec le
standard SQL et pour réduire la configuration d'un traitement incohérent de constructions
équivalentes logiquement. Dans la plupart des cas, ces changements ne résulteront pas en un
changement de comportement. Il peut arriver que des échecs du type « opérateur inconnu »
surviennent, auquel cas un ajout de parenthèses devrait corriger le problème. Néanmoins, il
existe des cas particuliers où une requête pourrait voir son comportement changé sans qu'une
erreur d'analyse soit renvoyée. Si vous êtes inquiet qu'un de ces changements puisse avoir cassé
quelque chose silencieusement, vous pouvez tester votre application en activant le paramètre
operator_precedence_warning pour voir si des messages d'avertissement sont tracés.
42
Syntaxe SQL
UPDATE, ou dans les conditions de recherche d'un certain nombre de commandes. Le résultat d'une
expression de valeurs est quelquefois appelé scalaire, pour le distinguer du résultat d'une expression
de table (qui est une table). Les expressions de valeurs sont aussi appelées des expressions scalaires
(voire simplement des expressions). La syntaxe d'expression permet le calcul des valeurs à partir de
morceaux primitifs en utilisant les opérations arithmétiques, logiques, d'ensemble et autres.
• une référence de la position d'un paramètre, dans le corps d'une définition de fonction ou
d'instruction préparée ;
• un appel d'opérateur ;
• un appel de fonction ;
• un constructeur de tableau ;
• un constructeur de ligne ;
• toute expression de valeur entre parenthèses, utile pour grouper des sous-expressions et surcharger
la précédence.
En plus de cette liste, il existe un certain nombre de constructions pouvant être classées comme une
expression, mais ne suivant aucune règle de syntaxe générale. Elles ont généralement la sémantique
d'une fonction ou d'un opérateur et sont expliquées au Chapitre 9. Un exemple est la clause IS NULL.
Nous avons déjà discuté des constantes dans la Section 4.1.2. Les sections suivantes discutent des
options restantes.
correlation.nom_colonne
correlation est le nom d'une table (parfois qualifié par son nom de schéma) ou un alias d'une
table définie au moyen de la clause FROM. Le nom de corrélation et le point de séparation peuvent
être omis si le nom de colonne est unique dans les tables utilisées par la requête courante (voir aussi
le Chapitre 7).
43
Syntaxe SQL
$numéro
Dans cet exemple, $1 référence la valeur du premier argument de la fonction à chaque appel de cette
commande.
4.2.3. Indices
Si une expression récupère une valeur de type tableau, alors un élément spécifique du tableau peut
être extrait en écrivant :
expression[indice]
Des éléments adjacents (un « morceau de tableau ») peuvent être extraits en écrivant :
expression[indice_bas:indice_haut]
Les crochets [ ] doivent apparaître réellement. Chaque indice est elle-même une expression, qui
sera arrondie à la valeur entière la plus proche.
En général, l'expression de type tableau doit être entre parenthèses, mais ces dernières peuvent
être omises lorsque l'expression utilisée comme indice est seulement une référence de colonne ou
un paramètre de position. De plus, les indices multiples peuvent être concaténés lorsque le tableau
original est multidimensionnel. Par exemple :
ma_table.colonnetableau[4]
ma_table.colonnes_deux_d[17][34]
$1[10:42]
(fonctiontableau(a,b))[42]
Dans ce dernier exemple, les parenthèses sont requises. Voir la Section 8.15 pour plus d'informations
sur les tableaux.
expression.nom_champ
En général, l'expression de ligne doit être entre parenthèses, mais les parenthèses peuvent être
omises lorsque l'expression à partir de laquelle se fait la sélection est seulement une référence de table
ou un paramètre de position. Par exemple :
ma_table.macolonne
$1.unecolonne
(fonctionligne(a,b)).col3
En fait, une référence de colonne qualifiée est un cas spécial de syntaxe de sélection de champ. Un
cas spécial important revient à extraire un champ de la colonne de type composite d'une table :
44
Syntaxe SQL
(colcomposite).unchamp
(matable.colcomposite).unchamp
Les parenthèses sont requises ici pour montrer que colcomposite est un nom de colonne, et non pas
un nom de table, ou que matable est un nom de table, pas un nom de schéma dans le deuxième cas.
Vous pouvez demander tous les champs d'une valeur composite en écrivant .* :
(compositecol).*
Cette syntaxe se comporte différemment suivant le contexte. Voir Section 8.16.5 pour plus de détails.
où le jeton opérateur suit les règles de syntaxe de la Section 4.1.3, ou est un des mots-clés AND,
OR et NOT, ou est un nom d'opérateur qualifié de la forme
OPERATOR(schema.nom_operateur)
Le fait qu'opérateur particulier existe et qu'il soit unaire ou binaire dépend des opérateurs définis par
le système ou l'utilisateur. Le Chapitre 9 décrit les opérateurs internes.
sqrt(2)
La liste des fonctions intégrées se trouve dans le Chapitre 9. D'autres fonctions pourraient être ajoutées
par l'utilisateur.
Lors de l'exécution de requêtes dans une base de données où certains utilisateurs ne font pas confiance
aux autres utilisateurs, observez quelques mesures de sécurité disponibles dans Section 10.3 lors de
l'appel de fonctions.
En option, les arguments peuvent avoir leur nom attaché. Voir la Section 4.3 pour les détails.
Note
Une fonction qui prend un seul argument de type composite peut aussi être appelée en utilisant
la syntaxe de sélection de champ. Du coup, un champ peut être écrit dans le style fonctionnel.
Cela signifie que les notations col(table) et table.col sont interchangeables. Ce
comportement ne respecte pas le standard SQL, mais il est fourni dans PostgreSQL, car il
45
Syntaxe SQL
permet l'utilisation de fonctions émulant les « champs calculés ». Pour plus d'informations,
voir la Section 8.16.5.
où nom_agregat est un agrégat précédemment défini (parfois qualifié d'un nom de schéma),
expression est toute expression de valeur qui ne contient pas elle-même une expression
d'agrégat ou un appel à une fonction de fenêtrage. Les clauses optionnelles clause_order_by et
clause_filtre sont décrites ci-dessous.
La première forme d'expression d'agrégat appelle l'agrégat une fois pour chaque ligne en entrée. La
seconde forme est identique à la première, car ALL est une clause active par défaut. La troisième
forme fait appel à l'agrégat une fois pour chaque valeur distincte de l'expression (ou ensemble distinct
de valeurs, pour des expressions multiples) trouvée dans les lignes en entrée. La quatrième forme
appelle l'agrégat une fois pour chaque ligne en entrée ; comme aucune valeur particulière en entrée
n'est spécifiée, c'est généralement utile pour la fonction d'agrégat count(*). La dernière forme est
utilisée avec les agrégats à ensemble trié qui sont décrits ci-dessous.
La plupart des fonctions d'agrégats ignorent les entrées NULL, pour que les lignes qui renvoient une
ou plusieurs expressions NULL soient disqualifiées. Ceci peut être considéré comme vrai pour tous
les agrégats internes sauf indication contraire.
Par exemple, count(*) trouve le nombre total de lignes en entrée, alors que count(f1) récupère
le nombre de lignes en entrée pour lesquelles f1 n'est pas NULL. En effet, la fonction count ignore
les valeurs NULL, mais count(distinct f1) retrouve le nombre de valeurs distinctes non NULL
de f1.
D'habitude, les lignes en entrée sont passées à la fonction d'agrégat dans un ordre non spécifié. Dans
la plupart des cas, cela n'a pas d'importance. Par exemple, min donne le même résultat quel que
soit l'ordre dans lequel il reçoit les données. Néanmoins, certaines fonctions d'agrégat (telles que
array_agg et string_agg) donnent un résultat dépendant de l'ordre des lignes en entrée. Lors
de l'utilisation de ce type d'agrégat, la clause clause_order_by peut être utilisée pour préciser
l'ordre de tri désiré. La clause clause_order_by a la même syntaxe que la clause ORDER BY
d'une requête, qui est décrite dans la Section 7.5, sauf que ses expressions sont toujours des expressions
simples et ne peuvent pas être des noms de colonne en sortie ou des numéros. Par exemple :
Lors de l'utilisation de fonctions d'agrégat à plusieurs arguments, la clause ORDER BY arrive après
tous les arguments de l'agrégat. Par exemple, il faut écrire ceci :
46
Syntaxe SQL
Ce dernier exemple est syntaxiquement correct, mais il concerne un appel à une fonction d'agrégat à
un seul argument avec deux clés pour le ORDER BY (le deuxième étant inutile, car il est constant).
Si DISTINCT est indiqué en plus de la clause clause_order_by, alors toutes les expressions de
l'ORDER BY doivent correspondre aux arguments de l'agrégat ; autrement dit, vous ne pouvez pas
trier sur une expression qui n'est pas incluse dans la liste DISTINCT.
Note
La possibilité de spécifier à la fois DISTINCT et ORDER BY dans une fonction d'agrégat est
une extension de PostgreSQL.
Placer la clause ORDER BY dans la liste des arguments standards de l'agrégat, comme décrit
jusqu'ici, est utilisé pour un agrégat de type général et statistique pour lequel le tri est optionnel.
Il existe une sous-classe de fonctions d'agrégat appelée agrégat d'ensemble trié pour laquelle la
clause clause_order_by est requise, habituellement parce que le calcul de l'agrégat est seulement
sensible à l'ordre des lignes en entrée. Des exemples typiques d'agrégat avec ensemble trié incluent les
calculs de rang et de pourcentage. Pour un agrégat d'ensemble trié, la clause clause_order_by est
écrite à l'intérieur de WITHIN GROUP (...), comme indiqué dans la syntaxe alternative finale. Les
expressions dans clause_order_by sont évaluées une fois par ligne en entrée, comme n'importe
quel argument d'un agrégat, une fois triées suivant la clause clause_order_by, et envoyées à la
fonction en tant qu'arguments en entrée. (Ceci est contraire au cas de la clause clause_order_by
en dehors d'un WITHIN GROUP , qui n'est pas traité comme argument de la fonction d'agrégat.)
Les expressions d'argument précédant WITHIN GROUP, s'il y en a, sont appelées des arguments
directs pour les distinguer des arguments agrégés listés dans clause_order_by. Contrairement
aux arguments normaux d'agrégats, les arguments directs sont évalués seulement une fois par appel
d'agrégat et non pas une fois par ligne en entrée. Cela signifie qu'ils peuvent contenir des variables
seulement si ces variables sont regroupées par GROUP BY ; cette restriction équivaut à des arguments
directs qui ne seraient pas dans une expression d'agrégat. Les arguments directs sont typiquement
utilisés pour des fractions de pourcentage, qui n'ont de sens qu'en tant que valeur singulière par calcul
d'agrégat. La liste d'arguments directs peut être vide ; dans ce cas, écrivez simplement (), et non pas
(*). (PostgreSQL accepte actuellement les deux écritures, mais seule la première est conforme avec
le standard SQL.)
qui obtient le 50e pourcentage ou le médian des valeurs de la colonne revenu de la table
proprietes. Ici, 0.5 est un argument direct ; cela n'aurait pas de sens si la fraction de pourcentage
était une valeur variant suivant les lignes.
Si la clause FILTER est spécifiée, alors seules les lignes en entrée pour lesquelles filter_clause
est vraie sont envoyées à la fonction d'agrégat ; les autres lignes sont ignorées. Par exemple :
SELECT
count(*) AS nonfiltres,
count(*) FILTER (WHERE i < 5) AS filtres
FROM generate_series(1,10) AS s(i);
47
Syntaxe SQL
nonfiltres | filtres
------------+---------
10 | 4
(1 row)
Les fonctions d'agrégat prédéfinies sont décrites dans la Section 9.20. D'autres fonctions d'agrégat
pourraient être ajoutées par l'utilisateur.
Une expression d'agrégat peut seulement apparaître dans la liste de résultats ou dans la clause HAVING
d'une commande SELECT. Elle est interdite dans d'autres clauses, telles que WHERE, parce que ces
clauses sont logiquement évaluées avant que les résultats des agrégats ne soient calculés.
Lorsqu'une expression d'agrégat apparaît dans une sous-requête (voir la Section 4.2.11 et la
Section 9.22), l'agrégat est normalement évalué sur les lignes de la sous-requête. Cependant,
une exception survient si les arguments de l'agrégat (et clause_filtre si fourni) contiennent
seulement des niveaux externes de variables : ensuite, l'agrégat appartient au niveau externe le plus
proche et est évalué sur les lignes de cette requête. L'expression de l'agrégat est une référence externe
pour la sous-requête dans laquelle il apparaît et agit comme une constante sur toute évaluation de cette
requête. La restriction apparaissant seulement dans la liste de résultats ou dans la clause HAVING
s'applique avec respect du niveau de requête auquel appartient l'agrégat.
[ nom_fenêtrage_existante ]
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC | USING opérateur ] [ NULLS
{ FIRST | LAST } ] [, ...] ]
[ clause_portée ]
48
Syntaxe SQL
UNBOUNDED PRECEDING
décalage PRECEDING
CURRENT ROW
décalage FOLLOWING
UNBOUNDED FOLLOWING
Ici, expression représente toute expression de valeur qui ne contient pas elle-même d'appel à des
fonctions de fenêtrage.
nom_fenêtrage est une référence à la spécification d'une fenêtre nommée, définie dans la clause
WINDOW de la requête. Les spécifications de fenêtres nommées sont habituellement référencées avec
OVER nom_fenêtrage, mais il est aussi possible d'écrire un nom de fenêtre entre parenthèses, puis
de fournir en option une clause de tri et/ou une clause de portée (la fenêtre référencée ne doit pas avoir
ces clauses si elles sont fournies ici). Cette dernière syntaxe suit les mêmes règles que la modification
d'un nom de fenêtre existant dans une clause WINDOW ; voir la page de référence de SELECT pour
les détails.
La clause PARTITION BY groupe les lignes de la requête en partitions, qui sont traitées séparément
par la fonction de fenêtrage. PARTITION BY fonctionne de la même façon qu'une clause GROUP BY
au niveau de la requête, sauf que ses expressions sont toujours des expressions et ne peuvent pas être
des noms ou des numéros de colonnes en sortie. Sans PARTITION BY, toutes les lignes produites par
la requête sont traitées comme une seule partition. La clause ORDER BY détermine l'ordre dans lequel
les lignes d'une partition sont traitées par la fonction de fenêtrage. Cela fonctionne de la même façon
que la clause ORDER BY d'une requête, mais ne peut pas non plus utiliser les noms ou les numéros
des colonnes en sortie. Sans ORDER BY, les lignes sont traitées dans n'importe quel ordre.
La clause clause_portée indique l'ensemble de lignes constituant la portée de la fenêtre, qui est
un sous-ensemble de la partition en cours, pour les fonctions de fenêtrage qui agissent sur ce sous-
ensemble plutôt que sur la partition entière. L'ensemble de lignes dans la portée peut varier suivant
la ligne courante. Le sous-ensemble peut être spécifié avec le mode RANGE, avec le mode ROWS
ou avec le mode GROUPS. Dans les deux cas, il s'exécute de début_portée à fin_portée. Si
fin_portée est omis, la fin vaut par défaut CURRENT ROW.
Dans les modes RANGE et GROUPS, un début_portée à CURRENT ROW signifie que le sous-
ensemble commence avec la ligne suivant la ligne courante (une ligne que la clause ORDER BY de
la fenêtre considère comme équivalente à la ligne courante), alors qu'un fin_portée à CURRENT
ROW signifie que le sous-ensemble se termine avec la dernière ligne homologue de la ligne en cours.
Dans le mode ROWS, CURRENT ROW signifie simplement la ligne courante.
Dans les options de portée, offset de PRECEDING et offset de FOLLOWING, le offset doit
être une expression ne contenant ni variables, ni fonctions d'agrégat, ni fonctions de fenêtrage. La
signification de offset dépend du mode de porté :
49
Syntaxe SQL
• Dans le mode ROWS, offset doit renvoyer un entier non négatif non NULL, et l'option signifie
que la portée commence ou finit au nombre spécifié de lignes avant ou après la ligne courante.
• Dans le mode GROUPS, offset doit de nouveau renvoyer un entier non négatif non NULL,
et l'option signifie que la portée commence ou finit au nombre spécifié de groupes de lignes
équivalentes avant ou après le groupe de la ligne courante, et où un groupe de lignes équivalentes
est un ensemble de lignes équivalentes dans le tri ORDER BY. (Il doit y avoir une clause ORDER
BY dans la définition de la fenêtre pour utiliser le mode GROUPS.)
• Dans le mode RANGE, ces options requièrent que la clause ORDER BY spécifient exactement une
colonne. offset indique la différence maximale entre la valeur de cette colonne dans la ligne
courante et sa valeur dans les lignes précédentes et suivantes de la portée. Le type de données
de l'expression offset varie suivant le type de données de la colonne triée. Pour les colonnes
ordonnées numériques, il s'agit habituellement du même type que la colonne ordonnée. Mais pour
les colonnes ordonnées de type date/heure, il s'agit d'un interval. Par exemple, si la colonne
ordonnée est de type date ou timestamp, on pourrait écrire RANGE BETWEEN '1 day'
PRECEDING AND '10 days' FOLLOWING. offset est toujours requis pour être non NULL
et non négatif, bien que la signification de « non négatif » dépend de son type de données.
Dans tous les cas, la distance jusqu'à la fin de la portée est limitée par la distance jusqu'à la fin de
la partition, pour que les lignes proche de la fin de la partition, la portée puisse contenir moins de
lignes qu'ailleurs.
Notez que dans les deux modes ROWS et GROUPS, 0 PRECEDING et 0 FOLLOWING sont équivalents
à CURRENT ROW. Le mode RANGE en fait aussi partie habituellement, pour une signification
appropriée de « zéro » pour le type de données spécifique.
L'option frame_exclusion permet aux lignes autour de la ligne courante d'être exclues de la
portée, même si elles seraient incluses d'après les options de début et de fin de portée. EXCLUDE
CURRENT ROW exclut la ligne courante de la portée. EXCLUDE GROUP exclut la ligne courante et
ses équivalents dans l'ordre à partir de la portée. EXCLUDE TIES exclut de la portée tout équivalent
de la ligne courante mais pas la ligne elle-même. EXCLUDE NO OTHERS spécifie explicitement le
comportement par défaut lors de la non exclusion de la ligne courante ou de ses équivalents.
L'option par défaut est RANGE UNBOUNDED PRECEDING, ce qui est identique à RANGE BETWEEN
UNBOUNDED PRECEDING AND CURRENT ROW. Avec ORDER BY, ceci configure le sous-
ensemble pour contenir toutes les lignes de la partition à partir de la ligne courante. Sans ORDER BY,
toutes les lignes de la partition sont incluses dans le sous-ensemble de la fenêtre, car toutes les lignes
deviennent voisines de la ligne en cours.
Les restrictions sont que début_portée ne peut pas valoir UNBOUNDED FOLLOWING,
fin_portée ne peut pas valoir UNBOUNDED PRECEDING, et le choix de fin_portée ne peut
pas apparaître avant la liste ci-dessus des options début_portée et fin_portée que le choix de
frame_start -- par exemple, RANGE BETWEEN CURRENT ROW AND valeur PRECEDING
n'est pas autorisé. Par exemple, ROWS BETWEEN 7 PRECEDING AND 8 PRECEDING est autorisé,
même s'il ne sélectionnera aucune ligne.
Si FILTER est indiqué, seules les lignes en entrée pour lesquelles clause_filtre est vrai sont
envoyées à la fonction de fenêtrage. Les autres lignes sont simplement ignorées. Seules les fonctions
de fenêtrage qui sont des agrégats acceptent une clause FILTER.
Les fonctions de fenêtrage internes sont décrites dans la Tableau 9.57. D'autres fonctions de fenêtrage
peuvent être ajoutées par l'utilisateur. De plus, toute fonction d'agrégat de type général ou statistique
peut être utilisée comme fonction de fenêtrage. Néanmoins, les agrégats d'ensemble trié et d'ensemble
hypothétique ne peuvent pas être utilisés actuellement comme des fonctions de fenêtrage.
Les syntaxes utilisant * sont utilisées pour appeler des fonctions d'agrégats sans paramètres en tant que
fonctions de fenêtrage. Par exemple : count(*) OVER (PARTITION BY x ORDER BY y). Le
symbole * n'est habituellement pas utilisé pour les fonctions de fenêtrage. Les fonctions de fenêtrage
n'autorisent pas l'utilisation de DISTINCT ou ORDER BY dans la liste des arguments de la fonction.
50
Syntaxe SQL
Les appels de fonctions de fenêtrage sont autorisés seulement dans la liste SELECT et dans la clause
ORDER BY de la requête.
Il existe plus d'informations sur les fonctions de fenêtrages dans la Section 3.5, dans la Section 9.21
et dans la Section 7.2.5.
La syntaxe CAST est conforme à SQL ; la syntaxe avec :: est historique dans PostgreSQL.
Lorsqu'une conversion est appliquée à une expression de valeur pour un type connu, il représente une
conversion de type à l'exécution. Cette conversion réussira seulement si une opération convenable de
conversion de type a été définie. Notez que ceci est subtilement différent de l'utilisation de conversion
avec des constantes, comme indiqué dans la Section 4.1.2.7. Une conversion appliquée à une chaîne
constante représente l'affectation initiale d'un type pour une valeur constante, et donc cela réussira
pour tout type (si le contenu de la chaîne constante est une syntaxe acceptée en entrée pour le type
de donnée).
Une conversion de type explicite pourrait être habituellement omise s'il n'y a pas d'ambiguïté sur le type
qu'une expression de valeur pourrait produire (par exemple, lorsqu'elle est affectée à une colonne de
table) ; le système appliquera automatiquement une conversion de type dans de tels cas. Néanmoins, la
conversion automatique est réalisée seulement pour les conversions marquées « OK pour application
implicite » dans les catalogues système. D'autres conversions peuvent être appelées avec la syntaxe
de conversion explicite. Cette restriction a pour but d'empêcher l'exécution silencieuse de conversions
surprenantes.
Il est aussi possible de spécifier une conversion de type en utilisant une syntaxe de type fonction :
nom_type ( expression )
Néanmoins, ceci fonctionne seulement pour les types dont les noms sont aussi valides en tant que
noms de fonctions. Par exemple, double precision ne peut pas être utilisé de cette façon, mais
son équivalent float8 le peut. De même, les noms interval, time et timestamp peuvent
seulement être utilisés de cette façon s'ils sont entre des guillemets doubles, à cause des conflits de
syntaxe. Du coup, l'utilisation de la syntaxe de conversion du style fonction amène à des incohérences
et devrait probablement être évitée.
Note
La syntaxe par fonction est en fait seulement un appel de fonction. Quand un des deux
standards de syntaxe de conversion est utilisé pour faire une conversion à l'exécution, elle
appellera en interne une fonction enregistrée pour réaliser la conversion. Par convention, ces
fonctions de conversion ont le même nom que leur type de sortie et, du coup, la syntaxe
par fonction n'est rien de plus qu'un appel direct à la fonction de conversion sous-jacente.
Évidemment, une application portable ne devrait pas s'y fier. Pour plus d'informations, voir la
page de manuel de CREATE CAST.
51
Syntaxe SQL
où collationnement est un identificateur pouvant être qualifié par son schéma. La clause
COLLATE a priorité par rapport aux opérateurs ; des parenthèses peuvent être utilisées si nécessaire.
Si aucun collationnement n'est spécifiquement indiqué, le système de bases de données déduit cette
information du collationnement des colonnes impliquées dans l'expression. Si aucune colonne ne se
trouve dans l'expression, il utilise le collationnement par défaut de la base de données.
Les deux utilisations principales de la clause COLLATE sont la surcharge de l'ordre de tri dans une
clause ORDER BY, par exemple :
et la surcharge du collationnement d'une fonction ou d'un opérateur qui produit un résultat sensible
à la locale, par exemple :
Notez que, dans le dernier cas, la clause COLLATE est attachée à l'argument en entrée de l'opérateur.
Peu importe l'argument de l'opérateur ou de la fonction qui a la clause COLLATE, parce que le
collationnement appliqué à l'opérateur ou à la fonction est dérivé en considérant tous les arguments,
et une clause COLLATE explicite surchargera les collationnements des autres arguments. (Attacher
des clauses COLLATE différentes sur les arguments aboutit à une erreur. Pour plus de détails, voir la
Section 23.2.) Du coup, ceci donne le même résultat que l'exemple précédent :
car cette requête cherche à appliquer un collationnement au résultat de l'opérateur >, qui est du type
boolean, type non sujet au collationnement.
Par exemple, ce qui suit trouve la ville disposant de la population la plus importante dans chaque état :
52
Syntaxe SQL
SELECT ARRAY[1,2,3+4];
array
---------
{1,2,7}
(1 row)
Par défaut, le type d'élément du tableau est le type commun des expressions des membres, déterminé
en utilisant les mêmes règles que pour les constructions UNION ou CASE (voir la Section 10.5). Vous
pouvez surcharger ceci en convertissant explicitement le constructeur de tableau vers le type désiré.
Par exemple :
SELECT ARRAY[1,2,22.7]::integer[];
array
----------
{1,2,23}
(1 row)
Ceci a le même effet que la conversion de chaque expression vers le type d'élément du tableau
individuellement. Pour plus d'informations sur les conversions, voir la Section 4.2.9.
Les valeurs de tableaux multidimensionnels peuvent être construites par des constructeurs de tableaux
imbriqués. Pour les constructeurs internes, le mot-clé ARRAY peut être omis. Par exemple, ces
expressions produisent le même résultat :
SELECT ARRAY[[1,2],[3,4]];
array
---------------
{{1,2},{3,4}}
(1 row)
Comme les tableaux multidimensionnels doivent être rectangulaires, les constructeurs internes
du même niveau doivent produire des sous-tableaux de dimensions identiques. Toute conversion
appliquée au constructeur ARRAY externe se propage automatiquement à tous les constructeurs
internes.
Les éléments d'un constructeur de tableau multidimensionnel peuvent être tout ce qui récupère un
tableau du bon type, pas seulement une construction d'un tableau imbriqué. Par exemple :
Vous pouvez construire un tableau vide, mais comme il est impossible d'avoir un tableau sans type,
vous devez convertir explicitement votre tableau vide dans le type désiré. Par exemple :
SELECT ARRAY[]::integer[];
53
Syntaxe SQL
array
-------
{}
(1 row)
Il est aussi possible de construire un tableau à partir des résultats d'une sous-requête. Avec cette forme,
le constructeur de tableau est écrit avec le mot-clé ARRAY suivi par une sous-requête entre parenthèses
(et non pas des crochets). Par exemple :
La sous-requête doit renvoyer une seule colonne. Si la sortie de la sous- requête n'est pas de type
tableau, le tableau à une dimension résultant aura un élément pour chaque ligne dans le résultat de la
sous-requête, avec un type élément correspondant à celui de la colonne en sortie de la sous- requête. Si
la colonne en sortie de la sous-requête est de type tableau, le résultat sera un tableau du même type, mais
avec une dimension supplémentaire ; dans ce cas, toutes les lignes de la sous-requête doivent renvoyer
des tableaux de dimension identique (dans le cas contraire, le résultat ne serait pas rectangulaire).
Les indices d'un tableau construit avec ARRAY commencent toujours à un. Pour plus d'informations
sur les tableaux, voir la Section 8.15.
Le mot-clé ROW est optionnel lorsqu'il y a plus d'une expression dans la liste.
Un constructeur de ligne peut inclure la syntaxe valeurligne.*, qui sera étendue en une liste
d'éléments de la valeur ligne, ce qui est le comportement habituel de la syntaxe .* utilisée au niveau
haut d'une liste SELECT (voir Section 8.16.5). Par exemple, si la table t a les colonnes f1 et f2, ces
deux requêtes sont identiques :
Note
Avant PostgreSQL 8.2, la syntaxe .* n'était pas étendue dans les constructeurs de lignes. De ce
fait, ROW(t.*, 42) créait une ligne à deux champs dont le premier était une autre valeur de
ligne. Le nouveau comportement est généralement plus utile. Si vous avez besoin de l'ancien
54
Syntaxe SQL
comportement de valeurs de ligne imbriquées, écrivez la valeur de ligne interne sans .*, par
exemple ROW(t, 42).
Par défaut, la valeur créée par une expression ROW est d'un type d'enregistrement anonyme. Si
nécessaire, il peut être converti en un type composite nommé -- soit le type de ligne d'une table, soit
un type composite créé avec CREATE TYPE AS. Une conversion explicite pourrait être nécessaire
pour éviter toute ambiguïté. Par exemple :
Les constructeurs de lignes peuvent être utilisés pour construire des valeurs composites à stocker dans
une colonne de table de type composite ou pour être passés à une fonction qui accepte un paramètre
composite. De plus, il est possible de comparer deux valeurs de lignes ou de tester une ligne avec IS
NULL ou IS NOT NULL, par exemple
Pour plus de détails, voir la Section 9.23. Les constructeurs de lignes peuvent aussi être utilisés en
relation avec des sous-requêtes, comme discuté dans la Section 9.22.
55
Syntaxe SQL
L'ordre d'évaluation des sous-expressions n'est pas défini. En particulier, les entrées d'un opérateur
ou d'une fonction ne sont pas obligatoirement évaluées de la gauche vers la droite ou dans un autre
ordre fixé.
De plus, si le résultat d'une expression peut être déterminé par l'évaluation de certaines parties de celle-
ci, alors d'autres sous-expressions devraient ne pas être évaluées du tout. Par exemple, si vous écrivez :
alors une_fonction() pourrait (probablement) ne pas être appelée du tout. Pareil dans le cas
suivant :
Notez que ceci n'est pas identique au « court-circuitage » de gauche à droite des opérateurs booléens
utilisé par certains langages de programmation.
En conséquence, il est déconseillé d'utiliser des fonctions ayant des effets de bord dans une partie des
expressions complexes. Il est particulièrement dangereux de se fier aux effets de bord ou à l'ordre
d'évaluation dans les clauses WHERE et HAVING, car ces clauses sont reproduites de nombreuses fois
lors du développement du plan d'exécution. Les expressions booléennes (combinaisons AND/OR/NOT)
dans ces clauses pourraient être réorganisées d'une autre façon autorisée dans l'algèbre booléenne.
Quand il est essentiel de forcer l'ordre d'évaluation, une construction CASE (voir la Section 9.17) peut
être utilisée. Voici un exemple qui ne garantit pas qu'une division par zéro ne soit pas faite dans une
clause WHERE :
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
Une construction CASE utilisée de cette façon déjouera les tentatives d'optimisation, donc cela ne sera
à faire que si c'est nécessaire (dans cet exemple particulier, il serait sans doute mieux de contourner
le problème en écrivant y > 1.5*x).
Néanmoins, CASE n'est pas un remède à tout. Une limitation à la technique illustrée ci-dessus est
qu'elle n'empêche pas l'évaluation en avance des sous-expressions constantes. Comme décrit dans
Section 38.7, les fonctions et les opérateurs marqués IMMUTABLE peuvent être évalués quand la
requête est planifiée plutôt que quand elle est exécutée. Donc, par exemple :
SELECT CASE WHEN x > 0 THEN x ELSE 1/0 END FROM tab;
va produire comme résultat un échec pour division par zéro, car le planificateur a essayé de simplifier
la sous-expression constante, même si chaque ligne de la table a x > 0 de façon à ce que la condition
ELSE ne soit jamais exécutée.
Bien que cet exemple particulier puisse sembler stupide, il existe de nombreux cas moins évidents,
n'impliquant pas de constantes, mais plutôt des requêtes exécutées par des fonctions, quand les valeurs
des arguments des fonctions et de variables locales peuvent être insérées dans les requêtes en tant que
constantes toujours dans le but de la planification. À l'intérieur de fonctions PL/pgSQL, par exemple,
utiliser une instruction IF-THEN- ELSE pour protéger un calcul risqué est beaucoup plus sûr qu'une
expression CASE.
Une autre limitation de cette technique est qu'une expression CASE ne peut pas empêcher l'évaluation
d'une expression d'agrégat contenue dans cette expression, car les expressions d'agrégat sont calculées
avant les expressions « scalaires » dans une liste SELECT ou dans une clause HAVING. Par exemple,
la requête suivante peut provoquer une erreur de division par zéro bien qu'elle semble protégée contre
ce type d'erreurs :
56
Syntaxe SQL
Les agrégats min() et avg() sont calculés en même temps avec toutes les lignes en entrée, donc si
une ligne a une valeur 0 pour la colonne employees, l'erreur de division par zéro surviendra avant
d'avoir pu tester le résultat de min(). Il est préférable d'utiliser une clause WHERE ou une clause
FILTER pour empêcher les lignes problématiques en entrée d'atteindre la fonction d'agrégat.
Quel que soit la notation, les paramètres qui ont des valeurs par défaut dans leur déclaration n'ont
pas besoin d'être précisés dans l'appel. Ceci est particulièrement utile dans la notation nommée, car
toute combinaison de paramètre peut être omise, alors que dans la notation par position, les paramètres
peuvent seulement être omis de la droite vers la gauche.
PostgreSQL supporte aussi la notation mixée. Elle combine la notation par position avec la notation
par nom. Dans ce cas, les paramètres de position sont écrits en premier, les paramètres nommés
apparaissent après.
Les exemples suivants illustrent l'utilisation des trois notations, en utilisant la définition de fonction
suivante :
57
Syntaxe SQL
Tous les arguments sont indiqués dans l'ordre. Le résultat est en majuscule, car l'argument
majuscule est indiqué à true. Voici un autre exemple :
Ici, le paramètre majuscule est omis, donc il récupère la valeur par défaut, soit false, ce qui a
pour résultat une sortie en minuscule. Dans la notation par position, les arguments peuvent être omis
de la droite à la gauche à partir du moment où ils ont des valeurs par défaut.
Encore une fois, l'argument majuscule a été omis, donc il dispose de sa valeur par défaut, false,
implicitement. Un avantage à utiliser la notation par nom est que les arguments peuvent être saisis
dans n'importe quel ordre. Par exemple :
Une syntaxe plus ancienne basée sur « := » est supportée pour des raisons de compatibilité ascendante :
58
Syntaxe SQL
Dans la requête ci-dessus, les arguments a et b sont précisés par leur position alors que majuscule
est indiqué par son nom. Dans cet exemple, cela n'apporte pas grand-chose, sauf pour une
documentation de la fonction. Avec une fonction plus complexe, comprenant de nombreux paramètres
avec des valeurs par défaut, les notations par nom et mixées améliorent l'écriture des appels de fonction
et permettent de réduire les risques d'erreurs.
Note
Les notations par appel nommé ou mixe ne peuvent pas être utilisées lors de l'appel d'une
fonction d'agrégat (mais elles fonctionnent quand une fonction d'agrégat est utilisée en tant
que fonction de fenêtrage).
59
Chapitre 5. Définition des données
Ce chapitre couvre la création des structures de données amenées à contenir les données. Dans une
base relationnelle, les données brutes sont stockées dans des tables. De ce fait, une grande partie
de ce chapitre est consacrée à l'explication de la création et de la modification des tables et aux
fonctionnalités disponibles pour contrôler les données stockées dans les tables. L'organisation des
tables dans des schémas et l'attribution de privilèges sur les tables sont ensuite décrites. Pour finir,
d'autres fonctionnalités, telles que l'héritage, le partitionnement de tables, les vues, les fonctions et les
triggers sont passés en revue.
Chaque colonne a un type de données. Ce type limite l'ensemble de valeurs qu'il est possible d'attribuer
à une colonne. Il attribue également une sémantique aux données stockées dans la colonne pour
permettre les calculs sur celles-ci. Par exemple, une colonne déclarée dans un type numérique n'accepte
pas les chaînes textuelles ; les données stockées dans une telle colonne peuvent être utilisées dans
des calculs mathématiques. Par opposition, une colonne déclarée de type chaîne de caractères accepte
pratiquement n'importe quel type de donnée, mais ne se prête pas aux calculs mathématiques. D'autres
types d'opérations, telle la concaténation de chaînes, sont cependant disponibles.
PostgreSQL inclut un ensemble important de types de données intégrés pour s'adapter à diverses
applications. Les utilisateurs peuvent aussi définir leurs propres types de données.
La plupart des types de données intégrés ont des noms et des sémantiques évidents. C'est pourquoi
leur explication détaillée est reportée au Chapitre 8.
Parmi les types les plus utilisés, on trouve integer pour les entiers, numeric pour les éventuelles
fractions, text pour les chaînes de caractères, date pour les dates, time pour les heures et
timestamp pour les valeurs qui contiennent à la fois une date et une heure.
Pour créer une table, on utilise la commande bien nommée CREATE TABLE. Dans cette commande,
il est nécessaire d'indiquer, au minimum, le nom de la table, les noms des colonnes et le type de
données de chacune d'elles. Par exemple :
Cela crée une table nommée ma_premiere_table avec deux colonnes. La première
colonne, nommée premiere_colonne, est de type text ; la seconde colonne, nommée
deuxieme_colonne, est de type integer. Les noms des tables et colonnes se conforment à la
syntaxe des identifiants expliquée dans la Section 4.1.1. Les noms des types sont souvent aussi des
identifiants, mais il existe des exceptions. Le séparateur de la liste des colonnes est la virgule. La liste
doit être entre parenthèses.
60
Définition des données
L'exemple qui précède est à l'évidence extrêmement simpliste. On donne habituellement aux tables
et aux colonnes des noms qui indiquent les données stockées. L'exemple ci-dessous est un peu plus
réaliste :
(Le type numeric peut stocker des fractions telles que les montants.)
Astuce
Quand de nombreuses tables liées sont créées, il est préférable de définir un motif cohérent
pour le nommage des tables et des colonnes. On a ainsi la possibilité d'utiliser le pluriel ou le
singulier des noms, chacune ayant ses fidèles et ses détracteurs.
Le nombre de colonnes d'une table est limité. En fonction du type de colonnes, il oscille entre 250
et 1600. Définir une table avec un nombre de colonnes proche de cette limite est, cependant, très
inhabituel et doit conduire à se poser des questions quant à la conception du modèle.
Lorsqu'une table n'est plus utile, elle peut être supprimée à l'aide de la commande DROP TABLE.
Par exemple :
Tenter de supprimer une table qui n'existe pas lève une erreur. Il est néanmoins habituel, dans les
fichiers de scripts SQL, d'essayer de supprimer chaque table avant de la créer. Les messages d'erreur
sont alors ignorés afin que le script fonctionne, que la table existe ou non. (La variante DROP TABLE
IF EXISTS peut aussi être utilisée pour éviter les messages d'erreur, mais elle ne fait pas partie du
standard SQL.)
Pour la procédure de modification d'une table qui existe déjà, voir la Section 5.5 plus loin dans ce
chapitre.
Les outils précédemment décrits permettent de créer des tables fonctionnelles. Le reste de ce chapitre
est consacré à l'ajout de fonctionnalités à la définition de tables pour garantir l'intégrité des données,
la sécurité ou l'ergonomie. Le lecteur impatient d'insérer des données dans ses tables peut sauter au
Chapitre 6 et lire le reste de ce chapitre plus tard.
Si aucune valeur par défaut n'est déclarée explicitement, la valeur par défaut est la valeur NULL. Cela
a un sens dans la mesure où l'on peut considérer que la valeur NULL représente des données inconnues.
Dans la définition d'une table, les valeurs par défaut sont listées après le type de données de la colonne.
Par exemple:
61
Définition des données
La valeur par défaut peut être une expression, alors évaluée à l'insertion de cette valeur (pas à la
création de la table). Un exemple commun est la colonne de type timestamp dont la valeur par défaut
est now(). Elle se voit ainsi attribuer l'heure d'insertion. Un autre exemple est la génération d'un
« numéro de série » pour chaque ligne. Dans PostgreSQL, cela s'obtient habituellement par quelque
chose comme
où la fonction nextval() fournit des valeurs successives à partir d'un objet séquence (voir la
Section 9.16). Cet arrangement est suffisamment commun pour qu'il ait son propre raccourci :
5.3. Contraintes
Les types de données sont un moyen de restreindre la nature des données qui peuvent être stockées dans
une table. Pour beaucoup d'applications, toutefois, la contrainte fournie par ce biais est trop grossière.
Par exemple, une colonne qui contient le prix d'un produit ne doit accepter que des valeurs positives.
Mais il n'existe pas de type de données standard qui n'accepte que des valeurs positives. Un autre
problème peut provenir de la volonté de contraindre les données d'une colonne par rapport aux autres
colonnes ou lignes. Par exemple, dans une table contenant des informations de produit, il ne peut y
avoir qu'une ligne par numéro de produit.
Pour cela, SQL permet de définir des contraintes sur les colonnes et les tables. Les contraintes donnent
autant de contrôle sur les données des tables qu'un utilisateur peut le souhaiter. Si un utilisateur tente
de stocker des données dans une colonne en violation d'une contrainte, une erreur est levée. Cela
s'applique même si la valeur vient de la définition de la valeur par défaut.
La définition de contrainte vient après le type de données, comme pour les définitions de valeur par
défaut. Les valeurs par défaut et les contraintes peuvent être données dans n'importe quel ordre. Une
contrainte de vérification s'utilise avec le mot-clé CHECK suivi d'une expression entre parenthèses.
L'expression de la contrainte implique habituellement la colonne à laquelle elle s'applique, la contrainte
n'ayant dans le cas contraire que peu de sens.
62
Définition des données
La contrainte peut prendre un nom distinct. Cela clarifie les messages d'erreur et permet de faire
référence à la contrainte lorsqu'elle doit être modifiée. La syntaxe est :
Pour indiquer une contrainte nommée, on utilise le mot-clé CONSTRAINT suivi d'un identifiant et de
la définition de la contrainte (si aucun nom n'est précisé, le système en choisit un).
Une contrainte de vérification peut aussi faire référence à plusieurs colonnes. Dans le cas d'un produit,
on peut vouloir stocker le prix normal et un prix réduit en s'assurant que le prix réduit soit bien inférieur
au prix normal.
Si les deux premières contraintes n'offrent pas de nouveauté, la troisième utilise une nouvelle syntaxe.
Elle n'est pas attachée à une colonne particulière, mais apparaît comme un élément distinct dans la
liste des colonnes. Les définitions de colonnes et ces définitions de contraintes peuvent être définies
dans un ordre quelconque.
Les deux premières contraintes sont appelées contraintes de colonne, tandis que la troisième est
appelée contrainte de table parce qu'elle est écrite séparément d'une définition de colonne particulière.
Les contraintes de colonne peuvent être écrites comme des contraintes de table, mais l'inverse n'est pas
forcément possible puisqu'une contrainte de colonne est supposée ne faire référence qu'à la colonne
à laquelle elle est attachée (PostgreSQL ne vérifie pas cette règle, mais il est préférable de la suivre
pour s'assurer que les définitions de tables fonctionnent avec d'autres systèmes de bases de données).
L'exemple ci-dessus peut aussi s'écrire :
ou même :
63
Définition des données
Les contraintes de table peuvent être nommées, tout comme les contraintes de colonne :
Une contrainte de vérification est satisfaite si l'expression est évaluée vraie ou NULL. Puisque la
plupart des expressions sont évaluées NULL si l'un des opérandes est nul, elles n'interdisent pas les
valeurs NULL dans les colonnes contraintes. Pour s'assurer qu'une colonne ne contient pas de valeurs
NULL, la contrainte NOT NULL décrite dans la section suivante peut être utilisée.
Note
PostgreSQL ne supporte pas les contraintes CHECK qui référencent les données d'autres tables
que celle contenant la nouvelle ligne ou la ligne mise à jour en cours de vérification. Alors
qu'une contrainte CHECK qui viole cette règle pourrait apparaitre fonctionner dans des tests
simples, il est possible que la base de données atteigne un état dans lequel la condiction
de la contrainte est fausse (à cause de changements supplémentaires en dehors de la ligne
impliquée). Ceci sera la cause d'un échec du rechargement de la sauvegarde d'une base.
La restauration pourrait échouer même quand l'état complet de la base est cohérent avec la
contrainte, à cause de lignes chargées dans un autre différent qui satisferait la contrainte. Si
possible, utilisez les contraintes UNIQUE, EXCLUDE, et FOREIGN KEY pour exprimer des
restrictions inter-lignes et inter-tables.
Si ce que vous désirez est une vérification unique avec certaines lignes au moment
de l'insertion, plutôt qu'une garantie de cohérence maintenue en permanence, un trigger
personnalisé peut être utilisé pour l'implémenter. (Cette approche évite le problème de
sauvegarde/restauration car pg_dump ne réinstalle les triggers qu'après chargement des
données, donc cette vérification ne sera pas effectuée pendant une sauvegarde/restauration.)
Note
PostgreSQL suppose que les conditions des contraintes CHECK sont immutables, c'est-à-dire
qu'elles donneront toujours le même résultat pour la même ligne en entrée. Cette supposition
est ce qui justifie l'examen des contraintes CHECK uniquement quand les lignes sont insérées
ou mises à jour, et non pas à d'autres moments. (Cet avertissement sur la non référence aux
données d'autres tables est en fait un cas particulier de cette restriction.)
Un exemple d'une façon habituelle de casser cette supposition est de référencer une fonction
utilisateur dans une expression CHECK, puis de changer le comportement de cette fonction.
PostgreSQL n'interdit pas cela, mais il ne notera pas qu'il y a des lignes dans la table qui
violent maintenant la contrainte CHECK. Ceci sera la cause d'un échec de la restauration d'une
sauvegarde de cette base. La façon recommandée de gérer de tels changements revient à
supprimer la contrainte (en utilisant ALTER TABLE), d'ajuster la définition de la fonction,
et d'ajouter de nouveau la contrainte, ce qui causera une nouvelle vérification des lignes de
la table.
64
Définition des données
Une contrainte NOT NULL indique simplement qu'une colonne ne peut pas prendre la valeur NULL.
Par exemple :
Une contrainte NOT NULL est toujours écrite comme une contrainte de colonne. Elle est
fonctionnellement équivalente à la création d'une contrainte de vérification CHECK (nom_colonne
IS NOT NULL). Toutefois, dans PostgreSQL, il est plus efficace de créer explicitement une
contrainte NOT NULL. L'inconvénient est que les contraintes de non-nullité ainsi créées ne peuvent
pas être explicitement nommées.
Une colonne peut évidemment avoir plusieurs contraintes. Il suffit d'écrire les contraintes les unes
après les autres :
L'ordre n'a aucune importance. Il ne détermine pas l'ordre de vérification des contraintes.
La contrainte NOT NULL a un contraire ; la contrainte NULL. Elle ne signifie pas que la colonne
doit être NULL, ce qui est assurément inutile, mais sélectionne le comportement par défaut, à savoir
que la colonne peut être NULL. La contrainte NULL n'est pas présente dans le standard SQL et ne
doit pas être utilisée dans des applications portables (elle n'a été ajoutée dans PostgreSQL que pour
assurer la compatibilité avec d'autres bases de données). Certains utilisateurs l'apprécient néanmoins,
car elle permet de basculer aisément d'une contrainte à l'autre dans un fichier de script. On peut, par
exemple, commencer avec :
Astuce
Dans la plupart des bases de données, il est préférable que la majorité des colonnes soient
marquées NOT NULL.
65
Définition des données
Pour définir une contrainte unique pour un groupe de colonnes, saisissez-la en tant que contrainte de
table avec les noms des colonnes séparés par des virgules :
Cela précise que la combinaison de valeurs dans les colonnes indiquées est unique sur toute la table.
Sur une colonne prise isolément, ce n'est pas nécessairement le cas (et habituellement cela ne l'est pas).
Ajouter une contrainte unique va automatiquement créer un index unique B-tree sur la colonne ou le
groupe de colonnes listées dans la contrainte. Une restriction d'unicité couvrant seulement certaines
lignes ne peut pas être écrite comme une contrainte unique, mais il est possible de forcer ce type de
restriction en créant un index partiel unique.
En général, une contrainte d'unicité est violée si plus d'une ligne de la table possède des valeurs
identiques sur toutes les colonnes de la contrainte. En revanche, deux valeurs NULL ne sont jamais
considérées égales. Cela signifie qu'il est possible de stocker des lignes dupliquées contenant une
valeur NULL dans au moins une des colonnes contraintes. Ce comportement est conforme au standard
SQL, mais d'autres bases SQL n'appliquent pas cette règle. Il est donc préférable d'être prudent lors
du développement d'applications portables.
66
Définition des données
prix numeric
);
Les clés primaires peuvent également contraindre plusieurs colonnes ; la syntaxe est semblable aux
contraintes d'unicité :
Ajouter une clé primaire créera automatiquement un index unique B-tree sur la colonne ou le groupe
de colonnes listés dans la clé primaire, et forcera les colonnes à être marquées NOT NULL.
L'ajout d'une clé primaire créera automatiquement un index B-tree unique sur la colonne ou le groupe
de colonnes utilisé dans la clé primaire.
Une table a, au plus, une clé primaire. (Le nombre de contraintes UNIQUE NOT NULL, qui assurent
pratiquement la même fonction, n'est pas limité, mais une seule peut être identifiée comme clé
primaire.) La théorie des bases de données relationnelles impose que chaque table ait une clé primaire.
Cette règle n'est pas forcée par PostgreSQL, mais il est préférable de la respecter.
Les clés primaires sont utiles pour la documentation et pour les applications clientes. Par exemple, une
application graphique qui permet la modifier des valeurs des lignes a probablement besoin de connaître
la clé primaire d'une table pour être capable d'identifier les lignes de façon unique. Le système de
bases de données utilise une clé primaire de différentes façons. Par exemple, la clé primaire définit les
colonnes cibles par défaut pour les clés étrangères référençant cette table.
Soit également une table qui stocke les commandes de ces produits. Il est intéressant de s'assurer que
la table des commandes ne contient que des commandes de produits qui existent réellement. Pour
cela, une contrainte de clé étrangère est définie dans la table des commandes qui référence la table
« produits » :
Il est désormais impossible de créer des commandes pour lesquelles les valeurs non NULL de
no_produit n'apparaissent pas dans la table « produits ».
Dans cette situation, on dit que la table des commandes est la table qui référence et la table des
produits est la table référencée. De la même façon, il y a des colonnes qui référencent et des colonnes
référencées.
67
Définition des données
parce qu'en l'absence de liste de colonnes, la clé primaire de la table de référence est utilisée comme
colonne de référence.
Une clé étrangère peut aussi contraindre et référencer un groupe de colonnes. Comme cela a déjà été
évoqué, il faut alors l'écrire sous forme d'une contrainte de table. Exemple de syntaxe :
CREATE TABLE t1 (
a integer PRIMARY KEY,
b integer,
c integer,
FOREIGN KEY (b, c) REFERENCES autre_table (c1, c2)
);
Le nombre et le type des colonnes contraintes doivent correspondre au nombre et au type des colonnes
référencées.
Parfois, il est utile que l'« autre table » d'une clé étrangère soit la même table ; elle est alors appelée
une clé étrangère auto-référencée. Par exemple, si vous voulez que les lignes d'une table représentent
les nœuds d'une structure en arbre, vous pouvez écrire
Un nœud racine aura la colonne parent_id à NULL, et les enregistrements non NULL de
parent_id seront contraints de référencer des enregistrements valides de la table.
Une table peut contenir plusieurs contraintes de clé étrangère. Les relations n-n entre tables sont
implantées ainsi. Soit des tables qui contiennent des produits et des commandes, avec la possibilité
d'autoriser une commande à contenir plusieurs produits (ce que la structure ci-dessus ne permet pas).
On peut pour cela utiliser la structure de table suivante :
68
Définition des données
Les clés étrangères interdisent désormais la création de commandes qui ne sont pas liées à un produit.
Qu'arrive-t-il si un produit est supprimé alors qu'une commande y fait référence ? SQL permet aussi
de le gérer. Intuitivement, plusieurs options existent :
Pour illustrer ce cas, la politique suivante est implantée sur l'exemple de relations n-n évoqué plus haut :
• quand quelqu'un veut retirer un produit qui est encore référencé par une commande (au travers de
commande_produits), on l'interdit ;
• si quelqu'un supprime une commande, les éléments de la commande sont aussi supprimés.
Restreindre les suppressions et les réaliser en cascade sont les deux options les plus communes.
RESTRICT empêche la suppression d'une ligne référencée. NO ACTION impose la levée d'une erreur
si des lignes référençant existent lors de la vérification de la contrainte. Il s'agit du comportement par
défaut en l'absence de précision. La différence entre RESTRICT et NO ACTION est l'autorisation par
NO ACTION du report de la vérification à la fin de la transaction, ce que RESTRICT ne permet pas.
CASCADE indique que, lors de la suppression d'une ligne référencée, les lignes la référençant doivent
être automatiquement supprimées. Il existe deux autres options : SET NULL et SET DEFAULT.
Celles-ci imposent que les colonnes qui référencent dans les lignes référencées soient réinitialisées
à NULL ou à leur valeur par défaut, respectivement, lors de la suppression d'une ligne référencée.
Elles ne dispensent pas pour autant d'observer les contraintes. Par exemple, si une action précise SET
DEFAULT, mais que la valeur par défaut ne satisfait pas la clé étrangère, l'opération échoue.
À l'instar de ON DELETE existe ON UPDATE, évoqué lorsqu'une colonne référencée est modifiée
(actualisée). Les actions possibles sont les mêmes. Dans ce cas, CASCADE signifie que les valeurs
mises à jour dans la colonne référencée doivent être copiées dans les lignes de référence.
Habituellement, une ligne de référence n'a pas besoin de satisfaire la clé étrangère si une de ses
colonnes est NULL. Si la clause MATCH FULL est ajoutée à la déclaration de la clé étrangère, une ligne
de référence échappe à la clé étrangère seulement si toutes ses colonnes de référence sont NULL (donc
69
Définition des données
un mélange de valeurs NULL et non NULL échoue forcément sur une contrainte MATCH FULL).
Si vous ne voulez pas que les lignes de référence soient capables d'empêcher la satisfaction de la clé
étrangère, déclarez les colonnes de référence comme NOT NULL.
Une clé étrangère doit référencer les colonnes qui soit sont une clé primaire, soit forment une contrainte
d'unicité. Cela signifie que les colonnes référencées ont toujours un index (celui qui garantit la clé
primaire ou la contrainte unique). Donc les vérifications sur la ligne de référence seront performantes.
Comme la suppression d'une ligne de la table référencée ou la mise à jour d'une colonne référencée
nécessitera un parcours de la table référée pour trouver les lignes correspondant à l'ancienne valeur, il
est souvent intéressant d'indexer les colonnes référencées. Comme cela n'est pas toujours nécessaire
et qu'il y a du choix sur la façon d'indexer, l'ajout d'une contrainte de clé étrangère ne crée pas
automatiquement un index sur les colonnes référencées.
Une clé étrangère peut faire référence à des colonnes qui constituent une clé primaire ou forment
une contrainte d'unicité. Si la clé étrangère référence une contrainte d'unicité, des possibilités
supplémentaires sont offertes concernant la correspondance des valeurs NULL. Celles-ci sont
expliquées dans la documentation de référence de CREATE TABLE.
Voir aussi CREATE TABLE ... CONSTRAINT ... EXCLUDE pour plus de détails.
L'ajout d'une contrainte d'exclusion créera automatiquement un index du type spécifié dans la
déclaration de la contrainte.
oid
L'identifiant objet (object ID) d'une ligne. Cette colonne n'est présente que si la table a été créée
en précisant WITH OIDS ou si la variable de configuration default_with_oids était activée à ce
moment-là. Cette colonne est de type oid (même nom que la colonne) ; voir la Section 8.19 pour
obtenir plus d'informations sur ce type.
tableoid
L' OID de la table contenant la ligne. Cette colonne est particulièrement utile pour les requêtes
qui utilisent des hiérarchies d'héritage (voir Section 5.9). Il est, en effet, difficile, en son absence,
70
Définition des données
de savoir de quelle table provient une ligne. tableoid peut être joint à la colonne oid de
pg_class pour obtenir le nom de la table.
xmin
L'identifiant (ID de transaction) de la transaction qui a inséré cette version de la ligne. (Une version
de ligne est un état individuel de la ligne ; toute mise à jour d'une ligne crée une nouvelle version
de ligne pour la même ligne logique.)
cmin
xmax
L'identifiant (ID de transaction) de la transaction de suppression, ou zéro pour une version de ligne
non effacée. Il est possible que la colonne ne soit pas nulle pour une version de ligne visible ; cela
indique habituellement que la transaction de suppression n'a pas été effectuée, ou qu'une tentative
de suppression a été annulée.
cmax
ctid
La localisation physique de la version de ligne au sein de sa table. Bien que le ctid puisse être
utilisé pour trouver la version de ligne très rapidement, le ctid d'une ligne change si la ligne est
actualisée ou déplacée par un VACUUM FULL. ctid est donc inutilisable comme identifiant de
ligne sur le long terme. Il est préférable d'utiliser l'OID, ou, mieux encore, un numéro de série
utilisateur, pour identifier les lignes logiques.
Les OID sont des nombres de 32 bits et sont attribués à partir d'un compteur unique sur le cluster. Dans
une base de données volumineuse ou âgée, il est possible que le compteur boucle. Il est de ce fait peu
pertinent de considérer que les OID puissent être uniques ; pour identifier les lignes d'une table, il est
fortement recommandé d'utiliser un générateur de séquence. Néanmoins, les OID peuvent également
être utilisés sous réserve que quelques précautions soient prises :
• une contrainte d'unicité doit être ajoutée sur la colonne OID de chaque table dont l'OID est utilisé
pour identifier les lignes. Dans ce cas (ou dans celui d'un index d'unicité), le système n'engendre
pas d'OID qui puisse correspondre à celui d'une ligne déjà présente. Cela n'est évidemment possible
que si la table contient moins de 232 (4 milliards) lignes ; en pratique, la taille de la table a tout
intérêt à être bien plus petite que ça, dans un souci de performance ;
• l'unicité intertables des OID ne doit jamais être envisagée ; pour obtenir un identifiant unique sur
l'ensemble de la base, il faut utiliser la combinaison du tableoid et de l'OID de ligne ;
• les tables en question doivent être créées avec l'option WITH OIDS. Depuis PostgreSQL 8.1,
WITHOUT OIDS est l'option par défaut.
Les identifiants de transaction sont aussi des nombres de 32 bits. Dans une base de données âgée, il
est possible que les ID de transaction bouclent. Cela n'est pas un problème fatal avec des procédures
de maintenance appropriées ; voir le Chapitre 24 pour les détails. Il est, en revanche, imprudent de
considérer l'unicité des ID de transaction sur le long terme (plus d'un milliard de transactions).
Les identifiants de commande sont aussi des nombres de 32 bits. Cela crée une limite dure de 232
(4 milliards) commandes SQL au sein d'une unique transaction. En pratique, cette limite n'est pas un
problème -- la limite est sur le nombre de commandes SQL, pas sur le nombre de lignes traitées. De
plus, seules les commandes qui modifient réellement le contenu de la base de données consomment
un identifiant de commande.
Lorsqu'une table est créée et qu'une erreur a été commise ou que les besoins de l'application changent,
il est alors possible de la supprimer et de la récréer. Cela n'est toutefois pas pratique si la table contient
déjà des données ou qu'elle est référencée par d'autres objets de la base de données (une contrainte de
clé étrangère, par exemple). C'est pourquoi PostgreSQL offre une série de commandes permettant de
modifier une table existante. Cela n'a rien à voir avec la modification des données contenues dans la
table ; il ne s'agit ici, que de modifier la définition, ou structure, de la table.
Il est possible
Toutes ces actions sont réalisées à l'aide de la commande ALTER TABLE, dont la page de référence
est bien plus détaillée.
La nouvelle colonne est initialement remplie avec la valeur par défaut précisée (NULL en l'absence
de clause DEFAULT).
Astuce
À partir de PostgreSQL 11, ajouter une colonne avec une valeur par défaut constante ne signifie
plus que chaque ligne de la table doit être mise à jour quand l'instruction ALTER TABLE
doit être exécutée. À la place, la valeur par défaut sera renvoyée à chaque accès à la ligne et
appliquée quand la table est réécrite, rendant ainsi la commande ALTER TABLE bien plus
rapide, même sur de grosses tables.
Néanmoins, si la valeur par défaut est volatile (par exemple clock_timestamp()), chaque
ligne devra être mise à jour avec la valeur calculée à l'exécution du ALTER TABLE. Pour éviter
une opération de mise à jour potentiellement longue, et en particulier si vous avez de toute
façon l'intention de remplir la colonne avec des valeurs qui ne sont pas par défaut, il pourrait
être préférable d'ajouter la colonne sans valeur par défaut, d'insérer les valeurs correctes en
utilisant l'instruction UPDATE, et enfin d'ajouter la valeur par désirée comme décrit ci-dessous.
Des contraintes de colonne peuvent être définies dans la même commande, à l'aide de la syntaxe
habituelle :
En fait, toutes les options applicables à la description d'une colonne dans CREATE TABLE
peuvent être utilisées ici. Il ne faut toutefois pas oublier que la valeur par défaut doit satisfaire les
contraintes données. Dans le cas contraire, ADD échoue. Il est aussi possible d'ajouter les contraintes
ultérieurement (voir ci-dessous) après avoir rempli la nouvelle colonne correctement.
72
Définition des données
Toute donnée dans cette colonne disparaît. Les contraintes de table impliquant la colonne sont
également supprimées. Néanmoins, si la colonne est référencée par une contrainte de clé étrangère
d'une autre table, PostgreSQL ne supprime pas silencieusement cette contrainte. La suppression de
tout ce qui dépend de la colonne peut être autorisée en ajoutant CASCADE :
Pour ajouter une contrainte NOT NULL, qui ne peut pas être écrite sous forme d'une contrainte de
table, la syntaxe suivante est utilisée :
La contrainte étant immédiatement vérifiée, les données de la table doivent satisfaire la contrainte
avant qu'elle ne soit ajoutée.
(Dans le cas d'un nom de contrainte généré par le système, comme $2, il est nécessaire de l'entourer
de guillemets doubles (") pour en faire un identifiant valable.)
Comme pour la suppression d'une colonne, CASCADE peut être ajouté pour supprimer une contrainte
dont dépendent d'autres objets. Une contrainte de clé étrangère, par exemple, dépend d'une contrainte
de clé primaire ou d'unicité sur la(les) colonne(s) référencée(s).
Cela fonctionne de la même manière pour tous les types de contraintes, à l'exception des contraintes
NOT NULL. Pour supprimer une contrainte NOT NULL, on écrit :
Cela n'affecte pas les lignes existantes de la table, mais uniquement la valeur par défaut pour les futures
commandes INSERT.
73
Définition des données
C'est équivalent à mettre la valeur par défaut à NULL. En conséquence, il n'y a pas d'erreur à retirer
une valeur par défaut qui n'a pas été définie, car NULL est la valeur par défaut implicite.
Elle ne peut réussir que si chaque valeur de la colonne peut être convertie dans le nouveau type par
une conversion implicite. Si une conversion plus complexe est nécessaire, une clause USING peut être
ajoutée qui indique comment calculer les nouvelles valeurs à partir des anciennes.
PostgreSQL tente de convertir la valeur par défaut de la colonne le cas échéant, ainsi que toute
contrainte impliquant la colonne. Mais ces conversions peuvent échouer ou produire des résultats
surprenants. Il est souvent préférable de supprimer les contraintes de la colonne avant d'en modifier
le type, puis d'ajouter ensuite les contraintes convenablement modifiées.
5.6. Droits
Quand un objet est créé, il se voit affecter un propriétaire. Le propriétaire est normalement le rôle qui
a exécuté la requête de création. Pour la plupart des objets, l'état initial est que seul le propriétaire
(et les superutilisateurs) peut faire quelque chose avec cet objet. Pour permettre aux autres rôles de
l'utiliser, des droits doivent être donnés.
Il existe un certain nombre de droits différents : SELECT, INSERT, UPDATE, DELETE, TRUNCATE,
REFERENCES, TRIGGER, CREATE, CONNECT, TEMPORARY, EXECUTE et USAGE. Les droits
applicables à un objet particulier varient selon le type d'objet (table, fonction...). La page de référence
GRANT fournit une information complète sur les différents types de droits gérés par PostgreSQL. La
section et les chapitres suivants présentent l'utilisation de ces droits.
Un objet peut se voir affecter un nouveau propriétaire avec la commande ALTER correspondant
à l'objet, par exemple ALTER TABLE. Les superutilisateurs peuvent toujours le faire. Les rôles
ordinaires peuvent seulement le faire s'ils sont le propriétaire actuel de l'objet (ou un membre du rôle
propriétaire) et un membre du nouveau rôle propriétaire.
La commande GRANT est utilisée pour accorder des privilèges. Par exemple, si joe est un rôle et
comptes une table, le privilège d'actualiser la table comptes peut être accordé à joe avec :
74
Définition des données
Écrire ALL à la place d'un droit spécifique accorde tous les droits applicables à ce type d'objet.
Le nom de « rôle » spécial PUBLIC peut être utilisé pour donner un privilège à tous les rôles du
système. De plus, les rôles de type « group » peuvent être configurés pour aider à la gestion des droits
quand il y a beaucoup d'utilisateurs dans une base -- pour les détails, voir Chapitre 21.
Pour révoquer un privilège, on utilise la commande bien nommée REVOKE, comme dans l'exemple
ci-dessous :
Les privilèges spéciaux du propriétaire de l'objet (c'est-à-dire le droit d'exécuter DROP, GRANT,
REVOKE, etc.) appartiennent toujours implicitement au propriétaire. Ils ne peuvent être ni accordés ni
révoqués. Mais le propriétaire de l'objet peut choisir de révoquer ses propres droits ordinaires pour,
par exemple, mettre une table en lecture seule pour lui-même et pour les autres.
Lorsque la protection des lignes est activée sur une table (avec l'instruction ALTER TABLE ...
ENABLE ROW LEVEL SECURITY), tous les accès classiques à la table pour sélectionner ou
modifier des lignes doivent être autorisés par une politique de sécurité. Cependant, le propriétaire de
la table n'est typiquement pas soumis aux politiques de sécurité. Si aucune politique n'existe pour la
table, une politique de rejet est utilisée par défaut, ce qui signifie qu'aucune ligne n'est visible ou ne
peut être modifiée. Les opérations qui s'appliquent pour la table dans sa globalité, comme TRUNCATE
et REFERENCES, ne sont pas soumises à ces restrictions de niveau ligne.
Les politiques de sécurité niveau ligne peuvent s'appliquer en particulier soit à des commandes, soit à
des rôles, soit aux deux. Une politique est indiquée comme s'appliquant à toutes les commandes par
ALL, ou seulement à SELECT, INSERT, UPDATE ou DELETE. Plusieurs rôles peuvent être affectés
à une politique donnée, et les règles normales d'appartenance et d'héritage s'appliquent.
Pour indiquer les lignes visibles ou modifiables pour une politique, une expression renvoyant un
booléen est requise. Cette expression sera évaluée pour chaque ligne avant toutes conditions ou
fonctions qui seraient indiquées dans les requêtes de l'utilisateur. (La seule exception à cette règle
est les fonctions marquées leakproof, qui annoncent ne pas dévoiler d'information ; l'optimiseur
peut choisir d'appliquer de telles fonctions avant les vérifications de sécurité niveau ligne). Les lignes
pour lesquelles l'expression ne renvoie pas true ne sont pas traitées. Des expressions différentes
peuvent être indiquées pour fournir des contrôles indépendants pour les lignes qui sont visibles et pour
celles qui sont modifiées. Les expressions attachées à la politique sont exécutées dans le cours de la
requête et avec les droits de l'utilisateur qui exécute la commande, bien que les fonctions définies avec
l'attribut SECURITY DEFINER puissent être utilisées pour accéder à des données qui ne seraient pas
disponibles à l'utilisateur effectuant la requête.
Les superutilisateurs et les rôles avec l'attribut BYPASSRLS ne sont pas soumis au système de sécurité
niveau ligne lorsqu'ils accèdent une table. Il en est de même par défaut du propriétaire d'une table, bien
75
Définition des données
qu'il puisse choisir de se soumettre à ces contrôles avec ALTER TABLE ... FORCE ROW LEVEL
SECURITY.
L'activation ou la désactivation de la sécurité niveau ligne, comme l'ajout des polices à une table, est
toujours le privilège du seul propriétaire de la table.
Les politiques sont créées en utilisant l'instruction CREATE POLICY, modifiées avec la commande
ALTER POLICY et supprimées avec la commande DROP POLICY. Pour activer et désactiver la
sécurité niveau ligne pour une table donnée, utilisez la commande ALTER TABLE.
Chaque politique possède un nom et de multiples politiques peuvent être définies pour une table.
Comme les politiques sont spécifiques à une table, chaque politique pour une même table doit avoir
un nom différent. Différentes tables peuvent avoir des noms de politique de même nom.
Lorsque plusieurs politiques sont applicables pour une même requête, elles sont combinées en utilisant
OR (pour les politiques permissives, ce qui est le comportement par défaut) ou en utilisant AND (pour
les politiques restrictives). C'est similaire à la règle qu'un rôle donné a les privilèges de tous les rôles
dont il est membre. Les politiques permissives et restrictives sont discutées plus en détail ci-dessous.
À titre de simple exemple, nous allons ici créer une politique sur la relation comptes pour autoriser
seulement les membres du rôle admins à accéder seulement aux lignes de leurs propres comptes :
La politique ci-dessus prévoit implicitement une clause WITH CHECK identique à sa clause USING, de
sorte que la contrainte s'applique à la fois aux lignes sélectionnées par une commande (un gestionnaire
ne peut donc pas utiliser SELECT, UPDATE, ou DELETE sur des lignes existantes appartenant à
un gestionnaire différent) et aux lignes modifiées par une commande (les lignes appartenant à un
gestionnaire différent ne peuvent donc être créées avec INSERT ou UPDATE).
Si aucun rôle n'est indiqué ou si le nom de pseudo rôle PUBLIC est utilisé, alors la politique s'applique
à tous les utilisateurs du système. Pour autoriser tous les utilisateurs à accéder à leurs propres lignes
dans une table utilisateurs, une simple politique peut être utilisée :
Pour utiliser une politique différente pour les lignes ajoutées à la table de celle appliquée pour les
lignes visibles, plusieurs politiques peuvent être combinées. Cette paire de politiques autorisera tous
les utilisateurs à voir toutes les lignes de la table utilisateurs, mais seulement à modifier les
leurs :
76
Définition des données
Pour une commande SELECT, ces deux politique sont combinées à l'aide de OR, ayant pour effet que
toutes les lignes peuvent être sélectionnées. Pour les autres types de commandes, seule la deuxième
politique s'applique, de sorte que les effets sont les mêmes qu'auparavant.
La sécurité niveau ligne peut également être désactivée avec la commande ALTER TABLE. La
désactivation de la sécurité niveau ligne ne supprime pas les polices qui sont définies pour la table ;
elles sont simplement ignorées. L'ensemble des lignes sont alors visibles et modifiables, selon le
système standard des droits SQL.
Ci-dessous se trouve un exemple plus important de la manière dont cette fonctionnalité peut être
utilisée en production. La table passwd simule le fichier des mots de passe d'un système Unix.
-- Chargement de la table
INSERT INTO passwd VALUES
('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/
dash');
INSERT INTO passwd VALUES
('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/
zsh');
INSERT INTO passwd VALUES
('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/
bin/zsh');
77
Définition des données
shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/
tcsh')
);
Comme avec tous les réglages de sécurité, il est important de tester et de s'assurer que le système se
comporte comme attendu. En utilisant l'exemple ci-dessus, les manipulations ci-dessous montrent que
le système des droits fonctionne correctement.
78
Définition des données
Toutes les politiques construites jusqu'à maintenant étaient des politiques permissives, ce qui veut dire
que quand plusieurs politiques sont appliquées, elles sont combinées en utilisant l'opérateur booléen
« OR ». Bien que les politiques permissives puissent être construites pour autoriser l'accès à des
lignes dans les cas attendus, il peut être plus simple de combiner des politiques permissives avec des
politiques restrictives (que l'enregistrement doit passer et qui sont combinées en utilisant l'opérateur
booléen « AND »). En continuant sur l'exemple ci-dessus, nous ajoutons une politique restrictive pour
exiger que l'administrateur soit connecté via un socket unix locale pour accéder aux enregistrements
de la table passwd :
Nous pouvons alors voir qu'un administrateur se connectant depuis le réseau ne verra aucun
enregistrement, du fait de la politique restrictive :
79
Définition des données
Les vérifications d'intégrité référentielle, telles que les contraintes d'unicité ou de clefs primaires et
les références de clefs étrangères, passent toujours outre la sécurité niveau ligne pour s'assurer que
l'intégrité des données est maintenue. Une attention particulière doit être prise lors de la mise en place
des schémas et des politiques de sécurité de niveau ligne pour éviter qu'un canal caché (covert
channel) ne dévoile des informations à travers de telles vérifications d'intégrité référentielle.
Dans certains contextes, il est important de s'assurer que la sécurité niveau ligne n'est pas appliquée. Par
exemple, lors d'une sauvegarde, il serait désastreux si la sécurité niveau ligne avait pour conséquence
de soustraire silencieusement certaines lignes de la sauvegarde. Dans une telle situation, vous
pouvez positionner le paramètre de configuration row_security à off. En lui-même, ce paramètre ne
contourne pas la sécurité niveau ligne ; ce qu'il fait, c'est qu'il lève une erreur si le résultat d'une des
requêtes venait à être filtrée par une politique. La raison de l'erreur peut alors être recherchée et résolue.
Dans les exemples ci-dessus, les expressions attachées aux politiques considèrent uniquement les
valeurs de la ligne courante accédée ou modifiée. C'est le cas le plus simple et le plus performant ;
lorsque c'est possible, il est préférable de concevoir les applications qui utilisent cette fonctionnalité
de la sorte. S'il est nécessaire de consulter d'autres lignes ou tables pour que la politique puisse prendre
une décision, ceci peut être réalisé en utilisant dans les expressions des politiques des sous-requêtes
SELECT ou des fonctions qui contiennent des commandes SELECT. Cependant, faites attention que
de tels accès ne créent pas des accès concurrents qui pourraient permettre une fuite d'informations si
aucune précaution n'est prise. À titre d'exemple, considérez la création de la table suivante :
80
Définition des données
Maintenant, supposez qu'alice souhaite modifier l'information « légèrement secrète », mais décide
que mallory ne devrait pas pouvoir obtenir ce nouveau contenu, elle le fait ainsi :
BEGIN;
UPDATE utilisateurs SET groupe_id = 1 WHERE nom_utilisateur =
'mallory';
UPDATE information SET info = 'caché à mallory' WHERE groupe_id =
2;
COMMIT;
Ceci semble correct, il n'y a pas de fenêtre pendant laquelle mallory devrait pouvoir accéder à la
chaîne « caché à mallory ». Cependant, il y a une situation de compétition ici. Si mallory fait en
parallèle, disons :
et sa transaction est en mode READ COMMITED, il est possible qu'elle voie « caché à mallory ».
C'est possible si sa transaction accède la ligne information juste après qu'alice l'ait fait. Elle
est bloquée en attendant que la transaction d'alice valide, puis récupère la ligne mise à jour grâce
à la clause FOR UPDATE. Cependant, elle ne récupère pas une ligne mise à jour pour la commande
implicite SELECT sur la table utilisateurs parce que cette sous-commande n'a pas la clause FOR
UPDATE ; à la place, la ligne utilisateurs est lue avec une image de la base de données prise
au début de la requête. Ainsi, l'expression de la politique teste l'ancienne valeur du niveau de droit de
mallory et l'autorise à voir la valeur mise à jour.
Il y a plusieurs solutions à ce problème. Une simple réponse est d'utiliser SELECT ... FOR SHARE
dans la sous-commande SELECT de la politique de sécurité niveau ligne. Cependant, ceci demande
de donner le droit UPDATE sur la table référencée (ici utilisateurs) aux utilisateurs concernés,
ce qui peut ne pas être souhaité. (Une autre politique de sécurité niveau ligne pourrait être mise en
place pour les empêcher d'exercer ce droit ; ou la sous-commande SELECT pourrait être incluse
81
Définition des données
dans une fonction marquée security definer). De plus, l'utilisation intensive concurrente de
verrous partagés sur les lignes de la table référencée pourrait poser un problème de performance, tout
spécialement si des mises à jour de cette table sont fréquentes. Une autre solution envisageable, si les
mises à jour de la table référencée ne sont pas fréquentes, est de prendre un verrou de type ACCESS
EXCLUSIVE sur la table référencée lors des mises à jour, de telle manière qu'aucune autre transaction
concurrente ne pourrait consulter d'anciennes valeurs. Ou une transaction pourrait attendre que toutes
les transactions se terminent après avoir validé une mise à jour de la table référencée et avant de faire
des modifications qui reposent sur la nouvelle politique de sécurité.
5.8. Schémas
Une instance de bases de données PostgreSQL contient une ou plusieurs base(s) nommée(s). Les rôles
et quelques autres types d'objets sont partagés sur l'ensemble de l'instance. Une connexion cliente au
serveur ne peut accéder qu'aux données d'une seule base, celle indiquée dans la requête de connexion.
Note
Les rôles d'une instance n'ont pas obligatoirement le droit d'accéder à toutes les bases de
l'instance. Le partage des noms de rôles signifie qu'il ne peut pas y avoir plusieurs rôles
nommés joe, par exemple, dans deux bases de la même instance ; mais le système peut être
configuré pour n'autoriser joe à accéder qu'à certaines bases.
Une base de données contient un (ou plusieurs) schéma(s) nommé(s) qui, eux, contiennent des
tables. Les schémas contiennent aussi d'autres types d'objets nommés (types de données, fonctions
et opérateurs, par exemple). Le même nom d'objet peut être utilisé dans différents schémas sans
conflit ; par exemple, schema1 et mon_schema peuvent tous les deux contenir une table nommée
ma_table. À la différence des bases de données, les schémas ne sont pas séparés de manière rigide :
un utilisateur peut accéder aux objets de n'importe quel schéma de la base de données à laquelle il est
connecté, sous réserve qu'il en ait le droit.
• autoriser de nombreux utilisateurs à utiliser une base de données sans interférer avec les autres ;
• organiser les objets de la base de données en groupes logiques afin de faciliter leur gestion ;
• les applications tierces peuvent être placées dans des schémas séparés pour éviter les collisions avec
les noms d'autres objets.
Les schémas sont comparables aux répertoires du système d'exploitation, à ceci près qu'ils ne peuvent
pas être imbriqués.
Pour créer les objets d'un schéma ou y accéder, on écrit un nom qualifié constitué du nom du schéma
et du nom de la table séparés par un point :
schema.table
Cela fonctionne partout où un nom de table est attendu, ce qui inclut les commandes de modification
de la table et les commandes d'accès aux données discutées dans les chapitres suivants. (Pour des
82
Définition des données
raisons de simplification, seules les tables sont évoquées, mais les mêmes principes s'appliquent aux
autres objets nommés, comme les types et les fonctions.)
base.schema.table
peut aussi être utilisée, mais à l'heure actuelle, cette syntaxe n'existe que pour des raisons de conformité
avec le standard SQL. Si un nom de base de données est précisé, ce doit être celui de la base à laquelle
l'utilisateur est connecté.
Pour effacer un schéma vide (tous les objets qu'il contient ont été supprimés), on utilise :
Il n'est pas rare de vouloir créer un schéma dont un autre utilisateur est propriétaire (puisque c'est l'une
des méthodes de restriction de l'activité des utilisateurs à des namespaces prédéfinis). La syntaxe en
est :
Le nom du schéma peut être omis, auquel cas le nom de l'utilisateur est utilisé. Voir la Section 5.8.6
pour en connaître l'utilité.
Les noms de schéma commençant par pg_ sont réservés pour les besoins du système et ne peuvent
être créés par les utilisateurs.
et :
La possibilité de créer des objets de même nom dans différents schémas complique l'écriture d'une
requête qui référence précisément les mêmes objets à chaque fois. Cela ouvre aussi la possibilité
83
Définition des données
aux utilisateurs de modifier le comportement des requêtes des autres utilisations, par accident ou
volontairement. À cause de la prévalence des noms non qualifiés dans les requêtes et de leur utilisation
des internes de PostgreSQL, ajouter un schéma à search_path demande en effet à tous les
utilisateurs d'avoir le droit CREATE sur ce schéma. Quand vous exécutez une requête ordinaire, un
utilisateur mal intentionné capable de créer des objets dans un schéma de votre chemin de recherche
peut prendre le contrôle et exécuter des fonctions SQL arbitraires comme si vous les exécutiez.
Le premier schéma du chemin de recherche est appelé schéma courant. En plus d'être le premier
schéma parcouru, il est aussi le schéma dans lequel les nouvelles tables sont créées si la commande
CREATE TABLE ne précise pas de nom de schéma.
SHOW search_path;
search_path
--------------
"$user", public
Le premier élément précise qu'un schéma de même nom que l'utilisateur courant est recherché.
En l'absence d'un tel schéma, l'entrée est ignorée. Le deuxième élément renvoie au schéma public
précédemment évoqué.
C'est, par défaut, dans le premier schéma du chemin de recherche qui existe que sont créés les nouveaux
objets. C'est la raison pour laquelle les objets sont créés, par défaut, dans le schéma public. Lorsqu'il
est fait référence à un objet, dans tout autre contexte, sans qualification par un schéma (modification de
table, modification de données ou requêtes), le chemin de recherche est traversé jusqu'à ce qu'un objet
correspondant soit trouvé. C'est pourquoi, dans la configuration par défaut, tout accès non qualifié ne
peut que se référer au schéma public.
($user est omis à ce niveau, car il n'est pas immédiatement nécessaire.) Il est alors possible d'accéder
à la table sans qu'elle soit qualifiée par un schéma :
Puisque mon_schema est le premier élément du chemin, les nouveaux objets sont, par défaut, créés
dans ce schéma.
Dans ce cas, le schéma public n'est plus accessible sans qualification explicite. Hormis le fait qu'il
existe par défaut, le schéma public n'a rien de spécial. Il peut même être effacé.
On peut également se référer à la Section 9.25 qui détaille les autres façons de manipuler le chemin
de recherche des schémas.
Le chemin de recherche fonctionne de la même façon pour les noms de type de données, les noms de
fonction et les noms d'opérateur que pour les noms de table. Les noms des types de données et des
fonctions peuvent être qualifiés de la même façon que les noms de table. S'il est nécessaire d'écrire un
nom d'opérateur qualifié dans une expression, il y a une condition spéciale. Il faut écrire :
OPERATOR(schéma.opérateur)
84
Définition des données
SELECT 3 OPERATOR(pg_catalog.+) 4;
En pratique, il est préférable de s'en remettre au chemin de recherche pour les opérateurs, afin de ne
pas avoir à écrire quelque chose d'aussi étrange.
Un utilisateur peut aussi être autorisé à créer des objets dans le schéma d'un autre. Pour cela, le privilège
CREATE sur le schéma doit être accordé. Par défaut, tout le monde bénéficie des droits CREATE et
USAGE sur le schéma public. Cela permet à tous les utilisateurs qui peuvent se connecter à une base
de données de créer des objets dans son schéma public. Certaines méthodes d'usage demandent à
révoquer ce droit :
Le premier « public » est le schéma, le second « public » signifie « tout utilisateur ». Dans le premier
cas, c'est un identifiant, dans le second, un mot-clé, d'où la casse différente. (Se reporter aux règles
de la Section 4.1.1.)
Comme les noms des catalogues système commencent par pg_, il est préférable d'éviter d'utiliser
de tels noms pour se prémunir d'éventuels conflits si une version ultérieure devait définir une table
système qui porte le même nom que la table créée. (Le chemin de recherche par défaut implique qu'une
référence non qualifiée à cette table pointe sur la table système). Les tables système continueront de
suivre la convention qui leur impose des noms préfixés par pg_. Il n'y a donc pas de conflit possible
avec des noms de table utilisateur non qualifiés, sous réserve que les utilisateurs évitent le préfixe pg_.
5.8.6. Utilisation
Les schémas peuvent être utilisés de différentes façons pour organiser les données. Un modèle
d'utilisation de schéma sécurisé empêche tous les utilisateurs pour lesquels nous n'avons pas confiance
de modifier le comportement des requêtes des autres utilisateurs. Quand une base de données n'utilise
pas de modèle d'utilisation de schéma sécurisé, les utilisateurs souhaitant interroger cette base de
données en toute sécurité devront prendre des mesures de protection au début de chaque session. Plus
précisément, ils commenceraient chaque session par la configuration du paramètre search_path
en une chaîne vide ou en supprimant de leur search_path les schémas accessibles en écriture par
des utilisateurs standards. Il existe quelques modèles d'utilisation facilement pris en charge par la
configuration par défaut :
• Contraindre les utilisateurs ordinaires aux schémas privés des utilisateurs. Pour cela, exécutez
REVOKE CREATE ON SCHEMA public FROM PUBLIC, et créez un schéma pour
chaque utilisateur, du nom de cet utilisateur. Rappelez-vous que le chemin de recherche par défaut
commence avec $user, qui se résout avec le nom de l'utilisateur. Par conséquent, si chaque
utilisateur a un schéma distinct, ils accèdent à leur propres schémas par défaut. Après avoir
adopté ce modèle dans une base de données où des utilisateurs non fiables se sont déjà connectés,
pensez à vérifier dans le schéma public l'existence d'objets nommés comme des objets du schéma
85
Définition des données
pg_catalog. Ce modèle est sécurisé sauf si un utilisateur non fiable est le propriétaire de la base
de données ou détient l'attribut CREATEROLE, auquel cas aucun modèle d'utilisation de schéma
sécurisé n'existe.
• Conserver la valeur par défaut. Tous les utilisateurs ont accès au schéma public implicitement. Ceci
simule la situation où les schémas ne sont pas du tout disponibles, réalisant ainsi une transition en
douceur vers un monde qui ne connait pas les schémas. Néanmoins, ceci ne sera jamais un modèle
sécurisé. C'est uniquement acceptable si la base de données ne contient qu'un seul utilisateur ou
quelques utilisateurs qui se font mutuellement confiance.
Pour chaque méthode, pour installer des applications partagées (tables utilisées par tout le monde,
fonctions supplémentaires fournies par des tiers, etc.), placez-les dans des schémas séparés. N'oubliez
pas d'accorder les droits appropriés pour permettre aux autres utilisateurs d'y accéder. Les utilisateurs
peuvent ensuite faire référence à ces objets supplémentaires en les qualifiant avec le nom du schéma,
ou bien ils peuvent placer les schémas supplémentaires dans leur chemin de recherche, suivant leur
préférence.
5.8.7. Portabilité
Dans le standard SQL, la notion d'objets d'un même schéma appartenant à des utilisateurs différents
n'existe pas. De plus, certaines implantations ne permettent pas de créer des schémas de nom différent
de celui de leur propriétaire. En fait, les concepts de schéma et d'utilisateur sont presque équivalents
dans un système de base de données qui n'implante que le support basique des schémas tel que
spécifié dans le standard. De ce fait, beaucoup d'utilisateurs considèrent les noms qualifiés comme
correspondant en réalité à utilisateur.table. C'est comme cela que PostgreSQL se comporte
si un schéma utilisateur est créé pour chaque utilisateur.
Le concept de schéma public n'existe pas non plus dans le standard SQL. Pour plus de conformité
au standard, le schéma public ne devrait pas être utilisé.
Certains systèmes de bases de données n'implantent pas du tout les schémas, ou fournissent le support
de namespace en autorisant (peut-être de façon limitée) l'accès inter-bases de données. Dans ce cas,
la portabilité maximale est obtenue en n'utilisant pas les schémas.
5.9. L'héritage
PostgreSQL implante l'héritage des tables, qui peut s'avérer très utile pour les concepteurs de bases
de données. (SQL:1999 et les versions suivantes définissent une fonctionnalité d'héritage de type qui
diffère par de nombreux aspects des fonctionnalités décrites ici.)
Soit l'exemple d'un modèle de données de villes. Chaque état comporte plusieurs villes, mais une seule
capitale. Pour récupérer rapidement la ville capitale d'un état donné, on peut créer deux tables, une
pour les capitales et une pour les villes qui ne sont pas des capitales. Mais, que se passe-t-il dans le cas
où toutes les données d'une ville doivent être récupérées, qu'elle soit une capitale ou non ? L'héritage
peut aider à résoudre ce problème. La table capitales est définie pour hériter de villes :
86
Définition des données
);
Dans ce cas, la table capitales hérite de toutes les colonnes de sa table parente, villes. Les
capitales ont aussi une colonne supplémentaire, etat, qui indique l'état dont elles sont capitales.
Dans PostgreSQL, une table peut hériter de zéro à plusieurs autres tables et une requête faire référence
aux lignes d'une table ou à celles d'une table et de ses descendantes. Ce dernier comportement est
celui par défaut.
Par exemple, la requête suivante retourne les noms et elevations de toutes les villes, y compris les
capitales, situées à une élévation supérieure à 500 pieds :
Avec les données du tutoriel de PostgreSQL (voir Section 2.1), ceci renvoie :
nom | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
D'un autre côté, la requête suivante retourne les noms et elevations de toutes les villes, qui ne sont pas
des capitales, situées à une élévation supérieure à 500 pieds :
nom | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
Le mot-clé ONLY indique que la requête s'applique uniquement aux villes, et non pas à toutes les
tables en dessous de villes dans la hiérarchie de l'héritage. Un grand nombre des commandes déjà
évoquées -- SELECT, UPDATE et DELETE -- supportent le mot-clé ONLY.
Vous pouvez aussi écrire le nom de la table avec un astérisque (*) à la fin pour indiquer spécifiquement
que les tables filles sont incluses :
Écrire l'astérisque (*) n'est pas nécessaire, puisque ce comportement est toujours le comportement par
défaut. Toutefois, cette syntaxe est toujours supportée pour raison de compatibilité avec les anciennes
versions où le comportement par défaut pouvait être changé.
Dans certains cas, il peut être intéressant de savoir de quelle table provient une ligne donnée. Une
colonne système appelée TABLEOID, présente dans chaque table, donne la table d'origine :
87
Définition des données
qui renvoie :
(Reproduire cet exemple conduit probablement à des OID numériques différents). Une jointure avec
pg_class, permet d'obtenir les noms réels des tables :
ce qui retourne :
Une autre manière d'obtenir le même effet est d'utiliser le pseudo-type regclass qui affichera l'OID
de la table de façon symbolique :
L'héritage ne propage pas automatiquement les données des commandes INSERT ou COPY aux autres
tables de la hiérarchie de l'héritage. Dans l'exemple considéré, l'instruction INSERT suivante échoue :
On pourrait espérer que les données soient d'une manière ou d'une autre acheminées vers la table
capitales, mais ce n'est pas le cas : INSERT insère toujours dans la table indiquée. Dans certains
cas, il est possible de rediriger l'insertion en utilisant une règle (voir Chapitre 41). Néanmoins, cela
n'est d'aucune aide dans le cas ci-dessus, car la table villes ne contient pas la colonne etat. La
commande est donc rejetée avant que la règle ne puisse être appliquée.
Toutes les contraintes de vérification et toutes les contraintes NOT NULL sur une table parent sont
automatiquement héritées par les tables enfants, sauf si elles sont spécifiées explicitement avec des
clauses NO INHERIT. Les autres types de contraintes (unicité, clé primaire, clé étrangère) ne sont
pas hérités.
Une table peut hériter de plusieurs tables, auquel cas elle possède l'union des colonnes définies par les
tables mères. Toute colonne déclarée dans la définition de la table enfant est ajoutée à cette dernière. Si
le même nom de colonne apparaît dans plusieurs tables mères, ou à la fois dans une table mère et dans
la définition de la table enfant, alors ces colonnes sont « assemblées » pour qu'il n'en existe qu'une dans
la table enfant. Pour être assemblées, les colonnes doivent avoir le même type de données, sinon une
erreur est levée. Les contraintes de vérification et les contraintes non NULL héritables sont assemblées
de façon similaire. De ce fait, par exemple, une colonne assemblée sera marquée non NULL si une des
définitions de colonne d'où elle provient est marquée non NULL. Les contraintes de vérification sont
assemblées si elles ont le même nom, et l'assemblage échouera si leurs conditions sont différentes.
L'héritage de table est établi à la création de la table enfant, à l'aide de la clause INHERITS de
l'instruction CREATE TABLE. Alternativement, il est possible d'ajouter à une table, définie de façon
88
Définition des données
compatible, une nouvelle relation de parenté à l'aide de la clause INHERIT de ALTER TABLE. Pour
cela, la nouvelle table enfant doit déjà inclure des colonnes de mêmes nom et type que les colonnes
de la table parent. Elle doit aussi contenir des contraintes de vérification de mêmes nom et expression
que celles de la table parent.
De la même façon, un lien d'héritage peut être supprimé d'un enfant à l'aide de la variante NO
INHERIT d'ALTER TABLE. Ajouter et supprimer dynamiquement des liens d'héritage de cette
façon est utile quand cette relation d'héritage est utilisée pour le partitionnement des tables (voir
Section 5.10).
Un moyen pratique de créer une table compatible en vue d'en faire ultérieurement une table enfant est
d'utiliser la clause LIKE dans CREATE TABLE. Ceci crée une nouvelle table avec les mêmes colonnes
que la table source. S'il existe des contraintes CHECK définies sur la table source, l'option INCLUDING
CONSTRAINTS de LIKE doit être indiquée, car le nouvel enfant doit avoir des contraintes qui
correspondent à celles du parent pour être considéré compatible.
Une table mère ne peut pas être supprimée tant qu'elle a des enfants. Pas plus que les colonnes ou
les contraintes de vérification des tables enfants ne peuvent être supprimées ou modifiées si elles
sont héritées. La suppression d'une table et de tous ses descendants peut être aisément obtenue en
supprimant la table mère avec l'option CASCADE (voir Section 5.13).
ALTER TABLE propage toute modification dans les définitions des colonnes et contraintes de
vérification à travers la hiérarchie d'héritage. Là encore, supprimer des colonnes qui dépendent
d'autres tables mères n'est possible qu'avec l'option CASCADE. ALTER TABLE suit les mêmes règles
d'assemblage de colonnes dupliquées et de rejet que l'instruction CREATE TABLE.
Les requêtes sur tables héritées réalisent des vérifications de droit sur la table parent seulement. De
ce fait, par exemple, donner le droit UPDATE sur la table villes implique que les droits de mise
à jour des lignes dans la table capitales soient elles aussi vérifiées quand elles sont accédées via
la table villes. Ceci préserve l'apparence que les données proviennent (aussi) de la table parent.
Mais la table capitales ne pouvait pas être mise à jour directement sans droit supplémentaire.
Deux exceptions à cette règle sont TRUNCATE et LOCK TABLE, où les droits sur les tables filles sont
toujours vérifiées qu'elles soient traitées directement ou par récursivité via les commandes réalisées
sur la table parent.
De façon similaire, les politiques de sécurité au niveau ligne de la table parent (voir Section 5.7) sont
appliquées aux lignes provenant des tables filles avec une requête héritée. Les politiques de tables
enfant sont appliquées seulement quand la table enfant est explicitement nommée dans la requête.
Dans ce cas, toute politique attachée à ses parents est ignorée.
Les tables distantes (voir Section 5.11) peuvent aussi participer aux hiérarchies d'héritage, soit comme
table parent, soit comme table enfant, comme les tables standards peuvent l'être. Si une table distante
fait partie d'une hiérarchie d'héritage, toutes les opérations non supportées par la table étrangère ne
sont pas non plus supportées sur l'ensemble de la hiérarchie.
5.9.1. Restrictions
Notez que toutes les commandes SQL fonctionnent avec les héritages. Les commandes utilisées
pour récupérer des données, pour modifier des données ou pour modifier le schéma (autrement dit
SELECT, UPDATE, DELETE, la plupart des variantes de ALTER TABLE, mais pas INSERT ou
ALTER TABLE ... RENAME) incluent par défaut les tables filles et supportent la notation ONLY
pour les exclure. Les commandes qui font de la maintenance de bases de données et de la configuration
(par exemple REINDEX, VACUUM) fonctionnent typiquement uniquement sur les tables physiques,
individuelles et ne supportent pas la récursion sur les tables de l'héritage. Le comportement respectif
de chaque commande individuelle est documenté dans la référence (Commandes SQL).
Il existe une réelle limitation à la fonctionnalité d'héritage : les index (dont les contraintes d'unicité)
et les contraintes de clés étrangères ne s'appliquent qu'aux tables mères, pas à leurs héritiers. Cela est
valable pour le côté référençant et le côté référencé d'une contrainte de clé étrangère. Ce qui donne,
dans les termes de l'exemple ci-dessus :
89
Définition des données
• si villes.nom est déclarée UNIQUE ou clé primaire (PRIMARY KEY), cela n'empêche pas la
table capitales de posséder des lignes avec des noms dupliqués dans villes. Et ces lignes
dupliquées s'affichent par défaut dans les requêtes sur villes. En fait, par défaut, capitales
n'a pas de contrainte d'unicité du tout et, du coup, peut contenir plusieurs lignes avec le même nom.
Une contrainte d'unicité peut être ajoutée à capitales, mais cela n'empêche pas la duplication
avec villes ;
• de façon similaire, si villes.nom fait référence (REFERENCES) à une autre table, cette contrainte
n'est pas automatiquement propagée à capitales. Il est facile de contourner ce cas de figure en
ajoutant manuellement la même contrainte REFERENCES à capitales ;
• si une autre table indique REFERENCES villes(nom), cela l'autorise à contenir les noms des
villes, mais pas les noms des capitales. Il n'existe pas de contournement efficace de ce cas.
Certaines fonctionnalités non implémentées pour les hiérarchies d'héritage sont disponibles pour le
partitionnement déclaratif. Il est de ce fait nécessaire de réfléchir consciencieusement à l'utilité de
l'héritage pour une application donnée.
5.10.1. Aperçu
Le partitionnement fait référence à la division d'une table logique volumineuse en plusieurs parties
physiques plus petites. Le partitionnement comporte de nombreux avantages :
• les performances des requêtes peuvent être significativement améliorées dans certaines situations,
particulièrement lorsque la plupart des lignes fortement accédées d'une table se trouvent sur une
seule partition ou sur un petit nombre de partitions. Le partitionnement se substitue aux niveaux
élevés de index, facilitant la tenue en mémoire des parties les plus utilisées de l'index ;
• lorsque les requêtes ou les mises à jour accèdent à un pourcentage important d'une seule partition,
les performances peuvent être grandement améliorées par l'utilisation avantageuse d'un parcours
séquentiel sur cette partition plutôt que d'utiliser un index qui nécessiterait des lectures aléatoires
réparties sur toute la table ;
• les chargements et suppressions importants de données peuvent être obtenus par l'ajout ou la
suppression de partitions, sous réserve que ce besoin ait été pris en compte lors de la conception du
partitionnement. Supprimer une partition individuelle en utilisant DROP TABLE ou en exécutant
ALTER TABLE DETACH PARTITION est bien plus rapide qu'une opération groupée. Cela évite
également la surcharge due au VACUUM causé par un DELETE massif ;
• les données peu utilisées peuvent être déplacées sur un média de stockage moins cher et plus lent.
Ces bénéfices ne sont réellement intéressants que si cela permet d'éviter une table autrement plus
volumineuse. Le point d'équilibre exact à partir duquel une table tire des bénéfices du partitionnement
dépend de l'application. Toutefois, le partitionnement doit être envisagé si la taille de la table peut être
amenée à dépasser la taille de la mémoire physique du serveur.
La table est partitionnée en « intervalles » (ou échelles) définis par une colonne clé ou par un
ensemble de colonnes, sans recouvrement entre les intervalles de valeurs affectées aux différentes
partitions. Il est possible, par exemple, de partitionner par intervalles de date ou par intervalles
d'identifiants pour des objets métier particuliers. Chaque limite de l'intervalle est comprise comme
90
Définition des données
étant inclusive au point initial et exclusive au point final. Par exemple, si l'intervalle d'une partition
va de 1 à 10, et que le prochain intervalle va de 10 à 20, alors la valeur 10 appartient à la
deuxième partition, et non pas à la première.
La table est partitionnée en listant explicitement les valeurs clés qui apparaissent dans chaque
partition.
La table est partitionnée en spécifiant un module et un reste pour chaque partition. Chaque
partition contiendra les lignes pour lesquelles la valeur de hachage de la clé de partition divisée
par le module spécifié produira le reste spécifié.
Si votre application nécessite d'utiliser d'autres formes de partitionnement qui ne sont pas listées au-
dessus, des méthodes alternatives comme l'héritage et des vues UNION ALL peuvent être utilisées à la
place. De telles méthodes offrent de la flexibilité, mais n'ont pas certains des bénéfices de performance
du partitionnement déclaratif natif.
La table partitionnée est elle-même une table « virtuelle » sans stockage propre. À la place, le stockage
se fait dans les to partitions, qui sont en fait des tables ordinaires mais associées avec la table
partitionnée. Chaque partition enregistre un sous-ensemble de données correspondant à la définition
de ses limites de partition. Tous les lignes insérées dans une table partitionnée seront transférées sur la
partition appropriée suivant les valeurs des colonnes de la clé de partitionnement. Mettre à jour la clé
de partitionnement d'une ligne causera son déplacement dans une partition différente si elle ne satisfait
plus les limites de sa partition originale.
Les partitions peuvent elles-mêmes être définies comme des tables partitionnées, ce qui aboutirait à du
sous-partitionnement. Bien que toutes les partitions doivent avoir les mêmes clonnes que leur parent
partitionné, es partitions peuvent avoir leurs propres index, contraintes et valeurs par défaut, différents
de ceux des autres partitions. Voir CREATE TABLE pour plus de détails sur la création des tables
partitionnées et des partitions.
Il n'est pas possible de transformer une table standard en table partitionnée et inversement. Par contre,
il est possible d'ajouter une table standard ou une table partitionnée existante comme une partition
d'une table partitionnée, ou de supprimer une partition d'une table partitionnée, pour la transformer
en table standard ; ceci peut simplifier et accélérer de nombreux traitements de maintenance. Voir
ALTER TABLE pour en apprendre plus sur les sous-commandes ATTACH PARTITION et DETACH
PARTITION.
Les partitions peuvent également être des tables étrangères, mais il faut faire très attention car c'est de
la responsabilité de l'utilisateur que le contenu de la table distante satisfasse la clé de partitionnement.
Il existe aussi d'autres restrictions. Voir CREATE FOREIGN TABLE pour plus d'informations.
5.10.2.1. Exemple
Imaginons que nous soyons en train de construire une base de données pour une grande société de
crème glacée. La société mesure les pics de températures chaque jour, ainsi que les ventes de crème
glacée dans chaque région. Conceptuellement, nous voulons une table comme ceci :
91
Définition des données
temperature int,
ventes int
);
La plupart des requêtes n'accèdent qu'aux données de la dernière semaine, du dernier mois ou du
dernier trimestre, car cette table est essentiellement utilisée pour préparer des rapports en ligne pour
la direction. Pour réduire le nombre de données anciennes à stocker, seules les trois dernières années
sont conservées. Au début de chaque mois, les données du mois le plus ancien sont supprimées. Dans
cette situation, le partitionnement permet de répondre aux différents besoins identifiés sur la table des
mesures.
Pour utiliser le partitionnement déclaratif dans ce cas d'utilisation, il faut suivre les étapes suivantes :
1. Créer une table measurement comme une table partitionnée en spécifiant la clause PARTITION
BY, ce qui inclut la méthode de partitionnement ( RANGE dans ce cas) ainsi que la liste de la ou
des colonnes à utiliser comme clé de partitionnement.
2. Créez les partitions. La définition de chaque partition doit spécifier les limites qui correspondent
à la méthode de partitionnement ainsi qu'à la clé de partitionnement du parent. Veuillez noter que
spécifier des limites telles que les valeurs de la nouvelle partition pourront se chevaucher avec
celles d'une ou plusieurs autres partitions retournera une erreur.
Les partitions ainsi créées sont de tous les points de vue des tables PostgreSQL normales (ou,
potentiellement, des tables étrangères). Il est possible de spécifier un tablespace et des paramètres
de stockage pour chacune des partitions séparément.
Pour notre exemple, chaque partition devrait contenir un mois de données pour correspondre au
besoin de supprimer un mois de données à la fois. Les commandes pourraient ressembler à ceci :
...
CREATE TABLE measurement_y2007m11 PARTITION OF measurement
FOR VALUES FROM ('2007-11-01') TO ('2007-12-01');
(Pour rappel, les partitions adjacentes peuvent partager une valeur de limite car les limites hautes
sont traitées comme des limites exclusive.)
92
Définition des données
Après avoir créé les partitions de measurement_y2006m02, toute donnée insérée dans
measurement qui correspond à measurement_y2006m02 (ou donnée qui est directement
insérée dans measurement_y2006m02, ce qui est autorié à condition que la contrainte de
partition soit respectée) sera redirigée vers l'une de ses partitions en se basant sur la colonne
peaktemp. La clé de partition spécifiée pourrait se chevaucher avec la clé de partition du parent,
il faut donc faire spécialement attention lorsque les limites d'une sous-partition sont spécifiées afin
que l'ensemble de données qu'elle accepte constitue un sous-ensemble de ce que les propres limites
de la partition acceptent ; le système n'essayera pas de vérifier si c'est vraiment le cas.
Insérer des données dans la table parent, données qui ne correspondent pas à une des partitions
existantes, causera une erreur ; une partition appropriée doit être ajoutée manuellement.
Il n'est pas nécessaire de créer manuellement les contraintes de table décrivant les conditions des
limites de partition pour les partitions. De telles contraintes seront créées automatiquement.
3. Créez un index sur la ou les colonnes de la clé, ainsi que tout autre index que vous pourriez vouloir
pour chaque partition. (L'index sur la clé n'est pas strictement nécessaire, mais c'est utile dans la
plupart des scénarios.) Ceci crée automatiquement un index correspondant sur chaque partition, et
toutes les partitions que vous créerez ou attacherez plus tard auront elles-aussi cet index.
Dans l'exemple ci-dessus, nous créerions une nouvelle partition chaque mois, il serait donc avisé
d'écrire un script qui génère le DDL nécessaire automatiquement.
Le moyen le plus simple pour supprimer d'anciennes données est de supprimer la partition qui n'est
plus nécessaire :
Cela peut supprimer des millions d'enregistrements très rapidement, car il n'est pas nécessaire de
supprimer chaque enregistrement séparément. Veuillez noter toutefois que la commande ci-dessus
nécessite de prendre un verrou de type ACCESS EXCLUSIVE sur la table parente.
Une autre possibilité, généralement préférable, est de ne pas supprimer la partition de la table
partitionnée, mais de la conserver en tant que table à part entière :
93
Définition des données
Cela permet d'effectuer ensuite d'autres opérations sur les données avant de la supprimer. Par exemple,
il s'agit souvent du moment idéal pour sauvegarder les données en utilisant COPY, pg_dump, ou des
outils similaires. Cela pourrait également être le bon moment pour agréger les données dans un format
moins volumineux, effectuer d'autres manipulations de données ou exécuter des rapports.
De la même manière, nous pouvons ajouter une nouvelle partition pour gérer les nouvelles données.
Nous pouvons créer une partition vide dans la table partitionnée exactement comme les partitions
originales ont été créées précédemment :
De manière alternative, il est parfois plus utile de créer la nouvelle table en dehors de la structure
de la partition, et d'en faire une partition plus tard. Cela permet de charger de nouvelles données,
de les vérifier et d'y effectuer des transformations avant que les données apparaissent dans la table
partitionnée. L'option CREATE TABLE ... LIKE est utile pour éviter de répéter à chaque fois
la définition de la table parent :
Avant d'exécuter une commande ATTACH PARTITION, il est recommandé de créer une contrainte
CHECK sur la table qui doit être attachée correspondant à la contrainte de la partition désirée. De
cette manière, le système n'aura pas besoin d'effectuer un parcours de la table qui est habituellement
nécessaire pour valider la contrainte implicite de partition. Sans la contrainte CHECK, la table sera
parcourue pour valider la contrainte de partition, alors qu'elle aura pris un verrou de niveau ACCESS
EXCLUSIVE sur la table parente. Il est recommandé de supprimer la contrainte CHECK redondante
après la fin de la commande ATTACH PARTITION.
Comme expliqué ci-dessus, il est possible de créer des index sur les tables partitionnées pour qu'elles
soient automatiquement appliqués à la hiérarchie complète. Ceci est très pratique car, non seulement
les partitions existantes seront indexées, mais aussi toute nouvelle partition le sera. La seule limitation
est qu'il n'est pas possible d'utiliser la clause CONCURRENTLY lors de la création d'un tel index
partitionné. Pour éviter de longues périodes de verrous, il est possible d'utiliser CREATE INDEX
ON ONLY sur la table partitionnée ; un tel index est marqué invalide et les partitions l'obtiennent pas
automatiquement l'index. Les index sur les partitions peuvent être créés individuellement en utilisant
CONCURRENTLY, puis être attachés à l'index sur le parent en utilisant ALTER INDEX .. ATTACH
PARTITION. Une fois que les index des partitions sont attachés à l'index parent, l'index parent est
marqué valide automatiquement. Par exemple :
94
Définition des données
Cette technique peut aussi être utilisée avec les contraintes UNIQUE et PRIMARY KEY ; les index
sont créés implicitement quand la contrainte est créer. Par exemple :
5.10.2.3. Limitations
Les limitations suivantes s'appliquent aux tables partitionnées :
• Pour créer une contrainte d'unicité ou de clé primaire sur une table partitionnée, la clé de
partitionnement ne doit pas inclure d'expressions ou d'appels de fonction, et les colonnes de la
contrainte doivent inclure toutes les colonnes de la clé de partitionnement. Cette limitation existe
parce que les index individuels forçant la contrainte peuvent seulement garantir l'unicité dans leur
propre partition ; de ce fait, la structure même de la partition doit garantir qu'il n'y aura pas de
duplicats dans les différentes partitions.
• Il n'existe aucun moyen de créer une contrainte d'exclusion sur toute la table partitionnée. Il
est seulement possible de placer une telle contrainte sur chaque partition individuellement. Cette
limitation vient là-aussi de l'impossibilité de fixer les restrictions entre partitions.
• Alors que les clés primaires sont prises en charge sur les tables partitionnées, les clés étrangères
faisant référence à des tables partitionnées ne sont pas prises en charge. (Les références de clés
étrangères d'une table partitionnée vers une autre table sont supportées.)
• en cas de besoin, les triggers BEFORE ROW doivent être définies sur des partitions individuelles,
et non sur la table partitionnée.
• Mélanger des relations temporaires et permanentes dans la même arborescence de partitions n'est
pas autorisé. Par conséquent, si une table partitionnée est permanente, ses partitions doivent l'être
aussi ; de même si la table partitionnée est temporaire, ses partitions doivent l'être aussi. Lors de
l'utilisation de relations temporaires, tous les membres de l'arborescence des partitions doivent être
issus de la même session.
Les partitions individuelles sont liées à leur table partitionnée en utilisant l'héritage en arrière plan.
Néanmoins, il n'est pas possible d'utiliser toutes les fonctionnalités génériques de l'héritage avec les
tables en partitionnement déclaratif et leurs partitions, comme indiqué ci-dessous. Notamment, une
partition ne peut pas avoir d'autres parents que leur table partitionnée. Une table ne peut pas non plus
hériter d'une table partitionnée et d'une table normale. Cela signifie que les tables partitionnées et leur
partitions ne partagent jamais une hiérarchie d'héritage avec des tables normales.
Comme une hiérarchie de partitionnement consistant en la table partitionnée et ses partitions est
toujours une hiérarchie d'héritage, tableoid et toutes les règles normales d'héritage s'appliquent
comme décrites dans Section 5.9, avec quelques exceptions :
95
Définition des données
• Les partitions ne peuvent pas avoir des colonnes qui ne sont pas présentes chez le parent. Il n'est
pas possible d'indiquer des colonnes lors de la création de partitions avec CREATE TABLE, pas
plus qu'il n'est possible d'ajouter des colonnes aux partitions après leur création en utilisant ALTER
TABLE. Les tables pourraient être ajoutées en tant que partition avec ALTER TABLE ...
ATTACH PARTITION seulement si leurs colonnes correspondent exactement à leur parent, en
incluant toute colonne oid.
• Les contraintes CHECK et NOT NULL d'une table partitionnée sont toujours héritées par toutes ses
partitions. La création des contraintes CHECK marquées NO INHERIT n'est pas autorisée sur les
tables partitionnées. Vous ne pouvez pas supprimer une contrainte NOT NULL de la colonne d'une
partition si la même contrainte est présente dans la table parent.
• Utiliser ONLY pour ajouter ou supprimer une contrainte uniquement sur la table partitionnée est
supportée tant qu'il n'y a pas de partitions. Dès qu'une partition existe, utiliser ONLY renverra une
erreur. À la place, des constraintes sur les partitions elles-mêmes peuvent être ajoutées et (si elles
ne sont pas présentes sur la table parent) supprimées.
• Comme une table partitionnée n'a pas de données elle-même, toute tentative d'utiliser TRUNCATE
ONLY sur une table partitionnée renverra systématiquement une erreur.
• Pour le partitionnement déclaratif, les partitions doivent avoir exactement les mêmes colonnes que
la table partitionnée, alors qu'avec l'héritage de table, les tables filles peuvent avoir des colonnes
supplémentaires non présentes dans la table parente.
• Le partitionnement déclaratif ne prend en charge que le partitionnement par intervalle, par liste
et par hachage, tandis que l'héritage de table permet de diviser les données de la manière choisie
par l'utilisateur. (Notez, cependant, que si l'exclusion de contrainte n'est pas en mesure d'élaguer
efficacement les tables filles, la performance de la requête peut être faible).
• Certaines opérations nécessitent un verrou plus fort en utilisant le partitionnement déclaratif qu'en
utilisant l'héritage de table. Par exemple, ajouter ou supprimer une partition d'une table partitionnée
nécessite de prendre un verrou de type ACCESS EXCLUSIVE sur la table parente, alors qu'un
verrou de type SHARE UPDATE EXCLUSIVE est suffisant dans le cas de l'héritage classique.
5.10.3.1. Exemple
Cet exemple construit une structure de partitionnement équivalente à l'exemple de partitionnement
déclaratif ci-dessus. Procédez aux étapes suivantes :
1. Créez la table « master », de laquelle toutes les tables « filles » hériteront. Cette table ne contiendra
aucune donnée. Ne définissez aucune contrainte de vérification sur cette table, à moins que vous
n'ayez l'intention de l'appliquer de manière identique sur toutes les tables filles. Il n'y a aucun intérêt
à définir d'index ou de contrainte unique sur elle non plus. Pour notre exemple, la table master
correspond à la table measurement définie à l'origine :
96
Définition des données
);
2. Créez plusieurs tables « enfant », chacune héritant de la table master. Normalement, ces tables
n'ajouteront aucune colonne à celles héritées de la table master. Comme avec le partitionnement
déclaratif, ces tables filles sont des tables PostgreSQL à part entière (ou des tables étrangères)
PostgreSQL normales.
3. Ajoutez les contraintes de tables, sans qu'elles se chevauchent, sur les tables filles pour définir les
valeurs de clé autorisées dans chacune.
CHECK ( x = 1 )
CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire',
'Warwickshire' ))
CHECK ( outletID >= 100 AND outletID < 200 )
Assurez-vous que les contraintes garantissent qu'il n'y a pas de chevauchement entre les valeurs
de clés permises dans différentes tables filles. Une erreur fréquente est de mettre en place des
contraintes d'intervalle comme ceci :
Cet exemple est faux puisqu'on ne peut pas savoir à quelle table fille appartient la valeur de clé
200. À la place, les intervalles devraient être définis ainsi :
...
CREATE TABLE measurement_y2007m11 (
CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE
'2007-12-01' )
) INHERITS (measurement);
97
Définition des données
4. Pour chaque table fille, créez un index sur la ou les colonnes de la clé, ainsi que tout autre index
que vous voudriez.
5. Nous voulons que notre application soit capable de dire INSERT INTO measurement ...,
et de voir ses données redirigées dans la table fille appropriée. Nous pouvons réaliser cela en
ajoutant un trigger sur la table master. Si les données doivent être ajoutées sur la dernière table fille
uniquement, nous pouvons utiliser un trigger avec une fonction très simple :
Après avoir créé la fonction, nous créons le trigger qui appelle la fonction trigger :
Une telle fonction doit être redéfinie chaque mois pour toujours insérer sur la table fille active. La
définition du trigger n'a pas besoin d'être redéfinie.
Il est également possible de laisser le serveur localiser la table fille dans laquelle doit être insérée
la ligne. Une fonction plus complexe peut alors être utilisée :
98
Définition des données
La définition du trigger est la même qu'avant. Notez que chaque test IF doit correspondre
exactement à la contrainte CHECK de la table fille correspondante.
Bien que cette fonction soit plus complexe que celle pour un seul mois, il n'est pas nécessaire de
l'actualiser aussi fréquemment, les branches pouvant être ajoutées en avance.
Note
En pratique, il vaudrait mieux vérifier d'abord la dernière table fille créée si la plupart des
insertions lui sont destinées. Pour des raisons de simplicité, les tests du trigger sont présentés
dans le même ordre que les autres parties de l'exemple.
Une approche différente du trigger est la redirection des insertions par des règles sur la table master.
Par exemple :
Une règle a un surcoût bien plus important qu'un trigger, mais il n'est payé qu'une fois par requête
plutôt qu'une fois par ligne. Cette méthode peut donc être avantageuse pour les insertions en masse.
Toutefois, dans la plupart des cas, la méthode du trigger offrira de meilleures performances.
Soyez conscient que COPY ignore les règles. Si vous voulez utiliser COPY pour insérer des données,
vous devrez les copier dans la bonne table fille plutôt que dans la table master. COPY déclenche les
triggers, vous pouvez donc l'utiliser normalement si vous utilisez l'approche par trigger.
99
Définition des données
Un autre inconvénient à l'approche par règle est qu'il n'y a pas de moyen simple de forcer une erreur
si l'ensemble de règles ne couvre pas la date d'insertion ; les données iront silencieusement dans
la table master à la place.
6. Assurez-vous que le paramètre de configuration constraint_exclusion ne soit pas désactivé dans
postgresql.conf ; sinon il pourrait y avoir des accès inutiles aux autres tables.
Comme nous pouvons le voir, une hiérarchie complexe de tables peut nécessiter une quantité de DDL
non négligeable. Dans l'exemple ci-dessus, nous créerions une nouvelle table fille chaque mois, il
serait donc sage d'écrire un script qui génère le DDL automatiquement.
Pour enlever une table fille de la hiérarchie d'héritage, mais en en gardant l'accès en tant que table
normale :
Pour ajouter une nouvelle table fille pour gérer les nouvelles données, créez une table fille vide, tout
comme les tables filles originales ont été créées ci-dessus :
Une autre alternative est de créer et de remplir la nouvelle table enfant avant de l'ajouter à la hiérarchie
de la table. Ceci permet aux données d'être chargées, vérifiées et transformées avant d'être rendues
visibles aux requêtes sur la table parente.
5.10.3.3. Restrictions
Les restrictions suivantes s'appliquent au partitionnement par héritage :
• Il n'existe pas de moyen automatique de vérifier que toutes les contraintes de vérification (CHECK)
sont mutuellement exclusives. Il est plus sûr de créer un code qui fabrique les tables filles, et crée
et/ou modifie les objets associés plutôt que de les créer manuellement ;
• les schémas montrés ici supposent que les colonnes clés du partitionnement d'une ligne ne changent
jamais ou, tout du moins, ne changent pas suffisamment pour nécessiter un déplacement vers une
100
Définition des données
autre partition. Une commande UPDATE qui tentera de le faire échouera à cause des contraintes
CHECK. Si vous devez gérer ce type de cas, des triggers sur mise à jour peuvent être placés sur les
tables filles, mais cela rend la gestion de la structure beaucoup plus complexe.
• Si VACUUM ou ANALYZE sont lancés manuellement, n'oubliez pas de les lancer sur chaque table
fille. Une commande comme :
ANALYZE mesure;
• Les commandes INSERT avec des clauses ON CONFLICT ont peu de chances de fonctionner
comme attendu, puisque l'action du ON CONFLICT n'est effectuée que dans le cas de violations
d'unicité dans la table cible, pas dans les filles.
• Des triggers ou des règles seront nécessaires pour rediriger les lignes vers la table fille voulue, à
moins que l'application ne soit explicitement au courant du schéma de partitionnement. Les triggers
peuvent être compliqués à écrire, et seront bien plus lents que la redirection de ligne effectuée en
interne par le partitionnement déclaratif.
Sans l'élagage de partition, la requête ci-dessus parcourrait chacune des partitions de la table mesure.
Avec l'élagage de partition activé, le planificateur examinera la définition de chaque partition, et
montrera qu'il n'est pas nécessaire de la parcourir puisqu'elle ne contient aucune ligne respectant la
clause WHERE de la requête. Lorsque le planificateur peut l'établir, il exclut (élague) la partition du
plan de recherche.
QUERY PLAN
--------------------------------------------------------------------------------
Aggregate (cost=188.76..188.77 rows=1 width=8)
-> Append (cost=0.00..181.05 rows=3085 width=0)
-> Seq Scan on measurement_y2006m02 (cost=0.00..33.12
rows=617 width=0)
Filter: (logdate >= '2008-01-01'::date)
-> Seq Scan on measurement_y2006m03 (cost=0.00..33.12
rows=617 width=0)
Filter: (logdate >= '2008-01-01'::date)
...
-> Seq Scan on measurement_y2007m11 (cost=0.00..33.12
rows=617 width=0)
Filter: (logdate >= '2008-01-01'::date)
101
Définition des données
Quelques partitions, voire toutes, peuvent utiliser des parcours d'index à la place des parcours
séquentiels de la table complète, mais le fait est qu'il n'est pas besoin de parcourir les plus vieilles
partitions pour répondre à cette requête. Lorsque l'élagage de partitions est activé, nous obtenons un
plan significativement moins coûteux, pour le même résultat :
QUERY PLAN
--------------------------------------------------------------------------------
Aggregate (cost=37.75..37.76 rows=1 width=8)
-> Append (cost=0.00..36.21 rows=617 width=0)
-> Seq Scan on measurement_y2008m01 (cost=0.00..33.12
rows=617 width=0)
Il est à noter que l'élagage des partitions n'est piloté que par les contraintes définies implicitement
par les clés de partition, et non par la présence d'index : il n'est donc pas nécessaire de définir des
index sur les colonnes clés. Si un index doit être créé pour une partition donnée, ceci dépendra du fait
que vous vous attendez à ce que les requêtes qui parcourent la partition parcourent généralement une
grande partie de la partition ou seulement une petite partie. Un index sera utile dans ce dernier cas,
mais pas dans le premier.
L'élagage des partitions peut être effectuée non seulement lors de la planification d'une requête, mais
aussi lors de son exécution. Ceci est utile car cela peut permettre d'élaguer plus de partitions lorsque les
clauses contiennent des expressions dont les valeurs ne sont pas connues au moment de la planification
de la requête ; par exemple, des paramètres définis dans une instruction PREPARE, utilisant une valeur
obtenue d'une sous-requête ou utilisant une valeur paramétrée sur la partie interne du nœud de boucle
imbriqué (nested loop join. L'élagage de la partition pendant l'exécution peut être réalisé à
l'un des moments suivant :
• Lors de l'initialisation du plan d'exécution, l'élagage de partition peut être effectué pour les valeurs
de paramètres qui sont connues pendant cette phase. Les partitions qui ont été élaguées pendant
cette étape n'apparaîtront pas dans l'EXPLAIN ou l'EXPLAIN ANALYZE de la requête. Il est tout
de même possible de déterminer le nombre de partitions qui ont été supprimées pendant cette phase
en observant la propriété « Subplans Removed » (sous-plans supprimés) dans la sortie d'EXPLAIN.
• Pendant l'exécution effective du plan d'exécution. L'élagage des partitions peut également être
effectué pour supprimer des partitions en utilisant des valeurs qui ne sont connues que pendant
l'exécution de la requête. Cela inclut les valeurs des sous-requêtes et des paramètres issus de
l'exécution, comme des jointures par boucle imbriquée (nested loop join) paramétrées.
Comme la valeur de ces paramètres peut changer plusieurs fois pendant l'exécution de la requête,
l'élagage de la partition est effectué chaque fois que l'un des paramètres d'exécution utilisés pour
celui-ci change. Déterminer si les partitions ont été élaguées pendant cette phase nécessite une
inspection minutieuse de la propriété nloops de la sortie d'EXPLAIN ANALYZE. Les sous-plans
correspondant aux différentes partitions pourraient avoir différentes valeurs dépendant du nombre
de fois chacun d'entre eux a été évité lors de l'exécution. Certains pourraient être affichés comme
(never executed) (littéralement, jamais exécuté) s'ils sont évités à chaque fois.
102
Définition des données
Note
L'élagage de partitions au moment de l'exécution survient seulement pour le type de nœud
Append, mais pas pour les nœuds MergeAppend et ModifyTable. Ceci pourrait changer
dans une prochaine version de PostgreSQL.
Les contraintes d'exclusion fonctionnent d'une manière très similaire à l'élagage de partitions, sauf
qu'elles utilisent les contraintes CHECK de chaque table (d'où le nom) alors que l'élagage de partition
utilise les limites de partition de la table, qui n'existent que dans le cas d'un partitionnement déclaratif.
Une autre différence est qu'une contrainte d'exclusion n'est appliquée qu'à la planification ; il n'y a
donc pas de tentative d'écarter des partitions dès l'exécution.
Le fait que les contraintes d'exclusion utilisent les contraintes CHECK les rend plus lentes que l'élagage
de partitions, mais peut être un avantage : puisque les contraintes peuvent être définies même sur des
tables avec partitionnement déclaratif, en plus de leurs limites internes, les contraintes d'exclusion
peuvent être capables de supprimer des partitions supplémentaires pendant la phase de planification
de la requête.
La valeur par défaut (et donc recommandée) de constraint_exclusion n'est ni on ni off, mais un
état intermédiaire appelé partition, qui fait que la technique n'est appliquée qu'aux requêtes
qui semblent fonctionner avec des tables partitionnées par héritage. La valeur on entraîne que le
planificateur examine les contraintes CHECK dans toutes les requêtes, y compris les requêtes simples
qui ont peu de chance d'en profiter.
• Les contraintes d'exclusion ne sont appliquées que lors de la phase de planification de la requête,
contrairement à l'élagage de partition, qui peut être appliqué lors de la phase d'exécution.
• La contrainte d'exclusion ne fonctionne que si la clause WHERE de la requête contient des constantes
(ou des paramètres externes). Par exemple, une comparaison avec une fonction non immutable
comme CURRENT_TIMESTAMP ne peut pas être optimisée, car le planificateur ne peut pas savoir
dans quelle table fille la valeur de la fonction ira lors de l'exécution.
• Les contraintes de partitionnement doivent rester simples. Dans le cas contraire, le planificateur
peut rencontrer des difficultés à déterminer les tables filles qu'il n'est pas nécessaire de parcourir.
Des conditions simples d'égalité pour le partitionnement de liste, ou des tests d'intervalle simples
lors de partitionnement par intervalles sont recommandées, comme illustré dans les exemples
précédents. Une règle générale est que les contraintes de partitionnement ne doivent contenir que
des comparaisons entre les colonnes partitionnées et des constantes, à l'aide d'opérateurs utilisables
par les index B-tree, car seules les colonnes indexables avec un index B-tree sont autorisées dans
la clé de partitionnement.
• Toutes les contraintes sur toutes les filles de la table parente sont examinées lors de l'exclusion
de contraintes. De ce fait, un grand nombre de filles augmente considérablement le temps de
planification de la requête. Ainsi, l'ancien partitionnement par héritage fonctionnera bien jusqu'à,
peut-être, une centaine de tables enfant ; n'essayez pas d'en utiliser plusieurs milliers.
103
Définition des données
Une des décisions les plus critiques au niveau du design est le choix de la clé (ou des clés) de
partitionnement. Souvent, le meilleur choix revient à partitionner par la (ou les) colonne(s) qui
apparaissent le plus fréquemment dans les clauses WHERE des requêtes en cours d'exécution sur la table
partitionnée. Les éléments de la clause WHERE qui sont compatibles avec les contraintes des limites
des partitions peuvent être utilisés pour ignorer les partitions inutiles. La suppression des données
inutiles est aussi un facteur à considérer lors de la conception de votre stratégie de partitionnement. Une
partition entière peut être détachée rapidement, donc il peut être bénéfique de concevoir la stratégie
de partitionnement d'une telle façon que tout les données à supprimer d'un coup soient concentrées
sur une seule partition.
Choisir le nombre cible de partitions pour la table est aussi une décision critique à prendre. Ne pas avoir
suffisamment de partitions pourrait avoir pour conséquence des index trop gros, et un emplacement des
données pauvre qui résulterait en un ratio bas de lecture en cache. Néanmoins, diviser la table en trop
de partitions pourrait aussi causer des problèmes. Trop de partitions pourrait signifier une optimisation
plus longue des requêtes et une consommation mémoire plus importante durant l'optimisation et
l'exécution, comme indiqué plus bas. Lors de la conception du partitionnement de votre table, il est
aussi important de prendre compte les changements pouvant survenir dans le futur. Par exemple, si
vous choisissez d'avoir une partition par client et que vous avez un petit nombre de gros clients, il
est important de réfléchir aux implications si, dans quelques années, vous vous trouvez avec un grand
nombre de petits clients. Dans ce cas, il serait mieux de choisir de partitionner par RANGE et de choisir
un nombre raisonnable de partitions, chacune contenant un nombre fixe de clients, plutôt que d'essayer
de partitionner par LIST en espérant que le nombre de clients ne dépasse pas ce qui est possible au
niveau du partitionnement des données.
Le sous-partitionnement peut aussi être utile pour diviser encore plus les partitions qui pourraient
devenir plus grosses que les autres partitions. Une autre option est d'utiliser le partitionnement par
intervalle avec plusieurs colonnes dans la clé de partitionnement. Chacune de ses solutions peut
facilement amener à un nombre excessif de partitions, il convient donc de rester prudent.
Avec une charge de type entrepôt de données, il peut être sensé d'utiliser un plus grand nombre de
partitions que pour une charge de type OLTP. En général, dans les entrepôts de données, le temps
d'optimisation d'une requête est peu importante parce que la majorité du temps de traitement est passée
sur l'exécution de la requête. Avec l'une de ces deux types de charges, il est important de prendre
les bonnes décisions dès le début, car le re-partitionnement de grosses quantités de données peut être
très lent. Les simulations de la charge attendue sont souvent bénéfiques pour optimiser la stratégie de
partitionnement. Ne jamais supposer qu'un plus grand nombre de partitions est toujours mieux qu'un
petit nombre de partitions, et vice-versa.
PostgreSQL implémente des portions de la norme SQL/MED, vous permettant d'accéder à des données
qui résident en dehors de PostgreSQL en utilisant des requêtes SQL standards. On utilise le terme de
données distantes pour de telles données. (Notez qu'en anglais il y a ambiguïté : les données distantes
(foreign data) n'ont rien à voir avec les clés étrangères (foreign keys), qui sont un type de contrainte
à l'intérieur de la base de données.)
Les données distantes sont accédées grâce à un wrapper de données distantes. Ce dernier est une
bibliothèque qui peut communiquer avec une source de données externe, cachant les détails de la
connexion vers la source de données et de la récupération des données à partir de cette source. Il
existe des wrappers de données distantes disponibles en tant que modules contrib. D'autres types de
wrappers de données distantes peuvent faire partie de produits tiers. Si aucun des wrappers de données
distantes ne vous convient, vous pouvez écrire le vôtre. Voir Chapitre 57.
Pour accéder aux données distantes, vous devez créer un objet de type serveur distant qui définit la
façon de se connecter à une source de données externes particulière suivant un ensemble d'options
utilisées par un wrapper de données distantes. Ensuite, vous aurez besoin de créer une ou plusieurs
tables distantes, qui définissent la structure des données distantes. Une table distante peut être utilisée
dans des requêtes comme toute autre table, mais une table distante n'est pas stockée sur le serveur
PostgreSQL. À chaque utilisation, PostgreSQL demande au wrapper de données distantes de récupérer
les données provenant de la source externe, ou de transmettre les données à la source externe dans le
cas de commandes de mise à jour.
Accéder à des données distantes pourrait nécessiter une authentification auprès de la source de données
externes. Cette information peut être passée par une correspondance d'utilisateur, qui peut fournir des
données comme les noms d'utilisateurs et mots de passe en se basant sur le rôle PostgreSQL actuel.
Pour plus d'informations, voir CREATE FOREIGN DATA WRAPPER, CREATE SERVER,
CREATE USER MAPPING, CREATE FOREIGN TABLE et IMPORT FOREIGN SCHEMA.
• Vues
Pour garantir l'intégrité de la structure entière de la base, PostgreSQL s'assure qu'un objet dont d'autres
objets dépendent ne peut pas être supprimé. Ainsi, toute tentative de suppression de la table des
produits utilisée dans la Section 5.3.5, sachant que la table des commandes en dépend, lève un message
d'erreur comme celui-ci :
105
Définition des données
ou en français :
Le message d'erreur contient un indice utile : pour ne pas avoir à supprimer individuellement chaque
objet dépendant, on peut lancer
et tous les objets dépendants sont ainsi effacés, comme tous les objets dépendant de ces derniers,
récursivement. Dans ce cas, la table des commandes n'est pas supprimée, mais seulement la contrainte
de clé étrangère. Elle s'arrête là, car rien ne dépend d'une contrainte de clé étrangère. (Pour vérifier ce
que fait DROP ... CASCADE, on peut lancer DROP sans CASCADE et lire les messages DETAIL.)
Pratiquement toutes les commandes DROP dans PostgreSQL supportent l'utilisation de CASCADE.
La nature des dépendances est évidemment fonction de la nature des objets. On peut aussi écrire
RESTRICT au lieu de CASCADE pour obtenir le comportement par défaut, à savoir interdire les
suppressions d'objets dont dépendent d'autres objets.
Note
D'après le standard SQL, il est nécessaire d'indiquer RESTRICT ou CASCADE dans une
commande DROP. Aucun système de base de données ne force cette règle, en réalité, mais le
choix du comportement par défaut, RESTRICT ou CASCADE, varie suivant le système.
Si une commande DROP liste plusieurs objets, CASCADE est seulement requis quand il existe des
dépendances en dehors du groupe spécifié. Par exemple, en indiquant DROP TABLE tab1, tab2,
l'existence d'une clé étrangère référençant tab1 à partir de tab2 ne signifie pas que CASCADE est
nécessaire pour réussir.
Pour les fonctions définies par les utilisateurs, PostgreSQL trace les dépendances associées avec les
propriétés de la fonction visibles en externe, comme les types de données des arguments et du résultat.
Par contre, il ne trace pas les dépendances seulement connues en examinant le corps de la fonction.
Par exemple :
106
Définition des données
(Voir Section 38.5 pour une explication sur les fonctions en SQL.) PostgreSQL aura connaissance du
fait que la fonction get_color_note dépend du type rainbow : supprimer ce type de données
forcera la suppression de la fonction parce que le type de son argument ne serait plus défini. Mais
PostgreSQL ne considérera pas que la fonction get_color_note dépende de la table my_colors,
et donc ne supprimera pas la fonction si la table est supprimée. Bien qu'il y ait des inconvénients à
cette approche, il y a aussi des avantages. La fonction est toujours valide d'une certaine façon si la
table est manquante, bien que son exécution causera une erreur. Créer une nouvelle table de même
nom permettra à la fonction d'être valide de nouveau.
107
Chapitre 6. Manipulation de données
Le chapitre précédent présente la création des tables et des autres structures de stockage des données.
Il est temps de remplir ces tables avec des données. Le présent chapitre couvre l'insertion, la mise à
jour et la suppression des données des tables. Après cela, le chapitre présente l'élimination des données
perdues.
Pour créer une nouvelle ligne, la commande INSERT est utilisée. La commande a besoin du nom de
la table et des valeurs de colonnes.
Les données sont listées dans l'ordre des colonnes de la table, séparées par des virgules. Souvent, les
données sont des libellés (constantes), mais les expressions scalaires sont aussi acceptées.
La syntaxe précédente oblige à connaître l'ordre des colonnes. Pour éviter cela, les colonnes peuvent
être explicitement listées. Les deux commandes suivantes ont, ainsi, le même effet que la précédente :
Si les valeurs de certaines colonnes ne sont pas connues, elles peuvent être omises. Dans ce cas, elles
sont remplies avec leur valeur par défaut. Par exemple :
La seconde instruction est une extension PostgreSQL. Elle remplit les colonnes de gauche à droite
avec toutes les valeurs données, et les autres prennent leur valeur par défaut.
Il est possible, pour plus de clarté, d'appeler explicitement les valeurs par défaut pour des colonnes
particulières ou pour la ligne complète.
108
Manipulation de données
Il est aussi possible d'insérer le résultat d'une requête (qui pourrait renvoyer aucune ligne, une ligne
ou plusieurs lignes) :
Ceci montre la grande puissance du mécanisme des requêtes SQL (Chapitre 7) sur le traitement des
lignes à insérer.
Astuce
Lors de l'insertion d'une grande quantité de données en même temps, il est préférable d'utiliser
la commande COPY. Elle n'est pas aussi flexible que la commande INSERT, mais elle est plus
efficace. Se référer à Section 14.4 pour plus d'informations sur l'amélioration des performances
lors de gros chargements de données.
Pour mettre à jour les lignes existantes, utilisez la commande UPDATE. Trois informations sont
nécessaires :
Comme cela a été vu dans le Chapitre 5, le SQL ne donne pas, par défaut, d'identifiant unique pour
les lignes. Il n'est, de ce fait, pas toujours possible d'indiquer directement la ligne à mettre à jour.
On précise plutôt les conditions qu'une ligne doit remplir pour être mise à jour. Si la table possède
une clé primaire (qu'elle soit déclarée ou non), une ligne unique peut être choisie en précisant une
condition sur la clé primaire. Les outils graphiques d'accès aux bases de données utilisent ce principe
pour permettre les modifications de lignes individuelles.
La commande suivante, par exemple, modifie tous les produits dont le prix est 5 en le passant à 10.
Cela peut mettre à jour zéro, une, ou plusieurs lignes. L'exécution d'une commande UPDATE qui ne
met à jour aucune ligne ne représente pas une erreur.
Dans le détail de la commande, on trouve tout d'abord, le mot-clé UPDATE suivi du nom de la table.
Le nom de la table peut toujours être préfixé par un nom de schéma ; dans le cas contraire, elle est
109
Manipulation de données
recherchée dans le chemin. On trouve ensuite le mot-clé SET suivi du nom de la colonne, un signe
égal et la nouvelle valeur de la colonne, qui peut être une constante ou une expression scalaire.
Par exemple, pour augmenter de 10% le prix de tous les produits, on peut exécuter :
L'expression donnant la nouvelle valeur peut faire référence aux valeurs courantes de la ligne.
Il n'a pas été indiqué ici de clause WHERE. Si elle est omise, toutes les lignes de la table sont modifiées.
Si elle est présente, seules les lignes qui remplissent la condition WHERE sont mises à jour. Le signe
égal dans la clause SET réalise une affectation, alors que celui de la clause WHERE permet une
comparaison. Pour autant, cela ne crée pas d'ambiguïté. La condition WHERE n'est pas nécessairement
un test d'égalité ; de nombreux autres opérateurs existent (voir le Chapitre 9). Mais le résultat de
l'expression est booléen.
Il est possible d'actualiser plusieurs colonnes en une seule commande UPDATE par l'indication de
plusieurs colonnes dans la clause SET.
Par exemple :
Pour supprimer des lignes, on utilise la commande DELETE ; la syntaxe est très similaire à la
commande UPDATE.
Par exemple, pour supprimer toutes les lignes de la table produits qui ont un prix de 10, on exécute :
En indiquant simplement :
Le contenu autorisé d'une clause RETURNING est identique à celui de la liste de sortie d'une commande
SELECT (voir Section 7.3). Elle peut contenir les noms des colonnes de la table cible ou des
110
Manipulation de données
expressions utilisant ces colonnes. Un raccourci habituel est RETURNING *, qui sélectionne toutes
les colonnes de la table cible, dans l'ordre de définition.
Avec un INSERT, les données disponibles à RETURNING sont la ligne qui a été insérée. Ceci n'est
pas utile pour les insertions simples, car cela ne fera que répéter les données fournies par le client, mais
cela peut devenir très utile si la commande se base sur les valeurs calculées par défaut. Par exemple,
lors de l'utilisation d'une colonne serial fournissant des identifiants uniques, RETURNING peut
renvoyer l'identifiant affecté à une nouvelle ligne :
La clause RETURNING est aussi très utile avec un INSERT ... SELECT
Dans un UPDATE, les données disponibles pour la clause RETURNING correspondent au nouveau
contenu de la ligne modifiée. Par exemple :
Dans un DELETE, les données disponibles pour la clause RETURNING correspondent au contenu de
la ligne supprimée. Par exemple :
Si des triggers (Chapitre 39) sont définis sur la table cible, les données disponibles pour la clause
RETURNING correspondent à la ligne modifiée par les triggers. De ce fait, une utilisation courante de
la clause RETURNING est d'inspecter les colonnes calculées par les triggers.
111
Chapitre 7. Requêtes
Les précédents chapitres ont expliqué comme créer des tables, comment les remplir avec des données
et comment manipuler ces données. Maintenant, nous discutons enfin de la façon de récupérer ces
données depuis la base de données.
7.1. Aperçu
Le processus et la commande de récupération des données sont appelés une requête. En SQL, la
commande SELECT est utilisée pour spécifier des requêtes. La syntaxe générale de la commande
SELECT est
Les sections suivantes décrivent le détail de la liste de sélection, l'expression des tables et la
spécification du tri. Les requêtes WITH sont traitées en dernier, car il s'agit d'une fonctionnalité
avancée.
En supposant qu'il existe une table appelée table1, cette commande récupérera toutes les lignes
et toutes les colonnes, définies par l'utilisateur, de table1. La méthode de récupération dépend de
l'application cliente. Par exemple, le programme psql affichera une table, façon art ASCII, alors que les
bibliothèques du client offriront des fonctions d'extraction de valeurs individuelles à partir du résultat
de la requête. * comme liste de sélection signifie que toutes les colonnes de l'expression de table
seront récupérées. Une liste de sélection peut aussi être un sous-ensemble des colonnes disponibles ou
effectuer un calcul en utilisant les colonnes. Par exemple, si table1 dispose des colonnes nommées
a, b et c (et peut-être d'autres), vous pouvez lancer la requête suivante :
(en supposant que b et c soient de type numérique). Voir la Section 7.3 pour plus de détails.
FROM table1 est un type très simple d'expression de tables : il lit une seule table. En général,
les expressions de tables sont des constructions complexes de tables de base, de jointures et de sous-
requêtes. Mais vous pouvez aussi entièrement omettre l'expression de table et utiliser la commande
SELECT comme une calculatrice :
SELECT 3 * 4;
Ceci est plus utile si les expressions de la liste de sélection renvoient des résultats variants. Par
exemple, vous pouvez appeler une fonction de cette façon :
SELECT random();
112
Requêtes
Les clauses optionnelles WHERE, GROUP BY et HAVING dans l'expression de table spécifient un
tube de transformations successives réalisées sur la table dérivée de la clause FROM. Toutes ces
transformations produisent une table virtuelle fournissant les lignes à passer à la liste de sélection qui
choisira les lignes à afficher de la requête.
Une référence de table pourrait être un nom de table (avec en option le nom du schéma) ou de
table dérivée, telle qu'une sous-requête, une construction JOIN ou une combinaison complexe de ces
possibilités. Si plus d'une référence de table est listée dans la clause FROM, les tables sont jointes en
croisé (autrement dit, cela réalise un produit cartésien de leurs lignes ; voir ci-dessous). Le résultat de
la liste FROM est une table virtuelle intermédiaire pouvant être sujette aux transformations des clauses
WHERE, GROUP BY et HAVING, et est finalement le résultat des expressions de table.
Lorsqu'une référence de table nomme une table qui est la table parent d'une table suivant la hiérarchie
de l'héritage, la référence de table produit les lignes non seulement de la table, mais aussi des
descendants de cette table, sauf si le mot-clé ONLY précède le nom de la table. Néanmoins, la référence
produit seulement les colonnes qui apparaissent dans la table nommée... Toute colonne ajoutée dans
une sous-table est ignorée.
Au lieu d'écrire ONLY avant le nom de la table, vous pouvez écrire * après le nom de la table pour
indiquer spécifiquement que les tables filles sont incluses. Il n'y a plus de vraie raison pour encore
utiliser cette syntaxe, car chercher dans les tables descendantes est maintenant le comportement par
défaut. C'est toutefois supporté pour compatibilité avec des versions plus anciennes.
T1 type_jointure T2 [ condition_jointure ]
Des jointures de tous types peuvent être chaînées ensemble ou imbriquées : une des deux tables ou les
deux tables peuvent être des tables jointes. Des parenthèses peuvent être utilisées autour des clauses
JOIN pour contrôler l'ordre de jointure. Dans l'absence des parenthèses, les clauses JOIN s'imbriquent
de gauche à droite.
Types de jointures
Jointure croisée (cross join)
T1 CROSS JOIN T2
FROM T1 CROSS JOIN T2 est équivalent à FROM T1 INNER JOIN T2 ON TRUE (voir
ci-dessous). C'est aussi équivalent à : FROM T1, T2.
113
Requêtes
Note
Cette dernière équivalence ne convient pas exactement quand plusieurs tables
apparaissent, car JOIN lie de façon plus profonde que la virgule. Par exemple, FROM T1
CROSS JOIN T2 INNER JOIN T3 ON condition n'est pas identique à FROM
T1, T2 INNER JOIN T3 ON condition, car condition peut faire référence
à T1 dans le premier cas, mais pas dans le second.
Les mots INNER et OUTER sont optionnels dans toutes les formes. INNER est la valeur par
défaut ; LEFT, RIGHT et FULL impliquent une jointure externe.
INNER JOIN
Pour chaque ligne R1 de T1, la table jointe a une ligne pour chaque ligne de T2 satisfaisant
la condition de jointure avec R1.
Tout d'abord, une jointure interne est réalisée. Puis, pour chaque ligne de T1 qui ne satisfait
pas la condition de jointure avec les lignes de T2, une ligne jointe est ajoutée avec des valeurs
NULL dans les colonnes de T2. Du coup, la table jointe a toujours au moins une ligne pour
chaque ligne de T1, quelles que soient les conditions.
Tout d'abord, une jointure interne est réalisée. Puis, pour chaque ligne de T2 qui ne satisfait
pas la condition de jointure avec les lignes de T1, une ligne jointe est ajoutée avec des valeurs
NULL dans les colonnes de T1. C'est l'inverse d'une jointure gauche : la table résultante aura
toujours une ligne pour chaque ligne de T2, quelles que soient les conditions.
Tout d'abord, une jointure interne est réalisée. Puis, pour chaque ligne de T1 qui ne satisfait
pas la condition de jointure avec les lignes de T2, une ligne jointe est ajoutée avec des valeurs
NULL dans les colonnes de T2. De plus, pour chaque ligne de T2 qui ne satisfait pas la
condition de jointure avec les lignes de T1, une ligne jointe est ajoutée avec des valeurs NULL
dans les colonnes de T1.
La clause ON est le type de condition de jointure le plus utilisé : elle prend une valeur booléenne
du même type que celle utilisée dans une clause WHERE. Une paire de lignes provenant de T1 et
de T2 correspondent si l'expression de la clause ON vaut true.
La clause USING est un raccourci qui vous permet de prendre avantage d'une situation spécifique
où les deux côtés de la jointure utilisent le même nom pour la colonne jointe. Elle prend une liste
de noms de colonnes partagées, en séparant les noms par des virgules et forme une condition de
114
Requêtes
jointure qui inclut une comparaison d'égalité entre chaque. Par exemple, joindre T1 et T2 avec
USING (a, b) produit la même condition de jointure que la condition ON T1.a = T2.a
AND T1.b = T2.b.
De plus, la sortie de JOIN USING supprime les colonnes redondantes : il n'est pas nécessaire
d'imprimer les colonnes de correspondance, puisqu'elles doivent avoir des valeurs identiques.
Alors que JOIN ON produit toutes les colonnes de T2, JOIN USING produit une seule colonne
pour chaque paire de colonnes listées (dans l'ordre listé), suivi par chaque colonne restante
provenant de T1, suivi par chaque colonne restante provenant de T2.
Enfin, NATURAL est un raccourci de USING : il forme une liste USING constituée de tous
les noms de colonnes apparaissant dans les deux tables en entrée. Comme avec USING, ces
colonnes apparaissent une fois seulement dans la table en sortie. S'il n'existe aucun nom commun
de colonne, NATURAL JOIN se comporte comme JOIN ... ON TRUE et produit une jointure
croisée.
Note
USING est raisonnablement protégé contre les changements de colonnes dans les relations
jointes, car seuls les noms de colonnes listés sont combinés. NATURAL est considéré
comme plus risqué, car toute modification de schéma causant l'apparition d'un nouveau
nom de colonne correspondant fera en sorte de joindre la nouvelle colonne.
Pour rassembler tout ceci, supposons que nous avons une table t1 :
no | nom
----+------
1 | a
2 | b
3 | c
et une table t2 :
no | valeur
----+-------
1 | xxx
3 | yyy
5 | zzz
115
Requêtes
----+-----+----+-------
1 | a | 1 | xxx
3 | c | 3 | yyy
(2 rows)
La condition de jointure spécifiée avec ON peut aussi contenir des conditions sans relation directe
avec la jointure. Ceci est utile pour quelques requêtes, mais son utilisation doit avoir été réfléchie.
Par exemple :
116
Requêtes
no | nom | no | valeur
----+-----+----+-------
1 | a | 1 | xxx
2 | b | |
3 | c | |
(3 rows)
Notez que placer la restriction dans la clause WHERE donne un résultat différent :
Ceci est dû au fait qu'une restriction placée dans la clause ON est traitée avant la jointure, alors qu'une
restriction placée dans la clause WHERE est traitée après la jointure. Ceci n'a pas d'importance avec
les jointures internes, mais en a une grande avec les jointures externes.
ou
Une application typique des alias de table est l'affectation d'identifieurs courts pour les noms de tables
longs, ce qui permet de garder des clauses de jointures lisibles. Par exemple :
L'alias devient le nouveau nom de la table en ce qui concerne la requête en cours -- il n'est pas autorisé
de faire référence à la table par son nom original où que ce soit dans la requête. Du coup, ceci n'est
pas valide :
Les alias de table sont disponibles principalement pour aider à l'écriture de requête, mais ils deviennent
nécessaires pour joindre une table avec elle-même, par exemple :
De plus, un alias est requis si la référence de la table est une sous-requête (voir la Section 7.2.1.3).
117
Requêtes
Les parenthèses sont utilisées pour résoudre les ambiguïtés. Dans l'exemple suivant, la première
instruction affecte l'alias b à la deuxième instance de ma_table, mais la deuxième instruction affecte
l'alias au résultat de la jonction :
Une autre forme d'alias de tables donne des noms temporaires aux colonnes de la table ainsi qu'à la
table :
Si le nombre d'alias de colonnes spécifié est plus petit que le nombre de colonnes dont dispose la table
réelle, les colonnes suivantes ne sont pas renommées. Cette syntaxe est particulièrement utile dans le
cas de jointures avec la même table ou dans le cas de sous-requêtes.
Quand un alias est appliqué à la sortie d'une clause JOIN, l'alias cache le nom original référencé à
l'intérieur du JOIN. Par exemple :
n'est pas valide ; l'alias de table a n'est pas visible en dehors de l'alias c.
7.2.1.3. Sous-requêtes
Une sous-requête spécifiant une table dérivée doit être enfermée dans des parenthèses et doit se voir
affecter un alias de table (comme dans Section 7.2.1.2). Par exemple :
Cet exemple est équivalent à FROM table1 AS nom_alias. Des cas plus intéressants, qui
ne peuvent pas être réduits à une jointure pleine, surviennent quand la sous-requête implique un
groupement ou un agrégat.
De nouveau, un alias de table est requis. Affecter des noms d'alias aux colonnes de la liste VALUES
est optionnel, mais c'est une bonne pratique. Pour plus d'informations, voir Section 7.7.
Les fonctions de table peuvent aussi être combinées en utilisant la syntaxe ROWS FROM, avec les
résultats renvoyés dans des colonnes parallèles ; le nombre de lignes résultantes dans ce cas est celui du
résultat de fonction le plus large. Les résultats ayant moins de colonnes sont alignés avec des valeurs
NULL.
118
Requêtes
Si la clause WITH ORDINALITY est ajoutée, une colonne supplémentaire de type bigint sera
ajoutée aux colonnes de résultat de la fonction. Cette colonne numérote les lignes de l'ensemble de
résultats de la fonction, en commençant à 1. (Ceci est une généralisation de la syntaxe du standard
SQL pour UNNEST ... WITH ORDINALITY.) Par défaut, la colonne ordinale est appelée
ordinality, mais un nom de colonne différent peut être affecté en utilisant une clause AS.
La fonction de table UNNEST peut être appelée avec tout nombre de paramètres tableaux, et envoie
un nombre correspondant de colonnes comme si la fonction UNNEST avait été appelée sur chaque
paramètre séparément (Section 9.18) et combinée en utilisant la construction ROWS FROM.
Si aucun alias_table n'est précisé, le nom de la fonction est utilisé comme nom de table ; dans le
cas d'une construction ROWS FROM(), le nom de la première fonction est utilisé.
Si des alias de colonnes ne sont pas fournis pour une fonction renvoyant un type de données de base,
alors le nom de la colonne est aussi le même que le nom de la fonction. Pour une fonction renvoyant
un type composite, les colonnes résultats obtiennent les noms des attributs individuels du type.
Quelques exemples :
Dans certains cas, il est utile de définir des fonctions de table pouvant renvoyer des ensembles de
colonnes différentes suivant la façon dont elles sont appelées. Pour supporter ceci, la fonction de table
est déclarée comme renvoyant le pseudotype record sans paramètres OUT. Quand une telle fonction
est utilisée dans une requête, la structure de ligne attendue doit être spécifiée dans la requête elle-
même, de façon à ce que le système sache comment analyser et planifier la requête. Cette syntaxe
ressemble à ceci :
119
Requêtes
Lorsque la syntaxe ROWS FROM() n'est pas utilisée, la liste définition_colonne remplace
la liste d'alias de colonnes qui aurait été autrement attachée à la clause FROM ; les noms dans
les définitions de colonnes servent comme alias de colonnes. Lors de l'utilisation de la syntaxe
ROWS FROM(), une liste définition_colonne peut être attachée à chaque fonction membre
séparément ; ou s'il existe seulement une fonction membre et pas de clause WITH ORDINALITY,
une liste column_definition peut être écrite au lieu de la liste d'alias de colonnes suivant ROWS
FROM().
SELECT *
FROM dblink('dbname=mabd', 'SELECT proname, prosrc FROM
pg_proc')
AS t1(proname nom, prosrc text)
WHERE proname LIKE 'bytea%';
La fonction dblink (qui fait partie du module dblink) exécute une requête distante. Elle déclare
renvoyer le type record, car elle pourrait être utilisée pour tout type de requête. L'ensemble de
colonnes réelles doit être spécifié dans la requête appelante de façon à ce que l'analyseur sache, par
exemple, comment étendre *.
SELECT *
FROM ROWS FROM
(
json_to_recordset('[{"a":40,"b":"foo"},
{"a":"100","b":"bar"}]')
AS (a INTEGER, b TEXT),
generate_series(1, 3)
) AS x (p, q, s)
ORDER BY p;
p | q | s
-----+-----+---
40 | foo | 1
100 | bar | 2
| | 3
Il joint deux fonctions en une seule cible FROM. json_to_recordset() doit renvoyer
deux colonnes, la première de type integer et la seconde de type text. Le résultat de
generate_series() est utilisé directement. La clause ORDER BY trie les valeurs de la colonne
en tant qu'entiers.
Les fonctions renvoyant des ensembles et apparaissant dans le FROM peuvent aussi être précédées du
mot-clé LATERAL, mais, pour les fonctions, le mot-clé est optionnel. Les arguments de la fonction
peuvent contenir des références aux colonnes fournies par les éléments précédents dans le FROM.
Un élément LATERAL peut apparaître au niveau haut dans la liste FROM ou dans un arbre de jointures
(JOIN). Dans ce dernier cas, cela peut aussi faire référence à tout élément qui est sur le côté gauche
d'un JOIN, alors qu'il est positionné sur sa droite.
120
Requêtes
Quand un élément FROM contient des références croisées LATERAL, l'évaluation se fait ainsi : pour
chaque ligne d'un élément FROM fournissant les colonnes référencées, ou pour chaque ensemble
de lignes de plusieurs éléments FROM fournissant les colonnes, l'élément LATERAL est évalué en
utilisant cette valeur de ligne ou cette valeur d'ensemble de lignes. Les lignes résultantes sont jointes
comme d'habitude aux lignes résultant du calcul. C'est répété pour chaque ligne ou ensemble de lignes
provenant de la table source.
Ceci n'est pas vraiment utile, car cela revient exactement au même résultat que cette écriture plus
conventionnelle :
LATERAL est principalement utile lorsqu'une colonne référencée est nécessaire pour calculer la
colonne à joindre. Une utilisation habituelle est de fournir une valeur d'un argument à une fonction
renvoyant un ensemble de lignes. Par exemple, supposons que vertices(polygon) renvoie
l'ensemble de sommets d'un polygone, nous pouvons identifier les sommets proches des polygones
stockés dans une table avec la requête suivante :
ou dans diverses autres formulations équivalentes. (Nous l'avons déjà mentionné, le mot-clé LATERAL
est inutile dans cet exemple, mais nous l'utilisons pour plus de clarté.)
Il est souvent particulièrement utile d'utiliser LEFT JOIN sur une sous-requête LATERAL, pour que
les lignes sources apparaissent dans le résultat même si la sous-requête LATERAL ne produit aucune
ligne pour elles. Par exemple, si get_product_names() renvoie les noms des produits réalisés
par un manufacturier, mais que quelques manufacturiers dans notre table ne réalisent aucun produit,
nous pourrions les trouver avec cette requête :
SELECT m.name
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id)
pname ON true
WHERE pname IS NULL;
121
Requêtes
WHERE condition_recherche
où condition_recherche est toute expression de valeur (voir la Section 4.2) renvoyant une
valeur de type boolean.
Après le traitement de la clause FROM, chaque ligne de la table virtuelle dérivée est vérifiée avec la
condition de recherche. Si le résultat de la vérification est positif (true), la ligne est conservée dans la
table de sortie, sinon (c'est-à-dire si le résultat est faux ou nul), la ligne est abandonnée. La condition
de recherche référence typiquement au moins une colonne de la table générée dans la clause FROM ;
ceci n'est pas requis, mais, dans le cas contraire, la clause WHERE n'aurait aucune utilité.
Note
La condition de jointure d'une jointure interne peut être écrite soit dans la clause WHERE soit
dans la clause JOIN. Par exemple, ces expressions de tables sont équivalentes :
et :
ou même peut-être :
Laquelle utiliser est plutôt une affaire de style. La syntaxe JOIN dans la clause FROM n'est
probablement pas aussi portable vers les autres systèmes de gestion de bases de données SQL,
même si cela fait partie du standard SQL. Pour les jointures externes, il n'y a pas d'autres
choix : elles doivent être faites dans la clause FROM. La clause ON ou USING d'une jointure
externe n'est pas équivalente à une condition WHERE parce qu'elle détermine l'ajout de lignes
(pour les lignes qui ne correspondent pas en entrée) ainsi que pour la suppression de lignes
dans le résultat final.
SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 >
fdt.c1)
fdt est la table dérivée dans la clause FROM. Les lignes qui ne correspondent pas à la condition de
recherche de la clause WHERE sont éliminées de la table fdt. Notez l'utilisation de sous-requêtes
122
Requêtes
scalaires en tant qu'expressions de valeurs. Comme n'importe quelle autre requête, les sous-requêtes
peuvent employer des expressions de tables complexes. Notez aussi comment fdt est référencée dans
les sous-requêtes. Qualifier c1 comme fdt.c1 est seulement nécessaire si c1 est aussi le nom d'une
colonne dans la table d'entrée dérivée de la sous-requête. Mais qualifier le nom de colonne ajoute de la
clarté même lorsque cela n'est pas nécessaire. Cet exemple montre comment le nom de colonne d'une
requête externe est étendu dans les requêtes internes.
SELECT liste_selection
FROM ...
[WHERE ...]
GROUP
BY reference_colonne_regroupement[,reference_colonne_regroupement]...
La la section intitulée « Clause GROUP BY » est utilisée pour regrouper les lignes d'une table qui ont
les mêmes valeurs dans toutes les colonnes précisées. L'ordre dans lequel ces colonnes sont indiquées
importe peu. L'effet est de combiner chaque ensemble de lignes partageant des valeurs communes
en un seul groupe de lignes représentant toutes les lignes du groupe. Ceci est fait pour éliminer les
redondances dans la sortie et/ou pour calculer les agrégats s'appliquant à ces groupes. Par exemple :
Dans la seconde requête, nous n'aurions pas pu écrire SELECT * FROM test1 GROUP BY x
parce qu'il n'existe pas une seule valeur pour la colonne y pouvant être associée avec chaque autre
groupe. Les colonnes de regroupement peuvent être référencées dans la liste de sélection, car elles ont
une valeur constante unique par groupe.
En général, si une table est groupée, les colonnes qui ne sont pas listées dans le GROUP BY ne peuvent
pas être référencées sauf dans les expressions d'agrégats. Voici un exemple d'expression d'agrégat :
Ici, sum est la fonction d'agrégat qui calcule une seule valeur pour le groupe entier. La Section 9.20
propose plus d'informations sur les fonctions d'agrégats disponibles.
123
Requêtes
Astuce
Le regroupement sans expressions d'agrégats calcule effectivement l'ensemble des valeurs
distinctes d'une colonne. Ceci peut aussi se faire en utilisant la clause DISTINCT (voir la
Section 7.3.3).
Voici un autre exemple : il calcule les ventes totales pour chaque produit (plutôt que le total des ventes
sur tous les produits) :
Dans cet exemple, les colonnes id_produit, p.nom et p.prix doivent être dans la clause GROUP
BY, car elles sont référencées dans la liste de sélection de la requête (mais voir plus loin). La colonne
v.unite n'a pas besoin d'être dans la liste GROUP BY, car elle est seulement utilisée dans l'expression
de l'agrégat (sum(...)) représentant les ventes d'un produit. Pour chaque produit, la requête renvoie
une ligne de résumé sur les ventes de ce produit.
Si la table produits est configurée de façon à ce que id_produit soit la clé primaire, alors il serait
suffisant de grouper par la colonne id_produit dans l'exemple ci-dessus, car le nom et le prix
seraient dépendants fonctionnellement de l'identifiant du produit, et donc il n'y aurait pas d'ambiguïté
sur le nom et le prix à renvoyer pour chaque groupe d'identifiants de produits.
En SQL strict, GROUP BY peut seulement grouper les colonnes de la table source, mais PostgreSQL
étend ceci en autorisant GROUP BY à grouper aussi les colonnes de la liste de sélection. Grouper par
expressions de valeurs au lieu de simples noms de colonnes est aussi permis.
Si une table a été groupée en utilisant la clause GROUP BY, mais que seuls certains groupes sont
intéressants, la clause HAVING peut être utilisée, comme une clause WHERE, pour éliminer les groupes
du résultat. Voici la syntaxe :
Les expressions de la clause HAVING peuvent référer à la fois aux expressions groupées et aux
expressions non groupées (ce qui implique nécessairement une fonction d'agrégat).
Exemple :
124
Requêtes
Dans l'exemple ci-dessus, la clause WHERE sélectionne les lignes par une colonne qui n'est pas groupée
(l'expression est vraie seulement pour les ventes des quatre dernières semaines) alors que la clause
HAVING restreint la sortie aux groupes dont le total des ventes dépasse 5000. Notez que les expressions
d'agrégats n'ont pas besoin d'être identiques dans toutes les parties d'une requête.
Si une requête contient des appels à des fonctions d'agrégat, mais pas de clause GROUP BY, le
regroupement a toujours lieu : le résultat est une seule ligne de regroupement (ou peut-être pas de
ligne du tout si la ligne unique est ensuite éliminée par la clause HAVING). Ceci est vrai aussi si elle
comporte une clause HAVING, même sans fonction d'agrégat ou GROUP BY.
Chaque sous-liste de GROUPING SETS peut indiquer 0 ou plusieurs colonnes ou expressions et est
interprétée de la même manière que si elle était directement dans la clause GROUP BY. Un ensemble
de regroupement vide signifie que toutes les lignes sont agrégées pour former un simple groupe (qui
est renvoyé quand bien même aucune ligne ne serait sélectionnée), comme décrit ci-dessus dans le cas
de fonctions d'agrégat sans clause GROUP BY.
Les références aux colonnes de regroupement ou expressions sont remplacées par des valeurs NULL
dans les lignes renvoyées pour les ensembles de regroupement où ces colonnes n'apparaissent pas.
Pour identifier à quel ensemble de regroupement une ligne en particulier appartient, référez-vous à
Tableau 9.56.
Une notation raccourcie est fournie pour indiquer deux types classiques d'ensembles de regroupement.
Une clause sous la forme
125
Requêtes
représente la liste indiquée d'expressions ainsi que l'ensemble des préfixes de la liste, y compris la
liste vide. C'est donc équivalent à
GROUPING SETS (
( e1, e2, e3, ... ),
...
( e1, e2 ),
( e1 ),
( )
)
Cette notation est communément utilisée avec des données hiérarchiques ; par exemple, le total des
salaires par département, division et sur l'ensemble de l'entreprise.
représente la liste indiquée ainsi que l'ensemble des sous-ensembles possibles. De ce fait,
CUBE ( a, b, c )
est équivalent à
GROUPING SETS (
( a, b, c ),
( a, b ),
( a, c ),
( a ),
( b, c ),
( b ),
( c ),
( )
)
Les éléments individuels des clauses CUBE ou ROLLUP peuvent être des expressions individuelles, ou
des sous-listes d'éléments entre parenthèses. Dans ce dernier cas, les sous-listes sont traitées comme
simple élément pour la génération des ensembles de regroupements individuels. Par exemple :
est équivalent à
GROUPING SETS (
( a, b, c, d ),
( a, b ),
126
Requêtes
( c, d ),
( )
)
et
est équivalent à
GROUPING SETS (
( a, b, c, d ),
( a, b, c ),
( a ),
( )
)
Les éléments CUBE et ROLLUP peuvent être utilisés directement dans la clause GROUP BY, ou
imbriqués à l'intérieur d'une clause GROUPING SETS. Si une clause GROUPING SETS est imbriquée
dans une autre, l'effet est le même que si tous les éléments de la clause la plus imbriquée avaient été
écrits directement dans la clause de niveau supérieur.
Si de multiples clauses de regroupement sont indiquées dans une simple clause GROUP BY, alors
la liste finale des ensembles de regroupements est le produit cartésien des éléments individuels. Par
exemple :
est équivalent à
Note
La syntaxe (a, b) est normalement reconnue dans les expressions comme un constructeur
de ligne. À l'intérieur d'une clause GROUP BY, cette règle ne s'applique pas au premier niveau
d'expressions, et (a, b) est reconnu comme une liste d'expressions, comme décrit ci-dessus.
Si pour une quelconque raison vous avez besoin d'un constructeur de ligne dans une expression
de regroupement, utilisez ROW(a, b).
127
Requêtes
HAVING. C'est-à-dire que si la requête comporte des agrégats, GROUP BY ou HAVING, alors les
enregistrements vus par les fonctions Window sont les lignes regroupées à la place des enregistrements
originaux provenant de FROM/WHERE.
Quand des fonctions Window multiples sont utilisées, toutes les fonctions Window ayant des clauses
PARTITION BY et ORDER BY syntaxiquement équivalentes seront à coup sûr évaluées en une seule
passe sur les données. Par conséquent, elles verront le même ordre de tri, même si ORDER BY ne
détermine pas de façon unique un tri. Toutefois, aucune garantie n'est faite à propos de l'évaluation
de fonctions ayant des spécifications de PARTITION BY ou ORDER BY différentes. (Dans ces cas,
une étape de tri est généralement nécessaire entre les passes d'évaluations de fonctions Window, et le
tri ne garantit pas la préservation de l'ordre des enregistrements que son ORDER BY estime comme
identiques.)
À l'heure actuelle, les fonctions Window nécessitent toujours des données prétriées, ce qui fait que la
sortie de la requête sera triée suivant l'une ou l'autre des clauses PARTITION BY/ORDER BY des
fonctions Window. Il n'est toutefois pas recommandé de s'en servir. Utilisez une clause ORDER BY au
plus haut niveau de la requête si vous voulez être sûr que vos résultats soient triés d'une certaine façon.
Les noms de colonnes a, b et c sont soit les noms actuels des colonnes des tables référencées dans la
clause FROM, soit les alias qui leur ont été donnés (voir l'explication dans Section 7.2.1.2). L'espace de
nom disponible dans la liste de sélection est le même que dans la clause WHERE sauf si le regroupement
est utilisé, auquel cas c'est le même que dans la clause HAVING.
Si plus d'une table a une colonne du même nom, le nom de la table doit aussi être donné, comme dans :
En travaillant avec plusieurs tables, il est aussi utile de demander toutes les colonnes d'une table
particulière :
Si une expression de valeur arbitraire est utilisée dans la liste de sélection, il ajoute conceptuellement
une nouvelle colonne virtuelle dans la table renvoyée. L'expression de valeur est évaluée une fois pour
chaque ligne avec une substitution des valeurs de lignes avec les références de colonnes. Mais les
expressions de la liste de sélection n'ont pas à référencer les colonnes dans l'expression de la table de
la clause FROM ; elles pourraient être des expressions arithmétiques constantes, par exemple.
128
Requêtes
Les entrées de la liste de sélection peuvent se voir affecter des noms pour la suite de l'exécution,
peut-être pour référence dans une clause ORDER BY ou pour affichage par l'application cliente. Par
exemple :
Si aucun nom de colonne en sortie n'est spécifié en utilisant AS, le système affecte un nom de colonne
par défaut. Pour les références de colonne simple, c'est le nom de la colonne référencée. Pour les appels
de fonction, il s'agit du nom de la fonction. Pour les expressions complexes, le système générera un
nom générique.
Le mot-clé AS est optionnel, mais seulement si le nouveau nom de colonne ne correspond à aucun
des mots-clés PostgreSQL (voir Annexe C). Pour éviter une correspondance accidentelle à un mot-
clé, vous pouvez mettre le nom de colonne entre guillemets. Par exemple, VALUE est un mot-clé, ce
qui fait que ceci ne fonctionne pas :
Pour vous protéger de possibles ajouts futurs de mots-clés, il est recommandé de toujours écrire AS
ou de mettre le nom de colonne de sortie entre guillemets.
Note
Le nom des colonnes en sortie est différent ici de ce qui est fait dans la clause FROM (voir la
Section 7.2.1.2). Il est possible de renommer deux fois la même colonne, mais le nom affecté
dans la liste de sélection est celui qui sera passé.
7.3.3. DISTINCT
Après le traitement de la liste de sélection, la table résultante pourrait être optionnellement sujette à
l'élimination des lignes dupliquées. Le mot-clé DISTINCT est écrit directement après SELECT pour
spécifier ceci :
(au lieu de DISTINCT, le mot-clé ALL peut être utilisé pour spécifier le comportement par défaut,
la récupération de toutes les lignes).
Évidemment, les deux lignes sont considérées distinctes si elles diffèrent dans au moins une valeur de
colonne. Les valeurs NULL sont considérées égales dans cette comparaison.
Autrement, une expression arbitraire peut déterminer quelles lignes doivent être considérées
distinctes :
Ici, expression est une expression de valeur arbitraire, évaluée pour toutes les lignes. Les lignes
dont toutes les expressions sont égales sont considérées comme dupliquées et seule la première ligne
de cet ensemble est conservée dans la sortie. Notez que la « première ligne » d'un ensemble est non
129
Requêtes
prévisible sauf si la requête est triée sur assez de colonnes pour garantir un ordre unique des colonnes
arrivant dans le filtre DISTINCT (le traitement de DISTINCT ON parvient après le tri de ORDER
BY).
La clause DISTINCT ON ne fait pas partie du standard SQL et est quelques fois considérée comme
étant un mauvais style à cause de la nature potentiellement indéterminée de ses résultats. Avec
l'utilisation judicieuse de GROUP BY et de sous-requêtes dans FROM, la construction peut être évitée,
mais elle représente souvent l'alternative la plus agréable.
où requete1 et requete2 sont les requêtes pouvant utiliser toutes les fonctionnalités discutées ici.
UNION ajoute effectivement le résultat de requete2 au résultat de requete1 (bien qu'il n'y ait pas
de garantie qu'il s'agisse de l'ordre dans lequel les lignes sont réellement renvoyées). De plus, il élimine
les lignes dupliquées du résultat, de la même façon que DISTINCT, sauf si UNION ALL est utilisée.
INTERSECT renvoie toutes les lignes qui sont à la fois dans le résultat de requete1 et dans le
résultat de requete2. Les lignes dupliquées sont éliminées sauf si INTERSECT ALL est utilisé.
EXCEPT renvoie toutes les lignes qui sont dans le résultat de requete1 mais pas dans le résultat
de requete2 (ceci est quelquefois appelé la différence entre deux requêtes). De nouveau, les lignes
dupliquées sont éliminées sauf si EXCEPT ALL est utilisé.
Pour calculer l'union, l'intersection ou la différence de deux requêtes, les deux requêtes doivent être
« compatibles pour une union », ce qui signifie qu'elles doivent renvoyer le même nombre de colonnes
et que les colonnes correspondantes doivent avoir des types de données compatibles, comme décrit
dans la Section 10.5.
Les opérations sur les ensembles peuvent être combinées, par exemple :
Comme indiqué ici, vous pouvez utiliser les parenthèses pour contrôler l'ordre d'évaluation. Sans les
parenthèses, UNION et EXCEPT font une association de gauche à droite, mais INTERSECT a une
priorité plus forte que ces deux opérateurs. De ce fait :
signifie
Vous pouvez aussi entourer une requête individuelle avec des parenthèses. C'est important si la
requête a besoin d'utiliser une des clauses discutées dans les sections suivantes, telles que LIMIT.
Sans les parenthèses, vous obtiendrez soit une erreur de syntaxe soit une interprétation de cette clausse
130
Requêtes
comme s'appliquant à la sortie de l'opération ensembliste plutôt que sur une de ses entrées. Par
exemple :
et non pas :
SELECT liste_selection
FROM expression_table
ORDER BY expression_tri1 [ASC | DESC] [NULLS { FIRST | LAST }]
[, expression_tri2 [ASC | DESC] [NULLS { FIRST | LAST }] ...]
Les expressions de tri peuvent être toute expression qui serait valide dans la liste de sélection des
requêtes. Voici un exemple :
Quand plus d'une expression est indiquée, les valeurs suivantes sont utilisées pour trier les lignes qui
sont identiques aux valeurs précédentes. Chaque expression pourrait être suivie d'un ASC ou DESC
optionnel pour configurer la direction du tri (ascendant ou descendant). L'ordre ASC est la valeur par
défaut. L'ordre ascendant place les plus petites valeurs en premier, où « plus petit » est défini avec
l'opérateur <. De façon similaire, l'ordre descendant est déterminé avec l'opérateur >. 1
Les options NULLS FIRST et NULLS LAST sont utilisées pour déterminer si les valeurs NULL
apparaissent avant ou après les valeurs non NULL après un tri. Par défaut, les valeurs NULL sont
triées comme si elles étaient plus grandes que toute valeur non NULL. Autrement dit, NULLS FIRST
est la valeur par défaut pour l'ordre descendant (DESC) et NULLS LAST est la valeur utilisée sinon.
Notez que les options de tri sont considérées indépendamment pour chaque colonne triée. Par exemple,
ORDER BY x, y DESC signifie en fait ORDER BY x ASC, y DESC, ce qui est différent de
ORDER BY x DESC, y DESC.
Une expression_tri peut aussi être, à la place, le nom ou le numéro d'une colonne en sortie,
par exemple :
1
En fait, PostgreSQL utilise la classe d'opérateur B-tree par défaut pour le type de données de l'expression pour déterminer l'ordre de tri avec
ASC et DESC. De façon conventionnelle, les types de données seront initialisés de façon à ce que les opérateurs < et > correspondent à cet
ordre de tri, mais un concepteur des types de données définis par l'utilisateur pourrait choisir de faire quelque chose de différent.
131
Requêtes
les deux triant par la première colonne en sortie. Notez qu'un nom de colonne en sortie doit être unique,
il ne doit pas être utilisé dans une expression -- par exemple, ceci n'est pas correct :
Cette restriction est là pour réduire l'ambiguïté. Il y en a toujours si un élément ORDER BY est un
simple nom qui pourrait correspondre soit à un nom de colonne en sortie soit à une colonne d'une
expression de table. La colonne en sortie est utilisée dans de tels cas. Cela causera seulement de la
confusion si vous utilisez AS pour renommer une colonne en sortie qui correspondra à un autre nom
de colonne d'une table.
ORDER BY peut être appliqué au résultat d'une combinaison UNION, d'une combinaisonINTERSECT
ou d'une combinaison EXCEPT, mais, dans ce cas, il est seulement permis de trier par les noms ou
numéros de colonnes, pas par les expressions.
SELECT liste_selection
FROM expression_table
[ ORDER BY ...]
[ LIMIT { nombre | ALL } ] [OFFSET nombre]
Si un nombre limite est donné, pas plus que ce nombre de lignes ne sera renvoyé (mais peut-être moins
si la requête récupère moins de lignes). LIMIT ALL revient à ne pas spécifier la clause LIMIT.
OFFSET indique de passer ce nombre de lignes avant de renvoyer les lignes restantes. OFFSET 0
revient à oublier la clause OFFSET, tout comme OFFSET avec un argument NULL.
Si à la fois OFFSET et LIMIT apparaissent, alors les OFFSET lignes sont laissées avant de commencer
le renvoi des LIMIT lignes.
Lors de l'utilisation de LIMIT, il est important d'utiliser une clause ORDER BY contraignant les lignes
résultantes dans un ordre unique. Sinon, vous obtiendrez un sous-ensemble non prévisible de lignes de
la requête. Vous pourriez demander les lignes de 10 à 20, mais dans quel ordre ? L'ordre est inconnu
si vous ne spécifiez pas ORDER BY.
L'optimiseur de requêtes prend LIMIT en compte lors de la génération des plans de requêtes, de façon
à ce que vous obteniez différents plans (avec différents ordres de lignes) suivant ce que vous donnez
à LIMIT et OFFSET. Du coup, utiliser des valeurs LIMIT/OFFSET différentes pour sélectionner
des sous-ensembles différents d'un résultat de requête donnera des résultats inconsistants sauf si vous
forcez un ordre de résultat prévisible avec ORDER BY. Ceci n'est pas un bogue ; c'est une conséquence
inhérente au fait que le SQL ne promette pas de délivrer les résultats d'une requête dans un ordre
particulier sauf si ORDER BY est utilisé pour contraindre l'ordre.
Les lignes passées par une clause OFFSET devront toujours être traitées à l'intérieur du serveur ; du
coup, un OFFSET important peut être inefficace.
VALUES fournit une façon de générer une table de « constantes » qui peut être utilisée dans une requête
sans avoir à réellement créer et peupler une table sur disque. La syntaxe est
Chaque liste d'expressions entre parenthèses génère une ligne dans la table. Les listes doivent toutes
avoir le même nombre d'éléments (c'est-à-dire une liste de colonnes dans la table), et les entrées
correspondantes dans chaque liste doivent avoir des types compatibles. Le type réel affecté à chaque
colonne du résultat est déterminé en utilisant les mêmes règles que pour UNION (voir Section 10.5).
Voici un exemple :
Par défaut, PostgreSQL affecte les noms column1, column2, etc. aux colonnes d'une table
VALUES. Les noms des colonnes ne sont pas spécifiés par le standard SQL et les différents SGBD
le font de façon différente. Donc, il est généralement mieux de surcharger les noms par défaut avec
une liste d'alias, comme ceci :
=> SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t
(num,letter);
num | letter
-----+--------
1 | one
2 | two
3 | three
(3 rows)
Syntaxiquement, VALUES suivi par une liste d'expressions est traité de la même façon que
et peut apparaître partout où un SELECT le peut. Par exemple, vous pouvez l'utiliser comme
élément d'un UNION ou y attacher une spécification de tri (ORDER BY, LIMIT et/
ou OFFSET). VALUES est habituellement utilisée comme source de données dans une commande
INSERT command, mais aussi dans une sous-requête.
133
Requêtes
être vues comme des tables temporaires qui n'existent que pour une requête. Chaque ordre auxiliaire
dans une clause WITH peut être un SELECT, INSERT, UPDATE, ou DELETE; et la clause WITH
elle-même est attachée à un ordre primaire qui peut lui aussi être un SELECT, INSERT, UPDATE,
ou DELETE.
WITH ventes_regionales AS (
SELECT region, SUM(montant) AS ventes_totales
FROM commandes
GROUP BY region
), meilleures_regions AS (
SELECT region
FROM ventes_regionales
WHERE ventes_totales > (SELECT SUM(ventes_totales)/10 FROM
ventes_regionales)
)
SELECT region,
produit,
SUM(quantite) AS unites_produit,
SUM(montant) AS ventes_produit
FROM commandes
WHERE region IN (SELECT region FROM meilleures_regions)
GROUP BY region, produit;
qui affiche les totaux de ventes par produit seulement dans les régions ayant les meilleures
ventes. La clause WITH définit deux ordres auxiliaires appelés ventes_regionales
et meilleures_regions, où la sortie de ventes_regionales est utilisée dans
meilleures_regions et la sortie de meilleures_regions est utilisée dans la requête
SELECT primaire. Cet exemple aurait pu être écrit sans WITH, mais aurait alors nécessité deux niveaux
de sous-SELECT imbriqués. Les choses sont un peu plus faciles à suivre de cette façon.
Le modificateur optionnel RECURSIVE fait passer WITH du statut de simple aide syntaxique à celui
de quelque chose qu'il serait impossible d'accomplir avec du SQL standard. Grâce à RECURSIVE,
une requête WITH peut utiliser sa propre sortie. Un exemple très simple se trouve dans cette requête,
qui ajoute les nombres de 1 à 100 :
La forme générale d'une requête WITH est toujours un terme non récursif, puis UNION (ou UNION
ALL), puis un terme récursif. Seul le terme récursif peut contenir une référence à la sortie propre de
la requête. Une requête de ce genre est exécutée comme suit :
2. Tant que la table de travail n'est pas vide, répéter ces étapes :
134
Requêtes
b. Remplacer le contenu de la table de travail par celui de la table intermédiaire, puis supprimer
la table intermédiaire.
Note
Dans son appellation stricte, ce processus est une itération, pas une récursion, mais
RECURSIVE est la terminologie choisie par le comité de standardisation de SQL. Alors que
RECURSIVE autorise la spécification récursive des requêtes, en interne, ce type de requêtes
est évalué itérativement.
Dans l'exemple précédent, la table de travail a un seul enregistrement à chaque étape, et il prend les
valeurs de 1 à 100 en étapes successives. À la centième étape, il n'y a plus de sortie en raison de la
clause WHERE, ce qui met fin à la requête.
Les requêtes récursives sont utilisées généralement pour traiter des données hiérarchiques ou sous
forme d'arbres. Cette requête est un exemple utile pour trouver toutes les sous-parties directes et
indirectes d'un produit, si seule une table donne toutes les inclusions immédiates :
Quand on travaille avec des requêtes récursives, il est important d'être sûr que la partie récursive de
la requête finira par ne retourner aucun enregistrement, au risque sinon de voir la requête boucler
indéfiniment. Quelquefois, utiliser UNION à la place de UNION ALL peut résoudre le problème en
supprimant les enregistrements qui doublonnent ceux déjà retournés. Toutefois, souvent, un cycle
ne met pas en jeu des enregistrements de sortie qui sont totalement des doublons : il peut s'avérer
nécessaire de vérifier juste un ou quelques champs, afin de s'assurer que le même point a déjà été
atteint précédemment. La méthode standard pour gérer ces situations est de calculer un tableau de
valeurs déjà visitées. Par exemple, observez la requête suivante, qui parcourt une table graphe en
utilisant un champ lien :
135
Requêtes
Cette requête va boucler si la liaison lien contient des boucles. Parce que nous avons besoin de la
sortie « profondeur », simplement remplacer UNION ALL par UNION ne résoudra pas le problème.
À la place, nous avons besoin d'identifier si nous avons atteint un enregistrement que nous avons déjà
traité pendant notre parcours des liens. Nous ajoutons deux colonnes chemin et boucle à la requête :
En plus de prévenir les boucles, cette valeur de tableau est souvent pratique en elle-même pour
représenter le « chemin » pris pour atteindre chaque enregistrement.
De façon plus générale, quand plus d'un champ a besoin d'être vérifié pour identifier une boucle,
utilisez un tableau d'enregistrements. Par exemple, si nous avions besoin de comparer les champs f1
et f2 :
Astuce
Omettez la syntaxe ROW() dans le cas courant où un seul champ a besoin d'être testé pour
déterminer une boucle. Ceci permet, par l'utilisation d'un tableau simple plutôt que d'un tableau
de type composite, de gagner en efficacité.
Astuce
L'algorithme d'évaluation récursive de requête produit sa sortie en ordre de parcours en
largeur (algorithme breadth-first). Vous pouvez afficher les résultats en ordre de parcours
136
Requêtes
en profondeur (depth-first) en faisant sur la requête externe un ORDER BY sur une colonne
« chemin » construite de cette façon.
Si vous n'êtes pas certain qu'une requête puisse boucler, une astuce pratique pour la tester est d'utiliser
LIMIT dans la requête parente. Par exemple, cette requête bouclerait indéfiniment sans un LIMIT :
Ceci fonctionne parce que l'implémentation de PostgreSQL n'évalue que le nombre d'enregistrements
de la requête WITH récupérés par la requête parente. L'utilisation de cette astuce en production est
déconseillée parce que d'autres systèmes pourraient fonctionner différemment. Par ailleurs, cela ne
fonctionnera pas si vous demandez à la requête externe de trier les résultats de la requête récursive, ou
si vous les joignez à une autre table, parce dans ces cas, la requête extérieure essaiera habituellement
de récupérer toute la sortie de la requête WITH de toute façon.
Une propriété intéressante des requêtes WITH est qu'elles ne sont évaluées qu'une seule fois par
exécution de la requête parente ou des requêtes WITH sœurs. Par conséquent, les calculs coûteux
qui sont nécessaires à plusieurs endroits peuvent être placés dans une requête WITH pour éviter le
travail redondant. Un autre intérêt peut être d'éviter l'exécution multiple d'une fonction ayant des
effets de bord. Toutefois, le revers de la médaille est que l'optimiseur est moins capable d'extrapoler
les restrictions de la requête parente vers une requête WITH que vers une sous-requête classique.
La requête WITH sera généralement exécutée telle quelle, sans suppression d'enregistrements, que
la requête parente devra supprimer ensuite. (Mais, comme mentionné précédemment, l'évaluation
pourrait s'arrêter rapidement si la (les) référence(s) à la requête ne demande(nt) qu'un nombre limité
d'enregistrements).
Les exemples précédents ne montrent que des cas d'utilisation de WITH avec SELECT, mais on peut
les attacher de la même façon à un INSERT, UPDATE, ou DELETE. Dans chaque cas, le mécanisme
fournit en fait des tables temporaires auxquelles on peut faire référence dans la commande principale.
WITH lignes_deplacees AS (
DELETE FROM produits
WHERE
"date" >= '2010-10-01' AND
"date" < '2010-11-01'
RETURNING *
)
INSERT INTO log_produits
SELECT * FROM lignes_deplacees;
137
Requêtes
Un point important à noter de l'exemple précédent est que la clause WITH est attachée à l'INSERT,
pas au sous-SELECT de l' INSERT. C'est nécessaire parce que les ordres de modification de données
ne sont autorisés que dans les clauses WITH qui sont attachées à l'ordre de plus haut niveau. Toutefois,
les règles de visibilité normales de WITH s'appliquent, il est donc possible de faire référence à la sortie
du WITH dans le sous-SELECT.
Les ordres de modification de données dans WITH ont habituellement des clauses RETURNING (voir
Section 6.4), comme dans l'exemple précédent. C'est la sortie de la clause RETURNING, pas la table
cible de l'ordre de modification de données, qui forme la table temporaire à laquelle on pourra faire
référence dans le reste de la requête. Si un ordre de modification de données dans WITH n'a pas de
clause RETURNING, alors il ne produit pas de table temporaire et ne peut pas être utilisé dans le reste
de la requête. Un ordre de ce type sera toutefois exécuté. En voici un exemple (dénué d'intérêt):
WITH t AS (
DELETE FROM foo
)
DELETE FROM bar;
Cet exemple supprimerait tous les éléments des tables foo et bar. Le nombre d'enregistrements
retourné au client n'inclurait que les enregistrements supprimés de bar.
Les autoréférences récursives dans les ordres de modification de données ne sont pas autorisées. Dans
certains cas, il est possible de contourner cette limitation en faisant référence à la sortie d'un WITH,
par exemple:
Cette requête supprimerait toutes les pièces directes et indirectes d'un produit.
Les ordres de modification de données dans WITH sont exécutés exactement une fois, et toujours
jusqu'à la fin, indépendamment du fait que la requête primaire lise tout (ou même une partie) de leur
sortie. Notez que c'est différent de la règle pour SELECT dans WITH: comme précisé dans la section
précédente, l'exécution d'un SELECT n'est poursuivie que tant que la requête primaire consomme sa
sortie.
Les sous-requêtes du WITH sont toutes exécutées simultanément et simultanément avec la requête
principale. Par conséquent, quand vous utilisez un ordre de modification de données avec WITH, l'ordre
dans lequel les mises à jour sont effectuées n'est pas prévisible. Toutes les requêtes sont exécutées
dans le même instantané (voyez Chapitre 13), elles ne peuvent donc pas voir les effets des autres sur
les tables cibles. Ceci rend sans importance le problème de l'imprévisibilité de l'ordre des mises à jour,
et signifie que RETURNING est la seule façon de communiquer les modifications entre les différentes
sous-requêtes WITH et la requête principale. En voici un exemple:
WITH t AS (
UPDATE produits SET prix = prix * 1.05
RETURNING *
138
Requêtes
)
SELECT * FROM produits;
Le SELECT externe retournerait les prix originaux avant l'action de UPDATE, alors qu'avec :
WITH t AS (
UPDATE produits SET prix = prix * 1.05
RETURNING *
)
SELECT * FROM t;
Essayer de mettre à jour le même enregistrement deux fois dans le même ordre n'est pas supporté.
Seule une des deux modifications a lieu, mais il n'est pas aisé (et quelquefois impossible) de déterminer
laquelle. Ceci s'applique aussi pour la suppression d'un enregistrement qui a déjà été mis à jour dans le
même ordre : seule la mise à jour est effectuée. Par conséquent, vous devriez éviter en règle générale
de mettre à jour le même enregistrement deux fois en un seul ordre. En particulier, évitez d'écrire
des sous-requêtes qui modifieraient les mêmes enregistrements que la requête principale ou une autre
sous-requête. Les effets d'un ordre de ce type seraient imprévisibles.
À l'heure actuelle, les tables utilisées comme cibles d'un ordre modifiant les données dans un WITH
ne doivent avoir ni règle conditionnelle, ni règle ALSO, ni une règle INSTEAD qui génère plusieurs
ordres.
139
Chapitre 8. Types de données
PostgreSQL offre un large choix de types de données disponibles nativement. Les utilisateurs peuvent
ajouter de nouveaux types à PostgreSQL en utilisant la commande CREATE TYPE.
Le Tableau 8.1 montre tous les types de données généraux disponibles nativement. La plupart des types
de données alternatifs listés dans la colonne « Alias » sont les noms utilisés en interne par PostgreSQL
pour des raisons historiques. Il existe également d'autres types de données internes ou obsolètes, mais
ils ne sont pas listés ici.
140
Types de données
Compatibilité
Les types suivants sont conformes à la norme SQL: bigint, bit, bit varying,
boolean, char, character varying, character, varchar, date, double
precision, integer, interval, numeric, decimal, real, smallint, time
(avec et sans fuseau horaire), timestamp (avec et sans fuseau horaire), xml.
Chaque type de données a une représentation externe déterminée par ses fonctions d'entrée et de sortie.
De nombreux types de données internes ont un format externe évident. Cependant, certains types sont
spécifiques à PostgreSQL, comme les chemins géométriques, ou acceptent différents formats, comme
les types de données de date et d'heure. Certaines fonctions d'entrée et de sortie ne sont pas inversables :
le résultat de la fonction de sortie peut manquer de précision comparé à l'entrée initiale.
141
Types de données
La syntaxe des constantes pour les types numériques est décrite dans la Section 4.1.2. Les types
numériques ont un ensemble complet d'opérateurs arithmétiques et de fonctions. On peut se référer au
Chapitre 9 pour plus d'informations. Les sections suivantes décrivent ces types en détail.
Le type integer est le plus courant. Il offre un bon compromis entre capacité, espace utilisé et
performance. Le type smallint n'est utilisé que si l'économie d'espace disque est le premier critère
de choix. Le type bigint est conçu pour n'être utilisé que si l'échelle de valeurs du type integer
n'est pas suffisante.
SQL ne définit que les types de données integer (ou int), smallint et bigint. Les noms de
types int2, int4, et int8 sont des extensions, partagées par d'autres systèmes de bases de données
SQL.
Dans ce qui suit, on utilise les termes suivants. La précision d'un numeric est le nombre total de
chiffres significatifs dans le nombre complet, c'est-à-dire le nombre de chiffres de part et d'autre du
142
Types de données
séparateur. L'échelle d'un numeric est le nombre de chiffres décimaux de la partie fractionnaire, à
droite du séparateur de décimales. Donc, le nombre 23.5141 a une précision de 6 et une échelle de 4.
On peut considérer que les entiers ont une échelle de 0.
La précision maximale et l'échelle maximale d'une colonne numeric peuvent être toutes deux
réglées. Pour déclarer une colonne de type numérique, il faut utiliser la syntaxe :
NUMERIC(précision, échelle)
NUMERIC(précision)
NUMERIC
sans précision ni échelle crée une colonne dans laquelle on peut stocker des valeurs de n'importe
quelle précision ou échelle, dans la limite de la précision implantée. Une colonne de ce type n'impose
aucune précision à la valeur entrée, alors que les colonnes numeric ayant une échelle forcent les
valeurs entrées à cette échelle. (Le standard SQL demande une précision par défaut de 0, c'est-à-dire
de forcer la transformation en entier. Les auteurs trouvent cela inutile. Dans un souci de portabilité, il
est préférable de toujours indiquer explicitement la précision et l'échelle.)
Note
La précision maximale autorisée, si elle est explicitement spécifiée dans la déclaration du type,
est de 1000. NUMERIC sans précision est sujet aux limites décrites dans Tableau 8.2.
Si l'échelle d'une valeur à stocker est supérieure à celle de la colonne, le système arrondit la valeur au
nombre de décimales indiqué pour la colonne. Si le nombre de chiffres à gauche du point décimal est
supérieur à la différence entre la précision déclarée et l'échelle déclarée, une erreur est levée.
Les valeurs numériques sont stockées physiquement sans zéro avant ou après. Du coup, la précision
déclarée et l'échelle de la colonne sont des valeurs maximales, pas des allocations fixes (en ce sens, le
type numérique est plus proche de varchar(n) que de char(n)). Le besoin pour le stockage réel
est de deux octets pour chaque groupe de quatre chiffres décimaux, plus trois à huit octets d'en-tête.
En plus des valeurs numériques ordinaires, le type numeric autorise la valeur spéciale NaN qui
signifie « not-a-number » (NdT : pas un nombre). Toute opération sur NaN retourne NaN. Pour écrire
cette valeur comme une constante dans une requête SQL, elle doit être placée entre guillemets. Par
exemple, UPDATE table SET x = 'NaN'. En saisie, la chaîne NaN est reconnue, quelle que
soit la casse utilisée.
Note
Dans la plupart des implémentations du concept « not-a-number », NaN est considéré différent
de toute valeur numérique (ceci incluant NaN). Pour autoriser le tri des valeurs de type
numeric et les utiliser dans des index basés sur le tri, PostgreSQL traite les valeurs NaN
comme identiques entre elles, mais toutes supérieures aux valeurs non NaN.
Les types decimal et numeric sont équivalents. Les deux types sont dans le standard SQL.
Lors de l'arrondissement de valeurs, le type numeric arrondit en s'éloignant de zéro, alors que (sur
la plupart des machines) les types real et double precision arrondissent vers le nombre le
plus proche. Par exemple :
143
Types de données
SELECT x,
round(x::numeric) AS num_round,
round(x::double precision) AS dbl_round
FROM generate_series(-3.5, 3.5, 1) as x;
x | num_round | dbl_round
------+-----------+-----------
-3.5 | -4 | -4
-2.5 | -3 | -2
-1.5 | -2 | -2
-0.5 | -1 | -0
0.5 | 1 | 0
1.5 | 2 | 2
2.5 | 3 | 2
3.5 | 4 | 4
(8 rows)
Inexact signifie que certaines valeurs ne peuvent être converties exactement dans le format interne.
Elles sont, de ce fait, stockées sous une forme approchée. Ainsi, stocker puis réafficher ces valeurs
peut faire apparaître de légers écarts. Prendre en compte ces erreurs et la façon dont elles se propagent
au cours des calculs est le sujet d'une branche entière des mathématiques et de l'informatique, qui n'est
pas le sujet de ce document, à l'exception des points suivants :
• pour un stockage et des calculs exacts, comme pour les valeurs monétaires, le type numeric doit
être privilégié ;
• pour des calculs compliqués avec ces types pour quoi que ce soit d'important, et particulièrement
pour le comportement aux limites (infini, zéro), l'implantation spécifique à la plate-forme doit être
étudiée avec soin ;
• tester l'égalité de deux valeurs à virgule flottante peut ne pas donner le résultat attendu.
Sur la plupart des plates-formes, le type real a une étendue d'au moins 1E-37 à 1E37 avec une
précision d'au moins six chiffres décimaux. Le type double precision a généralement une
étendue de 1E-307 à 1E+308 avec une précision d'au moins quinze chiffres. Les valeurs trop grandes
ou trop petites produisent une erreur. Un arrondi peut avoir lieu si la précision d'un nombre en entrée
est trop grande. Les nombres trop proches de zéro qui ne peuvent être représentés autrement que par
zéro produisent une erreur (underflow).
Note
Le paramètre extra_float_digits contrôle le nombre de chiffres significatifs supplémentaires à
inclure quand une valeur à virgule flottante est convertie en texte. Avec la valeur par défaut
de 0, la sortie est la même sur chaque plate-forme supportée par PostgreSQL. L'augmenter va
produire une sortie qui représentera de façon plus précise la valeur stockée, mais cela pourrait
la rendre non portable.
144
Types de données
Note
Le paramètre extra_float_digits contrôle le nombre de chiffres significatifs inclus lorsqu'une
valeur à virgule flottante est convertie en texte. Avec la valeur par défaut de 0, la sortie est la
même sur chaque plate-forme supportée par PostgreSQL. L'augmenter va produire une sortie
représentant plus précisément la valeur stockée, mais il est possible que la sortie soit différente
suivant les plates-formes.
En plus des valeurs numériques ordinaires, les types à virgule flottante ont plusieurs valeurs spéciales :
Infinity
-Infinity
NaN
Elles représentent les valeurs spéciales de l'IEEE 754, respectivement « infinity » (NdT : infini),
« negative infinity » (NdT : infini négatif) et « not-a-number » (NdT : pas un nombre) (sur une
machine dont l'arithmétique à virgule flottante ne suit pas l'IEEE 754, ces valeurs ne fonctionnent
probablement pas comme espéré). Lorsqu'elles sont saisies en tant que constantes dans une commande
SQL, ces valeurs doivent être placées entre guillemets. Par exemple, UPDATE table SET x =
'-Infinity'. En entrée, ces valeurs sont reconnues, quelle que soit la casse utilisée.
Note
IEEE754 spécifie que NaN ne devrait pas être considéré égale à toute autre valeur en virgule
flottante (ceci incluant NaN). Pour permettre le tri des valeurs en virgule flottante et leur
utilisation dans des index basés sur des arbres, PostgreSQL traite les valeurs NaN comme
identiques entre elles, mais supérieures à toute valeur différente de NaN.
PostgreSQL autorise aussi la notation float du standard SQL, ainsi que float(p) pour
indiquer des types numériques inexacts. p indique la précision minimale acceptable en chiffres
binaires. PostgreSQL accepte de float(1) à float(24), qu'il transforme en type real, et de
float(25) à float(53), qu'il transforme en type double precision. Toute valeur de p
hors de la zone des valeurs possibles produit une erreur. float sans précision est compris comme
double precision.
Note
L'affirmation que les real et les double precision ont exactement 24 et 53 bits dans
la mantisse est correcte pour les implémentations des nombres à virgule flottante respectant le
standard IEEE. Sur les plates-formes non-IEEE, c'est peut-être un peu sous-estimé, mais, pour
plus de simplicité, la gamme de valeurs pour p est utilisée sur toutes les plates-formes.
Note
Cette section décrit une façon spécifique à PostgreSQL de créer une colonne autoincrémentée.
Une autre façon revient à utiliser les colonnes d'identité, décrite sur CREATE TABLE.
145
Types de données
Les types de données smallserial, serial et bigserial ne sont pas de vrais types, mais
plutôt un raccourci de notation pour créer des colonnes d'identifiants uniques (similaires à la propriété
AUTO_INCREMENT utilisée par d'autres SGBD). Dans la version actuelle, indiquer :
Ainsi a été créée une colonne d'entiers dont la valeur par défaut est assignée par un générateur de
séquence. Une contrainte NOT NULL est ajoutée pour s'assurer qu'une valeur NULL ne puisse pas
être insérée. (Dans la plupart des cas, une contrainte UNIQUE ou PRIMARY KEY peut être ajoutée
pour interdire que des doublons soient créés par accident, mais ce n'est pas automatique.) Enfin, la
séquence est marquée « owned by » (possédée par) la colonne pour qu'elle soit supprimée si la colonne
ou la table est supprimée.
Note
Comme smallserial, serial et bigserial sont implémentés en utilisant des
séquences, il peut y avoir des trous dans la séquence de valeurs qui apparait dans la colonne,
même si aucune ligne n'est jamais supprimée. Une valeur allouée à partir de la séquence
est toujours utilisée même si la ligne contenant cette valeur n'est pas insérée avec succès
dans la colonne de la table. Cela peut survenir si la transaction d'insertion est annulée. Voir
nextval() dans Section 9.16 pour plus de détails.
Pour insérer la valeur suivante de la séquence dans la colonne serial, il faut préciser que la valeur
par défaut de la colonne doit être utilisée. Cela peut se faire de deux façons : soit en excluant cette
colonne de la liste des colonnes de la commande INSERT, soit en utilisant le mot-clé DEFAULT.
Les types serial et serial4 sont identiques : ils créent tous les deux des colonnes integer. Les
types bigserial et serial8 fonctionnent de la même façon, mais créent des colonnes bigint.
bigserial doit être utilisé si plus de 231 identifiants sont prévus sur la durée de vie de la table.
Les noms de type smallserial et serial2 fonctionnent de la même façon, sauf qu'ils créent une
colonne de type smallint.
La séquence créée pour une colonne serial est automatiquement supprimée quand la colonne
correspondante est supprimée. La séquence peut être détruite sans supprimer la colonne, mais la valeur
par défaut de la colonne est alors également supprimée.
146
Types de données
Comme la sortie de type de données est sensible à la locale, la recharge de données de type money
dans une base de données pourrait ne pas fonctionner si la base a une configuration différente pour
lc_monetary. Pour éviter les problèmes, avant de restaurer une sauvegarde dans une nouvelle base
de données, assurez-vous que lc_monetary a la même valeur ou une valeur équivalente à celle de
la base qui a été sauvegardée.
Les valeurs de types numeric, int et bigint peuvent être converties en type money. La
conversion à partir du type real et double precision peut être faite en convertissant tout
d'abord vers le type numeric. Par exemple :
SELECT '12.34'::float8::numeric::money;
Néanmoins, ce n'est pas recommandé. Les nombres à virgules flottantes ne doivent pas être utilisés
pour gérer de la monnaie à cause des erreurs potentielles d'arrondis.
Une valeur money peut être convertie en numeric sans perdre de précision. Les conversions vers
d'autres types peuvent potentiellement perdre en précision et doivent aussi se faire en deux étapes :
SELECT '52093.89'::money::numeric::float8;
La division d'une valeur de type money par une valeur de type entier est réalisée en tronquant la partie
décimale. Pour obtenir un résultat arrondi, il faut diviser par une valeur en virgule flottante ou convertir
la valeur de type money en numeric avant de réaliser la division. Il faudra ensuite convertir vers le
type money. (Cette dernière méthode est préférable pour éviter de perdre en précision.) Quand une
valeur de type money est divisée par une autre valeur de type money, le résultat est du type double
precision (c'est-à-dire un nombre pur, pas une monnaie). Les unités de monnaie s'annulent dans
la division.
147
Types de données
De plus, PostgreSQL propose aussi le type text, qui permet de stocker des chaînes de n'importe
quelle taille. Bien que le type text ne soit pas dans le standard SQL, plusieurs autres systèmes de
gestion de bases de données SQL le proposent également.
Les valeurs de type character sont complétées physiquement à l'aide d'espaces pour atteindre la
longueur n indiquée. Ces valeurs sont également stockées et affichées de cette façon. Cependant, les
espaces de remplissage sont traités comme sémantiquement non significatifs et sont donc ignorés lors
de la comparaison de deux valeurs de type character. Dans les collationnements où les espaces de
remplissage sont significatifs, ce comportement peut produire des résultats inattendus, par exemple
SELECT 'a '::CHAR(2) collate "C" < E'a\n'::CHAR(2) retourne vrai, même si
la locale C considérerait qu'un espace est plus grand qu'un retour chariot. Les espaces de remplissage
sont supprimés lors de la conversion d'une valeur character vers l'un des autres types chaîne. Ces
espaces ont une signification sémantique pour les valeurs de type character varying et text,
et lors de l'utilisation de la correspondance de motifs, par exemple avec LIKE ou avec les expressions
rationnelles.
Les caractères pouvant être enregistrés dans chacun de ces types de données sont déterminés par le jeu
de caractères de la base de données, qui a été sélectionné à la création de la base. Quelque soit le jeu
de caractères spécifique, le caractère de code zéro (quelque fois appelé NUL) ne peut être enregistré.
Pour plus d'informations, voir Section 23.3.
L'espace nécessaire pour une chaîne de caractères courte (jusqu'à 126 octets) est de un octet, plus
la taille de la chaîne qui inclut le remplissage avec des espaces dans le cas du type character.
Les chaînes plus longues ont quatre octets d'en-tête au lieu d'un seul. Les chaînes longues sont
automatiquement compressées par le système, donc le besoin pourrait être moindre. Les chaînes
vraiment très longues sont stockées dans des tables supplémentaires, pour qu'elles n'empêchent pas
d'accéder rapidement à des valeurs plus courtes. Dans tous les cas, la taille maximale possible pour une
chaîne de caractères est de l'ordre de 1 Go. (La taille maximale pour n dans la déclaration de type est
inférieure. Il ne sert à rien de modifier ce comportement, car avec les encodages sur plusieurs octets,
les nombres de caractères et d'octets peuvent être très différents. Pour stocker de longues chaînes sans
limite supérieure précise, il est préférable d'utiliser les types text et character varying sans
taille, plutôt que d'indiquer une limite de taille arbitraire.)
Astuce
Il n'y a aucune différence de performance parmi ces trois types, si ce n'est la place disque
supplémentaire requise pour le type à remplissage et quelques cycles CPU supplémentaires
pour vérifier la longueur lors du stockage dans une colonne contrainte par la taille. Bien que
character(n) ait des avantages en termes de performance sur certains autres systèmes
de bases de données, il ne dispose pas de ce type d'avantages dans PostgreSQL ; en fait,
character(n) est habituellement le plus lent des trois à cause des coûts de stockage
supplémentaires. Dans la plupart des situations, les types text et character varying
peuvent être utilisés à leur place.
On peut se référer à la Section 4.1.2.1 pour obtenir plus d'informations sur la syntaxe des libellés de
chaînes, et le Chapitre 9 pour des informations complémentaires sur les opérateurs et les fonctions.
148
Types de données
a | char_length
------+-------------
ok | 2
b | char_length
-------+-------------
ok | 2
bien | 5
trop | 5
Il y a deux autres types caractère de taille fixe dans PostgreSQL. Ils sont décrits dans le Tableau 8.5.
Le type name existe uniquement pour le stockage des identifiants dans les catalogues système et n'est
pas destiné à être utilisé par les utilisateurs normaux. Sa taille est actuellement définie à 64 octets (63
utilisables plus le terminateur), mais doit être référencée en utilisant la constante NAMEDATALEN en
code source C. La taille est définie à la compilation (et est donc ajustable pour des besoins particuliers).
La taille maximale par défaut peut éventuellement être modifiée dans une prochaine version. Le type
"char" (attention aux guillemets) est différent de char(1), car il n'utilise qu'un seul octet de
stockage. Il est utilisé dans les catalogues système comme un type d'énumération simpliste.
Une chaîne binaire est une séquence d'octets. Les chaînes binaires se distinguent des chaînes de
caractères de deux façons : tout d'abord, les chaînes binaires permettent de stocker des octets de
149
Types de données
valeurs zéro ainsi que les autres caractères « non imprimables » (habituellement, les octets en dehors
de l'intervalle décimal de 32 à 126). Les chaînes de caractères interdisent les octets de valeur zéro et
interdisent aussi toute valeur d'octet ou séquence d'octets invalide selon l'encodage sélectionné pour
la base de données. Ensuite, les opérations sur les chaînes binaires traitent réellement les octets alors
que le traitement de chaînes de caractères dépend de la configuration de la locale. En résumé, les
chaînes binaires sont appropriées pour le stockage de données que le développeur considère comme
des « octets bruts », alors que les chaînes de caractères sont appropriées pour le stockage de texte.
Le type bytea accepte deux formats en entrée et en sortie le format « hex » et le format historique de
PostgreSQL, « escape ». Les deux sont acceptés en entrée. Le format de sortie dépend du paramètre
de configuration bytea_output ; ce dernier sélectionne par défaut le format hexadécimal. (Notez que le
format hexadécimal est disponible depuis PostgreSQL 9.0 ; les versions antérieures et certains outils
ne le comprennent pas.)
Le standard SQL définit un type de chaîne binaire différent, appelé BLOB ou BINARY LARGE
OBJECT. Le format en entrée est différent du bytea, mais les fonctions et opérateurs fournis sont
pratiquement les mêmes.
Exemple :
SELECT '\xDEADBEEF'::bytea;
bytea
------------
\xdeadbeef
Lors de la saisie de valeurs bytea dans le format d'échappement, les octets de certaines valeurs
doivent être échappés alors que les autres valeurs d'octets peuvent être échappés. En général, pour
échapper un octet, il suffit de le convertir dans sa valeur octale composée de trois chiffres et de la faire
précéder d'un antislash (ou de deux antislashs s'il faut utiliser la syntaxe d'échappement de chaînes).
L'antislash lui-même (octet en valeur décimal, 92) peut alternativement être représenté par un double
antislash. Le Tableau 8.7 affiche les caractères qui doivent être échappés et donne les séquences
d'échappement possibles.
150
Types de données
La nécessité d'échapper les octets non affichables dépend des paramétrages de la locale. Il est parfois
possible de s'en sortir sans échappement.
La raison pour laquelle les guillemets simples doivent être doublés, comme indiqué dans Tableau 8.7,
est que cela est vrai pour toute chaîne litérale dans une commande SQL. L'analyseur générique des
chaînes litérales utilise les guillemets simples externes et réduit toute paire de guillemets simples en un
seul caractère. La fonction en entrée du type bytea ne voit qu'un guillemet simple, qu'il traire comme
un caractère standard. Néanmoins, la fonction en entrée du type bytea traite les antislashs de façon
spéciale et les autres comportements montrés dans Tableau 8.7 sont implémentés par cette fonction.
Dans certains contextes, les antislashs doivent être doublés par rapport à ce qui est montré ci-dessus
car l'analyseur générique de chaîne litérale réduira aussi les paires d'antislashs en un seul caractère de
données ; voir Section 4.1.2.1.
Les octets Bytea sont affichés par défaut dans le format hex. Si vous modifiez bytea_output à
escape, les octets « non affichables » sont convertis dans leur équivalent sous la forme d'une valeur
octale à trois chiffres et précédé d'un antislash. La plupart des octets « affichables » sont affichés dans
leur représentation standard pour le jeu de caractères du client :
L'octet de valeur décimale 92 (antislash) est doublé en sortie. Les détails sont dans le Tableau 8.8.
151
Types de données
Note
Le standard SQL impose que timestamp soit un équivalent de timestamp without
time zone. timestamptz est accepté comme abréviation pour timestamp with
time zone ; c'est une extension PostgreSQL.
time, timestamp, et interval acceptent une précision optionnelle p, qui indique le nombre de
décimales pour les secondes. Il n'y a pas, par défaut, de limite explicite à cette précision. Les valeurs
acceptées pour p s'étendent de 0 à 6.
Le type interval a une option supplémentaire, qui permet de restreindre le jeu de champs stockés
en écrivant une de ces expressions :
YEAR
MONTH
DAY
152
Types de données
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND
Notez que si champs et p sont tous les deux indiqués, champs doit inclure SECOND, puisque la
précision s'applique uniquement aux secondes.
Le type time with time zone est défini dans le standard SQL, mais sa définition lui prête des
propriétés qui font douter de son utilité. Dans la plupart des cas, une combinaison de date, time,
timestamp without time zone et timestamp with time zone devrait permettre de
résoudre toutes les fonctionnalités de date et heure nécessaires à une application.
Les types abstime et reltime sont des types de précision moindre, utilisés en interne. Il n'est
pas recommandé de les utiliser dans de nouvelles applications, car ils pourraient disparaître dans une
prochaine version.
PostgreSQL est plus flexible que la norme SQL ne l'exige pour la manipulation des dates et des heures.
Voir l'Annexe B pour connaître les règles exactes de reconnaissance des dates et heures et les formats
reconnus pour les champs texte comme les mois, les jours de la semaine et les fuseaux horaires.
Tout libellé de date ou heure saisi doit être placé entre apostrophes, comme les chaînes de caractères.
La Section 4.1.2.7 peut être consultée pour plus d'information. SQL requiert la syntaxe suivante :
8.5.1.1. Dates
Le Tableau 8.10 regroupe les formats de date possibles pour la saisie de valeurs de type date.
153
Types de données
Exemple Description
01/02/03 2 janvier 2003 en mode MDY ; 1er février 2003 en mode DMY ; 3 février 2001 en
mode YMD
1999-Jan-08 8 janvier dans tous les modes
Jan-08-1999 8 janvier dans tous les modes
08-Jan-1999 8 janvier dans tous les modes
99-Jan-08 8 janvier en mode YMD, erreur sinon
08-Jan-99 8 janvier, sauf en mode YMD : erreur
Jan-08-99 8 janvier, sauf en mode YMD : erreur
19990108 ISO-8601 ; 8 janvier 1999 dans tous les modes
990108 ISO-8601 ; 8 janvier 1999 dans tous les modes
1999.008 Année et jour de l'année
J2451187 Date du calendrier Julien
January 8, 99 Année 99 avant Jésus Christ
BC
8.5.1.2. Heures
Les types « heure du jour » sont time [ (p) ] without time zone et time [ (p) ]
with time zone. time est équivalent à time without time zone.
Les saisies valides pour ces types sont constituées d'une heure suivie éventuellement d'un fuseau
horaire (voir le Tableau 8.11 et le Tableau 8.12). Si un fuseau est précisé pour le type time without
time zone, il est ignoré sans message d'erreur. Si une date est indiquée, elle est ignorée, sauf si
un fuseau horaire impliquant une règle de changement d'heure (heure d'été/heure d'hiver) est précisé,
America/New_York par exemple. Dans ce cas, la date est nécessaire pour pouvoir déterminer la
règle de calcul de l'heure qui s'applique. Le décalage approprié du fuseau horaire est enregistré dans la
valeur de time with time zone et est affiché de la façon dont il est stocké ; il n'est pas converti
vers le fuseau horaire actif.
154
Types de données
Exemple Description
America/
New_York
La Section 8.5.3 apporte des précisions quant à la façon d'indiquer les fuseaux horaires.
8.5.1.3. Horodatage
Les saisies valides sont constituées de la concaténation d'une date et d'une heure, éventuellement suivie
d'un fuseau horaire et d'un qualificatif AD (après Jésus Christ) ou BC (avant Jésus Christ). (AD/BC peut
aussi apparaître avant le fuseau horaire, mais ce n'est pas l'ordre préféré.) Ainsi :
1999-01-08 04:05:06
et :
sont des valeurs valides, qui suivent le standard ISO 8601. Le format très courant :
Le standard SQL différencie les libellés timestamp without time zone et timestamp
with time zone par la présence d'un symbole « + » ou d'un « - » et le décalage du fuseau horaire
après l'indication du temps. De ce fait, d'après le standard,
est du type timestamp with time zone. PostgreSQL n'examine jamais le contenu d'un
libellé avant de déterminer son type. Du coup, il traite les deux ci-dessus comme des valeurs de type
timestamp without time zone. Pour s'assurer qu'un littéral est traité comme une valeur de
type timestamp with time zone, il faut préciser explicitement le bon type :
155
Types de données
Dans un libellé de type timestamp without time zone, PostgreSQL ignore silencieusement
toute indication de fuseau horaire. C'est-à-dire que la valeur résultante est dérivée des champs date/
heure de la valeur saisie et n'est pas corrigée par le fuseau horaire.
Pour timestamp with time zone, la valeur stockée en interne est toujours en UTC (Universal
Coordinated Time ou Temps Universel Coordonné), aussi connu sous le nom de GMT (Greenwich
Mean Time). Les valeurs saisies avec un fuseau horaire explicite sont converties en UTC à l'aide du
décalage approprié. Si aucun fuseau horaire n'est précisé, alors le système considère que la date est
dans le fuseau horaire indiqué par le paramètre système TimeZone, et la convertit en UTC en utilisant
le décalage de la zone timezone.
Quand une valeur timestamp with time zone est affichée, elle est toujours convertie de
l'UTC vers le fuseau horaire courant (variable timezone), et affichée comme une heure locale. Pour
voir l'heure dans un autre fuseau horaire, il faut, soit changer la valeur de timezone, soit utiliser la
construction AT TIME ZONE (voir la Section 9.9.3).
Les conversions entre timestamp without time zone et timestamp with time zone
considèrent normalement que la valeur timestamp without time zone utilise le fuseau
horaire timezone. Un fuseau différent peut être choisi en utilisant AT TIME ZONE.
Les fonctions suivantes, compatibles avec le standard SQL, peuvent aussi être utilisées pour obtenir
l'heure courante pour le type de données correspondant : CURRENT_DATE, CURRENT_TIME,
CURRENT_TIMESTAMP, LOCALTIME, LOCALTIMESTAMP. (Voir la Section 9.9.4). Ce sont là des
fonctions SQL qui ne sont pas reconnues comme chaînes de saisie de données.
Attention
Bien qu'il n'y ait pas de problèmes à utiliser les chaînes now, today, tomorrow et
yesterday dans des commandes SQL interactives, elles peuvent avoir un comportement
surprenant quand la commande est sauvegardée pour une exécution ultérieure, par exemple
156
Types de données
dans des requêtes préparées, des vues ou des fonctions. La chaîne peut être convertie en une
valeur spécifique qui continue à être utilisée bien après qu'elle ne soit obsolète. Dans de tels
contextes, utilisez plutôt une des fonctions SQL. Par exemple, CURRENT_DATE + 1 est
plus sûr que 'tomorrow'::date.
Note
ISO 8601 spécifie l'utilisation d'une lettre T en majuscule pour séparer la date et l'heure.
PostgreSQL accepte ce format en entrée. En sortie, il utilise un espace plutôt qu'un T, comme
indiqué ci-dessus. C'est à la fois plus lisible et cohérent avec la RFC 3339 ainsi qu'avec d'autres
systèmes de bases de données.
Dans les styles SQL et POSTGRES, les jours apparaissent avant le mois si l'ordre des champs DMY
a été précisé, sinon les mois apparaissent avant les jours (voir la Section 8.5.1 pour savoir comment
ce paramètre affecte l'interprétation des valeurs en entrée). Le Tableau 8.15 présente des exemples.
Dans le style ISO, le fuseau horaire est toujours affiché sous la forme d'un décalage numérique signé
de UTC, avec un signe positif utilisé pour les zones à l'est de Greenwich. Le décalage sera affiché
sous la forme hh (heures seulement) s'il s'agit d'un nombre intégral d'heures, ou sous la forme hh:mm
157
Types de données
s'il s'agit d'un nombre intégral de minutes, et enfin sous la forme hh:mm:ss. (Le troisième cas n'est
pas possible pour tout standard moderne de fuseau horaire, mais il peut apparaître en travaillant sur
des jours antérieurs à l'adoption des fuseaux horaires standardisés.) Pour les autres styles de dates, le
fuseau horaire est affiché comme une abréviation alphabétique si l'une d'entre elles est d'utilisation
commune dans le fuseau actuel. Sinon, il apparaît comme un décalage numérique signé dans le format
basique ISO 8601 (hh ou hhmm).
Le style de date/heure peut être sélectionné à l'aide de la commande SET datestyle, du paramètre
datestyle du fichier de configuration postgresql.conf ou par la variable d'environnement
PGDATESTYLE sur le serveur ou le client.
La fonction de formatage to_char (voir Section 9.8) permet de formater les affichages de date/heure
de manière plus flexible.
PostgreSQL se veut compatible avec les définitions standard SQL pour un usage typique. Néanmoins,
le standard SQL possède un mélange étrange de types de date/heure et de possibilités. Deux problèmes
évidents sont :
• bien que le type date ne puisse pas se voir associer un fuseau horaire, le type heure peut en avoir
un. Les fuseaux horaires, dans le monde réel, ne peuvent avoir de sens qu'associés à une date et à
une heure, vu que l'écart peut varier avec l'heure d'été ;
• le fuseau horaire par défaut est précisé comme un écart numérique constant avec l'UTC. Il n'est, de
ce fait, pas possible de s'adapter à l'heure d'été ou d'hiver lorsque l'on fait des calculs arithmétiques
qui passent les limites de l'heure d'été et de l'heure d'hiver.
Pour éviter ces difficultés, il est recommandé d'utiliser des types date/heure qui contiennent à la fois
une date et une heure lorsque les fuseaux horaires sont utilisés. Il est également préférable de ne pas
utiliser le type time with time zone. (Ce type est néanmoins proposé par PostgreSQL pour les
applications existantes et pour assurer la compatibilité avec le standard SQL.) PostgreSQL utilise le
fuseau horaire local pour tous les types qui ne contiennent qu'une date ou une heure.
Toutes les dates et heures liées à un fuseau horaire sont stockées en interne en UTC. Elles sont
converties en heure locale dans le fuseau indiqué par le paramètre de configuration TimeZone avant
d'être affichées sur le client.
• un nom complet de fuseau horaire, par exemple America/New_York. Les noms reconnus de
fuseau horaire sont listés dans la vue pg_timezone_names (voir Section 52.90). PostgreSQL
utilise les données IANA pour cela, les mêmes noms sont donc reconnus par de nombreux autres
logiciels ;
• une abréviation de fuseau horaire, par exemple PST. Une telle indication ne définit qu'un décalage
particulier à partir d'UTC, en contraste avec les noms complets de fuseau horaire qui peuvent aussi
impliquer un ensemble de dates pour le changement d'heure. Les abréviations reconnues sont listées
dans la vue pg_timezone_abbrevs (voir Section 52.89). Les paramètres de configuration
TimeZone et log_timezone ne peuvent pas être configurés à l'aide d'une abréviation de fuseau
horaire, mais ces abréviations peuvent être utilisées dans les saisies de date/heure et avec l'opérateur
AT TIME ZONE ;
158
Types de données
• En plus des noms et abréviations des fuseaux horaires, PostgreSQL accepte les spécifications de
fuseau horaire du style POSIX, comme décrit dans Section B.5. Cette option n'est habituellement
pas préférable à utiliser un nom de fuseau horaire, mais cela pourrait se révéler nécessaire si aucune
entrée adéquate de fuseau horaire n'est disponible dans la base IANA.
Les abréviations représentent un décalage spécifique depuis UTC, alors qu'un grand nombre des noms
complets implique une règle de changement d'heure, et donc potentiellement deux décalages UTC.
Par exemple, 2014-06-04 12:00 America/New_York représente minuit à New York, ce
qui, pour cette date particulière, sera le fuseau Eastern Daylight Time (UTC-4). Donc 2014-06-04
12:00 EDT stipule ce moment précis. Mais 2014-06-04 12:00 EST représente minuit pour le
fuseau Eastern Standard Time (UTC-5), quel que soit le changement d'heure en effet à cette date.
Pour compliquer encore plus, certaines juridictions ont utilisé les mêmes abréviations de fuseau
horaire pour signifier des décalages UTC différents. Par exemple, Moscow MSK correspondait à UTC
+3 certaines années et UTC+4 à d'autres. PostgreSQL interprète ces abréviations suivant ce à quoi
elles correspondent (ou ont correspondu récemment) pour la date indiquée. Mais, comme le montre
l'exemple EST ci-dessus, ce n'est pas nécessairement la même chose que l'heure civile locale à ce
moment.
Dans tous les cas, les noms et les abréviations des fuseaux horaires sont insensibles à la casse. (C'est
un changement par rapport aux versions de PostgreSQL antérieures à la 8.2 qui étaient sensibles à la
casse dans certains cas et pas dans d'autres.)
Ni les noms ni les abréviations des fuseaux horaires ne sont codés en dur dans le serveur ; ils sont
obtenus à partir des fichiers de configuration stockés sous .../share/timezone/ et .../
share/timezonesets/ du répertoire d'installation (voir Section B.4).
Le paramètre de configuration TimeZone peut être fixé dans le fichier postgresql.conf ou par
tout autre moyen standard décrit dans le Chapitre 19. Il existe aussi quelques manières spéciales de
le configurer :
• la commande SQL SET TIME ZONE configure le fuseau horaire pour une session. C'est une autre
façon d'indiquer SET TIMEZONE TO avec une syntaxe plus compatible avec les spécifications
SQL ;
• la variable d'environnement PGTZ est utilisée par les applications clientes fondées sur libpq pour
envoyer une commande SET TIME ZONE au serveur lors de la connexion.
[@] quantité
unité [quantité
unité...]
[direction]
Les quantités de jours, heures, minutes et secondes peuvent être spécifiées sans notations explicites
d'unités. Par exemple '1 12:59:10' est comprise comme '1 day 12 hours 59 min
159
Types de données
10 sec'. Par ailleurs, une combinaison d'années et de mois peut être spécifiée avec un tiret ; par
exemple, '200-10' est compris comme '200 years 10 months'. (Ces formes raccourcies
sont en fait les seules autorisées par le standard SQL, et sont utilisées pour la sortie quand la variable
IntervalStyle est positionnée à sql_standard.)
Les valeurs d'intervalles peuvent aussi être écrites en tant qu'intervalles de temps ISO 8601, en utilisant
soit le « format avec désignateurs » de la section 4.4.3.2 ou le « format alternatif » de la section 4.4.3.3.
Le format avec désignateurs ressemble à ceci :
La chaîne doit commencer avec un P, et peut inclure un T qui introduit les unités de ce type. Les
abréviations d'unité disponibles sont données dans Tableau 8.16. Des unités peuvent être omises,
et peuvent être spécifiées dans n'importe quel ordre, mais les unités inférieures à un jour doivent
apparaître après T. En particulier, la signification de M dépend de son emplacement, c'est-à-dire avant
ou après T.
P [ années-mois-jours ] [ T heures:minutes:secondes ]
la chaîne doit commencer par P, et un T sépare la zone de date et la zone de temps de l'intervalle. Les
valeurs sont données comme des nombres, de façon similaire aux dates ISO 8601.
Lors de l'écriture d'une constante d'intervalle avec une spécification de champs, ou lors de
l'assignation d'une chaîne à une colonne d'intervalle qui a été définie avec une spécification de
champs, l'interprétation de quantité sans unité dépend des champs. Par exemple, INTERVAL '1'
YEAR est interprété comme 1 an, alors que INTERVAL '1' est interprété comme 1 seconde. De
plus, les valeurs du champ « à droite » du champ le moins significatif autorisé par la spécification de
champs sont annulées de façon silencieuse. Par exemple, écrire INTERVAL '1 day 2:03:04'
HOUR TO MINUTE implique la suppression du champ des secondes, mais pas celui des journées.
D'après le standard SQL, toutes les valeurs de tous les champs d'un intervalle doivent avoir le
même signe, ce qui entraîne qu'un signe négatif initial s'applique à tous les champs ; par exemple,
le signe négatif dans l'expression d'intervalle '-1 2:03:04' s'applique à la fois aux jours et
aux heures/minutes/secondes. PostgreSQL permet que les champs aient des signes différents, et
traditionnellement traite chaque champ de la représentation textuelle comme indépendamment signé,
ce qui fait que la partie heure/minute/seconde est considérée comme positive dans l'exemple. Si
IntervalStyle est positionné à sql_standard, alors un signe initial est considéré comme
s'appliquant à tous les champs (mais seulement si aucun autre signe n'apparaît). Sinon, l'interprétation
traditionnelle de PostgreSQL est utilisée. Pour éviter les ambiguïtés, il est recommandé d'attacher un
signe explicite à chaque partie, si au moins un champ est négatif.
160
Types de données
Les valeurs des champs peuvent avoir des parties fractionnelles : par exemple, '1.5 weeks' ou
'01:02:03.45'. Néanmoins, comme l'intervalle stocke en interne seulement les trois unités sous
forme d'entier (mois, jours, microsecondes), les unités fractionelles doivent être divisées en plus petites
unités. Les parties fractionnelles des unités supérieures aux mois est tronquées en un nombre entier
de mois, par exemple '1.5 years' devient '1 year 6 mons'. Les parties fractionnelles des
semaines et jours sont calculées comme un nombre entier de jours et de microsecondes, en supposant
30 jours par mois et 24 heures par jour, par exemple '1.75 months' devient 1 mon 22 days
12:00:00. Seules les secondes seront affichées en fractionné en sortie.
En interne, les valeurs interval sont enregistrées comme des mois, jours et microsecondes. C'est
fait ainsi parce que le nombre de jours dans un mois varie, et un jour peut avoir 23 ou 25 heures s'il
y a eu un changement d'heure. Les champs mois et jours sont des entiers, alors que le champ des
microsecondes peut contenir des secondes fractionnelles. Comme les intervalles sont habituellement
créés à partir de chaînes constantes ou de soustractions de timestamp, cette méthode de stockage
fonctionne bien dans la plupart des cas, mais peut être la cause de résultats inattendus :
Les fonctions justify_days et justify_hours sont disponibles pour ajuster les jours et heures
qui dépassent l'étendue normale.
Le style sql_standard produit une sortie qui se conforme à la spécification du standard SQL pour
les chaînes littérales d'intervalle, si la valeur de l'intervalle reste dans les restrictions du standard (soit
année-mois seul, ou jour-temps seul, et sans mélanger les composants positifs et négatifs). Sinon, la
161
Types de données
sortie ressemble au standard littéral année-mois suivi par une chaîne jour-temps littérale, avec des
signes explicites ajoutés pour désambiguer les intervalles dont les signes seraient mélangés.
La sortie du style postgres correspond à la sortie des versions de PostgreSQL précédant la 8.4, si
le paramètre datestyle était positionné à ISO.
La sortie du style iso_8601 correspond au « format avec designateurs » décrit dans la section 4.4.3.2
du standard ISO 8601.
Les constantes booléennes peuvent être représentées dans les requêtes SQL avec les mots clés SQL
TRUE, FALSE et NULL.
La fonction en entrée pour le type boolean accepte ces représentations, sous forme de chaîne de
caractères, pour l'état « true » :
true
yes
on
1
false
no
off
0
Les préfixes uniques de ces chaînes sont aussi acceptés, par exemple t ou n. Les espaces avant ou
après, ainsi que la casse, sont ignorés.
La fonction en sortie pour le boolean renvoie toujours soit t soit f, comme indiqué dans
Exemple 8.2.
162
Types de données
Les mots clés TRUE et FALSE sont la méthode préférée (compatible SQL) pour l'écriture des
constantes booléennes dans les requêtes SQL. Cependant, vous pouvez aussi utiliser les représentations
sous forme de chaîne de caractères en suivant la syntaxe générique décrite dans Section 4.1.2.7, par
exemple 'yes'::boolean.
Notez que l'analyseur comprend automatiquement que TRUE et FALSE sont du type boolean, mais
ce n'est pas le cas pour NULL car il peut avoir tout type. Donc, dans certains contextes, vous devrez
convertir explicitement NULL vers le type boolean, par exemple NULL::boolean. À l'inverse, la
conversion peut être omise d'une valeur booléenne représentée sous la forme d'une chaîne de caractères
dans les contextes où l'analyseur peut déduire que la constante doit être de type boolean.
Une fois créé, le type enum peut être utilisé dans des définitions de table et de fonction, comme tous
les autres types :
163
Types de données
8.7.2. Tri
L'ordre des valeurs dans un type enum correspond à l'ordre dans lequel les valeurs sont créées lors de
la déclaration du type. Tous les opérateurs de comparaison et les fonctions d'agrégats relatives peuvent
être utilisés avec des types enum. Par exemple :
SELECT nom
FROM personne
WHERE humeur_actuelle = (SELECT MIN(humeur_actuelle) FROM
personne);
nom
-------
Larry
(1 row)
164
Types de données
Si vous avez vraiment besoin de ce type de conversion, vous pouvez soit écrire un opérateur
personnalisé soit ajouter des conversions explicites dans votre requête :
Bien que les types enum aient principalement pour but d'être des ensembles statiques de valeurs, il est
possible d'ajouter de nouvelles valeurs à un type enum existant et de renommer les valeurs existantes
(voir ALTER TYPE). Les valeurs existantes ne peuvent pas être supprimées d'un type enum, pas plus
qu'il n'est possible de modifier l'ordre de tri de ces valeurs, si ce n'est en supprimant puis en re- créant
le type enum.
Une valeur enum occupe quatre octets sur disque. La longueur du label texte d'une valeur enum est
limité au paramètre NAMEDATALEN codé en dur dans PostgreSQL ; dans les constructions standard,
cela signifie un maximum de 63 octets.
Les traductions des valeurs enum internes vers des labels texte sont gardées dans le catalogue système
pg_enum. Interroger ce catalogue directement peut s'avérer utile.
165
Types de données
8.8.1. Points
Les points sont les briques fondamentales des types géométriques. Les valeurs de type point sont
indiquées à l'aide d'une des syntaxes suivantes :
( x , y )
x , y
8.8.2. Lines
Les lignes sont représentées par l'équation linéaire Ax + By + C = 0, où A et B ne valent pas zéro tous
les deux. Les valeurs de type line sont fournies et récupérées sous la forme suivante :
{ A, B, C }
Il est également possible d'utiliser n'importe laquelle des formes suivantes pour la saisie :
[ ( x1 , y1 ) , ( x2 , y2 ) ]
( ( x1 , y1 ) , ( x2 , y2 ) )
( x1 , y1 ) , ( x2 , y2 )
x1 , y1 , x2 , y2
[ ( x1 , y1 ) , ( x2 , y2 ) ]
( ( x1 , y1 ) , ( x2 , y2 ) )
( x1 , y1 ) , ( x2 , y2 )
x1 , y1 , x2 , y2
8.8.4. Boîtes
Les boîtes (rectangles) sont représentées par les paires de points des coins opposés de la boîte selon
une des syntaxes suivantes :
( ( x1 , y1 ) , ( x2 , y2 ) )
( x1 , y1 ) , ( x2 , y2 )
x1 , y1 , x2 , y2
166
Types de données
Les deux coins opposés peuvent être fournis en entrée, mais les valeurs seront réordonnées pour stocker
les coins en haut à droite et en bas à gauche, dans cet ordre.
8.8.5. Chemins
Les chemins ( type path ) sont représentés par des listes de points connectés. Ils peuvent être ouverts,
si le premier et le dernier point ne sont pas considérés comme connectés, ou fermés, si le premier et
le dernier point sont considérés comme connectés.
Les valeurs de type path sont saisies selon une des syntaxes suivantes :
[ ( x1 , y1 ) , ... , ( xn , yn ) ]
( ( x1 , y1 ) , ... , ( xn , yn ) )
( x1 , y1 ) , ... , ( xn , yn )
( x1 , y1 , ... , xn , yn )
x1 , y1 , ... , xn , yn
où les points sont les extrémités des segments de droite qui forment le chemin. Les crochets ([])
indiquent un chemin ouvert alors que les parenthèses (()) indiquent un chemin fermé. Quand les
parenthèses externes sont omises, comme dans les syntaxes trois à cinq, un chemin fermé est utilisé.
8.8.6. Polygones
Les polygones (type polygon) sont représentés par des listes de points (les vertex du polygone). Ils
sont très similaires à des chemins fermés, mais ils sont stockés différemment et disposent de leurs
propres routines de manipulation.
Les valeurs de type polygon sont saisies selon une des syntaxes suivantes :
( ( x1 , y1 ) , ... , ( xn , yn ) )
( x1 , y1 ) , ... , ( xn , yn )
( x1 , y1 , ... , xn , yn )
x1 , y1 , ... , xn , yn
où les points sont les extrémités des segments de droite qui forment les limites du polygone.
8.8.7. Cercles
Les cercles (type circle) sont représentés par un point central et un rayon. Les valeurs de type
circle sont saisies selon une des syntaxes suivantes :
< ( x , y ) , r >
( ( x , y ) , r )
( x , y ) , r
x , y , r
PostgreSQL propose des types de données pour stocker des adresses IPv4, IPv6 et MAC. Ceux-ci sont
décrits dans le Tableau 8.21. Il est préférable d'utiliser ces types plutôt que des types texte standard
pour stocker les adresses réseau, car ils offrent un contrôle de syntaxe lors de la saisie et plusieurs
opérateurs et fonctions spécialisés (voir la Section 9.12).
Lors du tri de données de types inet ou cidr, les adresses IPv4 apparaissent toujours avant les
adresses IPv6, y compris les adresses IPv4 encapsulées, comme ::10.2.3.4 ou ::ffff:10.4.3.2.
8.9.1. inet
Le type inet stocke une adresse d'hôte IPv4 ou IPv6 et, optionnellement, son sous-réseau, le tout
dans un seul champ. Le sous-réseau est représenté par le nombre de bits de l'adresse hôte constituant
l'adresse réseau (le « masque réseau »). Si le masque réseau est 32 et l'adresse de type IPv4, alors la
valeur n'indique pas un sous-réseau, juste un hôte. En IPv6, la longueur de l'adresse est de 128 bits,
si bien que 128 bits définissent une adresse réseau unique. Pour n'accepter que des adresses réseau, il
est préférable d'utiliser le type cidr plutôt que le type inet.
Le format de saisie pour ce type est adresse/y où adresse est une adresse IPv4 ou IPv6 et y
est le nombre de bits du masque réseau. Si y est omis, alors le masque vaut 32 pour IPv4 et 128 pour
IPv6, et la valeur représente un hôte unique. À l'affichage, la portion /y est supprimée si le masque
réseau indique un hôte unique.
8.9.2. cidr
Le type cidr stocke une définition de réseau IPv4 ou IPv6. La saisie et l'affichage suivent les
conventions Classless Internet Domain Routing. Le format de saisie d'un réseau est address/y où
address est le réseau représenté sous forme d'une adresse IPv4 ou IPv6 et y est le nombre de bits du
masque réseau. Si y est omis, il calculé en utilisant les règles de l'ancien système de classes d'adresses,
à ceci près qu'il est au moins assez grand pour inclure tous les octets saisis. Saisir une adresse réseau
avec des bits positionnés à droite du masque indiqué est une erreur.
168
Types de données
Astuce
Les fonctions host, text et abbrev permettent de modifier le format d'affichage des
valeurs inet et cidr.
8.9.4. macaddr
Le type macaddr stocke des adresses MAC, connues par exemple pour les adresses de cartes réseau
Ethernet (mais les adresses MAC sont aussi utilisées dans d'autres cas). Les saisies sont acceptées
dans les formats suivants :
'08:00:2b:01:02:03'
'08-00-2b-01-02-03'
'08002b:010203'
'08002b-010203'
'0800-2b01-0203'
'08002b010203'
Ces exemples indiquent tous la même adresse. Les majuscules et les minuscules sont acceptées pour
les chiffres a à f. L'affichage se fait toujours selon le premier des formats ci-dessus.
Le standard IEEE 802-2001 spécifie la seconde forme affichée (avec les tirets) comme forme
canonique pour les adresses MAC, et la première forme (avec les :) comme utilisé avec la notation à
bits retournés, MSB en premier, ce qui donne l'équivalence 08-00-2b-01-02-03 = 01:00:D4:80:40:C0.
Cette convention est largement ignorée aujourd'hui et n'a de sens que pour des protocoles réseau
obsolètes (comme Token Ring). PostgreSQL ne tient pas compte des bits retournés ; tous les formats
acceptés utilisent l'ordre canonique LSB.
8.9.5. macaddr8
Le type macaddr8 stocke des adresses MAC au format EUI-64, connu par exemple pour les adresses
de cartes réseau Ethernet (mais les adresses MAC sont aussi utilisées dans d'autres cas). Ce type
accepte à la fois des adresses MAC d'une longueur de six et huit octets. Les adresses MAC fournies
dans un format de six octets seront stockées dans un format de huit octets avec les quatrième et
cinquième octets respectivement positionnés à FF et FE. Veuillez noter qu'IPv6 utilise un format
169
Types de données
modifié de EUI-64 où le septième bit devrait être positionné à un après la conversion depuis EUI-48.
La fonction macaddr8_set7bit est fournie pour réaliser ce changement. De manière générale,
n'importe quelle valeur en entrée constituée de paires de chiffres au format hexadécimal (dans les
limites d'un octet), systématiquement séparées ou non d'un de ces caractères ':', '-' ou '.' est
acceptée. Le nombre de chiffres hexadécimaux doit être 16 (huit octets) ou 12 (six octets). Les espaces
non significatifs présents avant ou après sont ignorés. Voici un ensemble d'exemples de formats
acceptés en entrée :
'08:00:2b:01:02:03:04:05'
'08-00-2b-01-02-03-04-05'
'08002b:0102030405'
'08002b-0102030405'
'0800.2b01.0203.0405'
'0800-2b01-0203-0405'
'08002b01:02030405'
'08002b0102030405'
Ces exemples spécifient tous la même adresse. Les majuscules et les minuscules sont acceptées pour
les caractères de a jusqu'à f. La sortie sera toujours au même format que le premier exemple. Les
six derniers formats en entrée qui sont mentionnés au-dessus ne font partie d'aucun standard. Pour
convertir une adresse MAC traditionnelle de 48 bits au format EUI-48 vers le format modifié EUI-64
pour pouvoir être incluse dans la partie hôte d'une adresse IPv6, utilisez macaddr8_set7bit
comme ceci :
SELECT macaddr8_set7bit('08:00:2b:01:02:03');
macaddr8_set7bit
-------------------------
0a:00:2b:ff:fe:01:02:03
(1 row)
Les données de type bit doivent avoir une longueur de n bits exactement. Essayer de lui affecter une
chaîne de bits plus longue ou plus courte déclenche une erreur. Les données de type bit varying
ont une longueur variable, d'au maximum n bits ; les chaînes plus longues sont rejetées. Écrire bit
sans longueur est équivalent à bit(1), alors que bit varying sans longueur indique une taille
illimitée.
Note
Lors du transtypage explicite (cast) d'une chaîne de bits en champ de type bit(n), la chaîne
obtenue est complétée avec des zéros ou bien tronquée pour obtenir une taille de n bits
exactement, sans que cela ne produise une erreur. De la même façon, si une chaîne de bits
est explicitement transtypée en un champ de type bit varying(n), elle est tronquée si
sa longueur dépasse n bits.
Voir la Section 4.1.2.5 pour plus d'information sur la syntaxe des constantes en chaîne de bits. Les
opérateurs logiques et les fonctions de manipulation de chaînes sont décrits dans la Section 9.6.
170
Types de données
a | b
-----+-----
101 | 00
100 | 101
Une valeur pour une chaîne de bits nécessite un octet pour chaque groupe de huit bits, plus cinq ou
huit octets d'en-tête suivant la longueur de la chaîne (les valeurs longues peuvent être compressées ou
déplacées, comme expliqué dans Section 8.3 pour les chaînes de caractères).
8.11.1. tsvector
Une valeur tsvector est une liste triée de lexemes distincts, qui sont des mots qui ont été normalisés
pour fusionner différentes variantes du même mot apparaissant (voir Chapitre 12 pour plus de détails).
Trier et éliminer les duplicats se font automatiquement lors des entrées, comme indiqué dans cet
exemple :
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;
tsvector
----------------------------------------------------
'a' 'and' 'ate' 'cat' 'fat' 'mat' 'on' 'rat' 'sat'
Pour représenter des lexèmes contenant des espaces blancs ou des signes de ponctuation, entourez-
les avec des guillemets simples :
(Nous utilisons les valeurs littérales entre guillemets simples dans cet exemple et dans le prochain
pour éviter une confusion en ayant à doubler les guillemets à l'intérieur des valeurs littérales.) Les
guillemets imbriqués et les antislashs doivent être doublés :
171
Types de données
SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10
fat:11 rat:12'::tsvector;
tsvector
-------------------------------------------------------------------------------
'a':1,6,10 'and':8 'ate':9 'cat':3 'fat':2,11 'mat':7 'on':5
'rat':12 'sat':4
Une position indique normalement l'emplacement du mot source dans le document. Les informations
de position sont utilisables pour avoir un score de proximité. Les valeurs des positions peuvent aller
de 1 à 16383 ; les grands nombres sont limités silencieusement à 16383. Les positions dupliquées du
même lexème sont rejetées.
Les lexèmes qui ont des positions peuvent aussi avoir un label d'un certain poids. Les labels possibles
sont A, B, C ou D. D est la valeur par défaut et n'est du coup pas affiché en sortie :
Les poids sont typiquement utilisés pour refléter la structure du document en marquant les mots du
titre de façon différente des mots du corps. Les fonctions de score de la recherche plein texte peuvent
assigner des priorités différentes aux marqueurs de poids différents.
Il est important de comprendre que le type tsvector lui-même ne réalise aucune normalisation de
mots ; il suppose que les mots qui lui sont fournis sont normalisés correctement pour l'application.
Par exemple,
Pour la plupart des applications de recherche en anglais, les mots ci-dessus seraient considérés
comme non normalisés, mais tsvector n'y prête pas attention. Le texte des documents bruts doit
habituellement passer via to_tsvector pour normaliser les mots de façon appropriée pour la
recherche :
172
Types de données
8.11.2. tsquery
Une valeur tsquery enregistre les lexèmes qui doivent être recherchés, et peut les combiner en
utilisant les opérateurs booléens & (AND), | (OR) et ! (NOT), ainsi que l'opérateur de recherche de
phrase <-> (FOLLOWED BY). Il existe aussi une variante de l'opérateur FOLLOWED BY, <N>,
où N est une constante entière indiquant la distance maximale entre les deux lexèmes recherchés. <-
> est équivalent à <1>.
Les parenthèses peuvent être utilisées pour forcer le regroupement des opérateurs. En l'absence de
parenthèses, ! (NOT) est prioritaire, <-> (FOLLOWED BY) suit, et enfin & (AND) et | (OR) sont
les moins prioritaires.
En option, les lexèmes dans une tsquery peuvent être labelisés avec une lettre de poids ou plus, ce
qui les restreint à une correspondance avec les seuls lexèmes tsvector pour un de ces poids :
Par ailleurs, les lexèmes d'une tsquery peuvent être marqués avec * pour spécifier une
correspondance de préfixe :
SELECT 'super:*'::tsquery;
tsquery
-----------
'super':*
Cette requête fera ressortir tout mot dans un tsvector qui commence par « super ».
Les règles de guillemets pour les lexèmes sont identiques à celles décrites ci-dessus pour les lexèmes
de tsvector ; et, comme avec tsvector, toute normalisation requise des mots doit se faire avant
de les placer dans le type tsquery. La fonction to_tsquery est convenable pour réaliser une telle
normalisation :
173
Types de données
Notez que to_tsquery traitera les préfixes de la même façon que les autres mots, ce qui signifie
que cette comparaison renvoie true :
Un UUID est écrit comme une séquence de chiffres hexadécimaux en minuscule, répartis en différents
groupes, séparés par un tiret. Plus précisément, il s'agit d'un groupe de huit chiffres suivis de trois
groupes de quatre chiffres terminés par un groupe de douze chiffres, ce qui fait un total de 32 chiffres
représentant les 128 bits. Voici un exemple d'UUID dans sa forme standard :
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
PostgreSQL accepte aussi d'autres formes en entrée : utilisation des majuscules, de crochets englobant
le nombre, suppression d'une partie ou de tous les tirets, ajout d'un tiret après n'importe quel groupe
de quatre chiffres. Voici quelques exemples :
A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}
174
Types de données
Pour générer des UUID, le module uuid-ossp fournit des fonctions qui implémentent les algorithmes
standards. Le module pgcrypto fournit également une fonction de génération d'UUID aléatoires. Sinon,
les UUID peuvent être générés par des applications clientes ou par d'autres bibliothèques appelées par
une fonction serveur.
Le type xml peut stocker des « documents » bien formés, suivant la définition du standard XML, ainsi
que des fragments de contenu (« content »), en référence au « nœud de document »1 plus permissif des
modèle de données XQuery et XPath. Cela signifie que les fragments de contenu peuvent avoir plus
d'un élément racine ou nœud caractère. L'expression valeurxml IS DOCUMENT permet d'évaluer
si une valeur xml particulière est un document complet ou seulement un fragment de contenu.
Les limites et notes de compatibilité pour le type de données xml sont disponibles dans Section D.3.
Quelques exemples :
Bien que cela soit la seule façon de convertir des chaînes de caractères en valeurs XML d'après le
standard XML, voici des syntaxes spécifiques à PostgreSQL :
xml '<foo>bar</foo>'
'<foo>bar</foo>'::xml
Le type xml ne valide pas les valeurs en entrée par rapport à une déclaration de type de document
(DTD), même quand la valeur en entrée indique une DTD. Il n'existe pas encore de support pour la
validation avec d'autres langages de schéma XML, comme XML Schema.
L'opération inverse, produisant une chaîne de caractères à partir d'une valeur au type xml, utilise la
fonction xmlserialize:
type peut être character, character varying ou text (ou un alias de ces derniers). Encore
une fois, d'après le standard SQL, c'est le seul moyen de convertir le type xml vers les types caractère,
mais PostgreSQL autorise aussi la conversion simple de la valeur.
1
https://www.w3.org/TR/2010/REC-xpath-datamodel-20101214/#DocumentNode
175
Types de données
Lorsque les valeurs des chaînes de caractères sont converties vers ou à partir du type xml sans
passer par XMLPARSE ou XMLSERIALIZE, respectivement, le choix de DOCUMENT ou de CONTENT
est déterminé par un paramètre de configuration niveau session, « XML OPTION » , qui peut être
configuré par la commande habituelle :
ou la syntaxe PostgreSQL :
La valeur par défaut est CONTENT, donc toutes les formes de données XML sont autorisées.
Lors de l'utilisation du mode binaire pour le passage des paramètres de la requête au serveur et des
résultats au client, aucune conversion de l'encodage n'est réalisée, donc la situation est différente. Dans
ce cas, une déclaration d'encodage dans les données XML sera observée et, si elle est absente, les
données seront supposées être en UTF-8 (comme requis par le standard XML ; notez que PostgreSQL
ne supporte pas du tout UTF-16). En sortie, les données auront une déclaration d'encodage spécifiant
l'encodage client, sauf si l'encodage client est UTF-8, auquel cas elle sera omise.
Le traitement des données XML avec PostgreSQL sera moins complexe et plus efficace si l'encodage
des données, l'encodage client et l'encodage serveur sont identiques. Comme les données XML sont
traitées en interne en UTF-8, les traitements seront plus efficaces si l'encodage serveur est aussi en
UTF-8.
Attention
Certaines fonctions relatives à XML pourraient ne pas fonctionner du tout sur des données
non ASCII quand l'encodage du serveur n'est pas UTF-8. C'est un problème connu pour
xmltable() et xpath() en particulier.
176
Types de données
XML. Une conséquence de ceci est que vous ne pouvez pas récupérer des lignes en comparant une
colonne xml avec une valeur de recherche. Les valeurs XML doivent du coup être typiquement
accompagnées par un champ clé séparé comme un identifiant. Une autre solution pour la comparaison
de valeurs XML est de les convertir en des chaînes de caractères, mais notez que la comparaison de
chaînes n'a que peu à voir avec une méthode de comparaison XML utile.
Comme il n'y a pas d'opérateurs de comparaison pour le type de données xml, il n'est pas possible
de créer un index directement sur une colonne de ce type. Si une recherche rapide est souhaitée dans
des données XML, il est toujours possible de convertir l'expression en une chaîne de caractères et
d'indexer cette conversion. Il est aussi possible d'indexer une expression XPath. La vraie requête devra
bien sûr être ajustée à une recherche sur l'expression indexée.
La fonctionnalité de recherche plein texte peut aussi être utilisée pour accélérer les recherches dans
des données XML. Le support du prétraitement nécessaire n'est cependant pas disponible dans la
distribution PostgreSQL.
Il y a deux types de données JSON : json et jsonb. Ils acceptent quasiment des ensembles de valeurs
identiques en entrée. La différence majeure réside dans l'efficacité. Le type de données json stocke
une copie exacte du texte en entrée, que chaque fonction doit analyser à chaque exécution, alors que le
type de données jsonb est stocké dans un format binaire décomposé qui rend l'insertion légèrement
plus lente du fait du surcoût de la conversion, mais est significativement plus rapide pour traiter les
données, puisqu'aucune analyse n'est nécessaire. jsonb gère également l'indexation, ce qui peut être
un avantage significatif.
Puisque le type json stocke une copie exacte du texte en entrée, il conservera les espaces
sémantiquement non significatifs entre les jetons, ainsi que l'ordre des clés au sein de l'objet JSON. De
plus, si un objet JSON contient dans sa valeur la même clé plus d'une fois, toutes les paires clé/valeur
sont conservées (les fonctions de traitement considèrent la dernière valeur comme celle significative).
À l'inverse, jsonb ne conserve ni les espaces non significatifs, ni l'ordre des clés d'objet, ni ne
conserve les clés d'objet dupliquées. Si des clés dupliquées sont présentées en entrée, seule la dernière
valeur est conservée.
En général, la plupart des applications devraient préférer stocker les données JSON avec jsonb, à
moins qu'il y ait des besoins spécifiques, comme la supposition légitime de l'ordre des clés d'objet.
PostgreSQL n'autorise qu'un seul encodage de caractères par base de données. Il n'est donc pas possible
pour les types JSON de se conformer de manière rigoureuse à la spécification JSON, à moins que
l'encodage de la base de données soit UTF8. Tenter d'inclure directement des caractères qui ne peuvent
pas être représentés dans l'encodage de la base de données échouera ; inversement, des caractères qui
peuvent être représentés dans l'encodage de la base de données, mais pas en UTF8, seront autorisés.
La RFC 7159 autorise les chaînes JSON à contenir des séquences Unicode échappées, indiquées avec
\uXXXX. Dans la fonction d'entrée pour le type json, les échappements Unicode sont autorisés quel
que soit l'encodage de la base de données, et sont vérifiés uniquement pour l'exactitude de la syntaxe
(qui est quatre chiffres hexadécimaux précédés d'un \u). Toutefois, la fonction d'entrée pour jsonb
est plus stricte : elle interdit les échappements Unicode pour les caractères autres que ASCII (ceux au-
delà de U+007F) à moins que l'encodage de la base de données soit UTF8. Le type jsonb rejette
aussi \u0000 (parce qu'il ne peut pas être représenté avec le type text de PostgreSQL), et il insiste
pour que chaque utilisation de paires de substitution Unicode désignant des caractères en dehors du
2
https://tools.ietf.org/html/rfc7159
177
Types de données
Unicode Basic Multilingual Plane soit correcte. Les échappements Unicode valides sont convertis en
leur caractère ASCII ou UTF8 équivalent pour du stockage ; ceci inclut les « folding surrogate pairs »
sur un seul caractère.
Note
De nombreuses fonctions de traitement JSON décrites dans Section 9.15 convertiront les
échappements Unicode vers des caractères standards, et généreront donc le même type
d'erreurs décrit juste avant si leur entrée est de type json et non jsonb. Le fait que la fonction
d'entrée json ne fasse pas ces vérifications peut être considéré comme un artefact historique,
bien qu'elle n'autorise pas un simple stockage (sans traitement) d'échappements Unicode JSON
dans une base de données en encodage non UTF8. En général, il est préférable d'éviter de
mélanger des échappements Unicode en JSON avec une base de données en encodage non
UTF8 si possible.
Lors de la conversion de données texte JSON vers jsonb, les types primitifs décrits par la RFC 7159
sont transcrits efficacement vers des types PostgreSQL natifs, comme indiqué dans Tableau 8.23.
Par conséquent, il y a quelques contraintes additionnelles mineures sur ce qui constitue des données
jsonb valides qui ne s'appliquent ni au type json, ni à JSON en définitive, correspondant aux limites
de ce qui peut être représenté par le type de données sous-jacent. Spécifiquement, jsonb rejettera
les nombres qui sont en dehors de la portée du type de données numeric de PostgreSQL, alors que
json les acceptera. De telles restrictions définies par l'implémentation sont permises par la RFC 7159.
Cependant, en pratique, de tels problèmes ont beaucoup plus de chances de se produire dans d'autres
implémentations, puisqu'il est habituel de représenter les types primitifs number JSON comme des
nombres flottants à double précision (IEEE 754 double precision floating point), ce que la RFC 7159
anticipe explicitement et autorise. Lorsque JSON est utilisé comme format d'échange avec de tels
systèmes, le risque de perte de précision pour les valeurs numériques comparées aux données stockées
à l'origine par PostgreSQL devrait être considéré.
À l'inverse, comme indiqué dans le tableau, il y a quelques restrictions mineures sur le format d'entrée
de types primitifs JSON qui ne s'appliquent pas aux types PostgreSQL correspondants.
Les exemples suivants sont tous des expressions json (ou jsonb) valides :
178
Types de données
Comme dit précédemment, quand une valeur JSON est renseignée puis affichée sans traitement
additionnel, json renvoie le même texte qui était fourni en entrée, alors que jsonb ne préserve pas
les détails sémantiquement non significatifs comme les espaces. Par exemple, il faut noter la différence
ici :
un détail sémantiquement non significatif qu'il faut souligner est qu'avec jsonb, les nombres seront
affichés en fonction du type numeric sous-jacent. En pratique, cela signifie que les nombres
renseignés avec la notation E seront affichés sans. Par exemple :
Toutefois, jsonb préservera les zéros en fin de partie fractionnaire, comme on peut le voir dans cet
exemple, même si ceux-ci ne sont pas sémantiquement significatifs, pour des besoins tels que des
tests d'égalité.
179
Types de données
Il est tout à fait possible que ces deux approches puissent coexister, et qu'elles soient complémentaires
au sein de la même application. Toutefois, même pour les applications où on désire le maximum de
flexibilité, il est toujours recommandé que les documents JSON aient une structure quelque peu fixée.
La structure est typiquement non vérifiée (bien que vérifier des règles métier de manière déclarative
soit possible), mais le fait d'avoir une structure prévisible rend plus facile l'écriture de requêtes qui
résument utilement un ensemble de « documents » (datums) dans une table.
Les données JSON sont sujettes aux mêmes considérations de contrôle de concurrence que pour
n'importe quel autre type de données quand elles sont stockées en table. Même si stocker de gros
documents est prévisible, il faut garder à l'esprit que chaque mise à jour acquiert un verrou de niveau
ligne sur toute la ligne. Il faut envisager de limiter les documents JSON à une taille gérable pour
réduire les contentions sur verrou lors des transactions en mise à jour. Idéalement, les documents JSON
devraient chacun représenter une donnée atomique, que les règles métiers imposent de ne pas pouvoir
subdiviser en données plus petites qui pourraient être modifiées séparément.
180
Types de données
Le principe général est que l'objet inclus doit correspondre à l'objet devant le contenir à la fois pour la
structure et pour les données, peut-être après la suppression d'éléments de tableau ou d'objets paires
clé/valeur ne correspondant pas à l'objet contenant. Mais rappelez-vous que l'ordre des éléments dans
un tableau n'est pas significatif lors d'une recherche de contenance, et que les éléments dupliqués d'un
tableau ne sont réellement considérés qu'une seule fois.
Comme exception qui confirme la règle que les structures doivent correspondre, un tableau peut inclure
une valeur primitive :
jsonb a également un opérateur d'existence, qui est une variation sur le thème de l'inclusion : il teste
si une chaîne (sous forme de valeur text) apparaît comme une clé d'objet ou un élément de tableau
au niveau supérieur de la valeur jsonb. Ces exemples renvoient vrai; sauf note explicite :
Les objets JSON sont plus adaptés que les tableaux pour tester l'inclusion ou l'existence quand il y a de
nombreux éléments ou clés impliqués, car contrairement aux tableaux, ils sont optimisés de manière
interne pour la recherche et n'ont pas besoin d'être parcourus linéairement.
Astuce
Comme les documents JSON sont imbriqués, une requête appropriée peut ignorer une sélection
explicite de sous-objets. Par exemple, supposons que nous ayons une colonne doc contenant
des objets au plus haut niveau, avec la plupart des objets contenant les champs tags qui
contiennent eux-mêmes des tableaux de sous-objets. Cette requête trouve des entrées dans
lesquelles les sous-objets contiennent à la fois "term":"paris" et "term":"food",
tout en ignorant ces clés en dehors du tableau tags :
181
Types de données
mais cette approche est moins flexible, et souvent bien moins efficace.
Mais l'opérateur JSON d'existence n'est pas imbriqué : il cherchera seulement pour la clé ou
l'élément de tableau spécifié à la racine de la valeur JSON.
Les différents opérateurs d'inclusion d'existence, avec tous les autres opérateurs et fonctions JSON,
sont documentés dans Section 9.15.
La classe d'opérateur GIN par défaut pour jsonb supporte les requêtes avec des opérateurs de haut
niveau clé-existe ?, ?& et des opérateurs ?| et l'opérateur chemin/valeur-existe @>. (Pour des détails
sur la sémantique que ces opérateurs implémentent, voir Tableau 9.44.) Un exemple de création d'index
avec cette classe d'opérateurs est :
La classe d'opérateurs GIN qui n'est pas par défaut jsonb_path_ops supporte l'indexation de
l'opérateur @> seulement. Un exemple de création d'index avec cette classe d'opérateurs est :
En étudiant l'exemple d'une table qui stocke des documents JSON récupérés par un service web tiers,
avec une définition de schéma documentée, un document typique serait :
{
"guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
"name": "Angela Barton",
"is_active": true,
"company": "Magnafone",
"address": "178 Howard Place, Gulf, Washington, 702",
"registered": "2009-11-07T08:53:22 +08:00",
"latitude": 19.793713,
"longitude": 86.513373,
"tags": [
"enim",
"aliquip",
"qui"
]
}
182
Types de données
Ces documents sont stockés dans une table nommée api, dans une colonne de type jsonb nommée
jdoc. Si un index GIN est créé sur cette colonne, des requêtes semblables à l'exemple suivant peuvent
utiliser cet index :
Toutefois, cet index ne pourrait pas être utilisé pour des requêtes comme dans l'exemple suivant, car
bien que l'opérateur ? soit indexable, il n'est pas appliqué directement sur la colonne indexée jdoc :
Toutefois, avec l'utilisation appropriée d'index sur expression, la requête ci-dessus peut utiliser un
index. Si le requêtage d'éléments particuliers de la clé "tags" est fréquent, définir un index comme
ceci pourrait être particulièrement bénéfique :
-- À noter que l'opérateur "jsonb -> text" ne peut être appelé que
sur un
-- objet JSON, donc la conséquence de créer cet index est que le
premier niveau de
-- chaque valeur "jdoc" doit être un objet. Ceci est vérifié lors
de chaque insertion.
CREATE INDEX idxgintags ON api USING GIN ((jdoc -> 'tags'));
Dorénavant, la clause WHERE jdoc -> 'tags' ? 'qui' sera reconnue comme une application
de l'opérateur indexable ? pour l'expression indexée jdoc -> 'tags'. (Plus d'informations sur
les index sur expression peuvent être trouvées dans Section 11.7.)
Un simple index GIN sur la colonne jdoc peut répondre à cette requête. Mais il faut noter qu'un tel
index stockera des copies de chaque clé et chaque valeur de la colonne jdoc, alors que l'index sur
expression de l'exemple précédent ne stockera que les données trouvées pour la clé tags. Alors que
l'approche d'index simple est bien plus souple (puisqu'elle supporte les requêtes sur n'importe quelle
clé), les index sur des expressions ciblées ont bien plus de chances d'être plus petits et plus rapides
pour la recherche qu'un simple index.
Bien que la classe d'opérateur jsonb_path_ops ne supporte que les requêtes avec l'opérateur
@>, elle a des avantages de performances notables par rapport à la classe d'opérateur par
défaut jsonb_ops. Un index jsonb_path_ops est généralement bien plus petit qu'un index
jsonb_ops pour les mêmes données, et la spécificité de la recherche est meilleure, particulièrement
quand les requêtes contiennent des clés qui apparaissent fréquemment dans les données. Par
183
Types de données
conséquent, les opérations de recherche sont généralement plus performantes qu'avec la classe
d'opérateur par défaut.
La différence technique entre des index GIN jsonb_ops et jsonb_path_ops est que le premier
crée des éléments d'index indépendants pour chaque clé et valeur dans les données, alors que le second
crée des éléments d'index uniquement pour chaque valeur dans les données. 3 Fondamentalement,
chaque élément d'index jsonb_path_ops est un hachage de la valeur et de la ou des clés y menant ;
par exemple pour indexer {"foo": {"bar": "baz"}}, un seul élément dans l'index sera créé,
incorporant les trois foo, bar et baz dans une valeur hachée. Ainsi, une requête d'inclusion cherchant
cette structure résulterait en une recherche d'index extrêmement spécifique, mais il n'y a pas d'autre
moyen de savoir si foo apparaît en tant que clé. D'un autre côté, un index jsonb_ops créerait trois
éléments d'index représentant foo, bar et baz séparément ; ainsi, pour faire la requête d'inclusion,
il faudrait rechercher les lignes contenant chacun des trois éléments. Bien que les index GIN puissent
effectuer de telles recherches et de manière tout à fait efficace, cela sera toujours moins spécifique et
plus lent que la recherche équivalente jsonb_path_ops, surtout s'il y a un très grand nombre de
lignes contenant n'importe lequel des trois éléments d'index.
Un désavantage de l'approche jsonb_path_ops est qu'elle ne produit d'entrées d'index que pour
les structures JSON ne contenant aucune valeur, comme {"a": {}}. Si une recherche pour des
documents contenant une telle structure est demandée, elle nécessitera un parcours de la totalité de
l'index, ce qui peut être assez long. jsonb_path_ops est donc mal adapté pour des applications
qui effectuent souvent de telles recherches.
jsonb supporte également les index btree et hash. Ceux-ci ne sont généralement utiles que s'il
est important de vérifier l'égalité de documents JSON entiers. Le tri btree pour des données jsonb
est rarement d'un grand intérêt, mais afin d'être exhaustif, il est :
Objet > Tableau > Booléen > Nombre > Chaîne > Null
Les objets avec le même nombre de paires sont comparés dans cet ordre :
À noter que les clés d'objet sont comparées dans leur ordre de stockage ; en particulier, puisque les
clés les plus courtes sont stockées avant les clés les plus longues, cela peut amener à des résultats
contre-intuitifs, tels que :
De la même manière, les tableaux avec le même nombre d'éléments sont comparés dans l'ordre :
Les valeurs JSON primitives sont comparées en utilisant les mêmes règles de comparaison que pour
les types de données PostgreSQL sous-jacents. Les chaînes sont comparées en utilisant la collation
par défaut de la base de données.
3
Dans ce contexte, le terme « valeur » inclut les éléments de tableau, bien que la terminologie JSON considère parfois que les éléments de
tableaux soient distincts des valeurs dans les objets.
184
Types de données
8.14.5. Transformations
Des extensions supplémentaires sont disponibles pour implémenter des transformations pour le type
jsonb pour différents langages de procédure stockée.
Les extensions pour PL/Perl sont appelées jsonb_plperl et jsonb_plperlu. Si vous les
utilisez, les valeurs jsonb sont transformées en tableaux, hachages et scalaires Perl, suivant le cas.
8.15. Tableaux
PostgreSQL permet de définir des colonnes de table comme des tableaux multidimensionnels de
longueur variable. Il est possible de créer des tableaux de n'importe quel type utilisateur : de base,
énuméré, composé, intervalle, domaine.
Comme indiqué ci-dessus, un type de données tableau est nommé en ajoutant des crochets ([])
au type de données des éléments du tableau. La commande ci-dessus crée une table nommée
sal_emp avec une colonne de type text (nom), un tableau à une dimension de type integer
(paye_par_semaine), représentant le salaire d'un employé par semaine et un tableau à deux
dimensions de type text (planning), représentant le planning hebdomadaire de l'employé.
La syntaxe de CREATE TABLE permet de préciser la taille exacte des tableaux, par exemple :
Néanmoins, l'implantation actuelle ignore toute limite fournie pour la taille du tableau, c'est-à-dire que
le comportement est identique à celui des tableaux dont la longueur n'est pas précisée.
De plus, l'implantation actuelle n'oblige pas non plus à déclarer le nombre de dimensions. Les tableaux
d'un type d'élément particulier sont tous considérés comme étant du même type, quels que soient leur
taille ou le nombre de dimensions. Déclarer la taille du tableau ou le nombre de dimensions dans
CREATE TABLE n'a qu'un but documentaire. Le comportement de l'application n'en est pas affecté.
Une autre syntaxe, conforme au standard SQL via l'utilisation du mot-clé ARRAY, peut être employée
pour les tableaux à une dimension. paye_par_semaine peut être défini ainsi :
185
Types de données
Néanmoins, comme indiqué précédemment, PostgreSQL n'impose aucune restriction sur la taille dans
tous les cas.
où delim est le caractère de délimitation pour ce type, tel qu'il est enregistré dans son entrée
pg_type. Parmi les types de données standards fournis par la distribution PostgreSQL, tous utilisent
une virgule (,), sauf pour le type box qui utilise un point-virgule (;). Chaque val est soit une
constante du type des éléments du tableau soit un sous-tableau.
'{{1,2,3},{4,5,6},{7,8,9}}'
Cette constante a deux dimensions, un tableau 3 par 3 consistant en trois sous-tableaux d'entiers.
Pour initialiser un élément d'un tableau à NULL, on écrit NULL pour la valeur de cet élément. (Toute
variante majuscule et/ou minuscule de NULL est acceptée.) Si « NULL » doit être utilisé comme valeur
de chaîne, on place des guillemets doubles autour.
Ces types de constantes tableau sont en fait un cas particulier des constantes de type générique abordées
dans la Section 4.1.2.7. La constante est traitée initialement comme une chaîne et passée à la routine
de conversion d'entrées de tableau. Une spécification explicite du type peut être nécessaire.
Les tableaux multidimensionnels doivent avoir des échelles correspondantes pour chaque dimension.
Une différence cause la levée d'une erreur. Par exemple :
186
Types de données
Les éléments du tableau sont des constantes SQL ordinaires ou des expressions ; par exemple, les
chaînes de caractères littérales sont encadrées par des guillemets simples au lieu de guillemets doubles
comme cela est le cas dans un tableau littéral. La syntaxe du constructeur ARRAY est discutée plus
en profondeur dans la Section 4.2.12.
nom
-------
Carol
(1 row)
Les indices du tableau sont écrits entre crochets. Par défaut, PostgreSQL utilise la convention des
indices commençant à 1 pour les tableaux, c'est-à-dire un tableau à n éléments commence avec
array[1] et finit avec array[n].
paye_par_semaine
------------------
10000
25000
(2 rows)
Il est également possible d'accéder à des parties rectangulaires arbitraires ou à des sous-tableaux. Une
partie de tableau est indiquée par l'écriture extrémité basse:extrémité haute sur n'importe
quelle dimension. Ainsi, la requête suivante retourne le premier élément du planning de Bill pour les
deux premiers jours de la semaine :
187
Types de données
planning
--------------------
{{rendez-vous},{entrainement}}
(1 row)
Si l'une des dimensions est écrite comme une partie, c'est-à-dire si elle contient le caractère deux-
points, alors toutes les dimensions sont traitées comme des parties. Toute dimension qui n'a qu'un
numéro (pas de deux-points), est traitée comme allant de 1 au nombre indiqué. Par exemple, [2] est
traitée comme [1:2], comme le montre cet exemple :
planning
---------------------------
{{rendez-vous,repas},{entrainement,présentation}}
(1 row)
Pour éviter la confusion avec le cas sans indice, il est préférable d'utiliser la syntaxe avec indice pour
toutes les dimensions, c'est-à-dire [1:2][1:1] et non pas [2][1:1].
Il est possible d'omettre la limite basse et/ou la limite haute dans les indices. La limite
manquante est remplacée par la limite basse ou haute des dimensions du tableau. Par exemple :
planning
------------------------
{{lunch},{presentation}}
(1 row)
schedule
------------------------
{{meeting},{training}}
(1 row)
Une expression indicée de tableau retourne NULL si le tableau ou une des expressions est NULL.
De plus, NULL est renvoyé si un indice se trouve en dehors de la plage du tableau (ce cas
n'amène pas d'erreur). Par exemple, si planning a les dimensions [1:3][1:2], faire référence à
planning[3][3] donne un résultat NULL. De la même façon, une référence sur un tableau avec
une valeur d'indices incorrecte retourne une valeur NULL plutôt qu'une erreur.
Une expression de découpage d'un tableau est aussi NULL si, soit le tableau, soit une des expressions
indicées est NULL. Néanmoins, dans certains cas particuliers comme la sélection d'une partie d'un
tableau complètement en dehors de la plage de ce dernier, l'expression de cette partie est un tableau
vide (zéro dimension) et non pas un tableau NULL. (Ceci ne correspond pas au comportement sans
indice, et est fait pour des raisons historiques.) Si la partie demandée surcharge partiellement les limites
du tableau, alors elle est réduite silencieusement à la partie surchargée au lieu de renvoyer NULL.
Les dimensions actuelles de toute valeur de type tableau sont disponibles avec la fonction
array_dims :
array_dims
188
Types de données
------------
[1:2][1:2]
(1 row)
array_dims donne un résultat de type text, ce qui est pratique à lire, mais peut s'avérer
plus difficile à interpréter par les programmes. Les dimensions sont aussi récupérables avec
array_upper et array_lower, qui renvoient respectivement la limite haute et la limite basse
du tableau précisé :
array_upper
-------------
2
(1 row)
array_length
--------------
2
(1 row)
cardinality renvoie le nombre total d'éléments d'un tableau sur toutes ses dimensions. Autrement
dit, c'est le nombre de lignes que renverrait un appel à la fonction unnest :
cardinality
-------------
4
(1 row)
189
Types de données
Les syntaxes des indices avec la limite basse et/ou la limite upper-bound omise peuvent
aussi être utilisées lors de la mise à jour d'une valeur d'un tableau qui est différent de NULL ou à plus
de zéro dimension (sinon, il n'existe pas de limite à substituer).
Un tableau peut être agrandi en y stockant des éléments qui n'y sont pas déjà présents. Toute position
entre ceux déjà présents et les nouveaux éléments est remplie avec la valeur NULL. Par exemple, si le
tableau mon_tableau a actuellement quatre éléments, il en aura six après une mise à jour qui affecte
mon_tableau[6], car mon_tableau[5] est alors rempli avec une valeur NULL. Actuellement,
l'agrandissement de cette façon n'est autorisé que pour les tableaux à une dimension, pas pour les
tableaux multidimensionnels.
L'affectation par parties d'un tableau permet la création de tableaux dont l'indice de départ n'est pas 1.
On peut ainsi affecter, par exemple, mon_tableau[-2:7] pour créer un tableau avec des valeurs
d'indices allant de -2 à 7.
Les valeurs de nouveaux tableaux peuvent aussi être construites en utilisant l'opérateur de
concaténation, || :
L'opérateur de concaténation autorise un élément à être placé au début ou à la fin d'un tableau à une
dimension. Il accepte aussi deux tableaux à N dimensions, ou un tableau à N dimensions et un à N
+1 dimensions.
Quand un élément seul est poussé soit au début soit à la fin d'un tableau à une dimension, le résultat
est un tableau avec le même indice bas que l'opérande du tableau. Par exemple :
Lorsque deux tableaux ayant un même nombre de dimensions sont concaténés, le résultat conserve
la limite inférieure de l'opérande gauche. Le résultat est un tableau comprenant chaque élément de
l'opérande gauche suivi de chaque élément de l'opérande droit. Par exemple :
190
Types de données
Lorsqu'un tableau à N dimensions est placé au début ou à la fin d'un tableau à N+1 dimensions, le
résultat est analogue au cas ci-dessus. Chaque sous-tableau de dimension N est en quelque sorte un
élément de la dimension externe d'un tableau à N+1 dimensions. Par exemple :
Un tableau peut aussi être construit en utilisant les fonctions array_prepend, array_append
ou array_cat. Les deux premières ne supportent que les tableaux à une dimension alors que
array_cat supporte les tableaux multidimensionnels. Quelques exemples :
Dans les cas simples, l'opération de concaténation discutée ci-dessus est préférée à l'utilisation directe
de ces fonctions. Néanmoins, comme l'opérateur de concaténation est surchargé pour servir les trois
cas, certaines utilisations peuvent bénéficier de l'utilisation d'une fonction pour éviter toute ambiguïté.
Par exemple :
191
Types de données
-----------
{1,2,3,4}
Dans l'exemple ci-dessus, l'analyseur voit un tableau d'entiers d'un côté de l'opérateur de concaténation
et une constante de type indéterminé de l'autre. L'heuristique utilisée pour résoudre le type de la
constante revient à assumer qu'elle est de même type que l'autre entrée de l'opérateur -- dans ce cas,
un tableau d'entiers. Donc, l'opérateur de concaténation est supposé représenter array_cat, et non
pas array_append. Quand le choix est erroné, cela peut se corriger en convertissant la constante
dans le type de données d'un élément du tableau. L'utilisation de la fonction array_append peut
être préférable.
Ceci devient toutefois rapidement fastidieux pour les gros tableaux et n'est pas très utile si la taille
du tableau n'est pas connue. Une autre méthode est décrite dans la Section 9.23. La requête ci-dessus
est remplaçable par :
De la même façon, on trouve les lignes où le tableau n'a que des valeurs égales à 10000 avec :
SELECT * FROM
(SELECT paye_par_semaine,
generate_subscripts(paye_par_semaine, 1) AS s
FROM sal_emp) AS foo
WHERE paye_par_semaine[s] = 10000;
Vous pouvez aussi chercher dans un tableau en utilisant l'opérateur &&, qui vérifie si l'opérande gauche
a des éléments communs avec l'opérande droit. Par exemple :
192
Types de données
Les opérateurs sur les tableaux sont décrits plus en profondeur dans Section 9.18. Leurs performances
peuvent profiter d'un index approprié, comme décrit dans Section 11.2.
Vous pouvez aussi rechercher des valeurs spécifiques dans un tableau en utilisant les fonctions
array_position et array_positions. La première renvoie l'indice de la première occurrence
d'une valeur dans un tableau. La seconde renvoie un tableau avec les indices de toutes les occurrences
de la valeur dans le tableau. Par exemple :
SELECT
array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'],
'mon');
array_positions
-----------------
2
Astuce
Les tableaux ne sont pas des ensembles ; rechercher des éléments spécifiques dans un tableau
peut être un signe d'une mauvaise conception de la base de données. On utilise plutôt une
table séparée avec une ligne pour chaque élément faisant partie du tableau. Cela simplifie la
recherche et fonctionne mieux dans le cas d'un grand nombre d'éléments.
La routine de sortie du tableau place des guillemets doubles autour des valeurs des éléments si ce
sont des chaînes vides, si elles contiennent des accolades, des caractères délimiteurs, des guillemets
doubles, des antislashs ou des espaces ou si elles correspondent à NULL. Les guillemets doubles et
les antislashs intégrés aux valeurs des éléments sont échappés à l'aide d'un antislash. Pour les types de
données numériques, on peut supposer sans risque que les doubles guillemets n'apparaissent jamais,
mais pour les types de données texte, il faut être préparé à gérer la présence et l'absence de guillemets.
Par défaut, la valeur de la limite basse d'un tableau est initialisée à 1. Pour représenter des tableaux
avec des limites basses différentes, les indices du tableau doivent être indiqués explicitement avant
d'écrire le contenu du tableau. Cet affichage est constitué de crochets ([]) autour de chaque limite
basse et haute d'une dimension avec un délimiteur deux-points (:) entre les deux. L'affichage des
dimensions du tableau est suivi par un signe d'égalité (=). Par exemple :
193
Types de données
e1 | e2
----+----
1 | 6
(1 row)
La routine de sortie du tableau inclut les dimensions explicites dans le résultat uniquement lorsqu'au
moins une limite basse est différente de 1.
Si la valeur écrite pour un élément est NULL (toute variante), l'élément est considéré NULL. La
présence de guillemets ou d'antislashs désactive ce fonctionnement et autorise la saisie de la valeur
littérale de la chaîne « NULL ». De plus, pour une compatibilité ascendante avec les versions
antérieures à la version 8.2 de PostgreSQL, le paramètre de configuration array_nulls doit être
désactivé (off) pour supprimer la reconnaissance de NULL comme un NULL.
Comme indiqué précédemment, lors de l'écriture d'une valeur de tableau, des guillemets doubles
peuvent être utilisés autour de chaque élément individuel du tableau. Il faut le faire si leur absence
autour d'un élément induit en erreur l'analyseur de tableau. Par exemple, les éléments contenant des
crochets, virgules (ou tout type de données pour le caractère délimiteur correspondant), guillemets
doubles, antislashs ou espace (en début comme en fin) doivent avoir des guillemets doubles. Les
chaînes vides et les chaînes NULL doivent aussi être entre guillemets. Pour placer un guillemet
double ou un antislash dans une valeur d'élément d'un tableau, faites le précéder d'un antislash.
Alternativement, il est possible de se passer de guillemets et d'utiliser l'échappement par antislash pour
protéger tous les caractères de données qui seraient autrement interprétés en tant que caractères de
syntaxe de tableau.
Des espaces peuvent être ajoutées avant un crochet gauche ou après un crochet droit. Comme avant
tout élément individuel. Dans tous ces cas-là, les espaces sont ignorées. En revanche, les espaces à
l'intérieur des éléments entre guillemets doubles ou entourées de caractères autres que des espaces ne
sont pas ignorées.
Astuce
La syntaxe du constructeur ARRAY (voir Section 4.2.12) est souvent plus facile à utiliser que
la syntaxe de tableau littéral lors de l'écriture des valeurs du tableau en commandes SQL. Avec
ARRAY, les valeurs de l'élément individuel sont écrites comme elles le seraient si elles ne
faisaient pas partie d'un tableau.
194
Types de données
i double precision
);
La syntaxe est comparable à CREATE TABLE, sauf que seuls les noms de champs et leurs types
peuvent être spécifiés ; aucune contrainte (telle que NOT NULL) ne peut être incluse actuellement.
Notez que le mot-clé AS est essentiel ; sans lui, le système penserait à un autre genre de commande
CREATE TYPE et vous obtiendriez d'étranges erreurs de syntaxe.
Après avoir défini les types, nous pouvons les utiliser pour créer des tables :
ou des fonctions :
Quand vous créez une table, un type composite est automatiquement créé, avec le même nom que la
table, pour représenter le type de ligne de la table. Par exemple, si nous avions dit :
alors le même type composite element_inventaire montré ci-dessus aurait été créé et pourrait
être utilisé comme ci-dessus. Néanmoins, notez une restriction importante de l'implémentation
actuelle : comme aucune contrainte n'est associée avec un type composite, les contraintes indiquées
dans la définition de la table ne sont pas appliquées aux valeurs du type composite en dehors de la table.
(Pour contourner ceci, créer un domaine sur le type composite, et appliquer les contraintes désirées
en tant que contraintes CHECK du domaine.)
Voici un exemple :
'("fuzzy dice",42,1.99)'
195
Types de données
qui serait une valeur valide du type element_inventaire défini ci-dessus. Pour rendre un champ
NULL, n'écrivez aucun caractère dans sa position dans la liste. Par exemple, cette constante spécifie
un troisième champ NULL :
'("fuzzy dice",42,)'
Si vous voulez un champ vide au lieu d'une valeur NULL, saisissez deux guillemets :
'("",42,)'
Ici, le premier champ est une chaîne vide non NULL alors que le troisième est NULL.
(Ces constantes sont réellement seulement un cas spécial de constantes génériques de type discutées
dans la Section 4.1.2.7. La constante est initialement traitée comme une chaîne et passée à la routine de
conversion de l'entrée de type composite. Une spécification explicite de type pourrait être nécessaire
pour préciser le type à utiliser pour la conversion de la constante.)
La syntaxe d'expression ROW pourrait aussi être utilisée pour construire des valeurs composites. Dans
la plupart des cas, ceci est considérablement plus simple à utiliser que la syntaxe de chaîne littérale,
car vous n'avez pas à vous inquiéter des multiples couches de guillemets. Nous avons déjà utilisé cette
méthode ci-dessus :
Le mot-clé ROW est optionnel si vous avez plus d'un champ dans l'expression, donc ceci peut être
simplifié avec
La syntaxe de l'expression ROW est discutée avec plus de détails dans la Section 4.2.13.
Ceci ne fonctionnera pas, car le nom element est pris pour le nom d'une table, et non pas d'une
colonne de disponible, suivant les règles de la syntaxe SQL. Vous devez l'écrire ainsi :
ou si vous avez aussi besoin d'utiliser le nom de la table (par exemple dans une requête multitable),
de cette façon :
Maintenant, l'objet entre parenthèses est correctement interprété comme une référence à la colonne
element, puis le sous-champ peut être sélectionné à partir de lui.
Des problèmes syntaxiques similaires s'appliquent quand vous sélectionnez un champ à partir d'une
valeur composite. En fait, pour sélectionner un seul champ à partir du résultat d'une fonction renvoyant
une valeur composite, vous aurez besoin d'écrire quelque chose comme :
196
Types de données
Le nom du champ spécial * signifie « tous les champs », comme expliqué dans Section 8.16.5.
Le premier exemple omet ROW, le deuxième l'utilise ; nous pouvons le faire des deux façons.
Notez ici que nous n'avons pas besoin de (et, en fait, ne pouvons pas) placer des parenthèses autour
des noms de colonnes apparaissant juste après SET, mais nous avons besoin de parenthèses lors de la
référence à la même colonne dans l'expression à droite du signe d'égalité.
Et nous pouvons aussi spécifier des sous-champs comme cibles de la commande INSERT :
Si tous les sous-champs d'une colonne ne sont pas spécifiés, ils sont remplis avec une valeur NULL.
Dans PostgreSQL, une référence à un nom de table (ou à un alias) dans une requête est réellement
une référence au type composite de la ligne courante de la table. Par exemple, si nous avons une table
element_inventaire comme définie ci-dessus, nous pouvons écrire :
Cette requête renvoie une seule colonne comprenant une valeur composite, et nous pourrions obtenir
l'affichage suivant :
c
------------------------
("fuzzy dice",42,1.99)
(1 row)
Il faut noter néanmoins que les noms simples (c.-à-d. sans qualifiant) sont traités comme des noms de
colonnes puis comme des noms de table s'il n'y a pas de correspondance avec les noms de colonnes.
Donc cet exemple fonctionne seulement parce qu'il n'existe pas de colonne nommée c dans les tables
de la requête.
197
Types de données
alors, d'après le standard SQL, nous devrions obtenir le contenu de la table étendu en des colonnes
séparées :
PostgreSQL appliquera ce comportement étendu à toute expression de valeur composite, bien que,
comme indiqué ci-dessus, il est nécessaire d'ajouter des parenthèses autour de la valeur à qui .* est
appliquée à chaque fois qu'il ne s'agit pas d'un nom de table. Par exemple, si ma_fonction() est
une fonction renvoyant un type composite avec les colonnes a, b et c, alors ces deux requêtes donnent
le même résultat :
Astuce
PostgreSQL gère le fait d'étendre les colonnes en transformant la première forme en la seconde.
De ce fait, dans cet exemple, ma_fonction() serait appelé trois fois par ligne, quelle que
soit la syntaxe utilisée. S'il s'agit d'une fonction peu performante, vous pourriez souhaiter éviter
cela, ce que vous pouvez faire avec une requête de ce type :
Placer la fonction dans un élément LATERAL du FROM l'aide à ne pas être invoquée plus d'une
fois par ligne. m.* est toujours étendu en m.a, m.b, m.c, mais maintenant ces variables
sont juste des références à la sortie de l'élément FROM. (Le mot-clé LATERAL est optionnel
ici, mais nous le montrons pour clarifier que la fonction obtient x de la some_table.)
La syntaxe valeur_composite.* étend les colonnes avec un résultat de ce type quand il apparaît
au niveau haut d'une liste en sortie du SELECT, d'une liste RETURNING dans des commandes
INSERT/UPDATE/DELETE, d'une clause VALUES, ou d'un constructeur de ligne. Dans tous les autres
contextes (incluant l'imbrication dans une de ces constructions), attacher .* à une valeur composite
198
Types de données
value ne change pas la valeur, car cela signifie « toutes les colonnes » et donc la valeur composite est
produite de nouveau. Par exemple, si une_fonction() accepte un argument de valeur composite,
ces requêtes ont un résultat identique :