Le doublon que le rapprochement ne voyait pas
Deux fichiers sans clé commune, 7 305,00 d'écart : le rapprochement SQL par jointure laisse passer un loyer saisi deux fois, que seul le contrôle final rattrape.

Dans le cadre de la rubrique « Je partage mes connaissances », je vous propose de rapprocher le grand livre du premier semestre 2026 du relevé bancaire du même semestre. Deux fichiers de 86 lignes chacun, aucune clé commune, et un écart de 7 305,00 euros entre les deux soldes. Tout se fait en SQL dans DB Browser for SQLite, et la méthode la plus naturelle laisse passer un défaut que seul le contrôle final rattrape.
Le grand livre est celui de la série : importé dans une table ecritures. Le relevé s'importe de la même façon dans une table releve_bancaire (Fichier > Importer > Table depuis un fichier CSV, première ligne en en-têtes, séparateur virgule, UTF-8). Il a trois colonnes : date_releve, libelle_releve et un seul montant, positif pour un encaissement, négatif pour un décaissement.
Deux fichiers, la même histoire, pas la même fin
Le compte 512000 du grand livre finit le semestre à -79 570,00. Le relevé finit à -72 265,00. Les deux fichiers comptent 86 mouvements. Ce nombre identique est un piège : il donne l'impression que tout concorde, alors que l'écart est de 7 305,00.
Le réflexe Excel, c'est un RECHERCHEV du montant d'une feuille dans l'autre. Il trouve la première ligne qui correspond et s'arrête là. En SQL, le réflexe équivalent est une jointure. Mais il n'existe aucune clé commune entre une écriture comptable et une ligne bancaire. On rapproche par ressemblance : même date, même montant. Et une ressemblance n'est pas une identité.
Le rapprochement exact, et ce qu'il ne voit pas
Le cas
Sortir dans un seul résultat les mouvements présents dans les comptes et absents du relevé, et ceux présents sur le relevé et absents des comptes. Un mouvement est retrouvé si la date et le montant sont identiques des deux côtés.
La démarche
- Onglet Exécuter le SQL. Premier
LEFT JOIN, des écritures du compte 512000 vers le relevé, sur la date et le montant arrondi.WHERE r.montant IS NULLgarde les écritures sans partenaire. - Second
LEFT JOIN, dans l'autre sens, du relevé vers les écritures. - Une colonne constante
originedit de quel côté vient chaque ligne, puisUNION ALLassemble les deux moitiés. C'est la forme portable d'unFULL OUTER JOIN, que toutes les versions de SQLite n'acceptent pas. - F5.
SELECT 'COMPTES_SEULS' AS origine, e.date_ecriture AS date_mouvement, e.libelle,
ROUND(e.debit - e.credit, 2) AS montant
FROM ecritures e
LEFT JOIN releve_bancaire r
ON r.date_releve = e.date_ecriture
AND ROUND(r.montant, 2) = ROUND(e.debit - e.credit, 2)
WHERE e.compte = '512000' AND r.montant IS NULL
UNION ALL
SELECT 'BANQUE_SEULE', r.date_releve, r.libelle_releve, ROUND(r.montant, 2)
FROM releve_bancaire r
LEFT JOIN ecritures e
ON e.compte = '512000'
AND e.date_ecriture = r.date_releve
AND ROUND(e.debit - e.credit, 2) = ROUND(r.montant, 2)
WHERE e.compte IS NULL
ORDER BY origine, date_mouvement;
Ce que vous devez obtenir
5 lignes :
| Origine | Date | Libellé | Montant |
|---|---|---|---|
| BANQUE_SEULE | 02/04/2026 | Honoraires comptables mars 2026 | -1 850,00 |
| BANQUE_SEULE | 29/06/2026 | Virement recu - Client Meridian | 950,00 |
| BANQUE_SEULE | 30/06/2026 | Frais de tenue de compte T2 2026 | -45,00 |
| COMPTES_SEULS | 31/03/2026 | Honoraires comptables mars 2026 | -1 850,00 |
| COMPTES_SEULS | 30/06/2026 | Locations juin 2026 | -3 200,00 |
Le résultat a l'air propre. Il lui manque une ligne. Le loyer d'avril, -3 200,00 au 30/04, est saisi deux fois dans le grand livre, et la banque ne l'a prélevé qu'une fois. Les deux écritures trouvent chacune la même ligne bancaire : aucune ne reste sans partenaire, et le doublon disparaît du résultat.
La preuve tient en un comptage. Une jointure interne sur la date et le montant rend 84 couples, pour 84 écritures retrouvées mais seulement 83 lignes bancaires : une ligne du relevé a servi deux fois.
Une jointure ne consomme rien. Une ligne bancaire peut justifier deux écritures, et un rapprochement qui le permet ne trouvera jamais un doublon.
Un pour un : numéroter les copies
Le cas
Refaire le même rapprochement en imposant qu'une ligne bancaire ne justifie qu'une seule écriture, et inversement.
La démarche
- Dans une CTE par fichier, numéroter les lignes identiques avec
ROW_NUMBER() OVER (PARTITION BY date, montant ORDER BY rowid). Les deux loyers d'avril deviennent la copie 1 et la copie 2 ; la ligne bancaire n'a qu'une copie 1. - Ajouter le numéro de copie à la condition de jointure, dans les deux sens.
- F5.
WITH c AS (
SELECT date_ecriture AS d, libelle, ROUND(debit - credit, 2) AS montant,
ROW_NUMBER() OVER (PARTITION BY date_ecriture, ROUND(debit - credit, 2)
ORDER BY rowid) AS n
FROM ecritures
WHERE compte = '512000'
),
b AS (
SELECT date_releve AS d, libelle_releve AS libelle, ROUND(montant, 2) AS montant,
ROW_NUMBER() OVER (PARTITION BY date_releve, ROUND(montant, 2)
ORDER BY rowid) AS n
FROM releve_bancaire
)
SELECT 'COMPTES_SEULS' AS origine, c.d AS date_mouvement, c.libelle, c.montant
FROM c LEFT JOIN b ON b.d = c.d AND b.montant = c.montant AND b.n = c.n
WHERE b.d IS NULL
UNION ALL
SELECT 'BANQUE_SEULE', b.d, b.libelle, b.montant
FROM b LEFT JOIN c ON c.d = b.d AND c.montant = b.montant AND c.n = b.n
WHERE c.d IS NULL
ORDER BY origine, date_mouvement;
Ce que vous devez obtenir
6 lignes : les 5 précédentes, plus la copie en trop.
| Origine | Date | Libellé | Montant |
|---|---|---|---|
| COMPTES_SEULS | 30/04/2026 | Locations avril 2026 | -3 200,00 |
Deux jours de valeur ne font pas un écart
Les deux lignes « Honoraires comptables mars 2026 » portent le même libellé et le même montant, -1 850,00. La comptabilité les date du 31/03, la banque du 02/04. Le rapprochement exact en fait deux anomalies. Il n'y en a aucune.
Le cas
Parmi les lignes restées seules, retrouver celles qui sont le même mouvement à quelques jours de valeur près, avec une tolérance de cinq jours calendaires.
La démarche
- Reprendre les CTE
cetbci-dessus et isoler leurs lignes sans partenaire dans deux nouvelles CTE,comptes_seulsetbanque_seule. - Les joindre entre elles sur le montant, et sur un écart de date borné :
ABS(julianday(b.d) - julianday(c.d)) <= 5.julianday()convertit une date texte en nombre ; sans elle, une soustraction de dates ne fonctionne pas. - F5.
-- à la suite des CTE c et b
, comptes_seuls AS (
SELECT c.* FROM c LEFT JOIN b ON b.d = c.d AND b.montant = c.montant AND b.n = c.n
WHERE b.d IS NULL
),
banque_seule AS (
SELECT b.* FROM b LEFT JOIN c ON c.d = b.d AND c.montant = b.montant AND c.n = b.n
WHERE c.d IS NULL
)
SELECT cs.d AS date_comptable, cs.libelle, cs.montant, bs.d AS date_bancaire,
CAST(julianday(bs.d) - julianday(cs.d) AS INTEGER) AS ecart_jours
FROM comptes_seuls cs
JOIN banque_seule bs
ON bs.montant = cs.montant
AND ABS(julianday(bs.d) - julianday(cs.d)) <= 5;
Ce que vous devez obtenir
| Date comptable | Libellé | Montant | Date bancaire | Écart |
|---|---|---|---|---|
| 31/03/2026 | Honoraires comptables mars 2026 | -1 850,00 | 02/04/2026 | 2 jours |
La borne n'est pas un détail. Appliquée à tout le fichier sans limite de date, la même jointure apparierait le loyer de juin, -3 200,00, à chacun des cinq loyers des mois précédents, de 30 à 150 jours plus tôt. Un délai de valeur de quelques jours est normal ; un écart de plusieurs semaines se signale, il ne s'excuse pas.
Une fois la paire retirée, il reste quatre vrais écarts : deux côté comptes (le loyer de juin, le loyer d'avril en double), deux côté banque (le virement reçu, les frais).
L'état de rapprochement, contrôlé par un total indépendant
Le cas
Partir du solde comptable et appliquer les quatre écarts, avec leur signe, jusqu'à retrouver le solde du relevé.
La démarche
- Une CTE construite ligne par ligne avec
UNION ALL. La première ligne lit le solde comptable par une sous-requête : on ne tape jamais ce chiffre à la main. - Le signe se raisonne. Le chèque de loyer de juin est sorti des comptes mais pas encore de la banque : on ajoute 3 200,00. Le loyer d'avril en double est une sortie qui n'a jamais eu lieu : on ajoute 3 200,00. Le virement est déjà à la banque : on ajoute 950,00. Les frais ont déjà été prélevés : on retranche 45,00.
- Le cumul ligne à ligne se calcule avec
SUM() OVER, la même fonction de fenêtrage que pour le solde progressif du compte banque. - Une seconde requête, indépendante, totalise le relevé. Les deux chiffres doivent être égaux.
WITH ajustements AS (
SELECT 1 AS ordre, 'Solde comptable au 30/06/2026 (512000)' AS libelle,
(SELECT ROUND(SUM(debit - credit), 2) FROM ecritures WHERE compte = '512000') AS montant
UNION ALL SELECT 2, 'Chèque en circulation (loyer de juin)', 3200.00
UNION ALL SELECT 3, 'Écriture en double à corriger (loyer d''avril)', 3200.00
UNION ALL SELECT 4, 'Virement client reçu, non enregistré', 950.00
UNION ALL SELECT 5, 'Frais bancaires non enregistrés', -45.00
)
SELECT ordre, libelle, montant,
ROUND(SUM(montant) OVER (ORDER BY ordre
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 2) AS solde_cumule
FROM ajustements
ORDER BY ordre;
-- Contrôle indépendant
SELECT ROUND(SUM(montant), 2) AS solde_releve FROM releve_bancaire;
Ce que vous devez obtenir
| Ordre | Libellé | Montant | Solde cumulé |
|---|---|---|---|
| 1 | Solde comptable au 30/06/2026 (512000) | -79 570,00 | -79 570,00 |
| 2 | Chèque en circulation (loyer de juin) | 3 200,00 | -76 370,00 |
| 3 | Écriture en double à corriger (loyer d'avril) | 3 200,00 | -73 170,00 |
| 4 | Virement client reçu, non enregistré | 950,00 | -72 220,00 |
| 5 | Frais bancaires non enregistrés | -45,00 | -72 265,00 |
Contrôle indépendant : -72 265,00. Identique au centime.
Faites l'essai avec les seuls écarts du rapprochement simple, sans la ligne 3. L'état s'arrête à -75 465,00, à 3 200,00 du relevé. Le total indépendant refuse de boucler, et c'est lui qui oblige à chercher le doublon que la jointure avait caché.
Le doublon se signale et se corrige en comptabilité, par une écriture d'extourne. Il ne se supprime pas dans la requête pour faire tomber l'état juste.
Les erreurs qui coûtent une soirée
- Compter pour rapprocher. 86 lignes de chaque côté, et quatre vrais écarts.
- Laisser une ligne servir deux fois. Sans appariement un pour un, un doublon n'apparaît dans aucune liste d'écarts.
- Rapprocher sans borne de date. Avec des montants récurrents comme un loyer, tout finit par trouver un partenaire.
- Soustraire des dates texte.
'2026-04-02' - '2026-03-31'ne donne pas 2 jours : passez parjulianday(). - S'arrêter avant le contrôle. Un état qui ne retombe pas exactement sur le relevé n'est pas un état de rapprochement.
Pour revoir les fonctions de fenêtrage utilisées ici, cumul, classement et comparaison au mois précédent : le solde que le total ne raconte 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 fonctions de fenêtrage existent dans SQLite depuis la version 3.25.0 (septembre 2018). julianday() est propre à SQLite : dans PostgreSQL, on soustrait directement deux valeurs de type date.