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

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.

Pont du solde comptable de -79 570,00 au solde bancaire de -72 265,00 en quatre marches : chèque en circulation +3 200,00, écriture en double +3 200,00, virement reçu +950,00, frais bancaires -45,00
Un rapprochement est terminé quand chaque euro d'écart porte un nom, et pas avant.

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

  1. 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 NULL garde les écritures sans partenaire.
  2. Second LEFT JOIN, dans l'autre sens, du relevé vers les écritures.
  3. Une colonne constante origine dit de quel côté vient chaque ligne, puis UNION ALL assemble les deux moitiés. C'est la forme portable d'un FULL OUTER JOIN, que toutes les versions de SQLite n'acceptent pas.
  4. 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 :

OrigineDateLibelléMontant
BANQUE_SEULE02/04/2026Honoraires comptables mars 2026-1 850,00
BANQUE_SEULE29/06/2026Virement recu - Client Meridian950,00
BANQUE_SEULE30/06/2026Frais de tenue de compte T2 2026-45,00
COMPTES_SEULS31/03/2026Honoraires comptables mars 2026-1 850,00
COMPTES_SEULS30/06/2026Locations 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

  1. 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.
  2. Ajouter le numéro de copie à la condition de jointure, dans les deux sens.
  3. 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.

OrigineDateLibelléMontant
COMPTES_SEULS30/04/2026Locations avril 2026-3 200,00
Les deux écritures du loyer d'avril reliées à la même ligne bancaire par une jointure simple, puis numérotées 1 et 2 par ROW_NUMBER : seule la copie 1 trouve son partenaire, la copie 2 reste seule
Numéroter les copies transforme une ressemblance en appariement un pour un : la copie sans partenaire, c'est le doublon.

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

  1. Reprendre les CTE c et b ci-dessus et isoler leurs lignes sans partenaire dans deux nouvelles CTE, comptes_seuls et banque_seule.
  2. 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.
  3. 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 comptableLibelléMontantDate bancaireÉcart
31/03/2026Honoraires comptables mars 2026-1 850,0002/04/20262 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

  1. 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.
  2. 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.
  3. Le cumul ligne à ligne se calcule avec SUM() OVER, la même fonction de fenêtrage que pour le solde progressif du compte banque.
  4. 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

OrdreLibelléMontantSolde cumulé
1Solde comptable au 30/06/2026 (512000)-79 570,00-79 570,00
2Chè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
4Virement client reçu, non enregistré950,00-72 220,00
5Frais 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 par julianday().
  • 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.