Dalibo SCOP
Formation | Module S1 |
Titre | Premiers SELECTs |
Révision | 24.12 |
https://dali.bo/s1_pdf | |
EPUB | https://dali.bo/s1_epub |
HTML | https://dali.bo/s1_html |
Slides | https://dali.bo/s1_slides |
TP | https://dali.bo/s1_tp |
TP (solutions) | https://dali.bo/s1_solutions |
Vous trouverez en ligne les différentes versions complètes de ce document.
Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :
Vous n’avez pas le droit d’utiliser cette création à des fins commerciales.
Si vous modifiez, transformez ou adaptez cette création, vous n’avez le droit de distribuer la création qui en résulte que sous un contrat identique à celui-ci.
Vous devez citer le nom de l’auteur original de la manière indiquée par l’auteur de l’œuvre ou le titulaire des droits qui vous confère cette autorisation (mais pas d’une manière qui suggérerait qu’ils vous soutiennent ou approuvent votre utilisation de l’œuvre). À chaque réutilisation ou distribution de cette création, vous devez faire apparaître clairement au public les conditions contractuelles de sa mise à disposition. La meilleure manière de les indiquer est un lien vers cette page web. Chacune de ces conditions peut être levée si vous obtenez l’autorisation du titulaire des droits sur cette œuvre. Rien dans ce contrat ne diminue ou ne restreint le droit moral de l’auteur ou des auteurs.
Le texte complet de la licence est disponible sur http://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode
Cela inclut les diapositives, les manuels eux-mêmes et les travaux pratiques. Cette formation peut également contenir quelques images et schémas dont la redistribution est soumise à des licences différentes qui sont alors précisées.
PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.
Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 13 à 17.
Sur les versions précédentes susceptibles d’être encore rencontrées en production, seuls quelques points très importants sont évoqués, en plus éventuellement de quelques éléments historiques.
Sauf précision contraire, le système d’exploitation utilisé est Linux.
Ce module a pour but de présenter le standard SQL. Un module ne permet pas de tout voir, aussi ce module se concentrera sur la lecture de données déjà présentes en base. Cela permet d’aborder aussi la question des types de données disponibles.
Si des données sont récoltées, organisées et stockées afin de répondre à un besoin spécifique, alors on parle de base de données. Une base de données peut utiliser différents supports : papier, fichiers informatiques, etc.
Le Système de Gestion de Bases de Données (SGBD), appelé Database Management System (DBMS) en anglais, assure la gestion d’une base de données informatisée. Il permet l’accès aux données et assure également la cohérence des données.
Au fil des années ont été développés plusieurs modèles de données, que nous allons décrire.
Les modèles hiérarchiques et réseaux ont été les premiers modèles de données utilisées dans les années 60 sur les mainframes IBM ou Bull. Ils ont été rapidement supplantés par le modèle relationnel car les requêtes étaient dépendantes du modèle de données. Il était nécessaire de connaître les liens entre les différents nœuds de l’arborescence pour concevoir les requêtes. Les programmes sont donc complètement dépendants de la structure de la base de données.
Des recherches souhaitent néanmoins arriver à rendre indépendant la vue logique de l’implémentation physique de la base de données.
Le modèle relationnel est issu des travaux du Docteur Edgar F. Codd qu’il a menés dans les laboratoires d’IBM à la fin des années 60. Ses travaux avaient pour but de rendre indépendant le stockage physique de la vue logique de la base de données. Et, mathématicien de formation, il s’est appuyé sur la théorie des ensembles et la logique des prédicats pour établir les fondements des bases de données relationnelles. Pour manipuler les données de façon ensembliste, le Dr Codd a mis au point le langage SQL. Ce langage est à l’origine du standard SQL qui a émergé dans les années 80 et qui a rendu le modèle relationnel très populaire.
Le modèle objet est, quant à lui, issu de la mouvance autour des langages objets. Du fait de l’absence d’un standard avéré, le modèle objet n’a jamais été populaire et est toujours resté dans l’ombre du modèle relationnel.
Le modèle relationnel a néanmoins été étendu par la norme SQL:1999 pour intégrer des fonctionnalités objets. On parle alors de modèle relationnel-objet. PostgreSQL en est un exemple, c’est un SGBDRO (Système de Gestion de Bases de Données Relationnel-Objet).
Les bases NoSQL sont une famille de bases de données qui répondent à d’autres besoins et contraintes que les bases relationnelles. Les bases NoSQL sont souvent des bases « sans schéma », la base ne vérifiant plus l’intégrité des données selon des contraintes définies dans le modèle de données. Chaque base de ce segment dispose d’un langage de requête spécifique, qui n’est pas normé. Une tentative de standardisation, débutée en 2011, n’a d’ailleurs abouti à aucun résultat.
Ce type de base offre souvent la possibilité d’offrir du
sharding simple à mettre en œuvre. Le sharding consiste à
répartir les données physiquement sur plusieurs serveurs. Certaines
technologies semblent mieux marcher que d’autres de ce point de vue là.
En contre-partie, la durabilité des données n’est pas assurée, au
contraire d’une base relationnelle qui assure la durabilité dès la
réponse à un COMMIT
.
Exemple de requête SQL :
SELECT person, SUM(score), AVG(score), MIN(score), MAX(score), COUNT(*)
FROM demo
WHERE score > 0 AND person IN('bob','jake')
GROUP BY person;
La même requête, pour MongoDB :
db.demo.group({
"key": {
"person": true
},
"initial": {
"sumscore": 0,
"sumforaverageaveragescore": 0,
"countforaverageaveragescore": 0,
"countstar": 0
},
"reduce": function(obj, prev) {
prev.sumscore = prev.sumscore + obj.score - 0;
prev.sumforaverageaveragescore += obj.score;
prev.countforaverageaveragescore++;
prev.minimumvaluescore = isNaN(prev.minimumvaluescore) ? obj.score :
Math.min(prev.minimumvaluescore, obj.score);
prev.maximumvaluescore = isNaN(prev.maximumvaluescore) ? obj.score :
Math.max(prev.maximumvaluescore, obj.score);
if (true != null) if (true instanceof Array) prev.countstar +=
true.length;
else prev.countstar++;
},
"finalize": function(prev) {
prev.averagescore = prev.sumforaverageaveragescore /
prev.countforaverageaveragescore;
delete prev.sumforaverageaveragescore;
delete prev.countforaverageaveragescore;
},
"cond": {
"score": {
"$gt": 0
},
"person": {
"$in": ["bob", "jake"]
}
}
});
Un des avantages de ces technologies, c’est qu’un modèle clé-valeur permet facilement d’utiliser des algorithmes de type MapReduce : diviser le problème en sous-problèmes traités parallèlement par différents nœuds (phase Map), puis synthétisés de façon centralisée (phase Reduce).
Les bases de données relationnelles ne sont pas incompatibles avec Map Reduce en soit. Simplement, le langage SQL étant déclaratif, il est conceptuellement opposé à la description fine des traitements qu’on doit réaliser avec MapReduce. C’est (encore une fois) le travail de l’optimiseur d’être capable d’effectuer ce genre d’opérations : la parallélisation (répartition d’une tâche sur plusieurs processeurs) est possible dans certains cas avec PostgreSQL.
Le modèle relationnel garantit l’indépendance entre la vue logique et la vue physique. L’utilisateur ne se préoccupe que des objets logiques (pour lire ou écrire des enregistrements), et le SGBD traduit la demande exprimée avec des objets logiques en actions à réaliser sur des objets physiques.
Les objets logiques sont appelés des relations. Ce sont généralement les tables, mais il existe d’autres objets qui sont aussi des relations (les vues par exemple, mais aussi les index et les séquences).
Le modèle relationnel se base sur la théorie des ensembles. Chaque relation contient un ensemble de données et ces différents ensembles peuvent se joindre suivant certaines conditions.
La logique des prédicats est un sous-ensemble de la théorie des ensembles. Elle sert à exprimer des formules logiques qui permettent de filtrer les ensembles de départ pour créer de nouveaux ensembles (autrement dit, filtrer les enregistrements d’une relation).
Cependant, tout élément d’un enregistrement n’est pas forcément connu à un instant t. Les filtres et les jointures doivent donc gérer trois états lors d’un calcul de prédicat : vrai, faux ou inconnu.
Les propriétés ACID sont le fondement même de toute bonne base de données. Il s’agit de l’acronyme des quatre règles que toute transaction (c’est-à-dire une suite d’ordres modifiant les données) doit respecter :
Les bases de données relationnelles les plus courantes depuis des décennies (PostgreSQL bien sûr, mais aussi Oracle, MySQL, SQL Server, SQLite…) se basent sur ces principes, même si elles font chacune des compromis différents suivant leurs cas d’usage, les compromis acceptés à chaque époque avec la performance et les versions.
Atomicité :
Une transaction doit être exécutée entièrement ou pas du tout, et surtout pas partiellement, même si elle est longue et complexe, même en cas d’incident majeur sur la base de données. L’exemple basique est une transaction bancaire : le montant d’un virement doit être sur un compte ou un autre, et en cas de problème ne pas disparaître ou apparaître en double. Ce principe garantit que les données modifiées par des transactions valides seront toujours visibles dans un état stable, et évite nombre de problèmes fonctionnels comme techniques.
Cohérence :
Un état cohérent respecte les règles de validité définies dans le modèle, c’est-à-dire les contraintes définies dans le modèle : types, plages de valeurs admissibles, unicité, liens entre tables (clés étrangères), etc. Le non-respect de ces règles par l’applicatif entraîne une erreur et un rejet de la transaction.
Isolation :
Des transactions simultanées doivent agir comme si elles étaient seules sur la base. Surtout, elles ne voient pas les données non validées des autres transactions. Ainsi une transaction peut travailler sur un état stable et fixe, et durer assez longtemps sans risque de gêner les autres transactions.
Il existe plusieurs « niveaux d’isolation » pour définir précisément le comportement en cas de lectures ou écritures simultanées sur les mêmes données et pour arbitrer avec les contraintes de performances ; le niveau le plus contraignant exige que tout se passe comme si toutes les transactions se déroulaient successivement.
Durabilité :
Une fois une transaction validée par le serveur (typiquement :
COMMIT
ne retourne pas d’erreur, ce qui valide la cohérence
et l’enregistrement physique), l’utilisateur doit avoir la garantie que
la donnée ne sera pas perdue ; du moins jusqu’à ce qu’il décide de la
modifier à nouveau. Cette garantie doit valoir même en cas d’événément
catastrophique : plantage de la base, perte d’un disque… C’est donc au
serveur de s’assurer autant que possible que les différents éléments
(disque, système d’exploitation…) ont bien rempli leur office. C’est à
l’humain d’arbitrer entre le niveau de criticité requis et les
contraintes de performances et de ressources adéquates (et fiables) à
fournir à la base de données.
NoSQL :
À l’inverse, les outils de la mouvance (« NoSQL », par exemple MongoDB ou Cassandra), ne fournissent pas les garanties ACID. C’est le cas de la plupart des bases non-relationnelles, qui reprennent le modèle BASE (Basically Available, Soft State, Eventually Consistent, soit succintement : disponibilité d’abord ; incohérence possible entre les réplicas ; cohérence… à terme, après un délai). Un intérêt est de débarasser le développeur de certaines lourdeurs apparentes liées à la modélisation assez stricte d’une base de données relationnelle. Cependant, la plupart des applications ont d’abord besoin des garanties de sécurité et cohérence qu’offrent un moteur transactionnel classique, et la décision d’utiliser un système ne les garantissant pas ne doit pas être prise à la légère ; sans parler d’autres critères comme la fragmentation du domaine par rapport au monde relationnel et son SQL (à peu près) standardisé. Avec le temps, les moteurs transactionnels ont acquis des fonctionnalités qui faisaient l’intérêt des bases NoSQL (en premier lieu la facilité de réplication et le stockage de JSON), et ces dernières ont tenté d’intégrer un peu plus de sécurité dans leur modèle.
Le langage SQL a été normalisé par l’ANSI en 1986 et est devenu une norme ISO internationale en 1987. Elle a subi plusieurs évolutions dans le but d’ajouter des fonctionnalités correspondantes aux attentes de l’industrie logicielle. Parmi ces améliorations, notons l’intégration de quelques fonctionnalités objets pour le modèle relationnel-objet.
Il n’y a pas de règles établies concernant l’écriture de requêtes SQL. Il faut néanmoins avoir à l’esprit qu’il s’agit d’un langage à part entière et, au même titre que ce qu’un développeur fait avec n’importe quel code source, il convient de l’écrire de façon lisible.
Quelle est la requête la plus lisible ?
celle-ci ?
select groupeid,datecreationitem from itemagenda where typeitemagenda = 5 and
in(12225,12376) and datecreationitem > now() order by groupeid,
groupeid datecreationitem ;
ou celle-ci ?
SELECT groupeid, datecreationitem
FROM itemagenda
WHERE typeitemagenda = 5
AND groupeid IN (12225,12376)
AND datecreationitem > now()
ORDER BY groupeid, datecreationitem;
Cet exemple est tiré du forum postgresql.fr.
Une requête SQL peut être commentée au même titre qu’un programme standard.
Le marqueur --
permet de signifier à l’analyseur
syntaxique que le reste de la ligne est commenté, il n’en tiendra donc
pas compte dans l’analyse de la requête.
Un commentaire peut aussi se présenter sous la forme d’un bloc de commentaire, le bloc pouvant occuper plusieurs lignes :
/* Ceci est un commentaire
sur plusieurs
lignes
*/
Aucun des éléments compris entre le marqueur de début de bloc
/*
et le marqueur de fin de bloc */
ne sera
pris en compte. Certains SGBDR propriétaires utilisent ces commentaires
pour y placer des informations (appelées hints
sur Oracle)
qui permettent d’influencer le comportement de l’optimiseur, mais
PostgreSQL ne possède pas ce genre de mécanisme.
Le langage SQL est divisé en quatre sous-ensembles qui ont chacun un but différent.
Les ordres DDL (pour Data Definition Language
)
permettent de définir les structures de données. On y retrouve les
ordres suivants :
CREATE
: crée un objetALTER
: modifie la définition d’un objetDROP
: supprime un objetTRUNCATE
: vide un objetCOMMENT
: ajoute un commentaire sur un objetLes ordres DML (pour Data Manipulation Language
)
permettent l’accès et la modification des données. On y retrouve les
ordres suivants :
SELECT
: lit les données d’une ou plusieurs tablesINSERT
: ajoute des données dans une tableUPDATE
: modifie les données d’une tableDELETE
: supprime les données d’une tableLes ordres DCL (pour Data Control Language
) permettent
de contrôler l’accès aux données. Ils permettent plus précisément de
donner ou retirer des droits à des utilisateurs ou des groupes sur les
objets de la base de données :
GRANT
: donne un droit d’accès à un rôle sur un
objetREVOKE
: retire un droit d’accès d’un rôle sur un
objetEnfin, les ordres TCL (pour
Transaction Control Language
) permettent de contrôler les
transactions :
BEGIN
: ouvre une transactionCOMMIT
: valide les traitements d’une transactionROLLBACK
: annule les traitements d’une
transactionSAVEPOINT
: crée un point de reprise dans une
transactionSET TRANSACTION
: modifie les propriétés d’une
transaction en coursLes ordres BEGIN
et COMMIT
sont souvent
implicites dans le cas d’ordres isolés, si l’« autocommit » est activé.
Vous devez entrer donc manuellement BEGIN ;
/
COMMIT ;
pour faire des transactions de plus d’un ordre.
C’est en fait dépendant de l’outil client, et psql
a un
paramètre autocommit
à on
par défaut. Mais ce
n’est pas forcément le cas sur votre configuration précise et le défaut
peut être inversé sur d’autres bases de données (notamment Oracle).
Le ROLLBACK
est implicite en cas de sortie brutale.
Noter que, contrairement à d’autres bases (et surtout Oracle),
PostgreSQL n’effectue pas de COMMIT
implicite sur certaines
opérations : les ordres CREATE TABLE
,
DROP TABLE
, TRUNCATE TABLE
… sont
transactionnels, n’effectuent aucun COMMIT
et peuvent être
annulés par ROLLBACK
.
La lecture des données se fait via l’ordre SELECT
. Il
permet de récupérer des données d’une ou plusieurs tables (il faudra
dans ce cas joindre les tables). Il permet aussi de faire appel à des
fonctions stockées en base.
L’ordre SELECT
est composé de différents éléments dont
la plupart sont optionnels. L’exemple de syntaxe donné ici n’est pas
complet.
La syntaxe complète de l’ordre SELECT
est disponible
dans le manuel de
PostgreSQL.
La liste de sélection décrit le format de la table virtuelle qui est
retournée par l’ordre SELECT
. Les types de données des
colonnes retournées seront conformes au type des éléments donnés dans la
liste de sélection.
La liste de sélection décrit le format de la table virtuelle qui est
retournée par l’ordre SELECT
. Cette liste est composée
d’expressions séparées par une virgule.
Chaque expression peut être une simple constante, peut faire référence à des colonnes d’une table lue par la requête, et peut être un appel à une fonction.
Une expression peut être plus complexe. Par exemple, elle peut combiner plusieurs constantes et/ou colonnes à l’aide d’opérations. Parmi les opérations les plus classiques, les opérateurs arithmétiques classiques sont utilisables pour les données numériques. L’opérateur de concaténation permet de concaténer des chaînes de caractères.
L’expression d’une colonne peut être une constante :
SELECT 1;
column?
?----------
1
1 row) (
Elle peut aussi être une référence à une colonne d’une table :
SELECT appellation.libelle
FROM appellation;
Comme il n’y a pas d’ambiguïté avec la colonne libelle
,
la référence de la colonne appellation.libelle
peut être
simplifiée en libelle
:
SELECT libelle
FROM appellation;
Le SGBD saura déduire la table et la colonne mises en œuvre dans
cette requête. Il faudra néanmoins utiliser la forme complète
table.colonne
si la requête met en œuvre des tables qui
possèdent des colonnes qui portent des noms identiques.
Une requête peut sélectionner plusieurs colonnes. Dans ce cas, les expressions de colonnes sont définies sous la forme d’une liste dont chaque élément est séparé par une virgule :
SELECT id, libelle, region_id
FROM appellation;
Le joker *
permet de sélectionner l’ensemble des
colonnes d’une table, elles apparaitront dans leur ordre physique
(attention si l’ordre change !) :
SELECT *
FROM appellation;
Si une requête met en œuvre plusieurs tables, on peut choisir de retourner toutes les colonnes d’une seule table :
SELECT appellation.*
FROM appellation;
Enfin, on peut récupérer un tuple entier de la façon suivante :
SELECT appellation
FROM appellation;
Une expression de colonne peut également être une opération, par exemple une addition :
SELECT 1 + 1;
column?
?----------
2
1 row) (
Ou une soustraction :
SELECT annee, nombre - 10
FROM stock;
Afin de pouvoir nommer de manière adéquate les colonnes du résultat
d’une requête SELECT
, le mot clé AS
permet de
définir un alias de colonne. Cet alias sera utilisé dans le résultat
pour nommer la colonne en sortie :
SELECT 1 + 1 AS somme;
somme-------
2
1 row) (
Cet alias n’est pas utilisable dans le reste de la requête (par
exemple dans la clause WHERE
).
Par défaut, SELECT
retourne tous les résultats d’une
requête. Parfois, des doublons peuvent se présenter dans le résultat. La
clause DISTINCT
permet de les éviter en réalisant un
dédoublonnage des données avant de retourner le résultat de la
requête.
Il faut néanmoins faire attention à l’utilisation systématique de la
clause DISTINCT
. En effet, elle entraîne une déduplication
systématique des données juste avant de retourner les résultats de la
requête, ce qui va souvent consommer de la ressource mémoire, voire de
la ressource disque si le volume de données à trier est important. De
plus, cela va augmenter le temps de réponse de la requête du fait de
cette opération supplémentaire.
En règle générale, la clause DISTINCT
devient inutile
lorsqu’elle doit trier un ensemble qui contient des colonnes qui sont
déjà uniques. Si une requête récupère une clé primaire, les données sont
uniques par définition. Le SELECT DISTINCT
sera alors
transformé en simple SELECT
.
Les constantes et valeurs des colonnes peuvent être dérivées selon le type des données manipulées.
Les données numériques peuvent être dérivées à l’aide des opérateurs
arithmétiques standards : +
, -
,
/
, *
. Elles peuvent faire l’objet d’autres
calculs à l’aide de fonctions internes et de fonctions définies par
l’utilisateur.
La requête suivante permet de calculer le volume total en litres de vin disponible dans le stock du caviste :
SELECT SUM(c.contenance * s.nombre)
FROM stock s
JOIN contenant c
ON (contenant_id=c.id);
Les données de type chaînes de caractères peuvent être concaténées à
l’aide de l’opérateur dédié ||
. Cet opérateur permet de
concaténer deux chaînes de caractères mais également des données
numériques avec une chaîne de caractères.
Dans la requête suivante, l’opérateur de concaténation est utilisé pour ajouter l’unité. Le résultat est ainsi implicitement converti en chaîne de caractères.
SELECT SUM(s.contenance * s.nombre) || ' litres'
FROM stock AS s
JOIN contenant c
ON (contenant_id=c.id);
De manière générale, il n’est pas recommandé de réaliser les opérations de formatage des données dans la base de données. La base de données ne doit servir qu’à récupérer les résultats, le formatage étant assuré par l’application.
Différentes fonctions sont également applicables aux chaînes de caractères, de même qu’aux autres types de données.
Parmi les fonctions les plus couramment utilisées, la fonction
NOW()
permet d’obtenir la date et l’heure courante. Elle ne
prend aucun argument. Elle est souvent utilisée, notamment pour affecter
automatiquement la valeur de l’heure courante à une colonne.
La fonction AGE(timestamp)
permet de connaître l’âge
d’une date par rapport à la date courante.
La fonction CHAR_LENGTH(varchar)
permet de connaître la
longueur d’une chaîne de caractère.
Enfin, la fonction COUNT(*)
permet de compter le nombre
de lignes. Il s’agit d’une fonction d’agrégat, il n’est donc pas
possible d’afficher les valeurs d’autres colonnes sans faire appel aux
capacités de regroupement des lignes de SQL.
Exemples
Affichage de l’heure courante :
SELECT NOW();
NOW------------------------------
2017-08-29 14:45:17.213097+02
Affichage de l’âge du 1er janvier 2000 :
SELECT AGE(date '2000-01-01');
AGE------------------------
17 years 7 mons 28 days
Affichage de la longueur de la chaîne “Dalibo” :
SELECT CHAR_LENGTH('Dalibo');
CHAR_LENGTH-------------
6
Affichage du nombre de lignes de la table vin
:
SELECT COUNT(*) FROM vin;
COUNT
-------
6067
La clause FROM
permet de lister les tables qui sont
mises en œuvres dans la requêtes SELECT
. Il peut s’agir
d’une table physique, d’une vue ou d’une sous-requête. Le résultat de
leur lecture sera une table du point de vue de la requête qui la met en
œuvre.
Plusieurs tables peuvent être mises en œuvre, généralement dans le cadre d’une jointure.
De la même façon qu’on peut créer des alias de colonnes, on peut créer des alias de tables. La table sera ensuite référencée uniquement par cet alias dans la requête. Elle ne pourra plus être référencée par son nom réel. L’utilisation du nom réel provoquera d’ailleurs une erreur.
Le mot clé AS
permet de définir un alias de table. Le
nom réel de la table se trouve à gauche, l’alias se trouve à droite.
L’exemple suivant définie un alias reg
sur la table
region
:
SELECT id, libelle
FROM region AS reg;
Le mot clé AS
est optionnel :
SELECT id, libelle
FROM region reg;
La requête suivante montre l’utilisation d’un alias pour les deux
tables mises en œuvre dans la requête. La table stock
a
pour alias s
et la table contenant
a pour
alias c
. Les deux tables possèdent toutes les deux une
colonnes id
, ce qui peut poser une ambiguïté dans la clause
de jointure (ON (contenant_id=c.id)
). La condition de
jointure portant sur la colonne contenant_id
de la table
stock
, son nom est unique et ne porte pas à ambiguïté. La
condition de jointure porte également sur la colonne id
de
table contenant
, il faut préciser le nom complet de la
colonne en utilisant le préfixe c
pour la nommer :
c.id
.
SELECT SUM(c.contenance * s.nombre) AS volume_total
FROM stock s
JOIN contenant c
ON (contenant_id=c.id);
Enfin, la forme
reference_table AS alias (alias_colonne1, ...)
permet de
définir un alias de table et définir par la même occasion des alias de
colonnes. Cette forme est peu recommandé car les alias de colonnes
dépendent de l’ordre physique - ou de création - de ces colonnes. Cet
ordre peut changer dans le temps et donc amener à des erreurs :
SELECT id_region, nom_region
FROM region AS reg (id_region, nom_region);
Avec PostgreSQL, les noms des objets sont automatiquement convertis en minuscule, sauf s’ils sont englobés entre des guillemets doubles. Si jamais ils sont créés avec une casse mixte en utilisant les guillemets doubles, chaque appel à cet objet devra utiliser la bonne casse et les guillemets doubles. Il est donc conseillé d’utiliser une notation des objets ne comprenant que des caractères minuscules.
Il est aussi préférable de ne pas utiliser d’accents ou de caractères exotiques dans les noms des objets.
La clause WHERE
permet de définir des conditions de
filtrage des données. Ces conditions de filtrage sont appelées des
prédicats.
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 ignoré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é.
Un prédicat est composé d’une expression qui est soumise à un
opérateur de prédicat pour être éventuellement comparé à une autre
expression. L’opérateur de prédicat retourne alors true
si
la condition est vérifiée ou false
si elle ne l’est pas ou
NULL
si son résultat ne peut être calculé.
Les opérateurs de comparaison sont les opérateurs de prédicats les
plus souvent utilisés. L’opérateur d’égalité =
peut être
utilisé pour vérifier l’égalité de l’ensemble des types de données
supportés par PostgreSQL. Il faudra faire attention à ce que les données
comparées soient de même type.
L’opérateur <>
signifie « pas égal à » et peut
aussi s’écrire !=
.
L’opérateur NOT
est une négation. Si un prédicat est
vrai, l’opérateur NOT
retournera faux. À l’inverse, si un
prédicat est faux, l’opérateur NOT
retournera vrai. La
clause NOT
se place devant l’expression entière.
Exemples
Sélection de la région dont l’identifiant est égal à 3 (et ensuite différent de 3) :
SELECT *
FROM region
WHERE id = 3;
SELECT *
FROM region
WHERE NOT id = 3;
Les opérateurs logiques OR
et AND
permettent de combiner plusieurs prédicats dans la clause
WHERE
.
L’opérateur OR
est un OU logique. Il retourne vrai si au
moins un des deux prédicats combinés est vrai. L’opérateur
AND
est un ET logique. Il retourne vrai si et seulement si
les deux prédicats combinés sont vrais.
Au même titre qu’une multiplication ou une division sont prioritaires
sur une addition ou une soustraction dans un calcul, l’évaluation de
l’opérateur AND
est prioritaire sur celle de l’opérateur
OR
. Et, tout comme dans un calcul, il est possible de
protéger les opérations prioritaires en les encadrant de
parenthèses.
Exemples
Dans le stock, affiche les vins dont le nombre de bouteilles est inférieur à 2 ou supérieur à 16 :
SELECT *
FROM stock
WHERE nombre < 2
OR nombre > 16;
L’opérateur LIKE
permet de réaliser une recherche simple
sur motif. La chaîne exprimant le motif de recherche peut utiliser deux
caractères joker : _
et %
. Le caractère
_
prend la place d’un caractère inconnu, qui doit toujours
être présent. Le caractère %
est un joker qui permet
d’exprimer que PostgreSQL doit trouver entre 0 et plusieurs
caractères.
Exploiter la clause LIKE
avec un motif sans joker ne
présente pas d’intérêt. Il est préférable dans ce cas d’utiliser
l’opérateur d’égalité.
Le mot clé ESCAPE 'c'
permet de définir un caractère
d’échappement pour protéger les caractères _
et
%
qui font légitimement partie de la chaîne de caractère du
motif évalué. Lorsque PostgreSQL rencontre le caractère d’échappement
indiqué, les caractères _
et %
seront évalués
comme étant les caractères _
et %
et non comme
des jokers.
L’opérateur LIKE
dispose d’une déclinaison qui n’est pas
sensible à la casse. Il s’agit de l’opérateur ILIKE
.
Exemples
Création d’un jeu d’essai :
CREATE TABLE motif (chaine varchar(30));
INSERT INTO motif (chaine) VALUES ('Durand'), ('Dupont'), ('Dupond'),
'Dupon'), ('Dupuis'); (
Toutes les chaînes commençant par la suite de caractères
Dur
:
SELECT * FROM motif WHERE chaine LIKE 'Dur%';
chaine--------
Durand
Toutes les chaînes terminant par d
:
SELECT * FROM motif WHERE chaine LIKE '%d';
chaine--------
Durand Dupond
Toutes les chaînes qui commencent par Dupon
suivi d’un
caractère inconnu. La chaîne Dupon
devrait être
ignorée :
SELECT * FROM motif WHERE chaine LIKE 'Dupon_';
chaine------------
Dupont Dupond
La clause IN
permet de vérifier que l’expression de
gauche est égale à une valeur présente dans l’expression de droite, qui
est une liste d’expressions. La négation peut être utilisée en utilisant
la construction NOT IN
.
L’opérateur BETWEEN
permet de vérifier que la valeur
d’une expression est comprise entre deux bornes. Par exemple,
l’expression valeur BETWEEN 1 AND 10
revient à exprimer la
condition suivante : valeur >= 1 AND valeur<= 10
. La
négation peut être utilisée en utilisant la construction
NOT BETWEEN
.
Exemples
Recherche les chaînes qui sont présentes dans la liste
IN
:
SELECT * FROM motif WHERE chaine IN ('Dupont', 'Dupond', 'Ducobu');
chaine--------
Dupont Dupond
La clause ORDER BY
permet de trier les lignes du
résultat d’une requête selon une ou plusieurs expressions combinées.
L’expression la plus simple est le nom d’une colonne. Dans ce cas, les lignes seront triées selon les valeurs de la colonne indiquée, et par défaut dans l’ordre ascendant, c’est-à-dire de la valeur la plus petite à la plus grande pour une donnée numérique ou temporelle, et dans l’ordre alphabétique pour une donnée textuelle.
Les lignes peuvent être triées selon une expression plus complexe, par exemple en dérivant la valeur d’une colonne.
L’ordre de tri peut être modifié à l’aide de la clause
DESC
qui permet un tri dans l’ordre descendant, donc de la
valeur la plus grande à la plus petite (ou alphabétique inverse le cas
échéant).
La clause NULLS
permet de contrôler l’ordre d’apparition
des valeurs NULL
. La clause NULLS FIRST
permet
de faire apparaître d’abord les valeurs NULL
puis les
valeurs non NULL
selon l’ordre de tri. La clause
NULLS LAST
permet de faire apparaître d’abord les valeurs
non NULL
selon l’ordre de tri suivies par les valeurs NULL.
Si cette clause n’est pas précisée, alors PostgreSQL utilise
implicitement NULLS LAST
dans le cas d’un tri ascendant
(ASC
, par défaut) ou NULLS FIRST
dans le cas
d’un tri descendant (DESC
, par défaut).
Exemples
Tri de la table region
selon le nom de la région :
SELECT *
FROM region
ORDER BY libelle;
Tri de la table stock
selon le nombre de bouteille, dans
l’ordre décroissant :
SELECT *
FROM stock
ORDER BY nombre DESC;
Enfin, la clause COLLATE
permet d’influencer sur l’ordre
de tri des chaînes de caractères.
La clause OFFSET
permet d’exclure les n
premières lignes du résultat. Toutes les autres lignes sont
ramenées.
La clause FETCH
permet de limiter le résultat d’une
requête. La requête retournera au maximum n
lignes de
résultats. Elle en retournera moins, voire aucune, si la requête ne peut
ramener suffisamment de lignes. La clause FIRST
ou
NEXT
est obligatoire mais le choix de l’une ou l’autre n’a
aucune conséquence sur le résultat.
La clause FETCH
est synonyme de la clause
LIMIT
. Mais LIMIT
est une clause propre à
PostgreSQL et quelques autres SGBD. Il est recommandé d’utiliser
FETCH
pour se conformer au standard.
Ces deux opérations peuvent être combinées. La norme impose de faire
apparaître la clause OFFSET
avant la clause
FETCH
. PostgreSQL permet néanmoins d’exprimer ces clauses
dans un ordre différent, mais la requête ne pourra pas être portée sur
un autre SGBD sans transformation.
Il faut faire attention au fait que ces fonctions ne permettent pas
d’obtenir des résultats stables si les données ne sont pas triées
explicitement. En effet, le standard SQL ne garantie en aucune façon
l’ordre des résultats à moins d’employer la clause
ORDER BY
.
Exemples
La fonction generate_series
permet de générer une suite
de valeurs numériques. Par exemple, une suite comprise entre 1 et
10 :
SELECT * FROM generate_series(1, 10);
generate_series-----------------
1
...)
(10
10 rows) (
La clause FETCH
permet donc de limiter le nombre de
lignes du résultats :
SELECT * FROM generate_series(1, 10) FETCH FIRST 5 ROWS ONLY;
generate_series-----------------
1
2
3
4
5
5 rows) (
La clause LIMIT
donne un résultat équivalent :
SELECT * FROM generate_series(1, 10) LIMIT 5;
generate_series-----------------
1
2
3
4
5
5 rows) (
La clause OFFSET 4
permet d’exclure les quatre premières
lignes et de retourner les autres lignes du résultat :
SELECT * FROM generate_series(1, 10) OFFSET 4;
generate_series-----------------
5
6
7
8
9
10
6 rows) (
Les clauses LIMIT
et OFFSET
peuvent être
combinées pour ramener les deux lignes en excluant les quatre
premières :
SELECT * FROM generate_series(1, 10) OFFSET 4 LIMIT 2;
generate_series-----------------
5
6
2 rows) (
Il est possible d’utiliser plusieurs tables dans une requête
SELECT
. Lorsque c’est le cas, et sauf cas particulier, on
fera correspondre les lignes d’une table avec les lignes d’une autre
table selon certains critères. Cette mise en correspondance s’appelle
une jointure et les critères de correspondances s’appellent une
condition de jointure.
Si aucune condition de jointure n’est donnée, chaque ligne de la première table est mise en correspondance avec toutes les lignes de la seconde table. C’est un produit cartésien. En général, un produit cartésien n’est pas souhaitable et est généralement le résultat d’une erreur de conception de la requête.
Exemples
Création d’un jeu de données simple :
CREATE TABLE mere (id integer PRIMARY KEY, val_mere text);
CREATE TABLE fille (
integer PRIMARY KEY,
id_fille integer REFERENCES mere(id),
id_mere
val_fille text
);
INSERT INTO mere (id, val_mere) VALUES (1, 'mere 1');
INSERT INTO mere (id, val_mere) VALUES (2, 'mere 2');
INSERT INTO fille (id_fille, id_mere, val_fille) VALUES (1, 1, 'fille 1');
INSERT INTO fille (id_fille, id_mere, val_fille) VALUES (2, 1, 'fille 2');
Pour procéder à une jointure entre les tables mere
et
fille
, les identifiants id_mere
de la table
fille
doivent correspondre avec les identifiants
id
de la table mere
:
SELECT * FROM mere, fille
WHERE mere.id = fille.id_mere;
id | val_mere | id_fille | id_mere | val_fille
----+----------+----------+---------+-----------
1 | mere 1 | 1 | 1 | fille 1
1 | mere 1 | 2 | 1 | fille 2
2 rows) (
Un produit cartésien est créé en omettant la condition de jointure, le résultat n’a plus de sens :
SELECT * FROM mere, fille;
id | val_mere | id_fille | id_mere | val_fille
----+----------+----------+---------+-----------
1 | mere 1 | 1 | 1 | fille 1
1 | mere 1 | 2 | 1 | fille 2
2 | mere 2 | 1 | 1 | fille 1
2 | mere 2 | 2 | 1 | fille 2
4 rows) (
PostgreSQL propose l’ensemble des types de données du standard SQL, à
l’exception du type BLOB
qui a toutefois un équivalent.
Mais PostgreSQL a été conçu pour être extensible et permet de créer
facilement des types de données spécifiques. C’est pourquoi PostgreSQL
propose un certain nombre de types de données spécifiques qui peuvent
être intéressants.
On utilise des types de données pour représenter une information de manière pertinente. Les valeurs possibles d’une donnée vont dépendre de son type. Par exemple, un entier long ne permet par exemple pas de coder des valeurs décimales. De la même façon, un type entier ne permet pas de représenter une chaîne de caractère, mais l’inverse est possible.
L’intérêt du typage des données est qu’il permet également à la base
de données de valider les données manipulées. Ainsi un entier
integer
permet de représenter des valeurs comprises entre
-2,147,483,648 et 2,147,483,647. Si l’utilisateur tente d’insérer une
donnée qui dépasse les capacités de ce type de données, une erreur lui
sera retournée. On retrouve ainsi la notion d’intégrité des données.
Comme pour les langages de programmation fortement typés, cela permet de
détecter davantage d’erreurs, plus tôt : à la compilation dans les
langages typés, ou ici des la première exécution d’une requête, plutôt
que plus tard, quand une chaîne de caractère ne pourra pas être
convertie à la volée en entier par exemple.
Le choix d’un type de données va également influencer la façon dont
les données sont représentées. En effet, toute donnée a une
représentation textuelle, une représentation en mémoire et sur disque.
Ainsi, un integer
est représenté sous la forme d’une suite
de 4 octets, manipulables directement par le processeur, alors que sa
représentation textuelle est une suite de caractères. Cela a une
implication forte sur les performances de la base de données.
Le type de données choisi permet également de déterminer les
opérations que l’on pourra appliquer. Tous les types de données
permettent d’utiliser des opérateurs qui leur sont propres. Ainsi il est
possible d’additionner des entiers, de concaténer des chaînes de
caractères, etc. Si une opération ne peut être réalisée nativement sur
le type de données, il faudra utiliser des conversions coûteuses.
Vaut-il mieux additionner deux entiers issus d’une conversion d’une
chaîne de caractère vers un entier ou additionner directement deux
entiers ? Vaut-il mieux stocker une adresse IP avec un
varchar
ou avec un type de données dédié ?
Il est à noter que l’utilisateur peut contrôler lui-même certains
types de données paramétrés. Le paramètre représente la longueur ou la
précision du type de données. Ainsi, un type varchar(15)
permettra de représenter des chaînes de caractères de 15 caractères
maximum.
Les types de données standards permettent de traiter la plupart des situations qui peuvent survenir. Dans certains cas, il peut être nécessaire de faire appel aux types spécifiques à PostgreSQL, par exemple pour stocker des adresses IP avec le type spécifique et bénéficier par la même occasion de toutes les classes d’opérateurs qui permettent de manipuler simplement ce type de données.
Et si cela ne s’avère pas suffisant, PostgreSQL permet à l’utilisateur de créer lui-même ses propres types de données, ainsi que les classes d’opérateurs et fonctions permettant d’indexer ces données.
Le standard SQL propose des types standards pour stocker des chaînes de caractères (de taille fixe ou variable), des données numériques (entières, à virgule flottante) et des booléens.
Le standard SQL propose également des types standards pour stocker des éléments temporels (date, heure, la combinaison des deux avec ou sans fuseau horaire, intervalle).
D’utilisation plus rare, SQL permet également de stocker des chaînes de bit et des données validées au format XML. Le format JSON est de plus en plus courant.
Le type char(n)
permet de stocker des chaînes de
caractères de taille fixe, donnée par l’argument n
. Si la
chaîne que l’on souhaite stocker est plus petite que la taille donnée à
la déclaration de la colonne, elle sera complétée par des espaces à
droite. Si la chaîne que l’on souhaite stocker est trop grande, une
erreur sera levée.
Le type varchar(n)
permet de stocker des chaînes de
caractères de taille variable. La taille maximale de la chaîne est
donnée par l’argument n
. Toute chaîne qui excèdera cette
taille ne sera pas prise en compte et génèrera une erreur. Les chaînes
de taille inférieure à la taille limite seront stockées sans
altérations.
La longueur de chaîne est mesurée en nombre de caractères sous PostgreSQL. Ce n’est pas forcément le cas dans d’autres SGBD.
La norme SQL définit que les chaînes de caractères sont représentées
encadrées de guillemets simples (caractère '
). Le guillemet
double (caractère "
) ne peut être utilisé car il sert à
protéger la casse des noms d’objets. PostgreSQL interprétera alors la
chaîne comme un nom d’objet et générera une erreur.
Une représentation correcte d’une chaîne de caractères est donc de la forme suivante :
'chaîne de caractères'
Les caractères '
doivent être doublés s’ils apparaissent
dans la chaîne :
'J''ai acheté des croissants'
Une extension de la norme par PostgreSQL permet d’utiliser les
méta-caractères des langages tels que le C, par exemple \n
pour un retour de ligne, \t
pour une tabulation, etc. :
'chaîne avec un retour \nde ligne et une \ttabulation' E
Le standard SQL propose des types spécifiques pour stocker des
entiers signés. Le type smallint
permet de stocker des
valeurs codées sur 2 octets, soit des valeurs comprises entre -32768 et
+32767. Le type integer
ou int
, codé sur 4
octets, permet de stocker des valeurs comprises entre -2147483648 et
+2147483647. Enfin, le type bigint
, codé sur 8 octets,
permet de stocker des valeurs comprises entre -9223372036854775808 et
9223372036854775807. Le standard SQL ne propose pas de stockage
d’entiers non signés.
Le standard SQL permet de stocker des valeurs décimales en utilisant
les types à virgules flottantes. Avant de les utiliser, il faut avoir à
l’esprit que ces types de données ne permettent pas de stocker des
valeurs exactes, des différences peuvent donc apparaître entre la donnée
insérée et la donnée restituée. Le type real
permet
d’exprimer des valeurs à virgules flottantes sur 4 octets, avec une
précision relative de six décimales. Le type
double precision
permet d’exprimer des valeurs à virgules
flottantes sur huit octets, avec une précision relative de 15
décimales.
Beaucoup d’applications, notamment les applications financières, ne
se satisfont pas de valeurs inexactes. Pour cela, le standard SQL
propose le type numeric
, ou son synonyme
decimal
, qui permet de stocker des valeurs exactes, selon
la précision arbitraire donnée. Dans la déclaration
numeric(precision, echelle)
, la partie
precision
indique combien de chiffres significatifs sont
stockés, la partie echelle
exprime le nombre de chiffres
après la virgule. Au niveau du stockage, PostgreSQL ne permet pas
d’insérer des valeurs qui dépassent les capacités du type déclaré. En
revanche, si l’échelle de la valeur à stocker dépasse l’échelle déclarée
de la colonne, alors sa valeur est simplement arrondie.
On peut aussi utiliser numeric
sans aucune contrainte de
taille, pour stocker de façon exacte n’importe quel nombre.
Au moins un chiffre doit être placé avant ou après le point décimal,
s’il est utilisé. Au moins un chiffre doit suivre l’indicateur
d’exponentiel (caractère e
), s’il est présent. Il peut ne
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
forcément considéré comme faisant part de la constante ; il est un
opérateur appliqué à la constante.
Les exemples suivants montrent différentes représentations valides de constantes numériques :
42
3.5
4.
.001
5e2
1.925e-3
Une constante numérique contenant soit un point décimal soit un
exposant est tout d’abord présumée du type integer
si sa
valeur est contenue dans le type integer (4 octets). Dans le cas
contraire, il est présumé de type bigint
si sa valeur entre
dans un type bigint
(8 octets). Dans le cas contraire, 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écifiques en la convertissant. Par exemple,
vous pouvez forcer une valeur numérique à être traitée comme un type
real
(float4
) en écrivant :
REAL '1.23'
Le type boolean
permet d’exprimer des valeurs
booléennes, c’est-à-dire une valeur exprimant vrai ou faux. Comme tous
les types de données en SQL, une colonne booléenne peut aussi ne pas
avoir de valeur, auquel cas sa valeur sera NULL
.
Un des intérêts des types booléens est de pouvoir écrire :
SELECT * FROM ma_table WHERE valide;
SELECT * FROM ma_table WHERE not consulte;
Le type date
exprime une date. Ce type ne connaît pas la
notion de fuseau horaire.
Le type time
exprime une heure. Par défaut, il ne
connaît pas la notion de fuseau horaire. En revanche, lorsque le type
est déclaré comme time with time zone
, il prend en compte
un fuseau horaire. Mais cet emploi n’est pas recommandé. En effet, une
heure convertie d’un fuseau horaire vers un autre pose de nombreux
problèmes. En effet, le décalage horaire dépend également de la date :
quand il est 6h00, heure d’été, à Paris, il est 21H00 sur la côte
Pacifique aux États-Unis mais encore à la date de la veille.
Le type timestamp
permet d’exprimer une date et une
heure. Par défaut, il ne connaît pas la notion de fuseau horaire.
Lorsque le type est déclaré timestamp with time zone
, il
est adapté aux conversions d’heure d’un fuseau horaire vers un autre car
le changement de date sera répercuté dans la composante date du type de
données. Il est précis à la microseconde.
Le format de saisie et de restitution des dates et heures dépend du
paramètre DateStyle
. La documentation de ce paramètre
permet de connaître les différentes valeurs possibles. Il reste
néanmoins recommandé d’utiliser les fonctions de formatage de date qui
permettent de rendre l’application indépendante de la configuration du
SGBD.
La norme ISO (ISO-8601) impose le format de date « année-mois-jour ».
La norme SQL est plus permissive et permet de restituer une date au
format « jour/mois/année » si DateStyle est égal à
'SQL, DMY'
.
SET datestyle = 'ISO, DMY';
SELECT current_timestamp;
now-------------------------------
2017-08-29 16:11:58.290174+02
SET datestyle = 'SQL, DMY';
SELECT current_timestamp;
now--------------------------------
29/08/2017 16:12:25.650716 CEST
Expression d’une date, forcément sans gestion du fuseau horaire :
DATE '2017-08-29'
Expression d’une heure sans fuseau horaire :
TIME '10:20:10'
Ou, en spécifiant explicitement l’absence de fuseau horaire :
TIME WITHOUT TIME ZONE '10:20:10'
Expression d’une heure, avec fuseau horaire invariant. Cette forme est déconseillée :
TIME WITH TIME ZONE '10:20:10' AT TIME ZONE 'CEST'
Expression d’un timestamp sans fuseau horaire :
TIMESTAMP '2017-08-29 10:20:10'
Ou, en spécifiant explicitement l’absence de fuseau horaire :
TIMESTAMP WITHOUT TIME ZONE '2017-08-29 10:20:10'
Expression d’un timestamp avec fuseau horaire, avec microseconde :
SELECT TIMESTAMP WITH TIME ZONE '2017-08-29 10:20:10.123321'
AT TIME ZONE 'Europe/Paris' ;
timezone
---------------------------- 2017-08-29 10:20:10.123321
Expression d’un intervalle d’une journée :
INTERVAL '1 day'
Il est possible de cumuler plusieurs expressions :
INTERVAL '1 year 1 day'
Les valeurs possibles sont :
YEAR
pour une année ;MONTH
pour un mois ;DAY
pour une journée ;HOUR
pour une heure ;MINUTE
pour une minute ;SECOND
pour une seconde.Le paramètre timezone
du postgresql.conf
permet de positionner le fuseau horaire de l’instance PostgreSQL. Elle
est initialisée par défaut en fonction de l’environnement du système
d’exploitation.
Le fuseau horaire de l’instance peut également être défini au cours
de la session à l’aide de la commande SET TIME ZONE
.
La France métropolitaine utilise deux fuseaux horaires normalisés. Le
premier, CET
, correspond à Central European Time
ou autrement dit à l’heure d’hiver en Europe centrale. Le second,
CEST
, correspond à Central European Summer Time,
c’est-à-dire l’heure d’été en Europe centrale.
La liste des fuseaux horaires supportés est disponible dans la table
système pg_timezone_names
:
SELECT * FROM pg_timezone_names ;
name | abbrev | utc_offset | is_dst
----------------------------------+--------+------------+--------
GB | BST | 01:00:00 | t
ROK | KST | 09:00:00 | f
Greenwich | GMT | 00:00:00 | f (...)
Il est possible de positionner le fuseau horaire au niveau de la
session avec l’ordre SET TIME ZONE
:
SET TIME ZONE "Europe/Paris";
SELECT now();
now
------------------------------- 2017-08-29 10:19:56.640162+02
SET TIME ZONE "Europe/Kiev";
SELECT now();
now
------------------------------- 2017-08-29 11:20:17.199983+03
Conversion implicite d’une donnée de type timestamp
dans
le fuseau horaire courant :
SET TIME ZONE "Europe/Kiev";
SELECT TIMESTAMP WITH TIME ZONE '2017-08-29 10:20:10 CEST';
timestamptz
------------------------ 2017-08-29 11:20:10+03
ou encore (AT LOCAL TIME
est disponible depuis
PostgreSQL 17) :
SHOW timezone;
TimeZone
-------------- Europe/Paris
SELECT '2024-11-26 00:00:00 +11' AT LOCAL TIME ; -- minuit à Nouméas
time
--------------------- 2024-11-26 01:00:00
Conversion explicite d’une donnée de type timestamp
dans
un autre fuseau horaire :
SELECT '2017-08-29 06:00:00' AT TIME ZONE 'US/Pacific';
timezone
--------------------- 28/08/2017 21:00:00
ou encore :
SELECT '2023-10-23 6:30:00+02'::timestamptz AT TIME ZONE 'Europe/London';
timezone
--------------------- 2023-10-23 05:30:00
Les types bit
et bit varying
permettent de
stocker des masques de bits. Le type bit(n)
est à longueur
fixe alors que le type bit varying(n)
est à longueur
variable mais avec un maximum de n
bits.
Le type xml
permet de stocker des documents XML. Par
rapport à une chaîne de caractères simple, le type xml
apporte la vérification de la structure du document XML ainsi que des
fonctions de manipulations spécifiques (voir la
documentation officielle).
Les types json
et jsonb
permettent de
stocker des documents JSON. Ces deux types permettent de vérifier la
structure du document JSON ainsi que des fonctions de manipulations
spécifiques (voir la
documentation officielle). On préférera de loin le type
jsonb
pour son stockage optimisé (en binaire), et ses
fonctionnalités supplémentaires, notamment en terme d’indexation.
Les types smallserial
, serial
et
bigserial
permettent d’obtenir des fonctionnalités
similaires aux types autoincrement
rencontrés dans d’autres
SGBD.
Néanmoins, ces types restent assez proches de la norme car ils
définissent au final une colonne qui utilise un type et des objets
standards. Selon le type dérivé utilisé, la colonne sera de type
smallint
, integer
ou bigint
. Une
séquence sera également créée et la colonne prendra pour valeur par
défaut la prochaine valeur de cette séquence.
Il est à noter que la notion d’identité apparaît en version 10 et qu’il est préférable de passer par cette contrainte que par ces types dérivés.
Attention : ces types n’interdisent pas l’insertion manuelle de doublons. Une contrainte de clé primaire explicite reste nécessaire pour les éviter.
Le type text
est l’équivalent du type
varchar
mais sans limite de taille de la chaîne de
caractère.
Les types standards ne sont pas toujours suffisants pour représenter certaines données. À l’instar d’autres SGBDR, PostgreSQL propose des types de données pour répondre à certains besoins.
On notera le type bytea
qui permet de stocker des objets
binaires dans une table. Le type array
permet de stocker
des tableaux et enum
des énumérations.
Les types json
et hstore
permettent de
stocker des documents non structurés dans la base de données. Le premier
au format JSON, le second dans un format de type clé/valeur. Le type
hstore
est d’ailleurs particulièrement efficace car il
dispose de méthodes d’indexation et de fonctions de manipulations
performantes. Le type json
a été completé par
jsonb
qui permet de stocker un document JSON binaire et
optimisé, et d’accéder à une propriété sans désérialiser intégralement
le document.
Le type range
permet de stocker des intervalles de
données. Ces données sont ensuite manipulables par un jeu d’opérateurs
dédiés et par le biais de méthodes d’indexation permettant d’accélérer
les recherches.
PostgreSQL permet de créer ses propres types de données. Les usages
les plus courants consistent à créer des types composites pour permettre
à des fonctions de retourner des données sous forme tabulaire (retour de
type SETOF
).
L’utilisation du type énuméré (enum
) nécessite aussi la
création d’un type spécifique. Le type sera alors employé pour déclarer
les objets utilisant une énumération.
Enfin, si l’on souhaite étendre les types intervalles
(range
) déjà disponibles, il est nécessaire de créer un
type spécifique.
La création d’un type scalaire est bien plus marginale. Elle permet en effet d’étendre les types fournis par PostgreSQL mais nécessite d’avoir des connaissances fines des mécanismes de PostgreSQL. De plus, dans la majeure partie des cas, les types standards suffisent en général à résoudre les problèmes qui peuvent se poser à la conception.
Quant aux types tableaux, ils sont créés implicitement par PostgreSQL quand un utilisateur crée un type personnalisé.
Exemples
Utilisation d’un type enum
:
CREATE TYPE arc_en_ciel AS ENUM (
'red', 'orange', 'yellow', 'green', 'blue', 'purple'
);
CREATE TABLE test (id integer, couleur arc_en_ciel);
INSERT INTO test (id, couleur) VALUES (1, 'red');
INSERT INTO test (id, couleur) VALUES (2, 'pink');
value for enum arc_en_ciel: "pink"
ERROR: invalid input 1: INSERT INTO test (id, couleur) VALUES (2, 'pink'); LINE
Création d’un type interval float8_range
:
CREATE TYPE float8_range AS RANGE (subtype = float8, subtype_diff = float8mi);
Le standard SQL permet de traiter des ensembles d’enregistrements. Un
enregistrement correspond à une ligne dans une relation. Il est possible
de lire ces relations grâce à l’ordre SELECT
.
Bases de données - de la modélisation au SQL
Ce livre présente les notions essentielles pour modéliser une base de données et utiliser le langage SQL pour utiliser les bases de données créées. L’auteur appuie ses exercices sur PostgreSQL.
SQL avancé : programmation et techniques avancées
Ce livre est écrit par une personne ayant participé à l’élaboration du standard SQL. Il a souhaité montré les bonnes pratiques pour utiliser le SQL pour résoudre un certain nombre de problèmes de tous les jours. Le livre s’appuie cependant sur la norme SQL-92, voire SQL-89. L’édition anglaise SQL for Smarties est bien plus à jour. Pour les anglophones, la lecture de l’ensemble des livres de Joe Celko est particulièrement recommandée.
SQL : Au coeur des performances
Il s’agit du livre de référence sur les performances en SQL. Il dresse un inventaire des différents cas d’utilisation des index par la base de données, ce qui permettra de mieux prévoir l’indexation dès la conception. Ce livre s’adresse à un public avancé.
The Manga Guide to Databases
The Art of SQL
Ce livre s’adresse également à un public avancé. Il présente également les bonnes pratiques lorsque l’on utilise une base de données.
La version en ligne des solutions de ces TP est disponible sur https://dali.bo/s1_solutions.
Ce TP utilise la base tpc. La base tpc (dump de 31 Mo, pour 267 Mo sur le disque au final) et ses utilisateurs peuvent être installés comme suit :
curl -kL https://dali.bo/tp_tpc -o /tmp/tpc.dump
curl -kL https://dali.bo/tp_tpc_roles -o /tmp/tpc_roles.sql
# Exécuter le script de création des rôles
psql < /tmp/tpc_roles.sql
# Création de la base
createdb --owner tpc_owner tpc
# L'erreur sur un schéma 'public' existant est normale
pg_restore -d tpc /tmp/tpc.dump
rm -- /tmp/tpc.dump /tmp/tpc_roles.sql
Les mots de passe sont dans le script
/tmp/tpc_roles.sql
. Pour vous connecter :
$ psql -U tpc_admin -h localhost -d tpc
Le schéma suivant montre les différentes tables de la base :
Afficher l’heure courante, au méridien de Greenwich.
Afficher la date et l’heure qu’il sera dans 1 mois et 1 jour.
Ajouter 1 au nombre de type réel ‘1.42’. Pourquoi ce résultat ? Quel type de données permet d’obtenir un résultat correct ?
Afficher le contenu de la table
pays
en classant les pays dans l’ordre alphabétique.
Afficher les pays contenant la lettre
a
, majuscule ou minuscule. Plusieurs solutions sont possibles.
Afficher le nombre lignes de commandes dont la quantité commandée est comprise entre 5 et 10.
Pour les questions suivantes, il faudra se connecter à la base
tpc
.
Avec psql
, pour changer de base, vous pouvez lancer la
méta-commande suivante:
\c tpc
Pour chaque pays, afficher son nom et la région du monde dont il fait partie.
nom_pays | nom_region
--------------------+-----------
ALGÉRIE | Afrique
(...)
Afficher le nombre total de clients français et allemands.
Sortie attendue :
count
-------
12418
Afficher le numéro de commande et le nom du client ayant passé la commande. Seul un sous-ensemble des résultats sera affiché : les 20 premières lignes du résultat seront exclues et seules les 20 suivantes seront affichées. Il faut penser à ce que le résultat de cette requête soit stable entre plusieurs exécutions.
Sortie attendue :
numero_commande | nom_client
-----------------+--------------
67 | Client112078
68 | Client33842
(...)
Afficher les noms et codes des pays qui font partie de la région « Europe ».
Sortie attendue :
nom_pays | code_pays
----------------+-----------
ALLEMAGNE | DE
(...)
Pour chaque pays, afficher une chaîne de caractères composée de son nom, suivi entre parenthèses de son code puis, séparé par une virgule, du nom de la région dont il fait partie.
Sortie attendue :
detail_pays
----------------------
ALGÉRIE (DZ), Afrique
(...)
Pour les clients ayant passé des commandes durant le mois de janvier 2011, affichez les identifiants des clients, leur nom, leur numéro de téléphone et le nom de leur pays.
Sortie attendue :
client_id | nom | telephone | nom_pays
----------+--------------+-----------------+-------------------------------
83279 | Client83279 | 12-835-574-2048 | JAPON
Pour les dix premières commandes de l’année 2011, afficher le numéro de la commande, la date de la commande ainsi que son âge.
Sortie attendue :
numero_commande | date_commande | age
----------------+---------------+---------------------------
11364 | 2011-01-01 | 1392 days 15:25:19.012521
(...)
Afficher l’heure courante, au méridien de Greenwich.
SELECT now() AT TIME ZONE 'GMT';
Afficher la date et l’heure qu’il sera dans 1 mois et 1 jour.
SELECT now() + INTERVAL '1 month 1 day';
Ajouter 1 au nombre de type réel ‘1.42’. Pourquoi ce résultat ? Quel type de données permet d’obtenir un résultat correct ?
SELECT REAL '1.42' + 1 AS resultat;
resultat------------------
2.41999995708466
1 row) (
Le type de données real
est un type numérique à virgule
flottante, codé sur 4 octets. Il n’offre pas une précision suffisante
pour les calculs précis. Son seul avantage est la vitesse de calcul.
Pour effectuer des calculs précis, il vaut mieux privilégier le type de
données numeric
.
Pour les questions suivantes, il faudra se connecter à la base
tpc
.
Avec psql
, pour changer de base, vous pouvez lancer la
méta-commande suivante:
\c tpc
Afficher le contenu de la table
pays
en classant les pays dans l’ordre alphabétique.
SELECT * FROM pays ORDER BY nom_pays;
Afficher les pays contenant la lettre
a
, majuscule ou minuscule. Plusieurs solutions sont possibles.
SELECT * FROM pays WHERE lower(nom_pays) LIKE '%a%';
SELECT * FROM pays WHERE nom_pays ILIKE '%a%';
SELECT * FROM pays WHERE nom_pays LIKE '%a%' OR nom_pays LIKE '%A%';
En terme de performances, la seconde variante sera plus rapide sur un
volume de données important si l’on dispose du bon index. La taille de
la table pays
ne permet pas d’observer de différence
significative sur cette requête.
Afficher le nombre lignes de commandes dont la quantité commandée est comprise entre 5 et 10.
SELECT count(*)
FROM lignes_commandes
WHERE quantite BETWEEN 5 AND 10;
Autre écriture possible :
SELECT count(*)
FROM lignes_commandes
WHERE quantite >= 5
AND quantite <= 10;
Pour chaque pays, afficher son nom et la région du monde dont il fait partie.
SELECT nom_pays, nom_region
FROM pays p, regions r
WHERE p.region_id = r.region_id;
Afficher le nombre total de clients français et allemands.
SELECT count(*)
FROM clients cl, contacts cn, pays p
WHERE cl.contact_id = cn.contact_id
AND cn.code_pays = p.code_pays
AND p.nom_pays IN ('FRANCE', 'ALLEMAGNE');
À noter que cette syntaxe est obsolète, il faut utiliser la clause
JOIN
, plus lisible et plus complète, qui sera vue plus loin
:
SELECT count(*)
FROM clients cl
JOIN contacts cn ON (cl.contact_id = cn.contact_id)
JOIN pays p ON (cn.code_pays = p.code_pays)
WHERE p.nom_pays IN ('FRANCE', 'ALLEMAGNE');
En connaissant les codes de ces pays, il est possible d’éviter la
lecture de la table pays
:
SELECT count(*)
FROM clients cl, contacts cn
WHERE cl.contact_id = cn.contact_id
AND cn.code_pays IN ('FR', 'DE');
L’équivalent avec la syntaxe JOIN
serait :
SELECT count(*)
FROM clients cl
JOIN contacts cn ON (cl.contact_id = cn.contact_id)
WHERE cn.code_pays IN ('FR', 'DE');
Afficher le numéro de commande et le nom du client ayant passé la commande. Seul un sous-ensemble des résultats sera affiché : les 20 premières lignes du résultat seront exclues et seules les 20 suivantes seront affichées. Il faut penser à ce que le résultat de cette requête soit stable entre plusieurs exécutions.
La syntaxe normalisée SQL impose d’écrire la requête de la façon suivante. La stabilité du résultat de la requête est garantie par un tri explicite, s’il n’est pas précisé, la base de données va retourner les lignes dans l’ordre physique qui est susceptible de changer entre deux exécutions :
SELECT numero_commande, nom AS nom_client
FROM commandes cm, clients cl, contacts cn
WHERE cm.client_id = cl.client_id
AND cl.contact_id = cn.contact_id
ORDER BY numero_commande
FIRST 20 ROWS ONLY
FETCH 20; OFFSET
Mais PostgreSQL supporte également la clause LIMIT
:
SELECT numero_commande, nom AS nom_client
FROM commandes cm, clients cl, contacts cn
WHERE cm.client_id = cl.client_id
AND cl.contact_id = cn.contact_id
ORDER BY numero_commande
LIMIT 20
20; OFFSET
Et l’équivalent avec la syntaxe JOIN
serait :
SELECT numero_commande, nom AS nom_client
FROM commandes cm
JOIN clients cl ON (cm.client_id = cl.client_id)
JOIN contacts cn ON (cl.contact_id = cn.contact_id)
ORDER BY numero_commande
LIMIT 20
20; OFFSET
Afficher les noms et codes des pays qui font partie de la région « Europe ».
SELECT nom_pays, code_pays
FROM regions r, pays p
WHERE r.region_id = p.region_id
AND r.nom_region = 'Europe';
Et l’équivalent avec la syntaxe JOIN
serait :
SELECT nom_pays, code_pays
FROM regions r
JOIN pays p ON (r.region_id = p.region_id)
WHERE r.nom_region = 'Europe';
Pour chaque pays, afficher une chaîne de caractères composée de son nom, suivi entre parenthèses de son code puis, séparé par une virgule, du nom de la région dont il fait partie.
SELECT nom_pays || ' (' || code_pays || '), ' || nom_region
FROM regions r, pays p
WHERE r.region_id = p.region_id;
Et l’équivalent avec la syntaxe JOIN
serait :
SELECT nom_pays || ' (' || code_pays || '), ' || nom_region
FROM regions r
JOIN pays p ON (r.region_id = p.region_id);
Pour les clients ayant passé des commandes durant le mois de janvier 2011, affichez les identifiants des clients, leur nom, leur numéro de téléphone et le nom de leur pays.
SELECT cl.client_id, nom, telephone, nom_pays
FROM clients cl, commandes cm, contacts cn, pays p
WHERE cl.client_id = cm.client_id
AND cl.contact_id = cn.contact_id
AND cn.code_pays = p.code_pays
AND date_commande BETWEEN '2011-01-01' AND '2011-01-31';
Le troisième module de la formation abordera les jointures et leurs syntaxes. À l’issue de ce prochain module, la requête de cet exercice pourrait être écrite de la façon suivante :
SELECT cl.client_id, nom, telephone, nom_pays
FROM clients cl
JOIN commandes cm
USING (client_id)
JOIN contacts co
USING (contact_id)
JOIN pays p
USING (code_pays)
WHERE date_commande BETWEEN '2011-01-01' AND '2011-01-31';
Pour les dix premières commandes de l’année 2011, afficher le numéro de la commande, la date de la commande ainsi que son âge.
SELECT numero_commande, date_commande, now() - date_commande AS age
FROM commandes
WHERE date_commande BETWEEN '2011-01-01' AND '2011-12-31'
ORDER BY date_commande
LIMIT 10;