La version en ligne des solutions de ces TP est disponible sur https://dali.bo/s60_solutions .
Jointure latérale
Cette série de question utilise la base de TP
magasin . La base magasin (dump de
96 Mo, pour 667 Mo sur le disque au final) peut être téléchargée et
restaurée comme suit dans une nouvelle base
magasin :
createdb magasin
curl -kL https://dali.bo/tp_magasin -o /tmp/magasin.dump
pg_restore -d magasin /tmp/magasin.dump
# le message sur public préexistant est normal
rm -- /tmp/magasin.dump
Les données sont dans deux schémas, magasin et
facturation . Penser au search_path.
Pour ce TP, figer les paramètres suivants :
SET max_parallel_workers_per_gather to 0 ;
SET seq_page_cost TO 1 ;
SET random_page_cost TO 4 ;
Afficher les 10 derniers articles commandés.
Pour chacune des 10 dernières commandes passées, afficher le premier
article commandé.
CTE récursive
La table genealogie peut être téléchargée depuis https://dali.bo/tp_genealogie et restaurée à l’aide de
pg_restore :
curl -kL https://dali.bo/tp_genealogie -o genealogie.dump
createdb genealogie
pg_restore -O -d genealogie genealogie.dump
# le message d'erreur sur le schéma public est normal
Voici la description de la table genealogie qui sera
utilisée :
\d genealogie
Table "public.genealogie"
Column | Type | Modifiers
----------------+---------+---------------------------------------
id | integer | not null default +
| | nextval('genealogie_id_seq'::regclass)
nom | text |
prenom | text |
date_naissance | date |
pere | integer |
mere | integer |
Indexes:
"genealogie_pkey" PRIMARY KEY, btree (id)
À partir de la table genealogie, déterminer qui sont les
descendants de Fernand DEVAUX.
À l’inverse, déterminer qui sont les ancètres de Adèle
TAILLANDIER
Réseau social
La table socialnet peut être téléchargée et restaurée
ainsi :
curl -kL https://dali.bo/tp_socialnet -o /tmp/socialnet.dump
createdb socialnet
pg_restore -O -d socialnet /tmp/socialnet.dump
# le message d'erreur sur le schéma public est normal
Cet exercice est assez similaire au précédent et propose de manipuler
des arborescences.
Les tableaux et la fonction unnest() peuvent être utiles
pour résoudre plus facilement ce problème.
La table personnes contient la liste de toutes les
personnes d’un réseau social.
Table "public.personnes"
Column | Type | Modifiers
--------+---------+--------------------------------------------------------
id | integer | not null default nextval('personnes_id_seq'::regclass)
nom | text | not null
prenom | text | not null
Indexes:
"personnes_pkey" PRIMARY KEY, btree (id)
La table relation contient les connexions entre ces
personnes.
Table "public.relation"
Column | Type | Modifiers
--------+---------+-----------
gauche | integer | not null
droite | integer | not null
Indexes:
"relation_droite_idx" btree (droite)
"relation_gauche_idx" btree (gauche)
Déterminer le niveau de connexions entre Sadry Luettgen et Yelsi
Kerluke et afficher le chemin de relation le plus court qui permet de
les connecter ensemble.
Dépendance de vues
Les dépendances entre objets est un problème classique dans les bases
de données :
dans quel ordre charger des tables selon les clés étrangères ?
dans quel ordre recréer des vues ?
etc.
Le catalogue de PostgreSQL décrit l’ensemble des objets de la base de
données. Deux tables vont nous intéresser pour mener à bien cet
exercice :
pg_depend liste les dépendances entre objets
pg_rewrite stocke les définitions des règles de
réécritures des vues (RULES)
pg_class liste les objets que l’on peut interroger
comme une table, hormis les fonctions retournant des ensembles
La définition d’une vue peut être obtenue à l’aide de la fonction
pg_get_viewdef.
Pour plus d’informations sur ces tables, se référer à la
documentation :
L’objectif de se TP consiste à récupérer l’ordre de suppression et de
recréation des vues de la base brno2015 en fonction du
niveau de dépendances entre chacune des vues. Brno est une ville de
Tchéquie, dans la région de Moravie-du-Sud. Le circuit Brno-Masaryk est
situé au nord-ouest de la ville. Le Grand Prix moto de Tchéquie s’y
déroule chaque année.
La table brno2015 peut être téléchargée et restaurée
ainsi :
curl -kL https://dali.bo/tp_brno2015 -o /tmp/brno2015.dump
createdb brno2015
pg_restore -O -d brno2015 /tmp/brno2015.dump
# une erreur sur l'existence du schéma public est normale
Retrouver les dépendances de la vue pilotes_brno.
Déduisez également l’ordre de suppression et de recréation des vues.
Jointure latérale
Afficher les 10 derniers articles commandés.
Tout d’abord, nous positionnons le search_path pour
chercher les objets du schéma magasin :
SET search_path = magasin;
On commence par afficher les 10 dernières commandes :
SELECT *
FROM commandes
ORDER BY numero_commande DESC
LIMIT 10 ;
Une simple jointure nous permet de retrouver les 10 derniers articles
commandés :
SELECT lc.produit_id, p .nom
FROM commandes c
JOIN lignes_commandes lc
ON (c .numero_commande = lc.numero_commande)
JOIN produits p
ON (lc.produit_id = p .produit_id)
ORDER BY c .numero_commande DESC , numero_ligne_commande DESC
LIMIT 10 ;
Pour chacune des 10 dernières commandes passées, afficher le premier
article commandé.
La requête précédente peut être dérivée pour répondre à la question
demandée. Ici, pour chacune des dix dernières commandes, nous voulons
récupérer le nom du dernier article commandé, ce qui sera transcrit sous
la forme d’une jointure latérale :
SELECT numero_commande, produit_id, nom
FROM commandes c ,
LATERAL (SELECT p .produit_id, p .nom
FROM lignes_commandes lc
JOIN produits p
ON (lc.produit_id = p .produit_id)
WHERE (c .numero_commande = lc.numero_commande)
ORDER BY numero_ligne_commande ASC
LIMIT 1
) premier_article_par_commande
ORDER BY c .numero_commande DESC
LIMIT 10 ;
CTE récursive
À partir de la table genealogie, déterminer qui sont les
descendants de Fernand DEVAUX.
WITH RECURSIVE arbre_genealogique AS (
SELECT id , nom, prenom, date_naissance, pere, mere
FROM genealogie
WHERE nom = 'DEVAUX '
AND prenom = 'Fernand '
UNION ALL
SELECT g .*
FROM arbre_genealogique ancetre
JOIN genealogie g
ON (g .pere = ancetre.id OR g .mere = ancetre.id )
)
SELECT id , nom, prenom, date_naissance
FROM arbre_genealogique;
À l’inverse, déterminer qui sont les ancètres de Adèle
TAILLANDIER
WITH RECURSIVE arbre_genealogique AS (
SELECT id , nom, prenom, date_naissance, pere, mere
FROM genealogie
WHERE nom = 'TAILLANDIER '
AND prenom = 'Adèle '
UNION ALL
SELECT ancetre.id , ancetre.nom, ancetre.prenom, ancetre.date_naissance,
ancetre.pere, ancetre.mere
FROM arbre_genealogique descendant
JOIN genealogie ancetre
ON (descendant.pere = ancetre.id OR descendant.mere = ancetre.id )
)
SELECT id , nom, prenom, date_naissance
FROM arbre_genealogique;
Réseau social
Déterminer le niveau de connexions entre Sadry Luettgen et Yelsi
Kerluke et afficher le chemin de relation le plus court qui permet de
les connecter ensemble.
La requête suivante permet de répondre à cette question :
WITH RECURSIVE connexions AS (
SELECT gauche, droite, ARRAY [gauche] AS personnes_connectees,0 :: integer AS level
FROM relation
WHERE gauche = 1
UNION ALL
SELECT p .gauche, p .droite, personnes_connectees || p .gauche, level + 1 AS level
FROM connexions c
JOIN relation p ON (c .droite = p .gauche)
WHERE level < 4
AND p .gauche <> ANY (personnes_connectees)
), plus_courte_connexion AS (
SELECT *
FROM connexions
WHERE gauche = (
SELECT id FROM personnes WHERE nom = 'Kerluke ' AND prenom = 'Yelsi '
)
ORDER BY level ASC
LIMIT 1
)
SELECT list.id , p .nom, p .prenom, list.level - 1 AS level
FROM plus_courte_connexion,
unnest (personnes_connectees) WITH ORDINALITY AS list(id , level )
JOIN personnes p on (list.id = p .id )
ORDER BY list.level ;
Cet exemple fonctionne sur une faible volumétrie, mais les limites
des bases relationnelles sont rapidement atteintes sur de telles
requêtes.
Une solution consisterait à implémenter un algorithme de parcours de
graphe avec pgRouting ,
mais cela nécessitera de présenter les données sous une forme
particulière. Pour les problématiques de traitement de graphe, notamment
sur de grosses volumétries, une base de données orientée graphe comme
Neo4J sera probablement plus adaptée.
Dépendance de vues
Retrouver les dépendances de la vue pilotes_brno.
Déduisez également l’ordre de suppression et de recréation des vues.
Tout d’abord, nous positionnons le search_path pour
chercher les objets du schéma brno2015 :
SET search_path = brno2015;
Si la jointure entre pg_depend et
pg_rewrite est possible pour l’objet de départ, alors il
s’agit probablement d’une vue. En discriminant sur les objets qui
référencent la vue pilotes_brno, nous arrivons à la requête
de départ suivante :
SELECT DISTINCT pg_rewrite.ev_class as objid, refobjid as refobjid, 0 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
WHERE refobjid = 'pilotes_brno ' :: regclass;
La présence de doublons nous oblige à utiliser la clause
DISTINCT.
Nous pouvons donc créer un graphe de dépendances à partir de cette
requête de départ, transformée en requête récursive :
WITH RECURSIVE graph AS (
SELECT distinct pg_rewrite.ev_class as objid, refobjid as refobjid, 0 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
WHERE refobjid = 'pilotes_brno ' :: regclass
UNION ALL
SELECT distinct pg_rewrite.ev_class as objid, pg_depend.refobjid as refobjid,
depth + 1 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN graph on pg_depend.refobjid = graph.objid
WHERE pg_rewrite.ev_class != graph.objid
)
SELECT * FROM graph;
Il faut maintenant résoudre les OID pour déterminer les noms des vues
et leur schéma. Pour cela, nous ajoutons une vue resolved
telle que :
WITH RECURSIVE graph AS (
SELECT distinct pg_rewrite.ev_class as objid, refobjid as refobjid, 0 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
WHERE refobjid = 'pilotes_brno ' :: regclass
UNION ALL
SELECT distinct pg_rewrite.ev_class as objid, pg_depend.refobjid as refobjid,
depth + 1 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN graph on pg_depend.refobjid = graph.objid
WHERE pg_rewrite.ev_class != graph.objid
),
resolved AS (
SELECT n.nspname AS dependent_schema, d.relname as dependent,
n2.nspname AS dependee_schema, d2.relname as dependee,
depth
FROM graph
JOIN pg_class d ON d.oid = objid
JOIN pg_namespace n ON d.relnamespace = n.oid
JOIN pg_class d2 ON d2.oid = refobjid
JOIN pg_namespace n2 ON d2.relnamespace = n2.oid
)
SELECT * FROM resolved;
Nous pouvons maintenant présenter les ordres de suppression et de
recréation des vues, dans le bon ordre. Les vues doivent être supprimées
selon le numéro d’ordre décroissant et recrées selon le numéro d’ordre
croissant :
WITH RECURSIVE graph AS (
SELECT distinct pg_rewrite.ev_class as objid, refobjid as refobjid, 0 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
WHERE refobjid = 'pilotes_brno ' :: regclass
UNION ALL
SELECT distinct pg_rewrite.ev_class as objid, pg_depend.refobjid as refobjid,
depth + 1 as depth
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN graph on pg_depend.refobjid = graph.objid
WHERE pg_rewrite.ev_class != graph.objid
),
resolved AS (
SELECT n.nspname AS dependent_schema, d.relname as dependent,
n2.nspname AS dependee_schema, d2.relname as dependee,
d.oid as dependent_oid,
depth
FROM graph
JOIN pg_class d ON d.oid = objid
JOIN pg_namespace n ON d.relnamespace = n.oid
JOIN pg_class d2 ON d2.oid = refobjid
JOIN pg_namespace n2 ON d2.relnamespace = n2.oid
)
(SELECT 'DROP VIEW ' || dependent_schema || '. ' || dependent || '; '
FROM resolved
GROUP BY dependent_schema, dependent
ORDER BY max (depth ) DESC )
UNION ALL
(SELECT 'CREATE OR REPLACE VIEW ' || dependent_schema || '. ' || dependent ||
' AS ' || pg_get_viewdef (dependent_oid)
FROM resolved
GROUP BY dependent_schema, dependent, dependent_oid
ORDER BY max (depth ));