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.
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.
- Installer DB Browser for SQLite depuis sqlitebrowser.org, puis le lancer.
- 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. - 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. - Même import pour
plan_comptable.csv, sous le nomplan_comptes. - 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
- Onglet Exécuter le SQL, zone de saisie du haut.
- Écrire une requête sans GROUP BY : on veut un seul chiffre par indicateur, pour tout le fichier.
- 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_lignes | total_debit | total_credit | ecart |
|---|---|---|---|
| 173 | 815 020,00 | 814 670,00 | 350,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
- Joindre
ecrituresàplan_comptesavec 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. - Mettre dans le GROUP BY toutes les colonnes non agrégées du SELECT : le compte, le libellé, la classe.
- 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.
| compte | libellé | classe | débit | crédit | solde |
|---|---|---|---|---|---|
| 401000 | Fournisseurs | 4 | 0,00 | 72 100,00 | -72 100,00 |
| 411000 | Clients | 4 | 125 800,00 | 0,00 | 125 800,00 |
| 512000 | Banque | 5 | 367 550,00 | 447 120,00 | -79 570,00 |
| 601000 | Achats matières premières | 6 | 50 100,00 | 0,00 | 50 100,00 |
| 606100 | Fournitures non stockables | 6 | 8 860,00 | 0,00 | 8 860,00 |
| 613000 | Locations | 6 | 22 400,00 | 0,00 | 22 400,00 |
| 621000 | Personnel extérieur | 6 | 25 100,00 | 0,00 | 25 100,00 |
| 622600 | (vide) | (vide) | 1 850,00 | 0,00 | 1 850,00 |
| 641000 | Rémunérations du personnel | 6 | 136 800,00 | 0,00 | 136 800,00 |
| 645000 | Charges de sécurité sociale | 6 | 58 260,00 | 0,00 | 58 260,00 |
| 661000 | Charges d'intérêts | 6 | 5 350,00 | 0,00 | 5 350,00 |
| 681000 | Dotations aux amortissements | 6 | 12 950,00 | 0,00 | 12 950,00 |
| 701000 | Ventes de produits finis | 7 | 0,00 | 247 400,00 | -247 400,00 |
| 706000 | Prestations de services | 7 | 0,00 | 38 600,00 | -38 600,00 |
| 708000 | Produits des activités annexes | 7 | 0,00 | 7 280,00 | -7 280,00 |
| 761000 | Produits financiers | 7 | 0,00 | 2 170,00 | -2 170,00 |
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
- Reprendre le LEFT JOIN de la balance.
- 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 ». - 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
| compte | solde |
|---|---|
| 622600 | 1 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.