Exploiter les super-pouvoirs de PostgreSQL — Chapitre 1

The Whole Point of Views

Intro

À La Javaness, à moins qu’un client nous demande d’utiliser un autre SGBDR, nous choisissons toujours PostgreSQL. Open source, activement maintenu, conforme au standard ANSI, très performant dans une grande variété de situations, c’est l’outil idéal pour nos besoins en données. Pourtant, notre usage de Postgres est assez vanilla, la plupart du temps : des CRUD basiques, des jointures, parfois PostGIS. Mais il nous arrive d’expérimenter de nouveaux patterns, de tester des fonctionnalités et nous n’en sommes jamais déçus.

Cette série d’articles mélangera habilement des sujets de base et quelques unes des pépites sur lesquels nous sommes tombés. Le tout avec un seul objectif en tête : vous aider à maîtriser vos données.


Nous parlerons toujours de fonctionnalités du point de vue de PostgreSQL, mais gardez en tête que tous les SGBDR implémentent le même standard ANSI SQL. Vous retrouverez donc très probablement ces fonctionnalités sur Oracle et SQL Server, une grande partie sera également présente sur MySQL ou MariaDB. On ne garantit rien pour SQLite, par contre.


Views

Les views sont un mécanisme qui n’a rien de récent. D’aussi loin que je me souvienne, elles ont toujours été disponibles dans Postgres.

Mais c’est le genre de feature dont on parle un jour au bureau, autour d’un café, et un collègue rétorque : “c’est cool mais pourquoi personne n’a l’air de l’utiliser ?”. Et il faut bien l’avouer : trouver une réponse décente à ça, ce n’est pas si facile. La vérité, c’est que ce n’est pas une fonctionnalité requise pour utiliser un SGBDR. Vous pouvez totalement vous en passer, c’est peut-être ce que vous faites actuellement. Pour cette raison, les views sont généralement absentes des programmes, supports de cours et autres matériels d’apprentissage génériques (= qui ne sont pas focalisés sur SQL). On va rattraper ça. Vous serez un meilleur développeur quand vous saurez que les views existent, ce qu’elles sont et comment elles fonctionnent.


Les views ne servent qu’à une chose : masquer la complexité, l’abstraire. La plupart du temps, la complexité peut être réduite (tout le monde a certainement l’acronyme KISS en tête). Mais elle ne peut pas être éliminée, car elle est inhérente au problème que vous essayez de résoudre.

La complexité dont je parle regroupe les expressions de colonne non-triviales, la définition d’une source de données (les jointures), l’agrégation, les requêtes imbriquées, les unions : à peu près n’importe quelle requête au delà de la simple SELECT * FROM my_table;. Et si je vous disais que tout ce qui est cité ci-dessus peut être caché derrière une requête comme cette dernière ?

Prenons un exemple concret, que nous reprendrons dans les articles suivants. Nous avons eu récemment à tacler un problème récurrent dans le développement d’une application : le multilingue (le terme plus smart est l’internationalisation, tout ça pour que tout le monde dise i18n au final). Le principe, c’est de fournir des libellés, inscrits dans un espace de nom, dans plusieurs langues, le tout décliné en plusieurs versions, témoins de l’évolution des traductions. Et bien sûr, ces libellés ont des pluriels (en français, il n’y a qu’un seul pluriel, mais ce n’est pas le cas de toutes les langues).

Avant même de nous en rendre compte, nous étions face à ce genre de requête pour afficher de façon synthétique l’ensemble des traductions (ou, en language smart, des localisations, terme réduit en l10n) disponibles.



Sans être colossale, cette requête est déjà assez complexe, il vous faut un peu de temps pour la décomposer et comprendre ce qu’elle fait exactement. Imaginez un instant ce que ça représenterait de l’écrire avec un query builder comme Knex. Fastidieux. Mais au final, ce que cette requête retourne, ce n’est jamais qu’une liste des localisations, non ? Alors appelons ça comme ça : localization (à l’anglaise).


Mais comment ça marche ?

Exactement comme si vous aviez exécuté la requête “entière”. Une view, c’est plus ou moins une requête sauvegardée et nommée qui attend d’être exécutée. Comparons les query plans (promis, on en reparle dans un prochain article) de la requête initiale et de la view : il n’y a aucune différence entre les deux. Donc aucun impact sur les performances non plus.

NB : Les requêtes sont exécutées à un moment précis : à ce même moment, mon antivirus peut être en train d’analyser la pièce jointe d’un mail que je viens de recevoir, une tâche cron peut être en cours d’exécution etc. Ce qui explique la légère variation entre ces deux mesures, effectuées avec les ressources disponibles à l’instant t. L’impact sur les performances se mesure plutôt sur une différence d’ordre de grandeur des résultats (moins d’une milliseconde d’un côté, plus de dix de l’autre, par exemple).

Et bien sûr, comme une table, une fonction ou à peu près tout ce qu’on peut créer avec le mot-clé CREATE, la view est là pour durer, elle est disponible dès l’instant qui suit sa création, pour tout le monde et jusqu'à sa suppression. Quand vous n’en avez plus besoin, il suffit d’exécuter DROP VIEW suivi du nom de la view et elle sera supprimée.

Bon à savoir

Retrouver la requête d’origine

Même si bon nombre d’entre nous utilise aujourd’hui des outils visuels comme DBeaver ou pgAdmin, il est toujours bon de connaître un moyen purement SQL de retrouver la définition d’une view, c’est-à-dire la requête qui est exécutée sous le capot à chaque mention de la view correspondante.

Notez bien que ces requêtes ne sont pas stockées textuellement par le SGBD, elles sont digérées par le parser et c’est le query plan (encore lui, désolé) résultant qui est stocké. Voilà pourquoi la fonction que je m’apprête à mentionner ne vous restituera pas parfaitement la requête que vous aviez écrite à la main mais plutôt une version “neutre” reconstituée à partir du query plan.

La fonction est pg_get_viewdef et la documentation officielle mentionne 3 formes, dont une dépréciée. Nous nous focaliserons sur pg_get_viewdef ( view oid [, pretty boolean ] ) -> text qui permet, via le paramètre pretty, d’afficher la requête dans un format digeste. Pour l'appeler correctement, il faut lui fournir l'OID de la view. Un OID, c'est juste un numéro unique qu'on retrouve dans le catalogue pg_catalog qui référence absolument tout ce que vous pouvez utiliser dans le dialecte SQL de Postgres : types, opérateurs, views, tables etc. Mais comme vous n’avez aucune envie d’aller fouiller dans ce catalogue, on vous livre une astuce : on peut récupérer l’OID d’une view à partir de son nom, à l’aide d'un cast. On reparle du cast dans un prochain article, retenez la formule pour l’instant.

On remarque quelques casts supplémentaires ainsi que les AS, qui étaient implicites dans la déclaration initiale. Mais à part ça, la requête est bien la même.

Cette fonction est celle utilisée par psql lorsque vous exécutez par exemple \d+ localization pour afficher le format et la définition d’une view. Elle est aussi utilisée par tous les outils graphiques comme pgAdmin. Elle ne vous sera donc utile que si vous ne pouvez utiliser aucun de ces outils.


Éviter l’effet boule de neige

Il y a un point de vigilance à connaître quand on commence à utiliser les views. En général, on tombe dessus quand il est déjà trop tard, et le problème est alors très concret. Je vais tenter de vous éviter de tomber dedans un jour.

Puisqu’une view agit comme une source de donnée (=ce qu’on spécifie après le mot-clé FROM) et que ce n’est ni plus ni moins qu’une requête SELECT qui va elle-même chercher des données dans sa propre source, la définition d’une view peut mentionner une autre view (ou plusieurs).

Disons donc que vous créez une viewA qui récupère les données d’une authentique table puis une viewB qui joint une autre table à votre viewA. Pour satisfaire un chargé de projet, vous créez enfin une viewC qui agrège les données de la viewB. Jusque là, tout va bien, les 3 views fonctionnent à merveille et quelle joie d'écrire invariablement SELECT * FROM view(A|B|C); pour récupérer des informations complexes dans la base !

Mais les ennuis commencent quand, pour une raison quelconque, la définition de viewA doit évoluer. Elle doit inclure un nouveau calcul dans une colonne supplémentaire. Ou peut-être que vous devez simplement ajuster le type d’une colonne retournée. Peu importe la modification, une view n'étant pas quelque chose de “physique”, il n’y a rien à ALTER. Vous allez devoir supprimer la view et la recréer avec sa nouvelle définition. Sauf que… Postgres ne vous laisse pas faire.

Certes, vous pouvez suivre le “hint” proposé et tenter d’ajouter CASCADE à votre requête, mais cela supprimera la viewB qui dépend de votre viewA, ainsi que la viewC qui dépend de la B, et ce jusqu'à ce qu’il n’y ait plus rien qui dépende de ce que le SGBD doit supprimer. Si vous ne l’aviez pas anticipé, recréer tout ça va nécessiter pas mal de maintenance.


La solution est plutôt simple et efficace : ne créez jamais de telles chaînes de dépendance. Dans toutes les définitions de vos views, remplacez les views que vous pourriez mentionner par leur propre définition. De cette manière, chaque view restera parfaitement indépendante. Vous devrez toujours inclure une view dans la cascade si vous supprimez une table dont elle dépend, mais c’est la règle : la view ne peut pas fonctionner si vous supprimez tout ou partie de sa source de données.

Personne ne vous reprochera que les définitions de vos views sont massives et contiennent des parties redondantes : leur rôle est précisément de cacher cette complexité. Avant de la cacher, il faut commencer par l’exprimer. Rappelez-vous qu’en fin de compte, tous vos utilisateurs n’auront qu’à écrire un SELECT simpliste avec votre view derrière le mot-clé FROM, il n’en verront pas la définition.

Retenez donc la règle d’or de ne jamais mentionner une view dans la définition d’une autre. Gardez ces bijoux séparés et indépendants.


Les utiliser en toute simplicité

On a gardé le meilleur pour la fin : les views sont extrêmement simples à intégrer dans un projet existant. Vous utilisez probablement un outil de migration comme Sqitch, Flyway ou le système intégré à Knex. Quelle que soit la solution utilisée, les définitions de vos views y ont leur place, au même titre que celles de vos tables, de vos index ou de vos fonctions. Vous pouvez donc les créer progressivement et alléger peu à peu les requêtes SQL de votre application.


À propos

Jean Prulière est développeur back-end à La Javaness depuis 2021.

43 vues0 commentaire