ES
ERVIS SEFERI
Finance · Business · Technologie
← Retour · Je partage mes connaissances
IA & Automatisation · SQL et Python pour la finance

Le compte que la jointure faisait disparaître

Un grand livre de 173 lignes, un écart de 350,00 euros et un compte absent du plan comptable : trois requêtes SQL pour les voir avant qu'ils ne coûtent.

Dans le cadre de la rubrique « Je partage mes connaissances », je vous propose de contrôler un grand livre en SQL. Le fichier compte 173 lignes, couvre le premier semestre 2026 et ne tombe pas juste de 350,00 euros. Il contient aussi un compte que le plan comptable ne connaît pas. Trois requêtes suffisent pour le voir, à condition de choisir la bonne jointure.

Deux balances côte à côte : avec INNER JOIN, la ligne du compte 622600 disparaît ; avec LEFT JOIN, elle reste, libellé et classe vides
La jointure ne se choisit pas au goût : sur un contrôle, celle qui écarte ce qui ne s'apparie pas efface précisément ce que vous cherchez.

Pourquoi passer par SQL

Le réflexe du tableur, c'est de filtrer, de trier, puis de faire défiler. Sur 173 lignes, cela reste faisable. Sur 173 000, on ne voit plus rien, et surtout on ne peut pas rejouer le contrôle le mois suivant sans refaire chaque clic.

Une requête SQL est une question écrite une fois. Elle se relance sur le fichier du mois suivant, elle se relit, elle se transmet à un collègue. Et elle ne modifie rien : elle interroge les données sans y toucher.

Le grand livre du cas est au format long : une ligne par mouvement, avec six colonnes - compte, date_ecriture, journal, libelle, debit, credit. Le débit et le crédit sont deux colonnes distinctes, toutes deux positives. Le solde d'un compte vaut donc SUM(debit) - SUM(credit) : positif, il est débiteur ; négatif, il est créditeur.

Mise en place, une seule fois

L'outil retenu est DB Browser for SQLite : gratuit, hors ligne, sans compte à créer. Les requêtes de cet article sont du SQL standard ; elles tournent à l'identique dans PostgreSQL ou DuckDB.

  1. Installer DB Browser for SQLite depuis sqlitebrowser.org, puis le lancer.
  2. Menu Fichier > Nouvelle base de données..., nommer le fichier finance_s1.sqlite, Enregistrer. Dans la boîte « Créer une table » qui s'ouvre, cliquer Annuler : les tables viendront de l'import.
  3. Menu Fichier > Importer > Table depuis un fichier CSV..., choisir ecritures_2026_s1.csv. Nom de la table : ecritures. Cocher « La première ligne contient les noms de colonnes ». Séparateur : la virgule. Encodage : UTF-8. OK.
  4. Même import pour plan_comptable.csv, sous le nom plan_comptes.
  5. Cliquer Écrire les modifications.

Les colonnes du plan comptable arrivent sous les noms Compte, Libelle et Classe. Renommez-les une fois pour toutes dans l'onglet Exécuter le SQL, puis F5 et Écrire les modifications :

ALTER TABLE plan_comptes RENAME COLUMN "Compte" TO numero_compte;
ALTER TABLE plan_comptes RENAME COLUMN "Libelle" TO libelle_compte;
ALTER TABLE plan_comptes RENAME COLUMN "Classe" TO classe;
Si les accents des libellés apparaissent abîmés dans l'aperçu de l'import, l'encodage n'est pas sur UTF-8. Corrigez-le avant de valider : un libellé mal lu à l'import le reste dans toutes les requêtes.

Le premier contrôle : une requête, quatre chiffres

Le cas

On vous livre le grand livre du semestre. Avant toute analyse, il faut prouver deux choses : le fichier est chargé en entier, et l'ensemble des écritures est équilibré.

La démarche

  1. Onglet Exécuter le SQL, zone de saisie du haut.
  2. Écrire une requête sans GROUP BY : on veut un seul chiffre par indicateur, pour tout le fichier.
  3. F5, puis lire la grille sous la zone de saisie.
SELECT COUNT(*) AS nb_lignes,
       ROUND(SUM(debit), 2) AS total_debit,
       ROUND(SUM(credit), 2) AS total_credit,
       ROUND(SUM(debit) - SUM(credit), 2) AS ecart
FROM ecritures;

Ce que vous devez obtenir

nb_lignestotal_debittotal_creditecart
173815 020,00814 670,00350,00

173 est le bon nombre : le fichier est entier. L'écart, lui, n'est pas nul. Le jeu d'écritures n'est pas équilibré, à 350,00 euros près. Ce n'est pas une erreur d'import, c'est un défaut du grand livre. Un écart nul aurait clos le sujet ; un écart de 350,00 ouvre une enquête.

La balance qui garde ce qu'on n'attendait pas

Le cas

Produire la balance générale : pour chaque compte, le total débit, le total crédit et le solde, avec le libellé et la classe venus du plan comptable. Aucun compte mouvementé ne doit disparaître du résultat, même s'il n'est pas au plan.

La démarche

  1. Joindre ecritures à plan_comptes avec un LEFT JOIN, pas un JOIN. Le JOIN simple ne garde que ce qui s'apparie des deux côtés. Le LEFT JOIN garde toutes les écritures et laisse vide ce qui manque au plan.
  2. Mettre dans le GROUP BY toutes les colonnes non agrégées du SELECT : le compte, le libellé, la classe.
  3. Trier par numéro de compte, puis F5.
SELECT e.compte,
       p.libelle_compte,
       p.classe,
       ROUND(SUM(e.debit), 2) AS total_debit,
       ROUND(SUM(e.credit), 2) AS total_credit,
       ROUND(SUM(e.debit) - SUM(e.credit), 2) AS solde
FROM ecritures e
LEFT JOIN plan_comptes p ON e.compte = p.numero_compte
GROUP BY e.compte, p.libelle_compte, p.classe
ORDER BY e.compte;

Ce que vous devez obtenir

Seize lignes, dans cet ordre.

comptelibelléclassedébitcréditsolde
401000Fournisseurs40,0072 100,00-72 100,00
411000Clients4125 800,000,00125 800,00
512000Banque5367 550,00447 120,00-79 570,00
601000Achats matières premières650 100,000,0050 100,00
606100Fournitures non stockables68 860,000,008 860,00
613000Locations622 400,000,0022 400,00
621000Personnel extérieur625 100,000,0025 100,00
622600(vide)(vide)1 850,000,001 850,00
641000Rémunérations du personnel6136 800,000,00136 800,00
645000Charges de sécurité sociale658 260,000,0058 260,00
661000Charges d'intérêts65 350,000,005 350,00
681000Dotations aux amortissements612 950,000,0012 950,00
701000Ventes de produits finis70,00247 400,00-247 400,00
706000Prestations de services70,0038 600,00-38 600,00
708000Produits des activités annexes70,007 280,00-7 280,00
761000Produits financiers70,002 170,00-2 170,00
Trois lignes de la balance à lire avant de conclure : 622600 sans libellé, 613000 à 22 400 au lieu de 19 200, 512000 à -79 570 contre 264 900 dans le fichier d'origine
Une balance juste au format peut rester fausse au fond : trois lignes demandent une explication avant tout commentaire.

Trois lignes méritent qu'on s'y arrête.

  • 622600 n'a ni libellé ni classe. DB Browser affiche ces cellules vides en italique : c'est la valeur NULL. Le LEFT JOIN vous montre l'anomalie. Avec un JOIN simple, cette ligne n'existerait pas, et 1 850,00 euros de charge auraient quitté la balance sans bruit.
  • 613000 Locations affiche 22 400,00. Le loyer est de 3 200,00 par mois, soit 19 200,00 sur six mois. Les 3 200,00 de trop viennent d'une écriture passée deux fois ; nous la retrouverons dans le prochain article de la série.
  • 512000 Banque ressort à -79 570,00, alors que le fichier d'origine, au format large, affichait 264 900,00 sur cette ligne. Ici, la banque est reconstruite comme la contrepartie de chaque mouvement ; le chiffre du fichier large n'a pas la même définition. Deux chiffres, deux définitions : c'est à expliquer avant de le signaler comme un écart.

Ce qui manque, en une requête

Le cas

Sans lire la balance ligne à ligne, sortir la liste des comptes mouvementés au grand livre et absents du plan comptable, avec le montant en jeu.

La démarche

  1. Reprendre le LEFT JOIN de la balance.
  2. Ajouter WHERE p.numero_compte IS NULL. Un LEFT JOIN suivi d'un test IS NULL sur la clé de droite, c'est la façon standard de dire « ce qui est à gauche et pas à droite ».
  3. Grouper par compte pour obtenir une ligne par compte fautif, et sommer pour chiffrer l'enjeu. F5.
SELECT e.compte,
       ROUND(SUM(e.debit) - SUM(e.credit), 2) AS solde
FROM ecritures e
LEFT JOIN plan_comptes p ON e.compte = p.numero_compte
WHERE p.numero_compte IS NULL
GROUP BY e.compte;

Ce que vous devez obtenir

comptesolde
6226001 850,00

Une ligne, une écriture derrière elle. En situation réelle, ce résultat devient une phrase courte adressée à la comptabilité : le compte 622600 a été mouvementé de 1 850,00 euros au débit le 31 mars 2026 sur le journal des achats, et il n'est pas ouvert au plan comptable. On le signale. On ne le corrige pas en silence dans l'outil.

N'écrivez jamais = NULL. En SQL, NULL n'est égal à rien, ni à zéro, ni à une chaîne vide, ni à lui-même. La condition renvoie toujours faux et la requête sort vide, ce qui ressemble à s'y méprendre à « aucune anomalie ». Seuls IS NULL et IS NOT NULL testent l'absence.

Les erreurs qui coûtent une soirée

  • Le flottant. SUM(debit) - SUM(credit) peut renvoyer 0,000000001 sur des montants justes, parce que les décimales sont stockées en binaire. Un test « différent de zéro » signale alors un déséquilibre qui n'existe pas. Arrondissez à deux décimales avant de comparer : c'est pourquoi ROUND apparaît dès la première requête.
  • JOIN contre LEFT JOIN. Le JOIN simple fait disparaître ce qui ne s'apparie pas. Sur un contrôle, c'est l'inverse du but. Dès que la question est « qu'est-ce qui manque », c'est un LEFT JOIN suivi de IS NULL.
  • Sommer après une jointure qui duplique. Ici, le plan comptable a une ligne par compte : la jointure ne multiplie rien. Le jour où la table de droite porte plusieurs lignes par clé, chaque montant est compté autant de fois. Vérifiez la cardinalité avant de sommer, comme on vérifie le sens d'une relation dans un modèle - le sens unique qui protège vos totaux traite le même piège côté Power BI.
  • Conclure sur le contrôle global. Le total de 350,00 dit qu'il y a un problème, pas où il se trouve. Le contrôle global répond oui ou non ; il faut ensuite un contrôle groupé pour localiser.

Le grand livre n'est donc pas équilibré, et il contient un compte hors plan. Il reste à trouver où se cachent les 350,00 euros, et à comprendre pourquoi un fichier peut être parfaitement équilibré et pourtant faux.

La suite de la série localise les 350,00 euros par journal et par date, sans ouvrir le fichier : équilibré et pourtant faux. Dans le même esprit de contrôle, côté modèle Power BI : l'écart que le modèle ne voit pas.

Versions : article rédigé d'après DB Browser for SQLite 3.13.1, la version proposée au téléchargement sur sqlitebrowser.org (interface en français). Les requêtes sont du SQL standard et s'exécutent à l'identique dans PostgreSQL ou DuckDB ; les noms de menus de DB Browser peuvent changer d'une version à l'autre.