Accueil du site > Les articles > Générer un tableau d’amortissement d’immobilisation avec SQL DB2
Version à imprimer Enregistrer au format PDF

Générer un tableau d’amortissement d’immobilisation avec SQL DB2

où comment coder un process métier complexe dans une seule requête SQL.

vendredi 6 février 2015, par Grégory Jarrige Visites  1036

Dans les années 90, j’avais travaillé sur le développement d’un logiciel de gestion de société d’HLM, et j’avais notamment développé l’intégralité du module de gestion des immobilisations (module incluant la gestion des amortissements). Je m’étais régalé sur le développement de ce module (écrit en ADELIA), qui incluait différents types d’amortissements (linéaire, dégressif, linéaire-progressif). J’avoue que j’ai un peu oublié comment se calcule l’amortissement dégressif (légèrement plus complexe sur la fin du tableau que le linéaire), ainsi que le linéaire progressif (mode de calcul que je n’ai vu pratiquer que dans le secteur HLM). Je n’ai en revanche pas eu trop de mal pour me rappeler du principe de calcul de l’amortissement linéaire, mais je n’ai pas vraiment de mérite car il est relativement simple. C’est celui-ci que nous étudierons dans cet article, et surtout que nous allons implémenter en SQL DB2.

Pour ne rien vous cacher, cela faisait un moment que je me triturais le cerveau pour trouver un moyen de générer un tableau d’amortissement au moyen d’une requête SQL. Je me disais que cela devait être possible, mais je butais sur un problème, et c’est la découverte des RCTE (Recursive Common Table Expression) qui m’a apporté la solution. Pour rappel, j’avais présenté la technique des RCTE dans un précédent article de XDocs400 et je vous redonne juste un petit bout de code SQL pour rappeler le principe. Voici donc un exemple de RCTE qui permet de générer un jeu de données de 10 lignes :

WITH GEN_IDS(NX) AS (
SELECT 1 as N1 FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT NX+1 as N2 FROM GEN_IDS WHERE NX < 10
)
SELECT NX as N FROM GEN_IDS ;

Pour ceux qui ne connaîtraient pas le principe d’un tableau d’amortissement, voici un exemple :
- produit acheté pour une valeur de 100.000 euros
- durée de l’amortissement : 10 ans
- taux de l’amortissement : 10%
- date d’acquisition du bien : 1er juillet 2001

Le bien étant acquis en cours d’année (en l’occurrence le 1er juillet), la valeur de la première annuité d’amortissement sera divisée par 2, et la moitié résiduelle sera répercutée sur la 11ème année. On aura donc un amortissement sur 11 ans au lieu des 10 ans prévus initialement. Cette subtilité impacte bien évidemment l’algorithme de calcul du tableau.

A partir des données indiquées ci-dessus, voici le tableau que l’on souhaite obtenir :

ANNEE VNC_DEBUT_EXERCICE    ANNUITE    VNC_FIN_EXERCICE                
----- ------------------ ------------- ----------------
2001      100000,000000    5000,000000     95000,000000
2002       95000,000000   10000,000000     85000,000000
2003       85000,000000   10000,000000     75000,000000
2004       75000,000000   10000,000000     65000,000000
2005       65000,000000   10000,000000     55000,000000
2006       55000,000000   10000,000000     45000,000000
2007       45000,000000   10000,000000     35000,000000
2008       35000,000000   10000,000000     25000,000000
2009       25000,000000   10000,000000     15000,000000
2010       15000,000000   10000,000000      5000,000000
2011        5000,000000    5000,000000         0,000000

Pour pouvoir réaliser cette performance, nous allons utiliser une cascade de CTE (Common Table Expression), et une RCTE (CTE récursive).

Pour démarrer dans de bonnes conditions, je vous propose de créer une première CTE qui consistera à préparer les données utilisées pour la génération de notre tableau. On se contentera d’afficher le contenu de cette première CTE pour vérifier qu’elle fonctionne.

TMP_VALINITIALES (CAPITAL, TAUX, ANNEE_DEPART, NB_MOIS_AN1, NB_ANNUITES) as  (
 SELECT CAST(100000 AS DEC(11, 0)), -- capital initial
   CAST((10+0.0) / 100 AS DEC(5, 2)), -- taux d'amortissment
   CAST(2001 AS INTEGER), -- année de départ
   CAST(6 AS INTEGER), -- nbre mois pour calcul 1ère annuitée
   CAST(10 AS INTEGER) -- nombre d'années d'amortissement
 FROM SYSIBM.SYSDUMMY1
)
SELECT * FROM TMP_VALINITIALES ;

CAPITAL       TAUX    ANNEE_DEPART NB_MOIS_AN1 NB_ANNUITES
------------- ------- ------------ ----------- -----------
     100000,    0,10         2001           6          10

OK, ça marche, on a notre capital initial, notre taux, notre année de départ, le nombre de mois d’amortissement pour l’année de départ, et le nombre d’annuités. Si l’on souhaite par la suite travailler avec d’autres valeurs, il sera facile de les modifier étant donné qu’elles sont regroupées dans cette première CTE.

On peut maintenant enlever le "SELECT * FROM TMP_VALINITIALES" et ajouter une seconde CTE qui est la suivante :

-- Conversion de certaines valeurs initiales en format décimal et précalcul de certaines données
TMP_VALDEPART (CAPITAL, TAUX, ANNEE_DEPART, NB_MOIS_AN1, PRORATA, NB_ANNUITES) AS (
 SELECT CAPITAL, TAUX, ANNEE_DEPART, NB_MOIS_AN1,
   CAST( CAST(NB_MOIS_AN1 AS DEC(2, 0)) / 12.0 AS DEC(5, 4)) as PRORATA, -- prorata de la première annuité au format décimal
   CASE WHEN NB_MOIS_AN1 = 12 THEN NB_ANNUITES ELSE NB_ANNUITES + 1 END as NB_ANNUITES -- nombre d'années d'amortissement de type entier
 FROM TMP_VALINITIALES
)
select * from TMP_VALDEPART;

CAPITAL       TAUX    ANNEE_DEPART NB_MOIS_AN1 PRORATA NB_ANNUITES
------------- ------- ------------ ----------- ------- -----------
     100000,    0,10         2001           6  0,5000          11

L’objectif de cette seconde CTE était d’ajouter les colonnes PRORATA et NB_ANNUITES, qui vont être nécessaires pour la suite du calcul. Après avoir vérifié que cette seconde CTE fonctionne bien, nous pouvons supprimer la dernière ligne contenant le "SELECT * FROM TMP_VALDEPART", et le remplacer par une troisième CTE, ou plutôt RCTE, et dans la foulée une 4ème CTE, histoire d’avancer un peu plus vite :

-- Génération des lignes du tableau
GEN_IDS(NX) AS (
SELECT 1 as N1 FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT NX+1 as N2 FROM GEN_IDS WHERE NX < (SELECT NB_ANNUITES FROM  TMP_VALDEPART)
) ,
GEN_LIGNES AS (
SELECT cast(NX as integer) as VAL_INC FROM GEN_IDS
)
SELECT * FROM GEN_LIGNES ;

L’objectif du code ci-dessus est simplement de générer 11 lignes qui nous serviront de point d’appui pour le calcul de nos 11 annuités d’amortissement (je rappelle que notre bien a été acquis en cours d’année, donc il est normal que l’amortissement se fasse sur 11 années au lieu de 10).

Je ne vous donne pas le détail des 11 lignes générées ici, je vous invite plutôt à vérifier par vous même que cela fonctionne. Ensuite supprimez la dernière ligne "SELECT * FROM GEN_LIGNES" et remplacez-la par la CTE ci-dessous :

-- Calcul d'un premier tableau d'annuités théoriques
TMP_TABLEAU1 AS (
 SELECT VAL_INC, (SELECT TAUX FROM TMP_VALDEPART) as taux,
   (SELECT CAPITAL FROM TMP_VALDEPART) AS CAPITAL_INITIAL,
   CASE WHEN VAL_INC = 1 THEN
     -- la première année n'est pas forcément une année pleine, d'où application d'un prorata temporis sur la mensualité
     (SELECT CAPITAL FROM TMP_VALDEPART) * (SELECT TAUX FROM TMP_VALDEPART) * (SELECT PRORATA FROM TMP_VALDEPART)
   ELSE
     -- mensualité théorique pour une année pleine
     (SELECT CAPITAL FROM TMP_VALDEPART) * (SELECT TAUX FROM TMP_VALDEPART)
   END AS ANNUITES  
 FROM GEN_LIGNES
)
select * from TMP_TABLEAU1;

Si vous exécutez la requête en l’état, vous devez obtenir un premier tableau qui a belle allure, même si ce n’est pas encore un véritable tableau d’amortissement :

VAL_INC  TAUX   CAP_INITIAL ANNUITES              
------- ------ ------------ -------------
 1      0,10     100000,     5000,000000
 2      0,10     100000,    10000,000000
 3      0,10     100000,    10000,000000
 4      0,10     100000,    10000,000000
 5      0,10     100000,    10000,000000
 6      0,10     100000,    10000,000000
 7      0,10     100000,    10000,000000
 8      0,10     100000,    10000,000000
 9      0,10     100000,    10000,000000
10      0,10     100000,    10000,000000
11      0,10     100000,    10000,000000

Il nous reste une avant dernière CTE destinée à mettre en forme notre tableau d’amortissement :

-- Second tableau théorique incluant le calcul du CRD
TMP_TABLEAU2 AS (
 SELECT A.VAL_INC, A.CAPITAL_INITIAL, A.ANNUITES,
    A.CAPITAL_INITIAL - (SELECT SUM(ANNUITES) FROM TMP_TABLEAU1 X WHERE X.VAL_INC <= A.VAL_INC) AS CRD
 FROM TMP_TABLEAU1 A
),

Et une dernière CTE destinée à rattraper la dernière annuité, dans le cas des immobilisations acquises en cours d’exercice, comme c’est le cas dans notre exemple :

-- Rattrapage de la dernière annuité si CRD négatif sur la dernière année
TMP_TABLEAU3 AS (
 SELECT A.VAL_INC + (SELECT ANNEE_DEPART FROM TMP_VALDEPART) - 1 AS ANNEE,
    CASE WHEN CRD < 0 THEN
       A.ANNUITES + CRD
    ELSE
       A.ANNUITES
    END AS ANNUITE,
    CASE WHEN CRD < 0 THEN
       0
    ELSE
       A.CRD
    END AS VNC_FIN_EXERCICE
    FROM TMP_TABLEAU2 A
)
SELECT ANNEE, VNC_FIN_EXERCICE + ANNUITE AS VNC_DEBUT_EXERCICE, ANNUITE, VNC_FIN_EXERCICE
FROM TMP_TABLEAU3

Si tout s’est bien passé, votre tableau d’amortissement final devrait ressembler à ceci :

ANNEE VNC_DEBUT_EXERCICE    ANNUITE    VNC_FIN_EXERCICE                
----- ------------------ ------------- ----------------
2001      100000,000000    5000,000000     95000,000000
2002       95000,000000   10000,000000     85000,000000
2003       85000,000000   10000,000000     75000,000000
2004       75000,000000   10000,000000     65000,000000
2005       65000,000000   10000,000000     55000,000000
2006       55000,000000   10000,000000     45000,000000
2007       45000,000000   10000,000000     35000,000000
2008       35000,000000   10000,000000     25000,000000
2009       25000,000000   10000,000000     15000,000000
2010       15000,000000   10000,000000      5000,000000
2011        5000,000000    5000,000000         0,000000

Vous trouverez, attaché à cet article, un fichier texte contenant l’intégralité de la requête SQL présentée dans cet article. Ce code a été testé sur DB2 Express C (en version 9.7) et DB2 for i (en V7).

Il est bien évident que ce type d’approche est quelque peu radical, et peut heurter quand on n’y est pas habitué. Il aurait sans doute été plus simple d’écrire ce même code métier en PL/SQL selon une algorithmie plus "procédurale". C’est une affaire de style, et honnêtement je n’ai aucune préférence.

J’espère surtout, au travers de cet article, vous avoir fait toucher du doigt toute la puissance que la base de données DB2 est en mesure de mettre à votre disposition, dans le cadre du développement de code "métier". Peut être cet article donnera-t-il à certains lecteurs des idées pour implémenter leur propre code métier, si c’est le cas j’en serai vraiment ravi.

Tiens, j’essaierai un de ces jours de m’atteler au calcul de l’amortissement dégressif, j’ai dans l’idée que le calcul des dernières lignes du tableau sera gratiné :)

P.-S.

Pour des digressions autour du développement en général, et du développement web en particulier, je vous invite à me retrouver sur mon blog http://gregphplab.com

Documents joints