Bases de données orientées vues avec MariaDB
Les bases de données ne posent en général pas de problème immédiat. Les tables sont en place, les relations tiennent, les données sont bien stockées, et l’application fonctionne. La difficulté apparaît lorsque les besoins évoluent et que certaines informations doivent être croisées, enrichies ou restituées différemment. En fonction de la structure des tables et de leurs relations, la requête nécessaire pour produire cette lecture devient alors plus complexe, parfois alambiquée, avec des jointures multiples et des règles implicites dispersées dans le SQL. Qu’elle soit écrite dans le PHP ou encapsulée dans une vue, c’est toujours cette même requête qui s’exécute.
Ce n’est donc pas une faiblesse du modèle, mais la conséquence directe d’une lecture construite pas à pas. À mesure que de nouvelles données doivent être remontées, cette lecture s’alourdit, devient plus difficile à relire, à maintenir ou à partager. Le problème n’est alors plus le stockage, mais la manière dont cette lecture est formulée et réutilisée.
C’est dans ce contexte que les vues SQL prennent leur véritable sens. Non pas parce qu’elles rendraient la requête plus efficace ou plus élégante, mais parce qu’elles déplacent l’endroit où cette lecture est définie. Tant que la requête vit dans le PHP, une API ou un script, elle reste extérieure au système de stockage. Chaque nouvel écran, chaque nouvel export, chaque nouvel usage finit par recréer sa propre version de cette lecture. Avec une vue, cette construction devient une partie intégrante de la base elle même, au même titre que les tables.
Une vue ne modifie pas les données. Elle ne change pas non plus la requête. Elle change l’endroit où cette requête existe. Elle fait passer la définition de la lecture depuis le code applicatif vers le système qui gère déjà les données, leurs relations et leur évolution.
Quand une requête commence à raconter trop de choses
Une requête SQL n’est presque jamais réduite à un SELECT portant sur une seule donnée, elle combine le plus souvent plusieurs informations. Dans AlloAgri par exemple, elle agrège, filtre, relie et traduit des intentions métier. On part d’une table, puis on en joint une autre, puis une troisième, on ajoute des conditions, des alias, parfois des calculs. À la fin, la requête remonte bien les données, mais elle commence surtout à raconter une histoire qu’il faut apprendre à lire.
SELECT
DECLARATIONS.ch_decl_id AS DECL_ID,
DECLARATIONS.ch_decl_reference AS DECL_REF,
DECLARATIONS.ch_decl_date AS DECL_DATE,
USERS.ch_user_id AS USER_ID,
CONCAT(USERS.ch_user_name, ' ', USERS.ch_user_surname) AS USER_NAME,
ACTIVITIES.ch_opts_label AS ACTIVITY_LABEL,
AGREEMENTS.ch_opts_label AS AGREEMENT_LABEL,
CODE_POSTAUX.ch_cp_label AS CITY_LABEL,
COUNT(INCIDENTS.ch_inc_id) AS INCIDENT_COUNT,
COUNT(DOC.ch_doc_id) AS DOCUMENT_COUNT
FROM tab_declarations DECLARATIONS
JOIN tab_users USERS ON USERS.ch_user_id = DECLARATIONS.ch_decl_user_id
JOIN tab_cp CODE_POSTAUX ON CODE_POSTAUX.ch_cp_code = DECLARATIONS.ch_decl_cp_id
LEFT JOIN tab_incidents INCIDENTS ON INCIDENTS.ch_inc_decl_id = DECLARATIONS.ch_decl_id AND INCIDENTS.ch_inc_etat = '1'
LEFT JOIN tab_documents DOC ON DOC.ch_doc_decl_id = DECLARATIONS.ch_decl_id AND DOC.ch_doc_etat = '1'
LEFT JOIN tab_options ACTIVITIES ON ACTIVITIES.ch_opts_slug = DECLARATIONS.ch_decl_activity AND ACTIVITIES.ch_opts_type = 'ch_etbs_activity'
LEFT JOIN tab_options AGREEMENTS ON AGREEMENTS.ch_opts_id = DECLARATIONS.ch_decl_agreement AND AGREEMENTS.ch_opts_type = 'ch_decl_agreement'
WHERE DECLARATIONS.ch_decl_etat = '1'
GROUP BY
DECLARATIONS.ch_decl_id,
USERS.ch_user_id,
ACTIVITIES.ch_opts_label,
AGREEMENTS.ch_opts_label,
CODE_POSTAUX.ch_cp_label
ORDER BY DECLARATIONS.ch_decl_date DESC;Cette requête fonctionne, mais elle porte déjà toute une vision métier. Elle assemble des fragments venant de plusieurs tables, applique des règles de filtrage, compte des incidents, recense des documents et produit une lecture directement exploitable par l’interface. Le tout est recalculé à chaque appel, ce que l’on perçoit très concrètement lorsqu’une simple exécution dans phpMyAdmin prend plusieurs dizaines de secondes, même avec un LIMIT court.

Ce n’est pas une erreur de conception. C’est le reflet naturel de la manière dont les données sont structurées et des besoins qui s’y greffent. Une base relationnelle est faite de tables spécialisées, chacune portant une partie de l’information. Dès que l’on veut produire une vue d’ensemble, par exemple une fiche, une liste ou un tableau de bord, il faut reconstruire cette vision à partir de fragments éparpillés.
Dans phpMyAdmin, cela apparaît clairement. Les tables, prises séparément, restent lisibles et simples. Mais la lecture réelle de l’application se joue dans les requêtes qui les relient, voir Types de Relations entre Tables dans MySQL/MariaDB. C’est là que la complexité se concentre, non pas dans les données elles mêmes, mais dans la manière dont elles sont combinées, filtrées et agrégées pour être affichées.

Au fil des usages, ces requêtes deviennent des lieux de convergence. Elles décrivent à la fois quelles données sont nécessaires, comment elles sont reliées et sous quelle forme elles doivent être présentées. Elles portent alors une part croissante de la logique de lecture, souvent dupliquée ou légèrement divergente selon les écrans et les scripts PHP.
C’est généralement à ce moment là que l’on ressent une gêne très concrète, faite à la fois de lenteur et de perte de lisibilité. Pas parce que la base est mauvaise, mais parce que la même lecture est reconstruite encore et encore par des requêtes toujours plus lourdes. Ce constat ouvre naturellement la voie à une autre manière de travailler avec la base.
Donner un nom à la lecture
La requête du chapitre précédent n’a rien d’excessif. Elle est simplement fidèle à ce que l’application doit montrer. Elle rassemble les déclarations, les personnes, les localisations, les incidents, les documents et les libellés métier pour produire une lecture complète, directement exploitable par l’interface. Si son exécution est lente dans PHPMyAdmin, surtout en l’absence d’index, ce n’est pas parce qu’elle est mal écrite, mais parce qu’elle porte déjà à elle seule une vue métier entière.
Plutôt que de la réécrire dans chaque page PHP, ou de la factoriser via des include ou une API pour des usages différents, une autre approche consiste à la poser une fois pour toutes dans la base, sous la forme d’une vue SQL, CREATE VIEW. On ne change rien à la logique, on ne simplifie pas les jointures, on ne triche pas avec les données. On fait simplement un geste de mise en forme, on donne un nom à ce que la requête raconte.
CREATE VIEW view_alloagri_declarations AS
SELECT
DECLARATIONS.ch_decl_id AS DECL_ID,
DECLARATIONS.ch_decl_reference AS DECL_REF,
DECLARATIONS.ch_decl_date AS DECL_DATE,
USERS.ch_user_id AS USER_ID,
CONCAT(USERS.ch_user_name, ' ', USERS.ch_user_surname) AS USER_NAME,
ACTIVITIES.ch_opts_label AS ACTIVITY_LABEL,
AGREEMENTS.ch_opts_label AS AGREEMENT_LABEL,
CODE_POSTAUX.ch_cp_label AS CITY_LABEL,
COUNT(INCIDENTS.ch_inc_id) AS INCIDENT_COUNT,
COUNT(DOC.ch_doc_id) AS DOCUMENT_COUNT
FROM tab_declarations DECLARATIONS
JOIN tab_users USERS ON USERS.ch_user_id = DECLARATIONS.ch_decl_user_id
JOIN tab_cp CODE_POSTAUX ON CODE_POSTAUX.ch_cp_code = DECLARATIONS.ch_decl_cp_id
LEFT JOIN tab_incidents INCIDENTS ON INCIDENTS.ch_inc_decl_id = DECLARATIONS.ch_decl_id AND INCIDENTS.ch_inc_etat = '1'
LEFT JOIN tab_documents DOC ON DOC.ch_doc_decl_id = DECLARATIONS.ch_decl_id AND DOC.ch_doc_etat = '1'
LEFT JOIN tab_options ACTIVITIES ON ACTIVITIES.ch_opts_slug = DECLARATIONS.ch_decl_activity AND ACTIVITIES.ch_opts_type = 'ch_etbs_activity'
LEFT JOIN tab_options AGREEMENTS ON AGREEMENTS.ch_opts_id = DECLARATIONS.ch_decl_agreement AND AGREEMENTS.ch_opts_type = 'ch_decl_agreement'
WHERE DECLARATIONS.ch_decl_etat = '1'
GROUP BY
DECLARATIONS.ch_decl_id,
USERS.ch_user_id,
ACTIVITIES.ch_opts_label,
AGREEMENTS.ch_opts_label,
CODE_POSTAUX.ch_cp_label;À ce stade, rien n’a été optimisé. Aucune table n’a reçu d’index supplémentaire. La vue ne rend pas la requête plus rapide par magie. En revanche, elle change radicalement la manière dont cette lecture existe dans le système. Elle devient un objet de la base, visible dans phpMyAdmin, sélectionnable comme une table, documentable et réutilisable.
Côté PHP, cela se traduit immédiatement par un changement de posture. Là où l’on écrivait auparavant une requête longue et fragile, on peut désormais interroger la vue et adapter la lecture selon le besoin, tri, filtres, pagination.
SELECT * FROM view_alloagri_declarations ORDER BY DECL_DATE DESC LIMIT 10;
SELECT * FROM view_alloagri_declarations WHERE ACTIVITY_LABEL = 'Viticulteur/trice' ORDER BY DECL_DATE DESC;
SELECT * FROM view_alloagri_declarations ORDER BY INCIDENT_COUNT DESC LIMIT 20;
La complexité n’a pas disparu, mais elle a été déplacée. Elle n’est plus disséminée dans le code, elle est concentrée dans un seul point, nommé, stable et lisible. C’est cette bascule qui marque le passage d’une base faite de tables à une base pensée comme un ensemble de points de vue.
Lire la base à travers ses vues
Une fois la vue créée, quelque chose change immédiatement dans phpMyAdmin. Jusqu’ici, la base se présentait comme une collection de tables, chacune lisible, mais incapable à elle seule de raconter ce que voit réellement l’application. Avec la vue, une nouvelle catégorie d’objets apparaît, qui ne stocke rien, mais qui reflète la lecture attendue par l’application.
Dans la liste des tables, view_alloagri_declarations se comporte comme un objet à part entière. On peut la parcourir, trier par date, filtrer, exporter. Pourtant, aucune donnée n’y est enregistrée. Chaque ligne est le résultat de la lecture que nous avons formalisée au chapitre précédent. La base commence à offrir non plus seulement des briques de stockage, mais des points de vue prêts à l’emploi.

Cette différence est décisive pour comprendre un schéma de données. Observer les tables revient à analyser la structure du stockage. Parcourir les vues revient à voir comment ces mêmes données sont croisées, filtrées et présentées pour produire une lecture directement utilisable. La vue met en avant une visualisation expressive des données, là où les tables ne montrent que leur organisation interne.
Dans AlloAgri, la vue des déclarations permet par exemple de parcourir directement les dossiers tels que les opérateurs les voient. Chaque ligne correspond à une situation complète, un déclarant, une activité, une localisation, un nombre d’incidents, un volume de documents. La logique métier, auparavant enfouie dans des requêtes PHP, devient visible dans la base elle même.

C’est aussi à ce stade que la maintenance change de nature. Modifier la manière dont une déclaration est présentée ne passe plus par la recherche de requêtes disséminées dans le code, mais par l’ajustement d’une seule vue. La base devient l’endroit où l’on règle concrètement ce que l’application doit afficher, et comment, plutôt que de se limiter à empiler des enregistrements.
Même sans parler d’optimisation ni d’index, cette bascule change déjà la façon dont tout le monde travaille. Développeurs, administrateurs et opérateurs se retrouvent face aux mêmes objets, aux mêmes lignes, aux mêmes colonnes, simplement parce qu’ils passent désormais par les mêmes vues.
Qui fait quoi entre SQL et PHP
Avec les vues en place, on cesse peu à peu de bricoler la lecture des données dans chaque script. Jusqu’ici, chaque écran, chaque export, chaque tableau devait recomposer sa propre version d’une déclaration, d’un dossier ou d’un utilisateur, avec des requêtes souvent proches mais jamais tout à fait identiques. La vue change concrètement la donne. Elle pose noir sur blanc, dans la base, ce que signifie lire une déclaration AlloAgri, quelles tables sont croisées, quels filtres s’appliquent, quels libellés sont affichés, quels comptes sont faits. Cette lecture n’est plus dispersée dans le PHP, elle existe à un seul endroit.

Le PHP peut alors respirer. Il se concentre sur ce qu’il fait le mieux, l’interface, les parcours, les droits, les formulaires, sans avoir à réinventer la logique métier des données. Il interroge une vue qui dit déjà ce qu’il faut montrer. Dans la pratique, cela se voit tout de suite. Là où une page embarquait une requête longue et fragile, on se contente désormais d’un SELECT sur une vue. Le SQL reste dense, mais il n’est plus caché dans le code, il est visible, partagé et maîtrisé directement au sein de la base.
Pour AlloAgri, cela veut dire qu’un écran opérateur, un export CSV ou une API interne lisent tous la même chose, de la même façon. On évite les écarts subtils, les oublis et les incohérences qui finissent par coûter du temps. C’est aussi ce qui rend les évolutions plus tranquilles. Ajouter un champ, ajuster un libellé ou introduire un nouveau calcul se fait dans la vue, sans courir après des requêtes éparpillées. La base devient le point où la lecture des données est réellement décidée.
Indexer une lecture, pas une jungle
À ce stade, la vue existe. Elle porte la lecture métier, elle est utilisée par le PHP, les exports et les outils. C’est seulement maintenant que la question des performances peut être posée correctement. Avant, optimiser revenait à courir après des requêtes éparpillées. Désormais, on sait exactement ce qui doit être rapide, la vue. Les colonnes sollicitées par view_alloagri_declarations sont connues. Ce sont celles utilisées dans les jointures, les filtres, les regroupements et les tris. Ajouter des index n’est plus un geste aveugle, mais une réponse ciblée à une lecture identifiée, voir MySQL : index, recherches et performance.
Dans AlloAgri, cela conduit naturellement à indexer, par exemple, les clés de jointure des déclarations vers les utilisateurs, les codes postaux, les incidents et les documents, ainsi que les champs utilisés pour filtrer ou trier comme la date ou l’état. On n’optimise pas toutes les tables, on optimise la manière dont la vue les consulte.
CREATE INDEX idx_decl_user_id ON tab_declarations (ch_decl_user_id);
CREATE INDEX idx_decl_cp_id ON tab_declarations (ch_decl_cp_id);
CREATE INDEX idx_decl_date ON tab_declarations (ch_decl_date);
CREATE INDEX idx_decl_etat ON tab_declarations (ch_decl_etat);
CREATE INDEX idx_inc_decl_id ON tab_incidents (ch_inc_decl_id);
CREATE INDEX idx_inc_etat ON tab_incidents (ch_inc_etat);
CREATE INDEX idx_doc_decl_id ON tab_documents (ch_doc_decl_id);
CREATE INDEX idx_doc_etat ON tab_documents (ch_doc_etat);
CREATE INDEX idx_opts_slug_type ON tab_options (ch_opts_slug, ch_opts_type);
CREATE INDEX idx_opts_id_type ON tab_options (ch_opts_id, ch_opts_type);
CREATE INDEX idx_cp_code ON tab_cp (ch_cp_code);La vue ne stocke rien. Elle continue de recalculer la lecture à chaque appel. Les index ne la transforment pas en cache, mais ils rendent ce calcul beaucoup plus efficace, parce que la base sait désormais où chercher.

Cette approche change aussi la façon de faire évoluer la base. Lorsqu’une nouvelle information est ajoutée à la vue, on peut immédiatement décider si elle mérite un index, en s’appuyant sur les usages réels, les écrans qui l’exploitent et les volumes manipulés. La performance devient une conséquence de la lecture, pas une contrainte imposée à l’avance.
Une base orientée vues permet ainsi d’aligner trois choses qui sont souvent dissociées, le sens métier, la structure SQL et la performance. Les tables stockent, les vues lisent, les index accélèrent cette lecture. Rien de plus, rien de moins.
La vue comme point de vue assumé
Une vue n’est jamais neutre. En choisissant quelles tables sont croisées, quels champs sont exposés, quels filtres s’appliquent et quels calculs sont effectués, on produit déjà une interprétation des données. La vue ne montre pas tout, elle montre ce qui a été jugé pertinent pour une lecture donnée. Cette distinction est essentielle. Les tables stockent des faits bruts, des identifiants, des dates, des relations. La vue, elle, organise ces éléments pour produire du sens. Elle sépare ce qui relève de l’enregistrement de ce qui relève de la compréhension. Autrement dit, elle matérialise le passage entre données et information.
Dans un projet comme AlloAgri, cela revient à décider ce qu’est réellement une déclaration. Est ce une ligne dans une table, ou un ensemble cohérent fait d’un déclarant, d’une activité, d’un lieu, d’incidents et de documents. La vue tranche cette question et la rend explicite.
Concevoir une vue, c’est donc prendre position. C’est dire ce que l’application est censée voir et comment elle doit le voir. Cette responsabilité est rarement formulée quand on enchaîne des requêtes dans le code, mais elle devient visible dès que la lecture est posée dans la base. Cette éthique de la lisibilité est au cœur d’une base orientée vues. Elle ne cherche pas à cacher la complexité, mais à la rendre intelligible. En donnant des noms aux lectures, en les rendant visibles et partageables, on accepte que la base soit aussi un espace de dialogue sur le sens des données, pas seulement un lieu de stockage.
Limites et zones de vigilance
Penser une base à travers des vues ne règle pas tout. Certaines difficultés disparaissent, d’autres deviennent plus visibles. C’est une bonne chose, mais cela impose de rester attentif à quelques points concrets. La performance reste une contrainte réelle. Une vue est une requête exécutée à chaque appel. Sans index adaptés ou avec des jointures trop lourdes, elle peut devenir coûteuse, comme toute requête complexe. Les vues ne remplacent pas l’optimisation, elles la rendent simplement plus ciblée.

L’empilement de vues est un autre risque. Construire une vue à partir d’une autre peut sembler pratique, mais cela peut rapidement masquer la logique réelle et rendre le diagnostic plus difficile. Une vue doit rester lisible et proche de la réalité qu’elle décrit. Il existe aussi des dépendances invisibles. Une modification dans une table ou dans une vue peut avoir des effets sur plusieurs écrans, exports ou scripts. D’où l’importance de considérer chaque vue comme un contrat et de documenter son usage.
Enfin, MariaDB ne propose pas de vues matérialisées, voir MySQL! MySQL, vous avez dit MariaDB ?. Il n’y a pas de cache automatique des résultats. Toute lecture passe par le calcul à la volée. Cela impose de bien choisir ce que l’on met dans une vue et de ne pas lui demander de faire trop.
Les vues apportent une structure et une lisibilité nouvelles, mais elles ne remplacent ni la réflexion sur le modèle, ni le suivi des performances, ni la discipline de conception.
Conclusion
Penser une base de données comme un ensemble de vues change profondément la manière de travailler. On ne part plus uniquement de ce qui est stocké, mais de ce qui doit être lu, compris et partagé. Dans beaucoup de projets, cette approche permet de sortir de la multiplication des requêtes ad hoc pour poser des lectures stables, nommées et visibles dans la base. Le PHP, les exports et les APIs consomment alors la même réalité, sans la recomposer chacun de leur côté.
Les tables continuent de jouer leur rôle de stockage. Les vues deviennent le lieu où se définit le sens. Les index viennent ensuite accélérer cette lecture. Ce découpage simple rend la base plus lisible, plus cohérente et plus facile à faire évoluer. Une base orientée vues n’est pas une technique de plus. C’est une façon de reprendre la main sur ce que les données racontent réellement à l’application et à ceux qui la font vivre.
