Formation DEV1
Dalibo SCOP
25.09
5 septembre 2025
| Formation | Formation DEV1 |
| Titre | Introduction à SQL |
| Révision | 25.09 |
| ISBN | N/A |
| https://dali.bo/dev1_pdf | |
| EPUB | https://dali.bo/dev1_epub |
| HTML | https://dali.bo/dev1_html |
| Slides | https://dali.bo/dev1_slides |
Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :
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.
NULL (= inconnu)Gestion transactionnelle : la force des bases de données relationnelles :
SELECT
+, -,
/, *||now()age(timestamp)extract('year' FROM timestamp)date_part('Y',timestamp)char_length(chaine)count(*)a-z, _ et chiffres, si possibleNom_Objet devient nom_objet" conserve la casse
"Nom_Objet"predicat OR predicatpredicat AND predicatASC ou DESC
NULL : NULLS FIRST
ou NULLS LASTCOLLATE collationOFFSET nFETCH {FIRST | NEXT} n ROWS ONLYLIMIT nOFFSET doit apparaitre avant FETCHFROM
,char, varcharinteger, smallint,
bigintreal, double precision,
floatnumeric, decimalbooleandate, timetimestampintervalbit, bit varyingchar(n)
varchar(n)
''chaîne de caractères'E ou eE'chaîne de caractères'U&U&'chaîne de caractères'smallint, integer,
bigintreal, double precisionnumeric(precision, echelle),
decimal(precision, echelle)booleanTRUEFALSENULL (valeur absente/non pertinente)datetimetimestampintervalTYPE 'chaine''YYYY-MM-DD HH24:MI:SS.ssssss''YYYY-MM-DD HH24:MI:SS.ssssss+fuseau''YYYY-MM-DD HH24:MI:SS.ssssss' AT TIME ZONE 'fuseau'INTERVAL 'durée interval'timezoneSET TIME ZONE'Europe/Paris''CEST''+02'bit(n), bit varying(n)BB'01010101'XX'55'xmljson : texte, avec validation du format JSONjsonb (binaire)serialsmallserialbigserialIDENTITYtextbyteaarrayenumcidr, inet, macaddruuidjson, jsonb, hstorerangeSELECTN’hésitez pas, c’est le moment !
smallint : 2 octetsinteger : 4 octetsbigint : 8 octetsreal (= float4)double precision (= float8)numeric(…, …)>, >=, =,
<=,<+, -, /, *,
modulo (%), puissance (^)AND, OR, XOR
(&, |, #)>>,
<<integer ou biginteger :
numeric :
float, real :
SUM(), AVG(),
etc.date
YYYY-MM-DDtime
HH24:MI:SStimestamp (without time zone !)
timestamp with time zone = timestamptz
2019-11-13 15:33:00.824096+01timestamp without time zoneinterval
range pour tout couple « début/fin
»interval/generate_seriesvarchar(_n_), textbyteaSource des collations :
CREATE COLLATION danois (provider = icu, locale = 'da-x-icu') ;
hstore : clé/valeur historiquejson
jsonb
int4range, int8range,
numrangedaterange, tsrange,
tstzrange*, &&,
<@ ou @>point, line, lseg
(segment),polygon, circle, box,
pathData Definition Language)Data Manipulation Language)Transaction Control Language)Data Definition Languagepublic
CREATE SCHEMA nom_schémaALTER SCHEMA nom_schéma
DROP SCHEMA [ IF EXISTS ] nom_schéma [ CASCADE ]nom_schema.nom_objetsearch_pathSET search_path = schema1,schema2,public;$user, publicMINVALUEMAXVALUESTARTINCREMENTCACHECYCLEALTER SEQUENCE nom [ INCREMENT increment ]
[ MINVALUE valeurmin | NO MINVALUE ]
[ MAXVALUE valeurmax | NO MAXVALUE ]
[ START [ WITH ] début ]
[ RESTART [ [ WITH ] nouveau_début ] ]
[ CACHE cache ] [ [ NO ] CYCLE ]
[ OWNED BY { nom_table.nom_colonne | NONE } ]nextval('nom_sequence')currval('nom_sequence')nextval() doit être appelé avant dans la même
sessionserial/bigserial/smallserial
nextval(…)IDENTITYCREATE TABLEDEFAULT
CREATE TABLE … (LIKE table clause_inclusion)INCLUDING DEFAULTSINCLUDING CONSTRAINTSINCLUDING INDEXESALTER TABLEUNIQUEUNIQUEUNIQUE != index UNIQUEUNIQUE et NOT NULLFOREIGN KEYMATCH
MATCH FULL (complète)MATCH SIMPLE (partielle)GENERATED … AS IDENTITY
ALWAYSBY DEFAULTserialON UPDATEON DELETENOT NULLCHECKDEFERRABLE, NOT DEFERRABLEINITIALLY DEFERED,
INITIALLY IMMEDIATECREATE TABLE paquet (
code text PRIMARY KEY,
reception timestamptz DEFAULT now(),
livraison timestamptz DEFAULT now() + interval '3d',
largeur int, longueur int, profondeur int,
volume int
GENERATED ALWAYS AS ( largeur * longueur * profondeur )
STORED CHECK (volume > 0.0)
) ;DEFAULT : expressions très simples, modifiablesGENERATED
SELECT peut lire les données d’une table ou plusieurs
tables
INSERTUPDATEMERGEDELETESELECTBEGINCOMMITROLLBACKSAVEPOINTBEGIN ;COMMIT ;ROLLBACK ;SAVEPOINT nom_savepoint ;RELEASE SAVEPOINT nom_savepoint ;ROLLBACK TO SAVEPOINT nom_savepoint ;CREATE, ALTER, DROPINSERT, UPDATE, DELETENULLGROUP BY, HAVINGJOINCASEUNION, EXCEPT,
INTERSECTNULLNULLNULL :
NULL telle qu’elle est implémentée dans
SQL peut poser plus de problèmes qu’elle n’en résout. Son comportement
est parfois étrange et est source de nombreuses erreurs et de
confusions.NULL le plus possible
NULL correctement lorsqu’il le fautNULL pour INSERT et
UPDATENULL est indiqué explicitement dans les
assignationsINSERTNULL
NOT NULLNULLNULL est inapplicable
NULLWHERE :
IS NULL ou IS NOT NULLAND :
false si NULL AND falseNULL si NULL AND true ou
NULL AND NULLOR :
true si NULL OR trueNULL si NULL OR false ou
NULL OR NULLNULLcount(*)NULL par une autre valeur
COALESCE(attribut, …);NULL si l’ensemble est vide
count()regr_slope(Y,X)regr_intercept(Y,X)corr (Y,X)HAVINGWHERE s’applique sur les lignes luesHAVING s’applique sur les lignes groupées( et )SELECTWHEREHAVINGINANYALLIN vaut true
IN vaut false
NULLIN vaut NULL
NULLNULLNOT IN vaut true
NULLNOT IN vaut false
NOT IN vaut NULL
NULLNULLNULLNULLFROMJOIN ou dans
WHERE ?JOIN
WHERE
CROSS JOININNER JOIN
NULLNULLNULLNULLswitch en C ou JavaCASE à l’intérieur d’autres expressions
CASEELSE
UNIONINTERSECTEXCEPTCAST (expression AS type)expression::typechaîne1 || chaîne2char_length(chaîne)lower(chaîne)upper(chaîne)substring(chaîne [from int] [for int])position(sous-chaîne in chaîne)+-*/
%Arrondi : round (numeric)
Troncature : trunc (numeric [, precision])
Entier le plus petit : floor (numeric)
Entier le plus grand : ceil (numeric)
current_datecurrent_timecurrent_timestamp / now()age (timestamp)age (timestamp, timestamp)date_trunc(text, timestamp)date_trunc('month' from date_naissance)extract(text, timestamp)extract('year' from date_naissance)timestamp,
time ou date
date/time - date/time = intervaldate/time + time = date/timedate/time + interval = date/timeinterval
interval * numeric = intervalinterval / numeric = intervalinterval + interval = intervalto_char(timestamp, text)to_char(current_timestamp, 'DD/MM/YYYY HH24:MI:SS')to_date(text, text)
to_date('05/12/2000', 'DD/MM/YYYY')to_timestamp(text, text)
to_timestamp('05/12/2000 12:00:00', 'DD/MM/YYYY HH24:MI:SS')datestylegenerate_series (timestamp_debut, timestamp_fin, intervalle)SELECTSELECT * FROM vue;GRANT et REVOKEsecurity_barrierWITH CHECK OPTIONINSTEAD OFPREPARE, préparation du plan d’exécution d’une
requêteEXECUTE, passage des paramètres de la requête et
exécution réelleN’hésitez pas, c’est le moment !
Ces TP utilisent 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.dumpLes mots de passe sont dans le script
/tmp/tpc_roles.sql. Pour vous connecter :
Le schéma suivant montre les différentes tables de la base :
Ces TP utilisent 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.dumpLes mots de passe sont dans le script
/tmp/tpc_roles.sql. Pour vous connecter :
Le schéma suivant montre les différentes tables de la base :
LIMIT/OFFSETLATERALUPSERT : INSERT ou
UPDATELIMITFETCH FIRST xx ROWSLIMITOFFSET est problématique
RETURNINGINSERTUPDATEDELETEINSERT ou UPDATE ?
INSERT … ON CONFLICT DO { NOTHING | UPDATE }INSERTINSERTMERGEMERGE INTO mesures_capteurs c
USING import_mesures_capteurs i
ON c.id = i .id
WHEN NOT MATCHED THEN
INSERT (id, top_mesure, derniere_mesure, derniere_maj)
VALUES (i.id, i.mesure, i.mesure, current_timestamp)
WHEN MATCHED AND ( c.derniere_maj + INTERVAL '10 days' <= current_timestamp ) THEN
DELETE
WHEN MATCHED AND ( c.top_mesure > i.mesure ) THEN
UPDATE
SET derniere_mesure = i.mesure, derniere_maj = current_timestamp
WHEN MATCHED THEN
UPDATE SET top_mesure = i.mesure, derniere_mesure = i.mesure, derniere_maj = current_timestamp
;LATERAL
foreachSELECT titre,
top_5_messages.date_publication,
top_5_messages.extrait
FROM sujets,
LATERAL(SELECT date_publication,
substr(message, 0, 100) AS extrait
FROM messages
WHERE sujets.sujet_id = messages.sujet_id
ORDER BY date_publication DESC
LIMIT 5) top_5_messages
ORDER BY sujets.date_modification DESC,
top_5_messages.date_publication DESC
LIMIT 25;LATERAL optionnelleWITH et WITH RECURSIVEMATERIALIZED pour forcer une barrière
d’optimisationINSERT/UPDATE/DELETERETURNINGWITH RECURSIVEWITH RECURSIVE parcours_menu AS (
SELECT menu_id, libelle, parent_id,
libelle AS arborescence
FROM entrees_menu
WHERE libelle = 'Terminal'
AND parent_id IS NULL
UNION ALL
SELECT menu.menu_id, menu.libelle, menu.parent_id,
arborescence || '/' || menu.libelle
FROM entrees_menu menu
JOIN parcours_menu parent
ON (menu.parent_id = parent.menu_id)
)
SELECT * FROM parcours_menu;UPDATE perduSELECT FOR UPDATESERIALIZABLESELECT FOR UPDATESELECT FOR UPDATE SKIP LOCKEDSSI : Serializable Snapshot Isolation
serializabledefault_transaction_isolation=serializable dans la
configurationFILTERWINDOWGROUPING SETS, ROLLUP,
CUBEWITHIN GROUPSemployespopulation et continentsGROUP BYGROUP BYORDER BY dans la fonction d’agrégatarray_agg, string_agg
et xmlaggFILTERCASE complexesDes fonctionnalités trop peu connues
SELECT matricule, salaire, service,
SUM(salaire) OVER (PARTITION BY service)
AS total_salaire_service
FROM employes; matricule | salaire | service | total_salaire_service
-----------+----------+-------------+-----------------------
00000004 | 4500.00 | Courrier | 7500.00
00000020 | 3000.00 | Courrier | 7500.00
00000001 | 10000.00 | Direction | 10000.00
00000006 | 4000.00 | Publication | 7000.00
00000040 | 3000.00 | Publication | 7000.00OVER ( … )
SELECT matricule, nom, salaire, service,
rank() OVER (ORDER BY salaire),
dense_rank() OVER (ORDER BY salaire) -- pas de trous
FROM employes ; matricule | nom | salaire | service | rank | dense_rank
-----------+----------+----------+-------------+------+------------
00000020 | Lagaffe | 3000.00 | Courrier | 1 | 1
00000040 | Lebrac | 3000.00 | Publication | 1 | 1
00000006 | Prunelle | 4000.00 | Publication | 3 | 2
00000004 | Fantasio | 4500.00 | Courrier | 4 | 3
00000001 | Dupuis | 10000.00 | Direction | 5 | 4
OVER (PARTITION BY … ORDER BY …)SELECT continent, pays, population,
rank() OVER (PARTITION BY continent
ORDER BY population DESC)
AS rang
FROM population; continent | pays | population | rang
------------------+--------------------+------------+------
Afrique | Nigéria | 173.6 | 1
Afrique | Éthiopie | 94.1 | 2
Afrique | Égypte | 82.1 | 3
Afrique | Rép. dém. du Congo | 67.5 | 4
(…)
Amérique du Nord | États-Unis | 320.1 | 1
Amérique du Nord | Canada | 35.2 | 2
(…)first_value (), last_value,
nth (), lag(), lead()…SELECT pays, continent, population,
lag(population) OVER (PARTITION BY continent
ORDER BY population DESC)
FROM population
WHERE continent in ('Europe','Afrique'); pays | continent | population | lag
-----------------------+-----------+------------+--------
Nigéria | Afrique | 173.6 |
Éthiopie | Afrique | 94.1 | 173.6
Égypte | Afrique | 82.1 | 94.1
…
Tunisie | Afrique | 11.0 | 14.1
Féd. de Russie | Europe | 142.8 |
Allemagne | Europe | 82.7 | 142.8
France métropolitaine | Europe | 64.3 | 82.7
Royaume-Uni | Europe | 63.1 | 64.3
Italie | Europe | 61.0 | 63.1
…
Malte | Europe | 0.4 | 0.5
(53 lignes)first_value(colonne)
last_value(colonne)
nth_value(colonne, n)
SELECT pays, continent, population,
first_value(population)
OVER (PARTITION BY continent
ORDER BY population DESC)
FROM population
WHERE continent in ('Europe','Afrique'); pays | continent | population | first_value
-----------------------+-----------+------------+-------------
Nigéria | Afrique | 173.6 | 173.6
Éthiopie | Afrique | 94.1 | 173.6
Égypte | Afrique | 82.1 | 173.6
…
Féd. de Russie | Europe | 142.8 | 142.8
Allemagne | Europe | 82.7 | 142.8
France métropolitaine | Europe | 64.3 | 142.8
…OVER (ORDER BY …) par défaut est :RANGE (par plage ; ci-dessus)ROWS (par nombre de lignes)GROUPS (selon valeurs des lignes)UNBOUNDED PRECEDING: depuis le début de la
partitionCURRENT ROW : depuis la ligne couranteUNBOUNDED FOLLOWING : jusqu’à la fin de la
partitionCURRENT ROW : jusqu’à la ligne couranteSELECT pays, continent, population,
last_value(population) OVER (
PARTITION BY continent
ORDER BY population DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
FROM population
WHERE continent in ('Europe','Afrique'); pays | continent | population | last_value
-----------------------+-----------+------------+------------
Nigéria | Afrique | 173.6 | 11.0
Éthiopie | Afrique | 94.1 | 11.0
Égypte | Afrique | 82.1 | 11.0
Rép. dém. du Congo | Afrique | 67.5 | 11.0
…
Tunisie | Afrique | 11.0 | 11.0
Féd. de Russie | Europe | 142.8 | 0.4
Allemagne | Europe | 82.7 | 0.4
France métropolitaine | Europe | 64.3 | 0.4
…
Malte | Europe | 0.4 | 0.4
xxx PRECEDING : depuis les xxx valeurs devant la ligne
couranteCURRENT ROW : depuis la ligne courantexxx FOLLOWING : depuis les xxx valeurs derrière la
ligne couranteCURRENT ROW : jusqu’à la ligne courantexxx PRECEDING : depuis les xxx groupes de valeurs
identiques devant la ligne couranteCURRENT ROW : depuis la ligne courante ou le premier
élément identique selonORDER BYxxx FOLLOWING : depuis les xxx groupes de valeurs
identiques derrière la ligne couranteCURRENT ROW : jusqu’à la ligne courante ou le dernier
élément identique selon ORDER BYEXCLUDE CURRENT ROW : exclut la ligne couranteEXCLUDE GROUP : exclut la ligne courante et le groupe
de valeurs identiques dans l’ordreEXCLUDE TIES : exclut les valeurs identiques à la ligne
courante dans l’ordre mais pas la ligne couranteEXCLUDE NO OTHERS : pas d’exclusion (par défaut)WITHIN GROUPGROUPING SETS / ROLLUP / CUBEGROUP BYROLLUPCUBE piece | region | sum
--------+--------+-----
| | 320
ecrous | ouest | 0
clous | nord | 0
vis | nord | 60
clous | est | 70
vis | sud | 50
ecrous | est | 50
ecrous | sud | 40
vis | ouest | 50
vis | | 160
ecrous | | 90
clous | | 70
| ouest | 50
| nord | 60
| est | 120
| sud | 90
CUBE peut le pluspsql : \crosstabviewtablefunc, fonction
crosstab (text,text)