, Contrôler la cohérence des services SQL

On utilise de plus en plus les services SQL sur IBMi

Il peut être important de contrôler les services SQL installés sur votre machine

Vous avez 2 programmes qui sont fournis, nous vous proposons un petit habillage pour vous faciliter leurs utilisations
QSQIBMCHK et QSQSYSIBM

La commande

CMD        PROMPT('Contrôles services SQL')               
 PARM       KWD(TYPE) TYPE(*CHAR) LEN(4) RSTD(*YES) +      
              DFT(*CHK) VALUES(*CHK *FIX) PROMPT('Type +   
              de vérification') 

Le programme CLLE

pgm parm(&type)                                                         
dcl &type *char 4                                                       
dclf qsys/QADSPFFD                                                      
/* Récupération du CCSID de la première zone de qadbxref */             
             DSPFFD     FILE(QSYS/QADBXREF) OUTPUT(*OUTFILE) +          
                          OUTFILE(QTEMP/WADSPFFD) /* w */               
             OVRDBF     FILE(QADSPFFD) TOFILE(QTEMP/WADSPFFD) +         
                          LVLCHK(*NO)                                   
             RCVF                                                       
/* Changement du job au bon CCSID                              */       
             chgjob ccsid(&WHCSID)                                      
/* Pour contrôler les objets fournis par IBM qui sont manquants      */ 
if cond(&type = '*CHK') then(do)                                        
CALL QSYS/QSQIBMCHK                                                     
enddo                                                                   
/* Pour corriger                                                     */ 
if cond(&type = '*FIX') then(do)                                        
CALL QSYS/QSQSYSIBM                                                     
enddo                                                                   
             dltovr     FILE(QADSPFFD)  
endpgm    

Pour contrôler

==>CTLSRVSQL *CHK

Vous allez avoir ces messages dans la LOG

QSQXRLF OBJECTS FOUND = 55
QSQXRLF OBJECTS UNKNOWN = 0
QSQXRLF OBJECTS MISSING = 0
QSQSYSIBM OBJECTS FOUND = 716
QSQSYSIBM OBJECTS UNKNOWN = 0
QSQSYSIBM OBJECTS MISSING = 0
SYSTOOLS OBJECTS FOUND = 105
SYSTOOLS OBJECTS UNKNOWN = 0
SYSTOOLS OBJECTS MISSING = 0
TOTAL IBM OBJECTS FOUND = 876
TOTAL IBM OBJECTS UNKNOWN = 0
TOTAL IBM OBJECTS MISSING = 0
QSQIBMCHK – OBJECT VERIFICATION COMPLETE

Si vous avez des erreurs par exemple 1 dans OBJECTS UNKNOWN vous devrez corriger

Pour corriger

==>CTLSRVSQL *FIX

Vous allez avoir ces messages dans la LOG

QSQSYSIBM ASNEEDED PROCESSING SUCCESSFUL FOR 1236 COMPONENTS.

PS :
Vous n’avez pas besoin d’être en mode restreint
Mais vous devez être *SECADM et *ALLOBJ

Pour en savoir plus
https://www.ibm.com/support/pages/qsqibmchk-tool

Merci Jean-Marie pour le thème suggéré

SQL Pivot Table

Le pivot est une technique que vous permet de faire pivoter la sortie d’une requête de ligne à colonne et inversement.

Comme par exemple transformer ce résultat :

Par ce résultat :

Commençons par la création des tables

Imaginons que nous avons un ancien fichier des tarifs contenant 4 tarifs (dans 4 colonnes pour la table TARIF_H et 4 enregistrements pour la table TARIF_V)

TARIF_H : Table avec des tarifs sur une seule ligne mais dans plusieurs colonnes

CREATE TABLE TARIF_H ( 
	FOURNISSEUR INTEGER DEFAULT 0, 
	ARTICLE VARCHAR(10) CCSID 1208 DEFAULT '' , 
	TARIF1 DECIMAL(14, 6) DEFAULT 0 , 
	TARIF2 DECIMAL(14, 6) DEFAULT 0 , 
	TARIF3 DECIMAL(14, 6) DEFAULT 0 , 
	TARIF4 DECIMAL(14, 6) DEFAULT 0 );

insert into TARIF_H values(1, 'ART1', 14.1, 12.2, 11.3, 10.9);
insert into TARIF_H values(1, 'ART2', 15.6, 15.0, 14.5, 14.0);

TARIF_V : Table avec des tarifs dans une colonne mais sur plusieurs lignes

CREATE TABLE TARIF_V ( 
	FOURNISSEUR INTEGER DEFAULT 0 , 
	ARTICLE VARCHAR(10) CCSID 1208 DEFAULT '' , 
	NUMTARIF DECIMAL(2, 0) DEFAULT 0 , 
	TARIF DECIMAL(14, 6) DEFAULT 0 );

insert into TARIF_V values(1, 'ART1', 1, 14.1);
insert into TARIF_V values(1, 'ART1', 2, 12.2);
insert into TARIF_V values(1, 'ART1', 3, 11.3);
insert into TARIF_V values(1, 'ART1', 4, 10.9);
insert into TARIF_V values(1, 'ART2', 1, 15.6);
insert into TARIF_V values(1, 'ART2', 2, 15.0);
insert into TARIF_V values(1, 'ART2', 3, 14.5);
insert into TARIF_V values(1, 'ART2', 4, 14.0);

Passage d’un affichage horizontal à vertical

Utilisation du mot clé LATERAL pour faire une jointure LATERAL et afficher les tarifs sur plusieurs lignes. Nous allons la combiner avec la fonction VALUE ce qui nous permettra d’ajouter une colonne avec la position du tarif (Tarif colonne 1 = position tarif 1, Tarif colonne 2 = position tarif 2 etc…).

select T.fournisseur, T.article, V.POSITIONTARIF, V.VALEURTARIF
  from FG.TARIF_H as T,
    LATERAL(VALUES (1, T.TARIF1),
                   (2, T.TARIF2),
                   (3, T.TARIF3),
                   (4, T.TARIF4)) as V(POSITIONTARIF, VALEURTARIF)
  where FOURNISSEUR = 1;

ps : La clause where n’est pas indispensable pour notre cas.

Résultat :

Imaginons que nous avons pour chaque tarif une colonne « date début tarif  » (et certainement fin de tarif 🙂 ).

alter table TARIF_H add column DATEDEBUTTARIF1 date;
alter table TARIF_H add column DATEDEBUTTARIF2 date;
alter table TARIF_H add column DATEDEBUTTARIF3 date;
alter table TARIF_H add column DATEDEBUTTARIF4 date;

Il faudra simplement l’ajouter dans la jointure LATERAL ainsi que dans la sélection des zones à afficher :

select T.fournisseur, T.article, V.POSITIONTARIF, V.VALEURTARIF, V.DATEDEBUTTARIF
  from FG.TARIF_H as T,
    LATERAL(VALUES (1, T.TARIF1, T.DATEDEBUTTARIF1),
                   (2, T.TARIF2, T.DATEDEBUTTARIF2),
                   (3, T.TARIF3, T.DATEDEBUTTARIF3),
                   (4, T.TARIF4, T.DATEDEBUTTARIF4)) as V(POSITIONTARIF, VALEURTARIF, DATEDEBUTTARIF)
  where FOURNISSEUR = 1;

Résultat :

Passage d’un affichage vertical à horizontal

Cette fois-ci nous allons utiliser la fonction d’agrégation MAX (même si nous savons qu’il n’y aura qu’un tarif avec le numéro 1, 2, 3 etc…) avec un groupage sur le fournisseur et numéro d’article.

select fournisseur, article,
    max(case when numtarif = 1 then tarif end) as Tarif1,  
    max(case when numtarif = 2 then tarif end) as Tarif2,  
    max(case when numtarif = 3 then tarif end) as Tarif3,
    max(case when numtarif = 4 then tarif end) as Tarif4 
from TARIF_V
group by fournisseur, article;

Résultat :

Nous sommes d’accord qu’il existe d’autres méthodes pour faire pivoter des données (listagg etc…).

Contrôler la liste des utilisateurs inscrits à SMTP via sql

Petits rappels en préambule :

  • SNA n’est plus à utiliser, on le retrouve pourtant encore très souvent en usage sur de nombreux IBM i. Il faut passer au SMTP.
  • Les utilisateurs SMTP sont inscrits à un registre.
  • Pour accéder à ce registre on peut passer par la commande 5250 : WRKSMTPUSR – Work with All SMTP Users.
  • Si vous ne souhaitez pas inscrire tous vos profils au registre SMTP, il est d’usage de créer un profil NOREPLY afin de l’ajouter au registre, puis de soumettre les envois de mail, exemple :
SBMJOB CMD(SNDSMTPEMM RCP(('julien.laurier@gaia.fr')) SUBJECT(TEST) NOTE('This is not a test.')) USER(NOREPLY)

Lors de l’utilisation de la commande SNDSMTPEMM dans un programme, il est préférable de commencer par contrôler la présence du profil dans le registre SMTP. Ce registre est stocké non pas dans une table mais dans un fichier de configuration dans l’ifs : ‘/QTCPTMM/CONFIG/USERS.DAT’. C’est cette liste qui est affichée par WRKSMTPUSR, malheureusement, ces informations ne sont pas adressables directement via SQL. Il nous revient alors de créer nous même de quoi accéder à ces informations pour simplifier ces usages.

Voici une requête SQL qui permet de parser les informations présentes dans le fichier :

SELECT MAX(CASE WHEN entries.ordinal_position = 1 THEN entries.element END) AS "User profile",
       MAX(CASE WHEN entries.ordinal_position = 2 THEN entries.element END) AS "SMTP mailbox alias",
       MAX(CASE WHEN entries.ordinal_position = 3 AND details.ordinal_position = 1 THEN details.element END) AS "Domain index",
       MAX(CASE WHEN entries.ordinal_position = 3 AND details.ordinal_position = 2 THEN details.element END) AS "Domain Name",
       MAX(CASE WHEN entries.ordinal_position = 4 THEN entries.element END) AS "SDD name compatibility",
       MAX(CASE WHEN entries.ordinal_position = 5 THEN entries.element END) AS "SDD address compatibility",
       MAX(CASE WHEN entries.ordinal_position = 6 THEN entries.element END) AS "Forwarding to",
       MAX(CASE WHEN entries.ordinal_position = 7 THEN entries.element END) AS "Originating from",
       MAX(CASE WHEN entries.ordinal_position = 8 THEN entries.element END) AS "Data1",
       MAX(CASE WHEN entries.ordinal_position = 9 THEN entries.element END) AS "Data2"
  FROM TABLE (qsys2.ifs_read_utf8(path_name => '/QTCPTMM/CONFIG/USERS.DAT',
                                  maximum_line_length => 1024)) AS lines,
       TABLE (systools.split(input_list => CAST(lines.line AS VARCHAR(1024)),
                             delimiter => ' ')) AS entries,
       TABLE (systools.split(input_list => CAST(entries.element AS VARCHAR(1024)), 
                             delimiter => ':')) AS details
 WHERE line_number > 1
 GROUP BY lines.line_number);

Voici un exemple de résultat obtenu :

User profileSMTP mailbox aliasDomain indexDomain NameSDD name compatibilitySDD address compatibilityForwarding toOriginating fromData1Data2
FORM01*NONE00*NONEFORM01NEPTUNE*NONE*NONEY9132
FORM02*NONE00*NONEFORM02NEPTUNE*NONE*NONEY9134

Pour simplifier encore plus votre usage, je vous propose une vue, ainsi qu’une fonction table :