SELECTcount(*) AS compte_pays,count(CASEWHEN r.nom_region='Europe'THEN1ELSENULLEND) AS compte_pays_europeensFROM pays pJOIN regions rON (p.region_id = r.region_id);
Filtrer avec FILTER
La même requête écrite avec la clause FILTER :
SELECTcount(*) AS compte_pays,count(*) FILTER (WHERE r.nom_region='Europe')AS compte_pays_europeensFROM pays pJOIN regions rON (p.region_id = r.region_id);
Fonctions de fenêtrage
Fonctions WINDOW
travaille sur des ensembles de données regroupés et triés
indépendamment de la requête principale
Utilisation :
utiliser plusieurs critères d’agrégation dans la même requête
utiliser des fonctions de classement
faire référence à d’autres lignes de l’ensemble de données
Regroupement
Regroupement
clause OVER (PARTITION BY …)
Utilité :
plusieurs critères de regroupement différents
avec des fonctions de calcul d’agrégats
Regroupement : exemple
SELECT matricule, salaire, service,SUM(salaire) OVER (PARTITIONBY service)AS total_salaire_serviceFROM employes;
SELECT … agregation OVER (ORDERBY<colonnes>)FROM<liste_tables>WHERE<predicats>
Regroupement et tri
On peut combiner les deux
OVER (PARTITION BY .. ORDER BY ..)
Utilité :
travailler sur des jeux de données ordonnés et isolés les uns des
autres
Regroupement et tri : exemple
SELECT continent, pays, population,rank() OVER (PARTITIONBY continentORDERBY population DESC)AS rangFROM population;
continent|pays|population|rang------------------+--------------------+------------+------Afrique|Nigéria|173.6|1Afrique|Éthiopie|94.1|2Afrique|Égypte|82.1|3Afrique|Rép. dém. du Congo |67.5|4(…)Amérique du Nord |États-Unis|320.1|1Amérique du Nord |Canada|35.2|2(…)
Regroupement et tri :
principe
OVER (PARTITIONBY continentORDERBY population DESC)
SELECT continent,percentile_disc(0.5) WITHIN GROUP (ORDERBY population) AS"mediane",percentile_disc(0.95) WITHIN GROUP (ORDERBY population) AS"95pct",ROUND(AVG(population), 1) AS moyenneFROM populationGROUPBY continent;
continent|mediane|95pct|moyenne---------------------------+---------+--------+---------Afrique|33.0|173.6|44.3Amérique du Nord |35.2|320.1|177.7Amérique latine. Caraïbes |30.4|200.4|53.3Asie|53.3|1252.1|179.9Europe|9.4|82.7|21.8
Grouping Sets
GROUPING SETS/ROLLUP/CUBE
Extension de GROUP BY
Utilité :
présente le résultat de plusieurs agrégations différentes
réaliser plusieurs agrégations différentes dans la même requête
Un identifiant universellement unique sur 128 bits
Type : uuid
Avec des inconvénients…
UUID : avantages
Faciles à générer
gen_random_uuid() et d’autres
Pouvoir désigner des entités arbitrairement
Pouvoir exporter des données de sources différentes
au contraire des séquences traditionnelles
Pouvoir fusionner des bases
Pour toutes les clés primaires ?
UUID : inconvénients
Lisibilité
Temps de génération (mineur)
Taille
16 octets…
Surtout : fragmentation des index
mauvais pour le cache
sauf UUID v7 (tout récent)
UUID : utilisation sous
PostgreSQL
uuid :
type simple (16 octets)
garantit le format
Génération :
gen_random_uuid (v4, aléatoire)
extension uuid-ossp (v1,3,4,5)
extension (pg_idkit…) ou fonction en SQL (v7)
UUID : une indexation facile
CREATEUNIQUEINDEXON nomtable USING btree (champ_uuid) ;
UUID : résumé
Si vous avez besoin des UUID, préférez la version 7.
Les séquences habituelles restent recommandables en interne.
Types tableaux
Tableaux : principe
Collection ordonnée d’un même type
Types integer[], text[], etc.
SELECTARRAY [1,2,3] ;SELECT'{1,2,3}'::integer[] ;
-- lignes vers tableauSELECT array_agg (i) FROM generate_series (1,3) i ;-- tableau vers lignesSELECT unnest ( '{1,2,3}'::integer[] ) ;
CREATETABLE demotab ( idint, liste integer[] ) ;
Tableaux : recherche de
valeurs
-- Recherche de valeurs (toutes)SELECT*FROM demotabWHERE liste @>ARRAY[15,11] ;-- Au moins 1 élément commun ?SELECT*FROM demotabWHERE liste && ARRAY[11,55] ;-- 1 tableau exactSELECT*FROM demotabWHERE liste ='{11,55}'::int[] ;
Tableaux : performances
Quel intérêt par rapport à 1 valeur par ligne ?
Allège certains modèles en réduisant les jointures
ex : champ avec des n°s de téléphone
si pas de contraintes
Grosse économie en place (time series)
24 octets par ligne
et parfois mécanisme TOAST
Mais…
mise à jour compliquée/lente
indexation moins simple
Tableaux : indexation
B-tree inutilisable
GIN plus adapté pour recherche d’une valeur
opérateurs && , @>
mais plus lourd
Types composés
Types composés : généralités
Un champ = plusieurs attributs
De loin préférable à une table Entité/Attribut/Valeur
Uniquement si le modèle relationnel n’est pas assez souple
SELECT jsonb_array_elements (datas->'phoneNumbers')->>'number'FROM personnes ;
Conversions jsonb / relationnel
(1)
Construire un ensemble de tuples depuis un objet JSON :
jsonb_each()
Avec des types préexistants :
jsonb_populate_record(),
jsonb_populate_recordset
Types définis à la volée :
jsonb_to_record(),jsonb_to_recordset()
Construire un JSON depuis une requête :
to_jsonb (<requête>)
Attention aux types
jsonb_typeof()
Conversions jsonb /
relationnel (2) : JSON_TABLE
SELECT*FROM t,JSON_TABLE ( t.champ_json ,'$ ? (@.age == 42)'COLUMNS (age int PATH '$.age', nom text PATH '$.name')) ;
PostgreSQL 17+
conversion d’un JSON en vue, avec types
clause NESTED PATH pour les tableaux dans le JSON
paramétres : clause PASSING
JSON : performances
Inconvénients par rapport à un modèle normalisé :
Perte d’intégrité (types, contraintes, FK…)
Complexité du code
Pas de statistiques sur les clés JSON !
Pas forcément plus léger en disque
clés répétées
Lire 1 attribut = lire tout le JSON
voire accès table TOAST
Mise à jour : tout ou rien
Indexation délicate
Recherche dans un champ JSON
(1)
Sans JSONPath :
SELECT*FROM personnes …
WHERE datas->>'lastName'='Durand'; -- textesWHERE datas->'lastName'='"Durand"'::jsonb ; -- JSON
WHERE datas @>'{"nom": "Durand"}'::jsonb ; -- contient
WHERE datas ? 'spouse' ; -- attibut existe ?WHERE datas ?| '{spouse,children}'::text[] ; -- un des champs ?WHERE datas ?& '{spouse,children}'::text[] ; -- tous les champs ?
Mais comment indexer ?
Recherche
dans un champ JSON (2) : SQL/JSON et JSONPath
SQL:2016 introduit SQL/JSON et le langage JSONPath
JSONPath :
langage de recherche pour JSON
concis, flexible, plus rapide
depuis PostgreSQL 12, étendu à chaque version
Recherche
dans un champ JSON (3) : Exemples SQL/JSON & JSONPath
CREATEINDEX idx_prs_nom ON personnes ((datas->>'lastName')) ;ANALYZE personnes ;
Colonne générée (dénormalisée) :
champ normal, indexable, facile à utiliser, rapide à lire
statistiques
ALTERTABLE personnesADDCOLUMN lastname textGENERATED ALWAYS AS ((datas->>'lastName')) STORED ;
Préférer @> et @? que des
fonctions
jsonb : indexation (2/2)
Indexation « schemaless » grâce au GIN :
attention au choix de l’opérateur
-- opérateur par défaut-- pour critères : ?, ?|, ?& , @> , @?, @@)CREATEINDEX idx_prs ON personnes USING gin(datas) ;-- index plus performant-- mais que @> , @?, @@CREATEINDEX idx_prs ON personnes USING gin(datas jsonb_path_ops) ;
CREATEORREPLACEFUNCTION nb_lignes_table (sch text, tbl text) RETURNS bigint STABLEAS'DECLARE n bigint ;BEGIN SELECT n_live_tup INTO n FROM pg_stat_user_tables WHERE schemaname = sch AND relname = tbl ; RETURN n ;END ; 'LANGUAGE plpgsql ;
Exemple de fonction PL/Perl
complexe
Permet d’insérer une facture associée à un client
Si le client n’existe pas, une entrée est créée
Utilisation fréquente de spi_exec
Exemple de fonction
PL/pgSQL complexe
Même fonction en PL/pgSQL que précédemment
L’accès aux données est simple et naturel
Les types de données SQL sont natifs
La capacité de traitement est limitée par le langage
Attention au nommage des variables et
paramètres
Exemple de procédure
CREATEORREPLACEPROCEDURE vide_tables (dry_run BOOLEAN)AS'BEGIN TRUNCATE TABLE pgbench_history ; TRUNCATE TABLE pgbench_accounts CASCADE ; TRUNCATE TABLE pgbench_tellers CASCADE ; TRUNCATE TABLE pgbench_branches CASCADE ; IF dry_run THEN ROLLBACK ; END IF ;END ; ' LANGUAGE plpgsql ;
Exemple de bloc anonyme en
PL/pgSQL
Bloc procédural anonyme en PL/pgSQL :
DO $$DECLARE r record;BEGINFOR r IN (SELECT schemaname, relnameFROM pg_stat_user_tablesWHEREcoalesce(last_analyze, last_autoanalyze) ISNULL ) LOOP RAISE NOTICE 'Analyze %.%', r.schemaname, r.relname ;EXECUTE'ANALYZE '|| quote_ident(r.schemaname)||'.'|| quote_ident(r.relname) ;ENDLOOP;END$$;
Utiliser le type composite défini par la ligne d’une table
CREATEFUNCTION ma_fonction () RETURNS integerAS $$DECLARE ligne ma_table%ROWTYPE; … $$
Type RECORD
RECORD identique au type ROW
…sauf que son type n’est connu que lors de son affectation
RECORD peut changer de type au cours de l’exécution de
la routine
Curseur et boucle sur une requête
Type RECORD : exemple
CREATEFUNCTION ma_fonction () RETURNS integerAS $$DECLARE ligne RECORD;BEGIN-- récupération de la 1è ligne uniquementSELECT*INTO ligne FROM ma_première_table;-- ou : traitement ligne à ligneFOR ligne INSELECT*FROM ma_deuxième_table LOOP …ENDLOOP ;RETURN … ;END $$ ;
Exécution de requête
dans un bloc PL/pgSQL
Requête dans un bloc PL/pgSQL
Toutes opérations sur la base de données
Et calculs, comparaisons, etc.
Toute expression écrite en PL/pgSQL sera passée à
SELECT pour interprétation par le moteur
PREPARE implicite, avec cache
Affectation d’une valeur
à une variable
Utiliser l’opérateur := :
un_entier :=5;
Utiliser SELECT INTO :
SELECT5INTO un_entier;
Exécution d’une requête
Affectation de la ligne :
SELECT*INTO ma_variable_ligne -- type ROW ou RECORDFROM …;
INTO STRICT pour garantir unicité
INTO seul : juste 1è ligne !
Plus d’un enregistrement :
écrire une boucle
Ordre statique :
colonnes, clause WHERE, tables figées
Exécution d’une
requête sans besoin du résultat
PERFORM : résultat ignoré
PERFORM *FROM ma_table WHERE une_colonne>0 ;PERFORM mafonction (argument1) ;
pour mettre entre guillemets un identifiant d’un objet PostgreSQL
(table, colonne, etc.)
quote_literal ()
pour mettre entre guillemets une valeur (chaîne de caractères)
quote_nullable ()
pour mettre entre guillemets une valeur (chaîne de caractères), sauf
NULL qui sera alors renvoyé sans les guillemets
|| : concaténer
Ou fonction format(…), équivalent de
sprintf en C
Structures de contrôle en
PL/pgSQL
But du PL : les traitements procéduraux
Tests conditionnels - 2
Exemple :
IF nombre =0THEN resultat :='zero';ELSEIF nombre >0THEN resultat :='positif';ELSEIF nombre <0THEN resultat :='négatif';ELSE resultat :='indéterminé';ENDIF;
Tests conditionnels : CASE
CASEWHEN nombre =0THEN'zéro'WHEN variable >0THEN'positif'WHEN variable <0THEN'négatif'ELSE'indéterminé'ENDCASE
Attention lors de l’utilisation de type polymorphe…
# SELECT addition('un'::text, 'mot'::text);ERREUR: L'opérateur n'existe pas : text + textLIGNE 1 : SELECT $1+ $2^ASTUCE : Aucun opérateur correspond au nom donné et aux types d'arguments. Vous devez ajouter des conversions explicites de type.REQUÊTE : SELECT $1 + $2CONTEXTE : PL/pgSQL function "addition" line 4 at assignment
Fonctions trigger
Fonctions trigger :
introduction
Fonction stockée
Action déclenchée par INSERT (incluant
COPY), UPDATE, DELETE,
TRUNCATE
Mode par ligne ou par
instruction
Exécution d’une fonction stockée codée à partir de tout langage de
procédure activée dans la base de données
Fonctions trigger : variables
(1/5)
OLD :
type de données RECORD correspondant à la ligne avant
modification
valable pour un DELETE et un UPDATE
NEW :
type de données RECORD correspondant à la ligne après
modification
valable pour un INSERT et un UPDATE
Fonctions trigger : variables
(2/5)
Ces deux variables sont valables uniquement pour les triggers en
mode ligne
pour les triggers en mode instruction, la version 10 propose les
tables de transition
Accès aux champs par la notation pointée
NEW.champ1 pour accéder à la nouvelle valeur de
champ1
Fonctions trigger : variables
(3/5)
TG_NAME
nom du trigger qui a déclenché l’appel de la fonction
TG_WHEN
chaîne valant BEFORE, AFTER ou
INSTEAD OF suivant le type du trigger
TG_LEVEL
chaîne valant ROW ou STATEMENT suivant le
mode du trigger
TG_OP
chaîne valant INSERT, UPDATE,
DELETE, TRUNCATE suivant l’opération qui a
déclenché le trigger
Fonctions trigger : variables
(4/5)
TG_RELID
OID de la table qui a déclenché le trigger
TG_TABLE_NAME
nom de la table qui a déclenché le trigger
TG_TABLE_SCHEMA
nom du schéma contenant la table qui a déclenché le trigger
Fonctions trigger : variables
(5/5)
TG_NARGS
nombre d’arguments donnés à la fonction trigger
TG_ARGV
les arguments donnés à la fonction trigger (le tableau commence à
0)
Fonctions trigger : retour
Une fonction trigger a un type de retour spécial,
trigger
Trigger ROW, BEFORE :
si retour NULL, annulation de l’opération, sans déclencher
d’erreur
sinon, poursuite de l’opération avec cette valeur de ligne
attention au RETURN NEW; avec trigger
BEFORE DELETE
Trigger ROW, AFTER : valeur de retour
ignorée
Trigger STATEMENT : valeur de retour ignorée
Pour ces deux derniers cas, annulation possible dans le cas d’une
erreur à l’exécution de la fonction (que vous pouvez déclencher dans le
code du trigger)
Fonctions trigger : exemple -
1
Horodater une opération sur une ligne
CREATETABLE ma_table (id serial,-- un certain nombre de champs informatifsdate_ajout timestamp,date_modif timestamp);
FETCH [ direction { FROM | IN } ] curseur INTO cible
Récupère la prochaine ligne
FOUND indique si cette nouvelle ligne a été récupérée
Cible est
une variable RECORD
une variable ROW
un ensemble de variables séparées par des virgules
Curseurs : récupération des
données
direction du FETCH :
NEXT, PRIOR
FIRST, LAST
ABSOLUTE nombre, RELATIVE nombre
nombre
ALL
FORWARD, FORWARD nombre,
FORWARD ALL
BACKWARD, BACKWARD nombre,
BACKWARD ALL
Curseurs : modification des
données
Mise à jour d’une ligne d’un curseur :
UPDATE une_table SET...WHERECURRENTOF curseur;
Suppression d’une ligne d’un curseur :
DELETEFROM une_tableWHERECURRENTOF curseur;
Curseurs : fermeture d’un
curseur
Instruction SQL : CLOSE curseur
Ferme le curseur
Permet de récupérer de la mémoire
Permet aussi de réouvrir le curseur
Curseurs : renvoi d’un curseur
Fonction renvoyant une valeur de type refcursor
Permet donc de renvoyer plusieurs valeurs
Gestion des erreurs
Gestion des erreurs :
introduction
Sans exceptions :
toute erreur provoque un arrêt de la fonction
toute modification suite à une instruction SQL (INSERT,
UPDATE, DELETE) est annulée
d’où l’ajout d’une gestion personnalisée des erreurs avec le concept
des exceptions
Gestion des erreurs : une
exception
La fonction comporte un bloc supplémentaire,
EXCEPTION :
DECLARE-- déclaration des variables localesBEGIN-- instructions de la fonctionEXCEPTIONWHEN condition THEN-- instructions traitant cette erreurWHEN condition THEN-- autres instructions traitant cette autre erreur-- etc.END
Gestion des
erreurs : flot dans une fonction
L’exécution de la fonction commence après le BEGIN
Si aucune erreur ne survient, le bloc EXCEPTION est
ignoré
Si une erreur se produit
tout ce qui a été modifié dans la base dans le bloc est annulé
les variables gardent par contre leur état
l’exécution passe directement dans le bloc de gestion de
l’exception
Gestion des
erreurs : flot dans une exception
Recherche d’une condition satisfaisante
Si cette condition est trouvée
exécution des instructions correspondantes
Si aucune condition n’est compatible
sortie du bloc BEGIN/END comme si le bloc
d’exception n’existait pas
passage de l’exception au bloc BEGIN/END
contenant (après annulation de ce que ce bloc a modifié en base)
Dans un bloc d’exception, les instructions INSERT,
UPDATE, DELETE de la fonction ont été
annulées
Dans un bloc d’exception, les variables locales de la fonction ont
gardé leur ancienne valeur
Gestion des erreurs : codes
d’erreurs
SQLSTATE : code d’erreur
SQLERRM : message d’erreur
Par exemple :
Data Exception : division par zéro, overflow, argument
invalide pour certaines fonctions, etc.
Integrity Constraint Violation : unicité, CHECK, clé
étrangère, etc.
Syntax Error
PL/pgSQL Error : RAISE EXCEPTION, pas de
données, trop de lignes, etc.
Les erreurs sont contenues dans des classes d’erreurs plus
génériques, qui peuvent aussi être utilisées
Messages d’erreurs : RAISE - 1
Envoyer une trace dans les journaux applicatifs et/ou vers le client
RAISE niveau message
Niveau correspond au niveau d’importance du message
DEBUG, LOG, INFO,
NOTICE, WARNING, EXCEPTION
Message est la trace à enregistrer
Message dynamique… tout signe % est remplacé par la
valeur indiquée après le message
Champs DETAIL et HINT disponibles
Messages d’erreurs : RAISE - 2
Exemples :
RAISE WARNING 'valeur % interdite', valeur;
RAISE WARNING 'valeur % ambigue', valeurUSING HINT ='Controlez la valeur saisie en amont';
Messages d’erreurs :
configuration des logs
Deux paramètres importants pour les traces
log_min_messages
niveau minimum pour que la trace soit enregistrée dans les
journaux
client_min_messages
niveau minimum pour que la trace soit envoyée au client
Dans le cas d’un RAISE NOTICE message, il faut avoir
soit log_min_messages, soit
client_min_messages, soit les deux à la valeur
NOTICE au minimum.
Messages d’erreurs :
RAISE EXCEPTION - 1
Annule le bloc en cours d’exécution
RAISE EXCEPTION message
Sauf en cas de présence d’un bloc EXCEPTION gérant la
condition RAISE_EXCEPTION
message est la trace à enregistrer, et est dynamique… tout signe %
est remplacé par la valeur indiquée après le message
Messages d’erreurs :
RAISE EXCEPTION - 2
Exemple :
RAISE EXCEPTION'erreur interne';-- La chose à ne pas faire !
Flux des erreurs dans du code
PL
Les exceptions non traitées «remontent»
de bloc BEGIN/END imbriqués vers les blocs
parents (fonctions appelantes comprises)
jusqu’à ce que personne ne puisse les traiter
voir note pour démonstration
Flux des erreurs dans du
code PL - 2
Les erreurs remontent
Cette fois-ci, on rajoute un bloc PL pour intercepter l’erreur
Flux des erreurs dans du
code PL - 3
Cette fois-ci, on rajoute un bloc PL indépendant pour gérer le
second INSERT
Flux des erreurs dans du
code PL - 4
Illustrons maintenant la remontée d’erreurs
Nous avons deux blocs imbriqués
Une erreur non prévue va se produire dans le bloc intérieur
Abus des exceptions
Ne pas abuser des exceptions
les sous-transactions ne sont pas gratuites
Si saturation, paramétre SLRU dédié (v17)
Sécurité
Sécurité : droits
L’exécution de la routine dépend du droit EXECUTE
Par défaut, ce droit est donné à la création de la routine
au propriétaire de la routine
au groupe spécial PUBLIC
Sécurité : ajout
Ce droit peut être donné avec l’instruction SQL
GRANT :
la différence entre masquage dynamique et masquage statique
comment implémenter un masquage avancé
PostgreSQL Anonymizer
Principe
Principe :
Extension
Déclaratif (DDL)
Masquages
Principe :
statique
dynamique
sauvegardes anonymisées
« généralisation »
Pré-requis
Cet exemple nécessite :
une instance PostgreSQL ;
l’extension PostgreSQL Anonymizer
(anon)
installée, initialisée par un super-utilisateur
une base boutique
dont le propriétaire est paul,
super-utilisateur
les rôles pierre et jack
avec droits de connexion à boutique
Base d’exemple
pierre, paul, jack
et la base boutique :
CREATEROLE paul LOGIN SUPERUSER;CREATEROLE pierre LOGIN;CREATEROLE jack LOGIN;-- Define a password for each user with:-- \password paul or ALTER ROLE paul PASSWORD 'change-me';CREATEDATABASE boutique OWNER paul;
SECURITY LABELFOR anon ONCOLUMN customer.lastnameIS'MASKED WITH FUNCTION anon.fake_last_name()';SECURITY LABELFOR anon ONCOLUMN customer.phoneIS'MASKED WITH FUNCTION anon.partial(phone,2,$$X-XXX-XX$$,2)';
Appliquer les règles
de manière permanente
SELECT anon.anonymize_table('customer');
Exercices
E101 - Masquer les
prénoms des clients
E102 -
Masquer les 3 derniers chiffres du code postal
E103 -
Compter le nombre de clients dans chaque département.
E104 -
Ne garder que l’année dans les dates de naissance
E105 - Identifier un
client particulier
Solutions
Masquage dynamique avec postgresql_anonymizer
Principe du masquage
dynamique
Masquer les données personnelles à certains utilisateurs
mais pas tous
L’histoire
Paul a 2 employés :
Jack s’occupe du nouveau logiciel de ventes.
il a besoin d’accéder aux vraies données
pour le RGPD c’est un « processeur de données »
Pierre est un analyste qui exécute des requêtes
statistiques
il ne doit pas avoir accès aux données personnelles
Comment ça marche
Objectifs de la section
Nous allons voir :
comment écrire des règles de masquage simple
les avantages et limitations du masquage dynamique
le concept de « recoupement » d’une personne
(linkability)
Table « company »
DROPTABLEIFEXISTS supplier CASCADE;DROPTABLEIFEXISTS company CASCADE;CREATETABLE company (id SERIAL PRIMARYKEY, name TEXT, vat_id TEXT UNIQUE);
1{"meta":{"name":"Lee Perry","ip_addr":"40.87.29.113"},"content":"Hello Nasty!"}2{"meta":{"name":"","email":"biz@bizmarkie.com"},"content":"Great Shop"}3{"meta":{"name":"Jimmy"},"content":"Hi! This is me, Jimmy James "}