, , Traiter les membres d’un fichier

Vous avez parfois besoin de traiter tous les membres d’un fichier, par exemple pour analyser des sources ou des logs

Vous avez principalement 3 manières de le faire :

1) La manière historique par les fichiers modèles

C’est des fichiers qui sont dans qsys qu’on duplique et remplie le plus souvent avec le paramètre OUTFILE() de différentes commandes

dans un programme CLLE

DCLF FILE(QSYS/QAFDMBRL)

DSPFD FILE(LIB/file) TYPE(MBRLIST) OUTPUT(OUTFILE) OUTFILE(QTEMP/WAFDMBRL)
OUTMBR(*FIRST *ADD)

OVRDBF FILE(QAFDMBRL) TOFILE(QTEMP/WAFDMBRL)

RCVF

Votre traitement ici
DLTOVR FILE(QAFDMBRL)

2) En utilisant les services SQL

QSYS2.SYSPARTITIONSTAT

En sélectionnant la bibliothèque et le fichier, vous pouvez soit utiliser le résultat dans un programme CLLE.
Ou directement dans un select avec la fonctions SQL QCMDEXC

3) Sans générer de fichier

Cette méthode est moins connue, mais elle permet de traiter tous les membres d’un fichier sans générer de fichier intermédiaire

Vous allez avoir un programme CLLE, qui aura cette logique

Vous allez récupérer le premier membre
RTVMBRD FILE(LIB/FIL) MBR(*FIRSTMBR) RTNMBR(&MBR)
MONMSG MSGID(CPF0000) EXEC(do)
ENDDO

Vous allez ensuite boucler sur les suivants
RTVMBRD FILE(LIB/&FIL) MBR(&MBR *NEXT) RTNMBR(&MBR)
MONMSG MSGID(CPF3049) EXEC(leave)

Vous allez ainsi lire tous vos membres de votre fichier.

Vous pouvez également utiliser cette méthode pour traiter des membres de transfert qui arriveraient dans votre fichier.

Conclusion :

Vous avez 3 solutions pour les traiter les membres d’un fichier, à vous de choisir la méthode la plus adapter à votre traitement

, , RETROUVER UNE ADRESSE GRACE AUX API

Préambule

Cet article est une suite à l’article de Pierre-Louis BERTHOIN qui présente les fonctions géospatiales intégrées à DB2.

Choix de l’API

Sur le site https://adresse.data.gouv.fr/ En cliquant sur l’item « Outils et API », on accède librement à la documentation des API en rapport avec les adresses. Nous choisissons donc celle sobrement intitulée « API Adresse ». La documentation montre différentes manières d’utiliser cette API. Le retour est un geojson FeatureCollection respectant la spec GeoCodeJSON.

Récupération et manipulation des données

But du programme

Nous allons réaliser un programme qui permettra, en écrivant partiellement une adresse, de récupérer une adresse complète d’après une liste déroulante de 50 occurrences.

Nous choisirons pour notre programme une interrogation relativement simple et nous n’extrairons qu’une partie des données du geojson.

Nous écrirons les adresses dans un fichier, sous forme d’une fiche client contenant les éléments suivants :

  • Identifiant (integer auto incrémenté)
  • Raison Sociale (varchar)
  • Adresse (varchar)
  • Code Postal (varchar)
  • Ville (varchar)
  • Coordonnées géographiques (ST_POINT)

Préparation du fichier

create table GGEOLOC.MESCLIENTS
(ID int GENERATED ALWAYS AS IDENTITY (
START WITH 1 INCREMENT BY 1 
NO MINVALUE NO MAXVALUE     
NO CYCLE NO ORDER           
CACHE 20 ),
RAISOC varchar(64),
ADRESSE varchar(128),
CODEPOS varchar(16),
VILLE varchar(64),
COORDGEO QSYS2.ST_POINT);

Programme de saisie

**free
  ctl-opt dftactgrp(*no) ;

  // Fichiers
  dcl-f FORMCLIE workstn indds(DS_Ind) usropn;

  // Procédures
  dcl-pr  Touche_F4 EXTPGM('TOUCHE_F4');
    p_Sql char(1024) ;
    p_Titre char(35);
    p_Ret    char(116);
  end-pr;

  // Variables
  dcl-s reqSqlDelete varchar(2000);
  dcl-s reqSqlCreate varchar(2000);
  dcl-s reqSqlDrop   varchar(2000);

  dcl-s coordonees varchar(64) ;
  dcl-s queryapi varchar(64);
  dcl-s httpText varchar(256);

  dcl-s pIndicators Pointer Inz(%Addr(*In));


  // Pour F4 : liste des fichiers
  dcl-s w_Sql   char(1024) ;
  dcl-s w_Titre  char(35);
  dcl-s w_Ret    char(116);

  // DS Informations Programme
  dcl-ds *N PSDS;
    nom_du_pgm CHAR(10) POS(1);
    nom_du_prf CHAR(10) POS(358);
  end-ds;

  // Déclaration des indicateurs de l'écran
  Dcl-DS DS_Ind based(pIndicators);
    Ind_Sortie                        ind pos(3);
    Ind_Liste                         ind pos(4);
    Ind_Annuler                       ind pos(12);
    Ind_Valider                       ind pos(17);
    Ind_SFLCLR                        ind pos(40);
    Ind_SFLDSP                        ind pos(41);
    Ind_SFLDSPCTL                     ind pos(42);
    Ind_SFLEnd                        ind pos(43);
    Ind_DisplayCoord                  ind pos(80);
    Ind_RaisonS                       ind pos(81);
    Ind_5Lettres                      ind pos(82);
    Ind_CodePos                       ind pos(84);
    Ind_Ville                         ind pos(85);
    Indicators char(99)               pos(1);
  End-DS;

  // Paramètres en entrée
  dcl-pi *N;
  end-pi;

// SQL options --------------------------------------------- //
   Exec SQL
      Set Option
          Naming=*Sys,
          Commit=*None,
          UsrPrf=*User,
          DynUsrPrf=*User,
          Datfmt=*iso,
          CloSqlCsr=*EndMod;

//‚--------------------------------------------------------- //
// Contrôle taille écran
Monitor;
  Open FORMCLIE;
  On-Error;
    Dsply 'Nécessite un écran 27 * 132';
    *inlr=*on;
  Return;
EndMon;


  Dou Ind_Sortie or Ind_Annuler;

    znompgm = nom_du_pgm;
    znomprf = nom_du_prf;

    Exfmt FMT01;

    select ;
      when Ind_Sortie ;
        leave;
      when Ind_Annuler;
        leave;
      when Ind_Liste;
       if %len(%trim(zadresse)) <= 4 ;
         Ind_5Lettres = *on;
         iter;
       endif;
       traitementListe();
      when Ind_Valider;
       if zraisoc = *blanks;
         Ind_RaisonS = *on ;
       endif;
       if zcodpos = *blanks;
         Ind_CodePos= *on ;
       endif;
       if zville = *blanks;
         Ind_Ville = *on ;
       endif;
       if %subst(indicators:81:4) <> '0000';
         iter ;
       endif;

       Exec SQL
         insert into MESCLIENTS (RAISOC, ADRESSE, CODEPOS, VILLE, COORDGEO)
           values (:zraisoc, :zadresse, :zcodpos, :zville,
           QSYS2.ST_POINT(:zlongit, :zlatit)) ;

       Ind_DisplayCoord = *off;
       clear FMT01;

    endsl ;

  Enddo;

  *inlr = *on;

  //======================================================================== //
  // Procédures                                                              //
  //======================================================================== //

  //‚------------------------------------------------------------------------ //
  // Nom    : rechercheAdresse                                               //
  // But    : lister des adresses recueillies via une API                    //
  //          à partir d'une chaine de plus de 4 caractères                  //
  // Retour : N/A                                                            //
  //‚------------------------------------------------------------------------ //
  dcl-proc rechercheAdresse ;
    dcl-pi *n ;
      l_httpText varchar(256) value;
    end-pi;


    reqSqlDrop = 'drop table QTEMP/WADRESSE' ;
    Exec sql Execute immediate :reqSqlDrop ;

    reqSqlCreate = 'create table QTEMP/WADRESSE' +
       ' (address varchar(128), numero varchar(8), street varchar(128), ' +
       'postcode varchar(16), city varchar(64), coordinates blob)' ;
    Exec sql Execute immediate :reqSqlCreate ;

    reqSqlDelete = 'delete from QTEMP/WADRESSE' ;
    Exec sql Execute immediate :reqSqlDelete ;

    Exec sql
     insert into QTEMP/WADRESSE
     (select ltrim(ifnull(numero, '') || ' ' ||
             coalesce(street, locality, '') || ' ' ||
             postcode || ' ' || city),
             ifnull(numero, ''), coalesce(street, locality, ''),
             postcode,
             city,
             QSYS2.ST_POINT(longitude, latitude)
       from json_table(QSYS2.HTTP_GET(:l_httpText, ''), '$.features[*]'
        COLUMNS
        (numero varchar(8) PATH '$.properties.housenumber',
        street varchar(128) PATH '$.properties.street',
        locality varchar(128) PATH '$.properties.locality',
        name varchar(128) PATH '$.properties.name',
        municipality varchar(128) PATH '$.properties.municipality',
        postcode varchar(8) PATH '$.properties.postcode',
        city varchar(64) PATH '$.properties.city',
        longitude float PATH '$.geometry.coordinates[0]',
        latitude float PATH '$.geometry.coordinates[1]'))
        );

  end-proc ;

  //‚------------------------------------------------------------------------ //
  // Nom    : traitementListe                                                //
  // But    : Affichage d'une liste de 50 adresses maximum                   //
  // Retour : N/A                                                            //
  //‚------------------------------------------------------------------------ //
  dcl-proc traitementListe ;

    queryapi = %scanrpl(' ':'+':%trim(zadresse)) ;
    httpText ='https://api-adresse.data.gouv.fr/search/?q=' +
              queryapi + '&limit=50' ;
    rechercheAdresse(httpText);
    clear w_ret ;
    w_sql =
      'select address from QTEMP/WADRESSE' ;

    w_titre = 'Adresses proposées';

    touche_f4(W_Sql: W_titre : w_ret) ;
    if (w_ret  <> ' ') ;
      clear zadresse;
      clear zcodpos;
      clear zville;

      Exec SQL
        select
          ltrim(ifnull(numero, '') || ' ' || street), postcode, city,
          REPLACE(
             REPLACE(QSYS2.ST_asText(COORDINATES), 'POINT (', ''), ')', '')
            into :zadresse, :zcodpos, :zville, :coordonees
          from QTEMP.WADRESSE
          where address = :w_ret ;

      if sqlcode = 0 ;
        Ind_DisplayCoord = *on ;
        zlongit = %dec(%subst(coordonees: 1 : %scan(' ':coordonees)):15:12) ;
        zlatit =  %dec(%subst(coordonees: %scan(' ':coordonees) + 1
                        : %len(%trim(coordonees)) - %scan(' ':coordonees)):15:12) ;
      endif;

    endif ;

  end-proc ; 

Quelques explications sur les fonctions SQL utilisées

Tout d’abord nous choisissons de ne pas utiliser la propriété « label » proposée par l’API Adresse. En effet, si celle-ci semble pratique de prime abord, elle n’est pas toujours significative (voir photo du milieu qui où elle ne contient que le nom de la municipalité)

Nous, préférerons donc reconstituer cette adresse en concaténant des zones que l’on retrouve dans chaque occurrence du fichier JSON.

QSYS2.ST_POINT : Cette fonction est utilisée lors de la collecte des données fournie par l’API Adresse.

Elle permet de transformer les coordonnées longitude, latitude en une variable de type BLOB qui représente un point précis et qui est utilisable par les fonctions Géospatiales du SQL.

QSYS2.ST_ASTEXT : Cette fonction permet de transformer un champ géométrique (ST_POINT, ST_LINESTRING, ST_POLYGON, …) en un champ WKT (well-known-text) qui nous sera plus compréhensible.

Cinématique du programme

Ce programme est un simple écran qui nous permet la saisie d’un formulaire avec la possibilité de rechercher ou compléter une adresse en utilisant la touche F4 (la fonction externe appelée n’est pas décrite dans cet article). Une fois le formulaire validé, on l’efface.

Tout d’abord on commence à remplir le formulaire mais on ne connait pas précisément l’adresse.

Donc, après avoir tapé un morceau d’adresse on presse F4

On valide, le formulaire est alors complétement rempli

On presse F17 pour valider celui-ci (et réinitialiser l’écran).

Vérification des données enregistrées

Version BLOB

Version WKT (well-known-text)

Conclusion

Nous avons montré ici un exemple simple de l’utilisation d’une API couplée avec les fonctions géospatiales proposées par IBM. Il est possible d’envisager des requêtes plus complexes incluant le code postal, la ville ou encore le type de donnée (rue, lieu-dit ou municipalité). On peut aussi envisager des requêtes d’après les coordonnées géographiques pour retrouver une adresse. Le champ des possibles, comme le monde, est vaste …

Dans la dernière TR est apparue une nouveauté très intéressante « API RSE », c’est un ensemble d’API REST fournies avec votre système d’exploitation au travers du serveur ADMIN5.


Ces APIs sont utilisables depuis le web et permettent de lancer des commandes, accéder à l’IFS, de lancer de requêtes SQL…

Ce service ne fonctionne qu’en TLS vous devrez donc le sécuriser par l’administration http ://Votre_systeme:2001/HTTPAdmin

Vous devez ensuite le démarrer s’il ne l’est pas c’est le serveur ADMIN5

==>STRTCPSVR SERVER(*IAS) INSTANCE(admin5)

Ce serveur tourne par défaut sur le port 2012

https://Votre_systeme:2012/openapi/ui/

Vous devrez vous authentifiez pour commencer utilisez un user et un mot de passe ibmi, vous cliquez sur authorize

Nous allons tester une commnde CL

Vous cliquez sur try input

Vous avez un flux json avec vos commandes IBMI à l’intérieur vous cliquez sur execute

Vous avez alors le résultat

A noter que vous avez le lien, en haut pour l’intégrer dans vos applications

Conclusion
C’est gratuit c’est technologies actuelles, testez les possibilités de ce service

Pour en savoir plus


la pause café de volubis ici : https://www.volubis.fr/Pausecaf/PAUSECAF91.html

le site IBM : https://www.ibm.com/support/pages/node/6982701

Je n’ai pas voulu mettre optimisation dans le titre de l’article, c’est pourtant bien ce qui nous est souvent demandé.
Avant de chercher à optimiser les requêtes, il est utile de vérifier que quelques bonnes pratiques de base sont respectées dans l’écriture de la requête !

Dans le cas traité, on s’intéresse particulièrement à plusieurs éléments :

  • critères de jointure
  • critères de sélection
  • critères de groupage
    Objectif recherché : que tous ces critères soient exprimés, si possible, sans calcul !
    Une zone calculée ne peut être prise en charge via un index par l’optimiseur … nous allons donc réécrire tout ce qui a été écrit d’une façon « humaine » !

Exemple

La requête utilisée dans cet article est un extrait d’un requête réelle, pour laquelle les noms de tables et colonnes ont été modifiées …

Critère de jointure

Ce point n’impacte que peu les performances, car le moteur SQL réécrit la requête pour nous, mais apporte plus de lisibilité.
On évite ce genre de syntaxe (produit cartésien) :

SELECT …
FROM ADHENT,
     ADHDET
WHERE (ADHENT.GJCMP = ADHDET.GACMP#
              AND ADHENT.GJGL# = ADHDET.GAGL#A
              AND ADHDET.GAACCD = '1')
          AND ((ADHDET.GATYPE IN (
                      'EX',
                      'IN'
                  ))

Pour exprimer sur la jointure les critères :

select ADHENT.*
from ADHENT
join ADHDET ON (ADHENT.GJCMP = ADHDET.GACMP#
AND ADHENT.GJGL# = ADHDET.GAGL#A
AND ADHDET.GAACCD = '1')
WHERE (ADHDET.GATYPE IN (
'EX',
'IN'
)) ;

Critères de sélection

Requête de départ :

select ADHENT.*
from ADHENT
join ADHDET ON (ADHENT.GJCMP = ADHDET.GACMP#
AND ADHENT.GJGL# = ADHDET.GAGL#A
AND ADHDET.GAACCD = '1')
WHERE ADHDET.GATYPE IN ( 'EX', 'IN' )
AND (ADHENT.GJACMO <> 13)
AND ADHENT.GJCMP IN ('10')
AND CASE
WHEN ADHENT.GJJLDT = 0 THEN '01/01/9999'
ELSE DATE( SUBSTR(ADHENT.GJJLDT, 5, 2) || '/' ||
SUBSTR(ADHENT.GJJLDT, 7, 2) || '/' ||
SUBSTR(ADHENT.GJJLDT, 1, 4))
END BETWEEN '2016-01-01' AND '2017-12-31'
AND SUBSTR(ADHENT.GJGL#, 1, 6) BETWEEN '615540' AND '615540'
AND CASE
WHEN TRIM(ADHENT.GJLTG#) = '' THEN 'N'
ELSE 'Y'
END = 'N'

Il est possible de remplacer toutes les valeurs calculées et de les inverser !

   AND CASE
         WHEN ADHENT.GJJLDT = 0 THEN '01/01/9999'
         ELSE DATE( SUBSTR(ADHENT.GJJLDT, 5, 2) || '/' ||
                    SUBSTR(ADHENT.GJJLDT, 7, 2) || '/' ||
                    SUBSTR(ADHENT.GJJLDT, 1, 4))
       END BETWEEN '2016-01-01' AND '2017-12-31'

Devient  :

AND ADHENT.GJJLDT between int(date('2016-01-01')) AND int(date('2017-12-31'))

Sélection sur code :

   AND SUBSTR(ADHENT.GJGL#, 1, 6) BETWEEN '615540' AND '615540'

Devient :

AND ADHENT.GJGL# BETWEEN '615540' ||'0000000000' AND '615540' || '9999999999'

Sélection sur code :

   AND CASE
         WHEN TRIM(ADHENT.GJLTG#) = '' THEN 'N'
         ELSE 'Y'
       END = 'N'

Devient :

AND ADHENT.GJLTG# = ''

Ce dernier exemple illustre bien la capacité du cerveau humain à raisonner et non pas à exprimer des critères techniques !

Critères de groupage (et mise en forme)

Le groupage est souvent effectué en dernier, c’est-à-dire après l’ensemble des jointures. Vous êtes alors contraints d’ajouter un nombre important de colonnes dans le groupage, colonnes faisant l’objet de mise en forme pour un affichage adapté à l’utilisateur, et donc sur des zones calculées !
Nous proposons l’inverse :

  • d’abord on calcule les données, nécessitant groupage
  • ensuite on va chercher, par des jointures, des éléments complémentaires et on met en forme (calcul) les valeurs
    Pour cela les CTE (Common Table Expressions) nous sont d’un grand secours.

Par exemple :

with tmp as (
   SELECT ADHENT.GJCMP,
          ADHENT.GJGL#,
          ADHENT.GJJLTP,
          ADHENT.GJJLCD, 
          ADHENT.GJJLNO,
          ADHENT.GJINV#, 
          ADHENT.GJDESC,
          ADHENT.GJJLDT,
          ADHENT.GJMVM#,
          ADHENT.GJTYPE,
          ADHENT.GJLTG#,
          ADHENT.GJLTGD,
          SUM(ADHENT.GJAMT$) as somme1,
          SUM( CASE
                 WHEN ADHENT.GJAMT$ > 0.00 THEN ADHENT.GJAMT$
               END) as somme2,
          SUM(
               CASE
                 WHEN ADHENT.GJAMT$ < 0.00 THEN (-1 * ADHENT.GJAMT$)
               END) as somme3
   from ADHENT
   join ADHDET ON ...
   WHERE ...
   GROUP BY ADHENT.GJCMP,
            ADHENT.GJGL#,
            ADHENT.GJJLTP,
            ADHENT.GJJLCD,
            ADHENT.GJJLNO,
            ADHENT.GJINV#,
            ADHENT.GJDESC,
            ADHENT.GJJLDT,
            ADHENT.GJMVM#,
            ADHENT.GJTYPE,
            ADHENT.GJLTG#,
            ADHENT.GJLTGD )

select tmp.GJCMP,
        SUBSTR(tmp.GJGL#, 7, 4),
        SUBSTR(tmp.GJGL#, 1, 6),
        tmp.GJJLTP,
        tmp.GJJLCD || '-' || RIGHT(CONCAT('00000000', TRIM(CHAR(tmp.GJJLNO))), 8),
        tmp.GJJLNO,
        TRIM(tmp.GJINV#),
        tmp.GJDESC,
        CASE WHEN tmp.GJJLDT = 0 THEN '01/01/9999'
             ELSE DATE( SUBSTR(tmp.GJJLDT, 5, 2) || '/' ||
                        SUBSTR(tmp.GJJLDT, 7, 2) || '/' ||
                        SUBSTR(tmp.GJJLDT, 1, 4))
        END,
        tmp.GJMVM#,
        TRIM(tmp.GJTYPE),
        tmp.GJLTG#,
        tmp.GJGL#,
        ADHCMP1.ZLARGN,                 
        TRIM(ADHCMP2.ZRNAME),           
        CASE WHEN tmp.GJLTGD = 0 THEN '01/01/9999'
             ELSE DATE( SUBSTR(tmp.GJLTGD, 5, 2) || '/' ||
                        SUBSTR(tmp.GJLTGD, 7, 2) || '/' ||
                        SUBSTR(tmp.GJLTGD, 1, 4))
        END,
        somme1,
        somme2,
        somme3
 from tmp 
   LEFT OUTER JOIN ADHCMP1 ON ADHCMP1.ZLCMP  = tmp.GJCMP
                           AND ADHCMP1.ZLLOC  = SUBSTR(tmp.GJGL#, 7, 4)
   LEFT OUTER JOIN ADHCMP2 ON ADHCMP1.ZLCMP  = ADHCMP2.ZRCMP
                           AND ADHCMP1.ZLIRGN = ADHCMP2.ZRRGN ;

Avec ces quelques règles, simples dans leur principe, vous vous assurez que le moteur SQL pourra utiliser pleinement vos index. Cela ne signifie pas qu’il ne sera pas nécessaire d’optimiser par la suite.

Pour finir le cas concret évoqué ici :
– 4 fichiers dans la jointures :
– 500 Millions , 1 Million, 1.000 et 70 enreg
– 4.600 enreg en retour

Requête d’origine : 2 min 40 s (ce qui est déjà très bien, avec un scan de table sur le plus gros fichier).
Après réécriture : 40 ms

Bien sûr, les index nécessaires étaient déjà en place pour atteindre ce niveau de temps de réponse.

Encore une fois, SQL est le meilleur moyen d’accéder à la donnée, aussi complexe soit elle.

Par le meilleur, j’entends :

  • le plus simple : écrire un programme RPG/COBOL équivalent demanderait une quantité de code importante (et donc probabilité de bug)
  • le plus efficace (40 ms) : à condition que l’on donne à SQL les moyens d’être efficace

En conclusion : travailler d’abord sur la donnée, occupez vous ensuite de la mise en forme !

, Faire un CSV avec SQL

Il existe plusieurs solutions pour faire du CSV, la plus connue c’est en utilisant la commande CPYTOIMPF, mais vous pouvez générer directement un fichier CSV en utilisant SQL, voici un petit tutoriel pour vous aider :

Vous devrez utiliser les procédures QSYS2.IFS_WRITE*

https://www.ibm.com/docs/en/i/7.4?topic=is-ifs-write-ifs-write-binary-ifs-write-utf8-procedures

Voici un exemple qui comporte la génération du fichier, l’ajout d’un entête et la génération des listes de détail. Nous avons choisi de faire sans délimiteur et avec « ; » comme séparateur. Le fichier obtenu est directement lisible par Excel.

Si vous voulez un délimiteur voici une variante sur les select avec  » comme délimiteur

Pour les entêtes de ligne :

LINE => ‘ »Les_options » ; « Les_commandes »‘)

Pour les lignes de détail, on enlève les blancs :

select ‘ »‘ concat trim(option) concat ‘ » ; « ‘ concat trim(command) concat ‘ »‘ as option_txt from qgpl.qauoopt

BEGIN
  -- Créér ou remplacer le fichier dans l'IFS
  CALL QSYS2.IFS_WRITE_UTF8(PATH_NAME =>'les_options_pdm.csv', 
                       LINE => '', 
                       OVERWRITE => 'REPLACE', 
                       END_OF_LINE => 'NONE');
  -- Mettre l'entête de colonne                     
  CALL QSYS2.IFS_WRITE_UTF8(PATH_NAME =>'les_options_pdm.csv', 
                       LINE => 'Les_options ; Les_commandes'); 
  -- Ecriture d'une ligne obtenue dans le select  
  FOR select option concat ';' concat command  as option_txt from qgpl.qauoopt DO
    CALL QSYS2.IFS_WRITE_UTF8(PATH_NAME => 'les_options_pdm.csv', 
                         LINE => option_txt);
  END FOR;
END;

Dans cet exemple, on a utilisé la procédure QSYS2.IFS_WRITE_UTF8 pour générer le fichier directement en UNICODE et éviter les problèmes de CCSID, c’est le sens de l’histoire …

Nous n’avons pas précisé de répertoire. Le fichier est créé dans le répertoire par défaut de l’utilisateur, en principe /home/<user>

.

Conclusion :

C’est une solution simple et efficace qui permet de générer un fichier sans passer par un fichier intermédiaire en DB2.

Si vos zones sont numériques vous devrez les convertir par la fonction CHAR()

Merci Nath pour ton aide.

Il est difficile de déboguer un watcher parce qu’on ne maitrise pas son lancement.

Voici une méthode en utilisant RDI, qui va vous permet de le faire :

  1. Trouver le nom du programme à analyser :

WRKWCH WCH(*ALL) :

  • 5 pour le détail
  • Dans RDI, clic droit sur le programme à déboguer => débogage ou couverture de code (entrée de service) => définir un point d’entrée de service

Le message d’affiche :

Pour tester, on peut simuler un traitement qui va planter. Dans notre cas, on fait un call d’un programme qui n’existe pas, et donc ça va faire un plantage dans QSYSOPR.

SBMJOB CMD(CALL PGM(GAIA/ERREURA)) 

        JOB(ERREURA)                

        JOBQ(QSYSNOMAX)         

Une fois le programme a été lancé, sur RDI s’affichera le message suivant :

Cliquer sur « Afficher *LISTING »

Pour avancer d’un pas on peut utiliser la touche F5 ou en cliquant sur la flèche :

Pour afficher les valeurs des variables il suffit de passer la souris sur le nom de la variable :

Conclusion : c’est une solution simple pour déboguer un watcher ou un programme dont vous ne maitrisez pas le lancement.

Le programme doit être compilé avec le source.

Vous devrez avoir le droit pour faire ce type d’opération. Soit au niveau de profil, soit par les fonctions usages.

, Comment Modifier un spool ?

Vous avez un spool généré et vous voulez le modifier, bien sur si vous pouvez agir avant la génération du spool c’est mieux.

Imaginons que vous ne pouvez pas le faire, voici la procédure en quelques étapes pour réaliser cette opération.

Vous devez créer un fichier qui a la longueur de votre spool + 1

CRTPF QTEMP/SPOOLPF RCDLEN(longueur + 1)

Ensuite vous avez besoin d’identifier votre spool
nom_spool, Numero_job, Utilisateur, Nom_job et numero_spool (souvent , on peut utiliser *LAST)

Vous allez copier votre spool en idiquant bien *FCFC

CPYSPLF FILE(NOM_SPOOL) TOFILE(QTEMP/SPOOLPF)
JOB(NUMERO_JOB/UTILISATEUR/NOM_JOB)
SPLNBR(LAST) CTLCHAR(*FCFC)

Vous pouvez alors modifier votre ficher DB généré, par SQL, DFU ou par programme

Votre fichier est modifié, vous allez devoir le recopier pour regénérer un spool.
D’abord, indiquez que vous avez besoin des caractères de contrôle


OVRPRTF FILE(NOM_SPOOL) CTLCHAR(*FCFC)


Transformation de votre fichier DB en SPOOL


CPYF FROMFILE(QTEMP/SPOOLPF) TOFILE(NOM_SPOOL)

Vous pouvez à nouveau gérer votre spool, exemple pour le changer de file :


CHGSPLFA FILE(NOM_SPOOL) JOB(NUMERO_JOB/UTILISATEUR/NOM_JOB)
SPLNBR(*LAST) OUTQ(TLXOFC/FAX)

Conclusion :

Ca reste du bricolage, mais ca peut dépanner.

, , Database Information Finder

Si vous n’administré pas au quotidien votre base de données, mais que vous devez intervenir ponctuellement,
Vous avez un lien qui référence les principales opérations à faire et qui peut vous aider

https://www.ibm.com/docs/en/i/7.5?topic=database-information-finder

Vous avez 5 manières de chercher

DB2 tasks
SQL quick reference
DB2 and SQL examples
SQL statements
DB2 topics

Sélectionner l’information dans la liste et faite GO

Nous on utilise souvent la première

DB2 tasks

Vous voulez ajouter une zone, Appuyer vers Go, ca vous emmènera vers des liens qui référencera les opérations à effectuées.

Rappel :

Pour les exemples, vous avez aussi ACS qui en propose une liste

dans exécution de scripts SQL

, Debug avec VSCode

La semaine dernière, Laurent revenait sur les possibilités de débogage avec RDi : https://www.gaia.fr/debug-rdi-conditionner-un-point-darret/

C’est donc l’occasion de voir ce que l’on peut faire maintenant avec VSCode côté débogage.

Nous partons donc du même exemple, même programme, on ne changera que le débogueur :

Considérons une table CLIENTS contenant les colonnes et lignes suivantes :

Le programme PROGTEST lit la tables CLIENTS et exécute la procédure traitement_CLIENTS pour chacun des clients de la table :

Prérequis

Pour déboguer par VSCode, vous devez installer l’extension « IBM i Debug »

Comme vous le voyez ici, cela nécessite des PTF côté serveur.

Côté IBM i, le déboguer ILE spécifique tourne dans PASE est nécessite un certificat pour garantir la confidentialité de la connexion.

L’assistant depuis VSCode permet d’effectuer automatiquement toutes ces actions :

Référence : https://halcyon-tech.github.io/docs/#/pages/developing/debug/debug?id=starting-to-debug

Une fois le service configuré et l’extension installée, il nous faut régler quelques options importantes.

Nous allons retrouver des options à deux endroits :

  • Au niveau de la connexion

Au niveau des options de l’extension :


Déboguer avec VSCode

Il y a plusieurs possibilités dans l’éditeur, nous en choisissons une.

Ouvrez le source du programme à déboguer (on parlera ILE plus tard) :

L’outil vous demande votre mot de passe sur l’IBM i pour valider votre profil, et prompte ensuite la commande d’appel du programme :

Vous basculez alors dans l’affichage du déboguer :

Voyons les outils :

  • affichage des variables

Affiche les variables locales (quand vous êtes dans une procédure, vous ne voyez que les variables locales de la procédures, pas les variables globales du module).

Vous pouvez copier les valeurs, les modifier, ajouter un guet

  • Guet (watch)

Affiche, et permet la saisie, de guets de variables.

  • Pile d’appel

Permet de voir la procédure en cours d’exécution :

L’ordre d’affiche est : procédure / module / programme (de service)

  • Points d’arrêt

Liste les points d’arrêt, permet la création de nouveaux points et la suppression :

  • Contrôle de l’avancement

Permet d’avancer dans le débogage :

Dans l’ordre :
  1. Avancer jusqu’au prochain point d’arrêt (équivalent F12 dans STRDBG)
  2. Avancer et entrer en débogage dans la procédure ou programme appelé (équivalent F22 dans STRDBG)
  3. Continuer jusqu’à ressortir de la procédure en cours
  4. restart : non supporté pour l’IBM i
  5. Arrêt du débogage (équivalent F3 dans STRDBG)
  • Editeur

Identification visuelle de la ligne en cours de débogage (non encore exécutée) :

Point d’arrêt

Pour ajouter un point d’arrêt :

Soit clique gauche dans la marge

Le débogueur s’arrête sur le point d’arrêt au prochain passage.

Les variables modifiées par la dernière instruction sont mises en évidence.

Point d’arrêt conditionné

Commencer par définir le point d’arrêt, puis clique droit -> Editer sur le point :

Puis indiquer votre condition avec la même syntaxe qu’avec STRDBG :

On ne s’arrêtera que lorsque la condition sera vraie !

Par rapport à RDi, le debug de VSCode ne permet pas, pour le moment, les points d’entrée de service ! Il faut donc que VSCode déclenche lui même l’exécution du programme à déboguer ! Gageons la situation évoluera très vite …

Une fois que vous êtes habitués au débogage, regardez les options de couverture de code …

, Debug RDi : conditionner un point d’arrêt

Vous savez peut-être déjà comment créer un point d’arrêt conditionné en Débogage RPG 5250 (commande STRDBG).

Le mode Débogage RDi offre la même possibilité.

Cette fonctionnalité est particulièrement intéressante pour réaliser un Débogage ciblé dans un programme batch qui traite un gros volume de données.

Nous allons le vérifier avec l’exemple ci-dessous :

Considérons une table CLIENTS contenant les colonnes et lignes suivantes :

Le programme PROGTEST lit la tables CLIENTS et exécute la procédure traitement_CLIENTS pour chacun des clients de la table :

Nous allons utiliser le Débogage pour créer un point d’arrêt conditionné sur la ligne d’exécution de la procédure traitement_CLIENTS, afin de pouvoir déboguer ce traitement uniquement pour le client dont le code est 69002.

Point d’arrêt conditionné en Débogage 5250

On crée un point d’arrêt conditionné par une commande BREAK n°ligne WHEN condition

La condition peut utiliser toutes les variables connues du programme, ici le code client = variable CODE

On exécute le programme :

Point d’arrêt juste avant le traitement du client souhaité :

Si on demande la reprise par F12, le programme se poursuit et se termine sans autre point d’arrêt

Point d’arrêt conditionné en Débogage RDi

On crée notre point d’entrée de service pour le programme PROGTEST

On ouvre la perspective Débogage sous RDi puis on exécute le programme en 5250 :

On crée tout d’abord notre point d’arrêt :

Clic droit sur le point d’arrêt + Editer le point d’arrêt :

La condition d’arrêt doit être indiquée sur la ligne « Expression » :

On reprend ensuite l’exécution du programme :

Le programme s’arrête sur notre point d’arrêt pour le client 69002 :

F8=Reprendre –> le programme s’exécute jusqu’à la fin sans nouveau point d’arrêt