Les bonnes techniques de programmation pour l'ERP Dynamics NAV
Date de publication : 23 janvier 2012 , Date de mise à jour : 23 février 2012
Par
Alain Krikilion
traduit par jpelaho
1. Introduction
2. Aperçu de quelques commandes
3. Lectures de données sans verrou
3-A. Vous devez vérifier l'existence d'au moins un enregistrement dans votre jeu de données, mais vous ne souhaitez pas utiliser les données de ce dernier s'il existe
3-B. Vous voulez extraire le premier/dernier enregistrement (s'il existe), mais pas plus
3-C. Vous souhaitez extraire toutes les lignes d'un jeu de données dans l'ordre ascendant
3-D. Vous souhaitez extraire toutes les lignes d'un jeu de données dans l'ordre descendant
4. Opérations de mise à jour d'enregistrements
4-A. Vous ne devez modifier qu'un seul enregistrement
4-B. Vous devez récupérer un enregistrement et éventuellement le mettre à jour
4-C. Vous devez extraire un ensemble d'enregistrements et tous les modifier
4-D. Vous devez extraire un ensemble d'enregistrements et en modifier certains
5. Autres opérations
5-A. Utilisation de l'instruction FILTERGROUPS
5-B. Lecture/Insertion/Mise à jour/Suppression d'enregistrements dans une autre société
5-C. Vous souhaitez savoir la quantité totale en stock d'un article donné dans un magasin donné
5-D. Vous souhaitez calculer la somme du champ « montant » en groupant par « Gen. Bus. Posting Group » (groupe de comptabilisation marché) et « Gen. Prod. Posting Group » (groupe de comptabilisation produit) de la table 15 : « G/L Entry » (écriture comptable).
6. Remerciements
1. Introduction
J'ai remarqué plusieurs fois que la plupart des gens utilisent les instructions d'extraction des données à partir de la base d'une façon peu performante. Ou alors saisissent des instructions non nécessaires qui rendent le code confus.
Ceci m'a poussé à écrire cet article afin d'expliquer à l'aide de quelques exemples comment utiliser (ou pas) ces différentes instructions et à quel moment le faire (ou pas).
La plupart des éléments traités ici supposent que le serveur de base de données utilisé est SQL Server. Mais certains sont aussi valables pour le serveur de base de données natif C/SIDE.
2. Aperçu de quelques commandes
Les instructions CLEAR, INIT et RESET.
Commençons par présenter les différences entre ces trois instructions.
I. TheTable.RESET : utilisée pour supprimer tous les filtres (inclus les filtres indiqués dans les instructions FILTERGROUP) d'une variable de type record. Cette instruction réinitialise aussi la commande SETCURRENTKEY afin qu'elle utilise la clé primaire comme clé active pour les recherches.
 | Ne jamais utiliser cette instruction pour initialiser les champs d'une variable de type record. Ceci n'est pas son rôle. |
a) TRÈS MAUVAIS : si une variable de type record a des valeurs dans ses champs, l'instruction RESET ne les initialisera pas.
TheTable.RESET;
TheTable."Primary Key" := Some Value;
TheTable."Field N" := Some Value;
TheTable.INSERT(FALSE);
|
b) CORRECT :
CLEAR(TheTable);
TheTable."Primary Key" := Some Value;
TheTable."Field N" := Some Value;
TheTable.INSERT(FALSE);
|
II. TheTable.INIT : cette instruction initialise (avec la valeur par défaut) tous les champs d'une variable de type record sauf les champs constituant la clé primaire. Elle n'affecte pas les filtres (FILTERS) et ne modifie pas la clé active (CURRENTKEY). Donc si vous souhaitez ajouter un nouvel enregistrement sans modifier les valeurs des champs constituant la clé primaire, cette instruction convient parfaitement.
III. CLEAR(TheTable) : cette instruction combine l'action d'un RESET et d'un INIT et initialise les valeurs des champs constituant la clé primaire. J'utilise toujours cette instruction pour créer un nouvel enregistrement. Il s'agit de l'unique commande qui réinitialise la commande CHANGECOMPANY.
IV. GET : cette instruction est la meilleure si vous voulez extraire un enregistrement à l'aide de sa clé primaire. Vous pouvez aussi filtrer sur la clé primaire et utiliser l'instruction FINDFIRST. La commande SELECT envoyée au serveur SQL est la même mais l'instruction GET nécessite moins de code et est plus facile à lire. Aucune commande RESET, SETCURRENTKEY, SETRANGE n'est nécessaire, ces dernières instructions n'influencent pas le fonctionnement de cette commande.
a) MAUVAIS (à cause de la confusion créée) :
TheTable.RESET;
TheTable.SETCURRENTKEY(...)
TheTable.SETRANGE/SETFILTER
TheTable.GET(...);
|
b) CORRECT :
Si vous souhaitez effectuer une commande GET sans générer une erreur même si l'enregistrement recherché n'existe pas, vous pouvez procéder de cette manière :
Version 1
CLEAR(TheTable);
IF TheTable.GET(....) THEN ;
|
Version 2
IF NOT TheTable.GET(....) THEN
CLEAR(TheTable);
|
Ces deux versions renverront un enregistrement avec des valeurs par défaut si elles ne trouvent pas l'enregistrement recherché. Aucune valeur de champ non enregistrée ne sera conservée sur la variable après l'exécution du code. Personnellement, je préfère la première instruction : j'initialise d'abord la variable, ensuite j'extrais l'enregistrement (s'il existe).
Les prochaines commandes nécessitent d'appliquer des filtres sur la variable de type record. Avant de les détailler, je voudrais commencer par vous donner une bonne technique de programmation qui s'applique à celles-ci : pour réaliser un code facilement maintenable, il est important de placer les instructions appliquant des filtres ensemble et après la commande RESET. Ceci vous permet de ne pas vous soucier si certains filtres ont été appliqués avant et s'ils doivent être supprimés ou pas. J'utilise toujours ces instructions ensemble, toute personne lisant mon code saura ainsi que je n'ai pas oublié d'appeler la commande SETCURRENTKEY.
TheTable.RESET;
TheTable.SETCURRENTKEY(...);
TheTable.SETRANGE(...)ou TheTable.SETFILTER(....)
|
 |
Quelques remarques :
|
(a) FIND,FINDSET: ces commandes ouvrent un curseur SQL Server. Utiliser un curseur est très coûteux en termes de performances, il vaut mieux toujours les éviter si cela est possible.
(b) Les exemples suivants présentent les commandes à utiliser si vous souhaitez extraire les données sans verrouiller les enregistrements. Ensuite je présenterai des exemples avec utilisation des verrous.
(c) FINDSET extrait les N premiers enregistrements d'une table en une passe. N est défini dans le champ « Jeu d'enregistrements » des propriétés de la base de données en suivant le menu : Fichier -> Base de données -> Modifier et en cliquant sur l'onglet « Avancé ».
V. FIND(‘-‘) : cette instruction ne doit plus être utilisée (sauf dans certaines situations que j'expliquerai plus tard), car il existe d'autres instructions plus optimisées comme FINDFIRST et FINDSET.
VI. FIND(‘+') : cette instruction ne doit plus être utilisée (sauf dans certaines situations que j'expliquerai plus tard), car il existe d'autres instructions plus optimisées comme FINDLAST.
VII. FIND ('=') : cette instruction me semble un peu bizarre. Elle agit la plupart du temps comme la commande GET afin d'extraire un enregistrement à l'aide de sa clé primaire. On l'appelle après le remplissage des valeurs des champs constituant la clé primaire. Tous les filtres appliqués sur la variable de type record sont pris en compte. Cette commande est utile si la variable Record contient déjà l'enregistrement que l'on recherche et on voudrait juste mettre à jour ses différents champs.
VIII. FIND ('>') / FIND('<') : cette commande s'appuie sur les valeurs des champs constituant la clé primaire pour extraire l'enregistrement suivant ou précédent. Tous les filtres appliqués sur la variable de type record sont pris en compte. Il vaut mieux utiliser l'instruction NEXT ou NEXT(-1).
IX. FIND ('=<>') : cette commande est la moins performante de toutes. Elle commence par exécuter l'instruction FIND(‘=') pour extraire l'enregistrement recherché, si ce dernier n'est pas trouvé, la commande FIND(‘<') est appelée, si rien n'est toujours trouvé, la commande FIND(‘>') est enfin appelée. À éviter absolument.
X. FINDFIRST : ceci est la commande à utiliser si on ne souhaite extraire que le premier enregistrement d'un jeu de données. Par contre, ne l'utilisez jamais si vous souhaitez parcourir les lignes du jeu de résultats obtenu.
XI. FINDLAST : ceci est la commande à utiliser si on ne souhaite extraire que le dernier enregistrement d'un jeu de données. Par contre, ne l'utilisez jamais si vous souhaitez parcourir les lignes du jeu de résultats obtenu.
XII. FINDSET : ceci est la commande à utiliser si vous souhaitez extraire un ensemble de lignes d'une table donnée. Elle récupère les N premières lignes en une seule passe. Si le nombre de lignes dans la table est plus petit que N, la commande va extraire toutes les lignes de la table. N est défini dans le champ « Jeu d'enregistrements » des propriétés de la base de données en suivant le menu : Fichier -> Base de données -> Modifier et en cliquant sur l'onglet « Avancé ».
XIII. ISEMPTY : ceci est la commande à utiliser si vous souhaitez vérifier si votre jeu de données comporte au moins une ligne ou pas.
XIV. COUNT : cette commande est utilisée si vous souhaitez avoir le nombre précis de lignes de votre jeu de données. Elle est plus lourde en termes de performances que la commande COUNTAPPROX.
XV. COUNTAPPROX : cette commande est à utiliser si vous souhaitez avoir un nombre approximatif de lignes de votre jeu de données. Elle peut servir pour mettre à jour le contrôle ProgressBar. Elle est moins gourmande en ressources que la commande COUNT.
3. Lectures de données sans verrou
3-A. Vous devez vérifier l'existence d'au moins un enregistrement dans votre jeu de données, mais vous ne souhaitez pas utiliser les données de ce dernier s'il existe
a) TRÈS MAUVAIS: parce que la commande compte toutes les lignes.
IF TheTable.COUNT > 0 THEN ...
|
b) MAUVAIS : parce que la commande retourne un ensemble de lignes à destination du poste client.
IF TheTable.FINDSET THEN ...
|
c) (MOINS) MAUVAIS: jusqu'à l'apparition de certaines versions (je ne me souviens plus lesquelles), vous deviez procéder uniquement ainsi. Un curseur est ouvert au niveau de SQL Server.
IF TheTable.FIND('-') THEN ...
|
d) TOUJOURS) MAUVAIS : la commande ne fait pas appel à un curseur mais retourne toujours le premier enregistrement (s'il en existe au moins un).
IF TheTable.FINDFIRST THEN ...
|
e) CORRECT : la commande ne renverra aucune ligne. Elle retourne une valeur booléenne indiquant s'il existe une ligne ou pas dans le jeu de données.
IF NOT TheTable.ISEMPTY THEN ...
|
3-B. Vous voulez extraire le premier/dernier enregistrement (s'il existe), mais pas plus
a) TRÈS MAUVAIS: la commande retourne un ensemble de lignes.
If TheTable.FINDSET THEN ...
|
b) MAUVAIS : ceci ouvre un curseur au niveau du serveur SQL Server.
If TheTable.FIND('-') THEN ...
|
c) CORRECT : la commande n'ouvre pas de curseur et retourne un seul enregistrement.
If TheTable.FINDFIRST THEN ...
|
3-C. Vous souhaitez extraire toutes les lignes d'un jeu de données dans l'ordre ascendant
a) TRÈS MAUVAIS : l'instruction FINDFIRST n'ouvre pas de curseur et retourne juste le premier enregistrement. La première fois que l'instruction NEXT est appelée, le système n'ayant pas de curseur pour parcourir le jeu de résultat en crée un.
IF TheTable.FINDFIRST THEN
REPEAT
...
UNTIL TheTable.NEXT = 0;
|
b) MAUVAIS : ceci vous renverra un seul enregistrement à la fois.
IF TheTable.FIND('-') THEN
REPEAT
...
UNTIL TheTable.NEXT = 0;
|
c) CORRECT : la commande vous renverra les N premiers enregistrements d'un seul coup et après les autres seront extraits un par un.
IF TheTable.FINDSET THEN
REPEAT
...
UNTIL TheTable.NEXT = 0;
|
3-D. Vous souhaitez extraire toutes les lignes d'un jeu de données dans l'ordre descendant
a) TRÈS MAUVAIS : l'instruction FINDFIRST n'ouvre pas de curseur et retourne juste le premier enregistrement. La première fois que l'instruction NEXT est appelée, le système n'ayant pas de curseur pour parcourir le jeu de résultat en crée un.
TheTable.ASCENDING(FALSE);
IF TheTable.FINDSET THEN
REPEAT
...
UNTIL TheTable.NEXT = 0;
|
b) CORRECT :
utilisez cette version si possible, elle emploie l'instruction FINDSET pour extraire plus rapidement les lignes et les enregistre dans une table temporaire. Ensuite elle parcourt la table temporaire dans un ordre descendant. Ceci peut être utile si le nombre de lignes est inférieur à N (le nombre de lignes retourné par la commande FINDSET). Cette commande ne doit pas être utilisée s'il y a trop d'enregistrements dans le jeu de données car ils seront tous enregistrés dans la table temporaire et donc sur le poste client.
IF TheTable.FINDSET THEN
REPEAT
tmpTheTable := TheTable;
tmpTheTable.INSERT(FALSE);
UNTIL TheTable.NEXT = 0;
tmpTheTable.RESET;
tmpTheTable.ASCENDING(FALSE);
IF tmpTheTable.FIND('-') THEN
REPEAT
...
UNTIL tmpTheTable.NEXT = 0;
|
c) CORRECT :
TheTable.ASCENDING(FALSE);
IF TheTable.FIND('-') THEN
REPEAT
...
UNTIL TheTable.NEXT= 0;
|
4. Opérations de mise à jour d'enregistrements
Pour commencer, quelques conseils sur la façon de mettre à jour les données dans la base.
1) Créez des transactions le plus tard possible dans le code (commencez par lire toutes les données nécessaires sans appliquer de verrou et ensuite effectuez les opérations de « mise à jour »).
2) Appliquez des verrous sur le moins d'enregistrements possible (si vous voulez changer une ligne dans une table qui en contient 10 millions, pourquoi verrouiller toute la table alors que vous pouvez verrouiller seulement un seul enregistrement ?).
3) Le code contenu dans vos transactions doit être le plus rapide possible (il est par exemple très mauvais de mettre une instruction SLEEP dans une transaction).
4) Le code contenu dans vos transactions doit être le plus court possible (OK, mais pas trop petit : si vous souhaitez mettre à jour 1000 lignes dans une table, n'appelez pas la commande COMMIT à chaque mise à jour).
5) Effectuez vos transactions de sorte que chacune d'elles contienne, le cas échéant et en un seul bloc, toutes les instructions de mise à jour prévues, sinon aucune.
 | LOCKTABLE(ou FINDSET(TRUE...)) : cette commande fonctionne différemment avec le serveur de base de données natif C/SIDE. Dans ce cas, toute la table est verrouillée. Avec le serveur SQL, à partir de ce point du code, tous les enregistrements extraits de la table seront verrouillés. Mais les enregistrements des autres tables ne le seront pas. Il est cependant possible que certains autres enregistrements soient verrouillés à cause du mécanisme de verrouillage de SQL Server sur lequel nous n'avons pas vraiment de contrôle (au moins à partir du langage C/AL). |
4-A. Vous ne devez modifier qu'un seul enregistrement
a) MAUVAIS: cette instruction va générer deux commandes SELECT au niveau de Serveur SQL. La première n'applique pas de verrou et est exécutée pour récupérer l'enregistrement recherché (instruction GET de la première ligne). La seconde applique un verrou exclusif et est exécutée pour la mise à jour de la ligne (instruction MODIFY). Ensuite la commande UPDATE est appelée. La deuxième commande SELECT est nécessaire pour verrouiller l'enregistrement, afin de s'assurer qu'aucune mise à jour ne peut se faire entre l'extraction de l'enregistrement (instruction GET) et sa mise à jour (instruction MODIFY).
TheTable.GET(...);
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
|
b) CORRECT : l'instruction GET va générer une commande SELECT avec un verrou exclusif. L'instruction MODIFY va générer une commande UPDATE.
TheTable.LOCKTABLE;
TheTable.GET(...);
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
|
4-B. Vous devez récupérer un enregistrement et éventuellement le mettre à jour
a) CORRECT : si les chances de mise à jour sont infimes, évitez de poser un verrou non nécessaire.
TheTable.GET(...);
IF (to be changed) THEN BEGIN
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
END;
|
b) CORRECT : si les chances de mise à jour sont grandes, ceci permet d'éviter une deuxième commande SELECT.
TheTable.LOCKTABLE;
TheTable.GET(...);
IF (to be changed) THEN BEGIN
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
END;
|
4-C. Vous devez extraire un ensemble d'enregistrements et tous les modifier
a) TRÈS MAUVAIS: déjà vous devez utiliser un FINDSET (lire l'explication plus haut). Vous utilisez la même variable de type record pour lire et mettre à jour les données. Certaines lignes peuvent être omises, vous pouvez modifier des lignes plus d'une fois, ou même réaliser une boucle infinie. De plus vous n'avez pas verrouillé la table.
IF TheTable.FINDFIRST THEN
REPEAT
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
UNTIL TheTable.NEXT= 0;
|
b) MAUVAIS: vous n'avez pas verrouillé la table, une commande SELECT supplémentaire sera donc créée par l'instruction MODIFY.
IF TheTable.FINDSET THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT = 0;
|
c) CORRECT: vous appliquez un verrou sur la table à l'aide du premier paramètre à TRUE de la commande FINSET. Le deuxième paramètre (TRUE) n'est important que dans certaines circonstances mais je vous conseille de toujours l'utiliser.
IF TheTable.FINDSET(TRUE,TRUE) THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT= 0;
|
4-D. Vous devez extraire un ensemble d'enregistrements et en modifier certains
Les méthodes « TRÈS MAUVAIS » et « MAUVAIS » présentées précédemment s'appliquent aussi ici.
a) MAUVAIS: cette méthode était correcte pour le point précédent mais ne l'est plus ici parce qu'elle verrouille tous les enregistrements lus. Les verrous sont gourmands en termes d'utilisation de la mémoire et aussi en termes de performances. Ceci parce que les autres utilisateurs seront bloqués même s'ils veulent modifier d'autres enregistrements. C'est aussi une cause d'importants blocages.
IF TheTable.FINDSET(TRUE,TRUE) THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT= 0;
|
b) CORRECT: vous lisez d'abord les lignes et celles qui seront modifiées sont copiées dans une table temporaire sans l'application d'un quelconque verrou.
IF TheTable.FINDSET THEN
REPEAT
IF (to be changed) THEN BEGIN
tmpTheTable := TheTable;
tmpTheTable.INSERT(FALSE);
END;
UNTIL TheTable.NEXT= 0;
CLEAR(TheTable);
TheTable.LOCKTABLE;
|
Il existe deux méthodes pour parcourir les lignes.
Méthode 1
tmpTheTable.RESET;
IF tmpTheTable.FINDSET THEN
REPEAT
TheTable := tmpTheTable;
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
UNTIL tmpTheTable.NEXT = 0;
|
Méthode 2
tmpTheTable.RESET;
IF tmpTheTable.FINDSET THEN
REPEAT
TheTable := tmpTheTable;
TheTable.FIND('=');
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
UNTIL tmpTheTable.NEXT = 0;
|
5. Autres opérations
5-A. Utilisation de l'instruction FILTERGROUPS
Cette commande permet d'appliquer plusieurs filtres sur le même champ et sert aussi à masquer certains filtres aux utilisateurs.
Commencez par utiliser les FILTERGROUPS de niveaux supérieurs ou égaux à 10. Les FILTERGROUPS de niveaux 0 à 6 sont réservés (voir la documentation de référence C/SIDE).
 | Les différents filtres appliqués sur le même champ se comportent comme si on les avait reliés avec l'opérateur logique « ET ». |
Cas 1: vous voulez éviter qu'un utilisateur ne change un filtre.
TheTable.RESET;
TheTable.SETCURRENTKEY(....);
TheTable.FILTERGROUP(10);
TheTable.SETRANGE(....);
TheTable.FILTERGROUP(0);
FORM.RUNMODAL(0,TheTable);
|
Cas 2 : vous devez appliquer plusieurs filtres au champ. Exemple : vous voulez extraire tous les enregistrements avec un certain champ commençant par ‘A', se terminant par un ‘B' et contenant un ‘C'.
Bien entendu, vous pouvez créer ce filtre avec une commande SETFILTER, mais cette dernière ne peut pas s'appliquer avec des requêtes plus complexes.
TheTable.RESET;
TheTable.SETCURRENTKEY(...);
TheTable.SETFILTER("The Field",'A*C*B');
IF TheTable.FINDSET THEN
|
Même exemple avec un FILTERGROUP
TheTable.RESET;
TheTable.SETCURRENTKEY(...);
TheTable.FILTERGROUP(10);
TheTable.SETFILTER("The Field",'A*');
TheTable.FILTERGROUP(11);
TheTable.SETFILTER("The Field",'*C*');
TheTable.FILTERGROUP(12);
TheTable.SETFILTER("The Field",'*B');
TheTable.FILTERGROUP(0);
IF TheTable.FINDSET THEN
|
5-B. Lecture/Insertion/Mise à jour/Suppression d'enregistrements dans une autre société
 | Notez bien que seule la table concernée par la mise à jour sera dans une société différente, toutes les autres variables de type record présentes dans la définition de cette table ou alors à partir de la propriété TABLERELATION d'un de ses champs seront dans la société courante. |
a) (TRÈS) MAUVAIS : vous ne devez jamais utiliser les triggers si vous voulez mettre à jour les données d'une table présente dans une autre société. Tout le code présent dans ces triggers s'exécutera dans la société courante.
CLEAR(TheTable);
TheTable.CHANGECOMPANY('Some Other Company');
TheTable.VALIDATE("Field 1",'Some Value');
TheTable.VALIDATE("Field 2",'Some Value');
TheTable.VALIDATE("Field 3",'Some Value');
TheTable.INSERT(TRUE);
|
b) CORRECT: pour insérer un enregistrement dans une autre société (MODIFY et DELETE fonctionnent de la même façon).
CLEAR(TheTable);
TheTable.CHANGECOMPANY('Some Other Company');
TheTable."Field 1" := 'Some Value';
TheTable."Field 2" := 'Some Value';
TheTable."Field 3" := 'Some Value';
TheTable.INSERT(FALSE);
|
c) CORRECT: pour insérer un enregistrement dans une autre société (MODIFY et DELETE fonctionnent de la même façon).
CLEAR(TheTable);
TheTable.CHANGECOMPANY('Some Other Company');
TheTable.GET(....);
|
d) CORRECT: lire/supprimer et mettre à jour un ou plusieurs enregistrements dans une autre société.
CLEAR(TheTable);
TheTable.CHANGECOMPANY('Some Other Company');
TheTable.SETRANGE("Field 1",'Some Value');
TheTable.FINDSET;
REPEAT
...
UNTIL TheTable.NEXT= 0;
TheTable.DELETEALL(FALSE);
TheTable.MODIFYALL(FALSE);
|
5-C. Vous souhaitez savoir la quantité totale en stock d'un article donné dans un magasin donné
Il existe deux façons de le faire, chacune des méthodes ayant ses avantages et ses inconvénients.
a) CORRECT : avantages : vous n'avez pas besoin de préciser l'index à utiliser pour effectuer la requête. Inconvénients : vous devez assigner une valeur à certains champs ou effectuer un GET et vous devez définir une valeur pour les FLOWFILTERS.
CLEAR(recItem);
recItem."No." := '1000';
recItem.SETRANGE("Location Filter",'BLU');
recItem.CALCFIELDS(Inventory);
MESSAGE('%1',recItem.Inventory);
|
b) CORRECT : avantages : vous n'effectuez que des filtres et pas d'affectations de valeurs. Inconvénients : vous devez indiquer la clé secondaire à utiliser par le champ de calcul.
recItemLedgerEntry.RESET;
recItemLedgerEntry.SETCURRENTKEY("Item no.","Location Code");
recItemLedgerEntry.SETRANGE("Item No."','1000');
recItemLedgerEntry.SETRANGE("Location Code",'BLU');
recItemLedgerEntry.CALCSUMS(Inventory);
MESSAGE('%1',recItemLedgerEntry.Quantity);
|
Si vous devez utiliser deux ou plusieurs variables de type record d'une même table, vous pouvez créer un tableau au lieu de créer deux variables. Les deux éléments obtenus se comporteront de façon indépendante comme si vous aviez créé deux variables.
recRecordVar1.RESET; => recRecordVar[1].RESET;
CLEAR(recRecordVar1); => CLEAR(recRecordVar[1]);
recRecordVar1.SETRANGE(...) => recRecordVar[1].SETRANGE(...)
|
Vous pouvez aussi utiliser une variable pour indiquer un élément du tableau.
Exemple:
intSomeInteger := 1;
recRecordVar1.RESET; => recRecordVar[ intSomeInteger ].RESET;
|
La technique du tableau peut aussi être utilisée pour des variables temporaires de type record. Les éléments du tableau fonctionneront toujours de façon indépendante comme pour une table normale. Mais il n'y a qu'une seule et unique table temporaire : ceci veut dire que si vous ajoutez un enregistrement à partir de element[1], ce dernier sera accessible par element[2]. Un exemple (ceci est le premier code avec la table temporaire donc elle ne contient aucun enregistrement).
CLEAR(recRecord[1]);
recRecord[1]."No." := '1');
recRecord[1].INSERT(FALSE);
recRecord[2].RESET;
recRecord[2].FINDFIRST;
MESSAGE('%1',recRecord[2]."No.");
|
5-D. Vous souhaitez calculer la somme du champ « montant » en groupant par « Gen. Bus. Posting Group » (groupe de comptabilisation marché) et « Gen. Prod. Posting Group » (groupe de comptabilisation produit) de la table 15 : « G/L Entry » (écriture comptable).
Avec une requête SQL vous aurez ceci :
SELECT "Gen_ Bus_ Posting Group","Gen_ Prod_ Posting Group",SUM(Amount)
FROM dbo."CRONUS International Ltd_$G_L Entry"
GROUP BY"Gen_ Bus_ Posting Group","Gen_ Prod_ Posting Group"
ORDER BY"Gen_ Bus_ Posting Group","Gen_ Prod_ Posting Group"
|
Mais avec le langage C/AL, on ne peut pas écrire du code SQL (à moins d'utiliser ADO), donc il faut trouver une autre façon de procéder. Je vous conseille de procéder comme ci-dessous chaque fois que vous devez faire des sommes avec le langage C/AL.
recGLEntry.SETCURRENKEY(...);
recGLEntry.SETRANGE(.....);
IF recGLEntry.FINDSET THEN
REPEAT
tmpGLEntry.RESET;
IF NOT tmpGLEntry.SETCURRENTKEY("Gen. Bus. Posting Group") THEN;
IF NOT tmpGLEntry.SETCURRENTKEY("Gen. Prod. Posting Group") THEN;
tmpGLEntry.SETRANGE("Gen. Bus. Posting Group",recGLEntry."Gen. Bus.Posting Group");
tmpGLEntry.SETRANGE("Gen. Prod. Posting Group",recGLEntry."Gen. Prod.Posting Group");
IF NOT tmpGLEntry.FINDFIRST THEN BEGIN
tmpGLEntry := recGLEntry;
tmpGLEntry.insert(FALSE);
END ELSE BEGIN
tmpGLEntry.Amount+= recGLEntry.Amount;
tmpGLEntry.MODIFY(FALSE);
END;
UNTIL recGLEntry.NEXT= 0;
tmpGLEntry.RESET;
FORM.RUNMODAL(0,tmpGLEntry);
|
6. Remerciements
Je tiens ici à remercier Alain Krikilion pour son aimable autorisation de traduire cet article.
Merci à
djug et
gorgonite pour leurs conseils.
Merci à
ClaudeLELOUP et
jacques_jean pour leur relecture attentive.


Copyright © 2009 Alain Krikilion. Aucune reproduction, même partielle, ne peut être faite
de ce site et de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 €
de dommages et intérêts.
Cette page est déposée.