Multi-membre et multi-format, expliqués avec des Pokémon
Un Pokédex rangé en un membre par génération, puis un fichier logique où chaque Pokémon est suivi de ses attaques : deux spécialités de l'IBM i, que l'on croise encore dans les applications anciennes. Comment elles marchent, OVRDBF compris, et pourquoi on les évite aujourd'hui. Tout est démontré sur un IBM i 7.5.
En une phrase
Un fichier multi-membre range ses données en plusieurs membres au même format, qu'il faut choisir à chaque accès ; un fichier logique multi-format présente plusieurs fichiers physiques, chacun avec son format, dans un seul ordre de clé. Les deux marchent, mais SQL et la plupart des outils les voient mal.
Le multi-membre : un Pokédex par génération
Imaginons un Pokédex où chaque génération a sa « page » : la génération 1 (Bulbizarre, Salamèche, Carapuce, Pikachu, Mewtwo), la génération 2 (Germignon, Héricendre, Kaiminus, Pichu, Lugia). Plutôt qu'une zone GEN, on range chaque génération dans son propre membre. Le fichier, décrit en DDS sans zone de génération :
A* Pokedex multi-membre : un membre par generation
A R POKEDEXR TEXT('Pokedex d''une generation')
A NUMPOK 4S 0 COLHDG('No' 'Pokedex')
A NOM 12A COLHDG('Nom')
A TYPE1 10A COLHDG('Type')
A K NUMPOK
Un PF créé par défaut n'accepte qu'un membre : sur notre fichier POKEMON, un deuxième ADDPFM est refusé.
ADDPFM FILE(JOBIPOK/POKEMON) MBR(GEN3)
CPF3213 Nombre maximal de membres atteint pour le fichier POKEMON.
CPF7306 Membre GEN3 non ajouté au fichier POKEMON de JOBIPOK.
Il faut donc prévoir les membres à la création, avec MAXMBRS, puis les ajouter :
CRTPF FILE(JOBIPOK/POKEDEX) SRCFILE(JOBIPOK/QDDSSRC) MBR(*NONE) MAXMBRS(*NOMAX)
ADDPFM FILE(JOBIPOK/POKEDEX) MBR(GEN1) TEXT('Generation 1')
ADDPFM FILE(JOBIPOK/POKEDEX) MBR(GEN2) TEXT('Generation 2')
En SQL : le premier membre, ou un ALIAS
Un SELECT sur le fichier ne voit que le premier membre, choisi, selon IBM, d'après sa date et son heure de création :
SELECT * FROM JOBIPOK.POKEDEX
NUMPOK NOM TYPE1
1 Bulbizarre Plante
4 Salamèche Feu
7 Carapuce Eau
25 Pikachu Électrik
150 Mewtwo Psy
Aucune erreur, aucun avertissement : la génération 2 n'existe tout simplement pas pour cette requête. Pour l'atteindre, SQL propose l'ALIAS, un objet qui désigne un membre précis, et qui évite, dit IBM, de passer par des substitutions :
CREATE ALIAS JOBIPOK.POKEDEX_GEN2 FOR JOBIPOK.POKEDEX (GEN2);
SELECT * FROM JOBIPOK.POKEDEX_GEN2;
INSERT INTO JOBIPOK.POKEDEX_GEN2 SELECT NUMPOK, NOM, TYPE1 FROM JOBIPOK.POKEMON WHERE GEN = 2;
En programme : OVRDBF
Un programme RPG déclare le fichier, pas le membre. Celui-ci lit POKEDEX du début à la fin :
**free
// Lit le fichier POKEDEX, sans dire quel membre : c'est l'OVRDBF qui choisit.
ctl-opt dftactgrp(*no) actgrp(*new);
dcl-f pokedex usage(*input);
read pokedex;
dow not %eof(pokedex);
snd-msg ' ' + %char(numpok) + ' ' + %trim(nom) + ' (' + %trim(type1) + ')';
read pokedex;
enddo;
*inlr = *on;
C'est la commande OVRDBF (substitution de fichier base de données), lancée avant l'appel, qui choisit le membre. Un programme CL l'appelle trois fois :
/* Lit le Pokedex trois fois : sans substitution, puis membre GEN2, puis tous */
PGM
SNDPGMMSG MSG('Sans OVRDBF :') TOPGMQ(*SAME)
CALL PGM(LIREDEX)
OVRDBF FILE(POKEDEX) MBR(GEN2)
SNDPGMMSG MSG('OVRDBF MBR(GEN2) :') TOPGMQ(*SAME)
CALL PGM(LIREDEX)
DLTOVR FILE(POKEDEX)
OVRDBF FILE(POKEDEX) MBR(*ALL)
SNDPGMMSG MSG('OVRDBF MBR(*ALL) :') TOPGMQ(*SAME)
CALL PGM(LIREDEX)
DLTOVR FILE(POKEDEX)
ENDPGM
Sans OVRDBF :
1 Bulbizarre (Plante)
4 Salamèche (Feu)
7 Carapuce (Eau)
25 Pikachu (Électrik)
150 Mewtwo (Psy)
OVRDBF MBR(GEN2) :
152 Germignon (Plante)
155 Héricendre (Feu)
158 Kaiminus (Eau)
172 Pichu (Électrik)
249 Lugia (Psy)
OVRDBF MBR(*ALL) :
1 Bulbizarre (Plante)
… les cinq de GEN1, puis :
152 Germignon (Plante)
… les cinq de GEN2
MBR(GEN2) fait lire la génération 2 au même programme, sans le recompiler ;
MBR(*ALL) enchaîne tous les membres. DLTOVR retire la substitution.
Le programme, lui, ne choisit rien : la substitution décide pour lui.
Pourquoi on évite le multi-membre
IBM le dit sobrement : en règle générale, un fichier n'a qu'un membre. Nos essais montrent pourquoi :
- Tout ce qui ne précise rien lit le premier membre, en silence. Le SELECT
ci-dessus, le programme sans OVRDBF, et aussi CPYF, dont le membre d'origine
vaut
*FIRSTpar défaut :
La copie « du Pokédex » ne contient que la génération 1.CPYF FROMFILE(JOBIPOK/POKEDEX) TOFILE(JOBIPOK/COPIEDEX) CRTFILE(*YES) CPC2955 5 enregistrement(s) du membre GEN1 copié(s). - Chaque accès doit être préparé. Un OVRDBF avant chaque programme, un ALIAS pour chaque membre en SQL, un paramètre MBR dans chaque commande : un seul oubli, et l'on traite les mauvaises données sans aucun message.
- L'information est cachée dans un nom. La génération n'est plus une donnée qu'on
peut filtrer, trier ou compter : il faut connaître la liste des membres. Avec une zone GEN, comme
dans notre fichier POKEMON, une requête suffit :
SELECT … WHERE GEN = 2, ouGROUP BY GEN.
Le multi-membre garde un usage légitime et quotidien : les fichiers source, où chaque membre est un programme. Et pour une table SQL vraiment trop grande, IBM propose les tables partitionnées (option 27 du système, Db2 Multisystem), dont chaque partition est un membre, mais que SQL gère tout seul.
Le multi-format : chaque Pokémon suivi de ses attaques
Un fichier physique n'a qu'un format : c'est une règle des DDS. Mais un fichier logique peut en avoir plusieurs, un par PF qu'il présente. Prenons deux PF : POKEMON (un enregistrement par Pokémon) et ATTAQUE (plusieurs par Pokémon : Pikachu a Éclair et Tonnerre). Un LF les réunit sur leur clé commune, le numéro du Pokédex :
A* LF multi-format : chaque Pokemon suivi de ses attaques
A R POKEMONR PFILE(POKEMON)
A K NUMPOK
A R ATTAQUER PFILE(ATTAQUE)
A K NUMPOK
Chaque ligne R déclare un format et son PF (PFILE). Lu par clé, le LF rend
les enregistrements des deux fichiers mélangés dans l'ordre du numéro : un Pokémon, ses attaques, le
Pokémon suivant. C'est l'exemple même des concepteurs du System/38 en 1978 : un LF où chaque
en-tête de commande est suivi de ses lignes de détail.
Le programme RPG doit savoir, à chaque lecture, quel format il vient de recevoir. Il le trouve dans la structure d'information du fichier (INFDS), positions 261 à 270 :
**free
// Lit le LF multi-format POKEATK : un Pokemon, puis ses attaques, puis le suivant.
ctl-opt dftactgrp(*no) actgrp(*new);
dcl-f pokeatk keyed infds(info);
// Positions 261 a 270 de l'INFDS : nom du format de l'enregistrement lu
dcl-ds info;
format char(10) pos(261);
end-ds;
read pokeatk;
dow not %eof(pokeatk);
if format = 'POKEMONR';
snd-msg %trim(format) + ' : ' + %char(numpok) + ' ' + %trim(nom);
else;
snd-msg %trim(format) + ' : ' + %trim(attaque) + ' (' + %char(puiss) + ')';
endif;
read pokeatk;
enddo;
*inlr = *on;
POKEMONR : 1 Bulbizarre
ATTAQUER : Fouet Lianes (45)
POKEMONR : 4 Salamèche
ATTAQUER : Flammèche (40)
POKEMONR : 7 Carapuce
ATTAQUER : Pistolet à O (40)
POKEMONR : 25 Pikachu
ATTAQUER : Éclair (40)
ATTAQUER : Tonnerre (90)
POKEMONR : 150 Mewtwo
POKEMONR : 152 Germignon
POKEMONR : 155 Héricendre
ATTAQUER : Flammèche (40)
POKEMONR : 158 Kaiminus
POKEMONR : 172 Pichu
POKEMONR : 249 Lugia
Pourquoi on évite le multi-format
- SQL ne sait pas le lire.
Pas de requête, pas d'outil de reporting, pas de vue : seul un programme peut l'exploiter.SELECT * FROM JOBIPOK.POKEATK SQL7003 Le fichier POKEATK de JOBIPOK a plusieurs formats. - Le programme porte la logique. Tester le format, garder en mémoire le dernier Pokémon lu pour savoir à qui appartient une attaque : tout ce qu'une jointure ferait toute seule.
- La jointure fait mieux. IBM le note : sans spécification de jointure, le LF aurait deux formats et le programme devrait faire deux lectures pour réunir ses données ; un LF de jointure, ou une vue SQL avec JOIN, rend en une ligne le nom du Pokémon et son attaque (voir les fichiers logiques).
À retenir
- Multi-membre : MAXMBRS à la création, ADDPFM, puis OVRDBF MBR(…) en programme ou CREATE ALIAS en SQL.
- Sans précision, c'est toujours le premier membre qui est lu, copié, sélectionné, sans avertissement.
- Multi-format : un LF à plusieurs formats, lu par un programme qui teste le format (INFDS 261-270) ; illisible en SQL (SQL7003).
- Aujourd'hui : une zone plutôt qu'un membre, une jointure plutôt que plusieurs formats.
Sources
Les exemples ont été créés et relevés sur un IBM i 7.5 (voir la méthode). Les définitions et les règles viennent de ces sources, consultées le 16 octobre 2026 :
- IBM System/38 Technical Developments : File and data definition facilities in System/38, IBM, C. D. Truxal et S. R. Ridenour (1978, PDF numérisé). Les descriptions de données redondantes et divergentes d'avant (« each program … has required a separate definition of the data »), la description centrale par les DDS réutilisée par les compilateurs et les utilitaires, le contrôle de niveau, les fichiers décrits dans le programme conservés pour la conversion, les PF à un seul format et leurs membres. Le même recueil contient l'article de Conway et Harvey sur le choix d'un formulaire déclaratif séparé du programme, et l'indépendance des programmes vis-à-vis de la structure physique grâce aux fichiers logiques ; le LF qui fait suivre chaque en-tête de ses lignes de détail.
- IBM i 7.5 Database : Database programming, IBM (PDF). Définitions du fichier physique, du fichier logique (« contains no data ») et du membre ; un seul membre « en règle générale » et au moins un membre avant toute entrée-sortie ; premier membre choisi selon la date et l'heure de création ; fichier de référence de zones et fichier créé MBR(*NONE) ; attributs des fichiers source (92 = 6 + 6 + 80, sans clé, plusieurs membres) ; LF qui présente l'ancien format ; règles des LF de jointure (lecture seule, pas de DFU, clé du fichier primaire, JDFTVAL, un seul format) ; vu de SQL, LF = vues et index.
- IBM i 7.5 Database : SQL programming, IBM (PDF). CREATE ALIAS pour un membre, « sans substitution » ; tables partitionnées (option 27, Db2 Multisystem), dont chaque partition est un membre ; ORDER BY pour garantir l'ordre des lignes.
- IBM i 7.2 Programming : DDS for physical and logical files, IBM (PDF). Mots-clés des fichiers logiques : PFILE, sélection (S et COMP), JFILE, JOIN, JFLD, JREF, JDFTVAL ; un seul format R par fichier physique.
- L'aide en ligne des commandes citées, livrée avec IBM i 7.5 et reprise en français dans les fiches des commandes CL de ce site. Paramètres de CRTPF, CRTLF, CRTSRCPF, ADDPFM, OVRDBF, UPDDTA, DSPPFM, RGZPFM, CPYF, CRTBNDRPG et CRTBNDCL.
Questions fréquentes
Qu'est-ce qu'un fichier multi-membre ?
Un fichier qui contient plusieurs membres, tous au même format : par exemple un Pokédex avec un membre par génération. Il faut le créer avec MAXMBRS supérieur à 1 (CRTPF met 1 par défaut, d'où CPF3213 au deuxième ADDPFM) et choisir le membre à chaque accès.
Comment lire un autre membre que le premier ?
En programme, avec OVRDBF FILE(fichier) MBR(membre) avant l'ouverture, ou MBR(*ALL) pour les lire tous à la suite. En SQL, avec CREATE ALIAS alias FOR bib.fichier (membre). Sans précision, c'est le premier membre, selon la date et l'heure de création, qui est lu.
Pourquoi éviter les fichiers multi-membres ?
Parce que chaque accès doit choisir le bon membre : un SELECT, un CPYF ou un programme sans OVRDBF ne voient que le premier, sans aucune erreur. Une table unique avec une colonne (la génération, le mois…) se lit et se filtre simplement en SQL. Les fichiers source, eux, sont multi-membres par construction.
Qu'est-ce qu'un fichier logique multi-format ?
Un LF qui présente plusieurs PF, chacun avec son format, dans un même ordre de clé : par exemple chaque Pokémon suivi de ses attaques. Le programme doit tester le format de chaque enregistrement lu, et SQL ne sait pas le lire (SQL7003).