EXEC SQL en COBOL

EXEC SQL en COBOL : Maîtriser les requêtes SELECT avancées

Tutoriel COBOL

EXEC SQL en COBOL : Maîtriser les requêtes SELECT avancées

L’EXEC SQL en COBOL représente une pierre angulaire du développement mainframe moderne. Ce mécanisme permet aux programmeurs de COBOL d’interagir directement avec les bases de données relationnelles (comme DB2 ou Oracle) en exécutant des commandes SQL nativement au sein du code. Il transforme ainsi un programme batch monolithique en une application capable de récupérer, de modifier ou d’insérer des données complexes. Ce guide est conçu pour les développeurs COBOL expérimentés ou les architectes souhaitant moderniser leur usage des données sur des plateformes mainframes.

Dans le contexte des systèmes transactionnels hérité, la récupération d’informations est vitale. Historiquement, les données étaient souvent manipulées via des fichiers plats (VSAM). Aujourd’hui, l’utilisation de l’EXEC SQL en COBOL est le moyen privilégié pour joindre la logique métier en COBOL à la puissance et la structure d’une base de données relationnelle. Cela assure non seulement la cohérence des données, mais facilite également la maintenance et l’évolution fonctionnelle.

Pour comprendre en profondeur ce mécanisme puissant, nous allons d’abord examiner les prérequis techniques indispensables. Ensuite, nous plongerons dans les concepts théoriques de l’intégration SQL/COBOL, y compris les mécanismes de préparation et de gestion des curseurs. Après cette base, nous détaillerons l’exécution de requêtes SELECT concrètes, puis explorerons des cas d’usage avancés (ETL, Batch, Temps Réel). Enfin, nous aborderons les erreurs courantes et les meilleures pratiques pour coder ce mécanisme de manière optimale. Préparez-vous à transformer votre compréhension de l’EXEC SQL en COBOL !

EXEC SQL en COBOL
EXEC SQL en COBOL — illustration

🛠️ Prérequis

Pour manipuler les bases de données depuis un programme COBOL, plusieurs prérequis techniques doivent être en place. Il ne s’agit pas seulement de savoir écrire du COBOL, mais aussi de comprendre l’environnement d’exécution et la connectivité.

1. Connaissances Linguistiques et Techniques

  • COBOL avancé : Maîtrise des structures de données (par exemple, REDEFINES, OCCURS) et de la gestion des fichiers.
  • SQL de base : Connaissance des commandes SELECT, FROM, WHERE, JOIN, et des types de données relationnels.
  • Gestion des Transactions : Compréhension des concepts ACID (Atomicité, Cohérence, Isolation, Durabilité).

2. Environnement et Configuration

Le choix du système de base de données est crucial. Nous recommandons fortement l’utilisation de DB2 for iSeries ou DB2 for z/OS, car ces systèmes offrent un support natif et optimisé pour l’intégration CICS/COBOL.

  • Version COBOL : Minimum COBOL 7.4 ou supérieur pour un support robuste de l’ANSI SQL.
  • Librairies et Connectivité : L’installation du client de base de données (JDBC/ODBC ou le pilote natif mainframe) doit être effectuée. Pour DB2, il faut s’assurer que les utilitaires de compilation COBOL (ex : COBOL/DB2 Precompiler) sont disponibles et configurés pour traiter les blocs EXEC SQL.

3. Pré-compilation et Compilation

Contrairement à de simples appels de fonctions, l’utilisation de l’EXEC SQL en COBOL nécessite généralement une étape de pré-compilation. Le pré-compilateur traduit les commandes SQL en appels de routine COBOL, garantissant la sécurité des types de données et optimisant les performances. Les commandes exactes dépendent de votre plateforme (ex: ibmdbmq* ou des utilitaires spécifiques au DB2/zOS).

📚 Comprendre EXEC SQL en COBOL

Comprendre l’EXEC SQL en COBOL, ce n’est pas seulement savoir écrire un SELECT dans un bloc spécial. C’est saisir comment le langage COBOL, conçu pour le traitement séquentiel de fichiers, interagit avec un modèle de données relationnel et transactionnel. Ce passage du concept de « registre physique » à celui de « données logiques liées » est le cœur de cette technique.

Le principe fondamental est l’incorporation du SQL dans le flux de contrôle COBOL. Le code EXEC SQL agit comme un pont, exécutant une requête et récupérant les résultats qui sont ensuite mappés dans des variables COBOL de type PIC. Ce mapping doit être précis : si vous spécifiez une colonne de type DATE en SQL, votre variable COBOL doit pouvoir accueillir ce format de date sans troncature ni corruption des données.

L’Architecture Interne du Mappage

Imaginez que le programme COBOL soit le chef d’orchestre. Les données de la base de données sont les musiciens. La commande EXEC SQL est le pupitre qui dirige les musiciens. Le pré-compilateur assure que chaque instrument (colonne SQL) est correctement connecté à sa partition de notation (variable COBOL). Le processus global est souvent comparé à l’utilisation des API modernes (comme JDBC en Java), mais historiquement, il était optimisé pour l’environnement mainframe, en minimisant l’overhead des appels externes.

L’exécution d’une requête SELECT ne se termine pas avec la simple récupération des données. Il faut impérativement gérer le curseur (cursor) pour parcourir l’ensemble des résultats. Sans gestion de curseur, COBOL ne saurait pas si un jeu de résultats est vide, ou s’il y a plusieurs enregistrements à traiter. L’EXEC SQL en COBOL gère donc un cycle de vie complet : Déclaration du curseur, Ouverture, Fetch (récupération des lignes) et Fermeture.

En comparaison avec les langages modernes qui utilisent souvent des Object-Relational Mappers (ORM), l’approche COBOL est plus brute mais incroyablement fiable dans des environnements haute disponibilité (HA). Elle exige une gestion manuelle des ressources (ouverture/fermeture), ce qui est à la fois un défi et une preuve de robustesse pour le développeur.

Exemple schématique du flux de données :

COBOL Logic (Control) --(EXEC SQL)--> Precompiler/Runtime --(Network)--> DB Engine --(Results)--> COBOL Data Area

La gestion transactionnelle (COMMIT/ROLLBACK) est le point de non-retour. Chaque bloc de logique impliquant des changements de données doit être enveloppé dans une transaction pour garantir la consistance, un concept essentiel quand on manipule l’état du système via l’EXEC SQL en COBOL.

EXEC SQL en COBOL
EXEC SQL en COBOL

🏦 Le code — EXEC SQL en COBOL

COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID    SELECT-COBOL.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE PANEL
DATA DIVISION.
WORKING-STORAGE SECTION.
* Définition des tables et variables.
01 WS-CONNECT-STATUS     PIC X(10) VALUE 'INITIAL'.
01 WS-CURSOR-STATUS      PIC X(10) VALUE 'CLOSED'.
01 WS-CLIENT-ID          PIC X(10).
01 WS-EMPLOYEE-ID        PIC X(10).
01 WS-NAME               PIC X(50).
01 WS-SALARY             PIC S9(7)V99.

PROCEDURE DIVISION.
MAIN-LOGIC.
* 1. Initialisation et établissement de la connexion
EXEC SQL
  SET DEFINE CURSOR CUR_EMPS
END-EXEC.

*> L'état de connexion est souvent géré automatiquement par le run-time,
*> mais un CALL initial est souvent nécessaire.
EXEC SQL
  SET WS-CONNECT-STATUS = 'CONNECTED'
END-EXEC.

*> 2. Déclaration et ouverture du curseur
MOVE 'EMPLOYEE_TABLE' TO WS-CURSOR-STATUS.
EXEC SQL
  DECLARE CUR_EMPS CURSOR FOR
  SELECT EMPLOYEE_ID, NAME, SALARY
  FROM EMPLOYEE_TABLE
  WHERE DEPARTMENT_ID = :WS-DEPT-ID
  ORDER BY SALARY DESC
END-EXEC.

EXEC SQL
  OPEN CUR_EMPS
END-EXEC.

* 3. Récupération et Traitement des données
PERFORM FETCH-AND-PROCESS
UNTIL WS-CURSOR-STATUS = 'NO_MORE_RECORDS'.

* 4. Nettoyage et clôture
EXEC SQL
  CLOSE CUR_EMPS
END-EXEC.

EXEC SQL
  SET WS-CONNECT-STATUS = 'DISCONNECTED'
END-EXEC.

STOP RUN.

*> Routine de traitement
FETCH-AND-PROCESS.
PERFORM 900-FETCH
UNTIL WS-CURSOR-STATUS = 'NO_MORE_RECORDS'.

900-FETCH.
EXEC SQL
  FETCH CUR_EMPS INTO :WS-EMPLOYEE-ID, :WS-NAME, :WS-SALARY
END-EXEC.

IF SQLCODE = 100.
  MOVE 'SUCCESS' TO WS-CURSOR-STATUS
ELSE
  MOVE 'NO_MORE_RECORDS' TO WS-CURSOR-STATUS
END-IF.

* (Note: Dans un vrai programme, le DEPT_ID serait rempli avant la boucle)

📖 Explication détaillée

Ce premier snippet illustre le cycle de vie complet d’une requête SELECT en utilisant l’EXEC SQL en COBOL. Il ne s’agit pas d’une simple récupération de données ; c’est une séquence d’opérations transactionnelles rigoureuses, typiques des applications Mainframe.

Analyse du Workflow SELECT en COBOL

1. Définition des Curseurs et des Statuts : Nous commençons par définir des variables de statut (WS-CURSOR-STATUS). Ces variables sont cruciales pour piloter la boucle de traitement. Le curseur (CUR_EMPS) est une notion logique qui permet de traiter les résultats d’une requête ligne par ligne, au lieu de devoir les charger en mémoire toutes neufs (ce qui serait très inefficace).

2. Déclaration et Ouverture du Curseur (DECLARE / OPEN) : L’étape DECLARE envoie à la base de données le schéma de la requête et définit l’ensemble de colonnes à récupérer. L’instruction OPEN active le curseur. C’est le point de départ de la transaction de lecture. Si l’ouverture échoue (mauvaise permission, table inexistante), tout le programme doit pouvoir récupérer cette erreur via SQLCODE.

3. La Boucle de Traitement (FETCH) : Le cœur du processus réside dans la routine FETCH-AND-PROCESS. L’instruction FETCH CUR_EMPS INTO :WS-EMPLOYEE-ID, :WS-NAME, :WS-SALARY END-EXEC est l’action qui tire la prochaine ligne du jeu de résultats vers nos variables COBOL. Le point crucial est la gestion de SQLCODE après le FETCH. Un code 0 signifie succès, 100 indique que le curseur est vide (la boucle doit s’arrêter), et les autres codes signalent des erreurs. C’est cette gestion précise de l’EXEC SQL en COBOL qui assure la robustesse.

4. Clôture et Finalisation (CLOSE) : Il est impératif de CLOSE CUR_EMPS pour libérer les ressources côté base de données. Si l’on oublie cette étape, la connexion peut rester ouverte (leak de ressources), provoquant des problèmes de performance ou des blocages de transaction sur le serveur DB. Le STOP RUN après la déconnexion complète (SET WS-CONNECT-STATUS = 'DISCONNECTED') signale la fin du cycle de vie de l’application.

Le bloc EXEC SQL... END-EXEC est le conteneur physique. Il doit englober les commandes SQL et est souvent pré-compilé pour garantir que la syntaxe COBOL et SQL soit compatible avant même l’exécution runtime. Le succès de l’EXEC SQL en COBOL dépend donc de cette coordination rigoureuse.

📖 Ressource officielle : Documentation COBOL — EXEC SQL en COBOL

🔄 Second exemple — EXEC SQL en COBOL

COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID    UPDATE-EMPLOYEE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-EMP-ID       PIC X(10).
01 WS-SALARY-INCREASE PIC S9(5)V99.

PROCEDURE DIVISION.
MAIN-UPDATE.
* Scénario : Augmenter le salaire d'un employé spécifique.
EXEC SQL
  UPDATE EMPLOYEE_TABLE
  SET SALARY = :WS-SALARY + :WS-SALARY-INCREASE
  WHERE EMPLOYEE_ID = :WS-EMP-ID
END-EXEC.

*> Vérification du résultat
IF SQLCODE = 0
  DISPLAY 'Mise à jour réussie pour l''ID: ' WS-EMP-ID
ELSE IF SQLCODE = 100
  DISPLAY 'Erreur: L''ID ' WS-EMP-ID ' n''existe pas.'
ELSE
  DISPLAY 'Erreur SQL: ' SQLCODE
END-IF.

EXEC SQL
  COMMIT WORK
END-EXEC.

▶️ Exemple d’utilisation

Imaginons un scénario typique de gestion de la paie : le programme doit récupérer tous les employés qui ont un salaire inférieur au minimum autorisé pour le département et, si trouvé, les notifier.

Le programme COBOL appellerait le code de manière séquentielle. Après que le bloc d’EXEC SQL en COBOL ait exécuté la requête (le cycle SELECT), il parcourrait chaque résultat trouvé. Pour chaque ligne, il effectuerait une validation métier simple (vérifier si le salaire est sous le seuil). Si la condition est remplie, il afficherait le nom et le montant. Enfin, si des anomalies sont trouvées, le programme pourrait exécuter un second bloc UPDATE pour créer un ticket de révision.

L’architecture ici est : 1. Lecture des données potentiellement problématiques (SELECT). 2. Logique métier en COBOL. 3. Action corrective (UPDATE ou INSERT).

Ce processus démontre la complémentarité parfaite entre le SQL (gestion des données) et le COBOL (gestion de la logique métier). L’efficacité réside dans la capacité à ne récupérer que les données strictement nécessaires, réduisant ainsi la charge réseau et le temps d’exécution, tout en assurant une conformité métier totale.

Code Call (simulé) :

CALL PAIE-PROCESSOR USING 12345, 0.75.

Sortie Console Attendue :

--- Rapport de paie - Anomalies détectées ---
[1] Employé: Jean Dupont | ID: 12345 | Salaire actuel: 35000.00
[2] Employé: Marie Dubois | ID: 67890 | Salaire actuel: 22000.00
3 enregistrements traités.
5 anomalies détectées. Mise à jour des tickets lancée.
--- Fin du rapport ---

Chaque ligne de sortie confirme qu’un enregistrement a été traité, avec l’ID et le salaire récupérés via l’EXEC SQL en COBOL, et qu’une action corrective (le « ticket ») a été déclenchée avec succès. Le système garantit ainsi qu’aucune anomalie n’est traitée sans traçabilité.

🚀 Cas d’usage avancés

Le potentiel de l’EXEC SQL en COBOL dépasse largement le simple affichage d’un rapport. Il est au cœur des processus métier critiques sur mainframe. Voici quatre scénarios avancés que vous pouvez implémenter.

Gestion des Workflows et État de Processus

Dans les systèmes de gestion de dossiers (DMS), un enregistrement client peut passer par plusieurs états (PENDING -> VALIDATED -> APPROVED). Au lieu de lire le statut et de le recalculer en COBOL, il est préférable de faire passer la logique dans la base de données. Le COBOL n’exécute que la commande de transition.

EXEC SQL UPDATE PROCESS_STATUS SET STATUS = 'APPROVED' WHERE FILE_ID = :WS-FILE-ID AND CURRENT_STATUS = 'PENDING' END-EXEC.

Cette méthode garantit l’atomicité et prévient les conditions de concurrence (race conditions). Le COBOL agit comme l’interface de déclenchement, pas comme le moteur de décision.

Chargement de Masse et État ETL (Extract, Transform, Load)

Lors de l’intégration de données provenant de systèmes externes (par exemple, fichiers CSV ou messages MQ), le COBOL est souvent utilisé pour orchestrer les étapes ETL. On lit les données du fichier et, plutôt que de les écrire dans un fichier cible, on les insère ou on les met à jour directement dans la base de données. L’opération INSERT ou MERGE est utilisée dans ce contexte.

EXEC SQL
MERGE INTO TARGET_TABLE USING SOURCE_DATA
ON (SOURCE_DATA.KEY = TARGET_TABLE.KEY)
WHEN NOT MATCHED THEN INSERT (KEY, VALUE) VALUES (:WS-KEY, :WS-VALUE)
WHEN MATCHED THEN UPDATE SET VALUE = :WS-VALUE
END-EXEC.

Cette requête MERGE est extrêmement puissante, permettant de gérer à la fois les nouveaux enregistrements (INSERT) et la mise à jour des anciens (UPDATE) en une seule opération atomique, optimisant ainsi les performances des lots de traitement.

Vérification de Disponibilité et Validation Temps Réel

Dans les systèmes de réservation ou de paiement, il est critique de savoir en temps réel si une ressource est libre. Au lieu de lire un flag dans un fichier, on exécute une requête conditionnelle. Ceci est souvent combiné à un verrouillage transactionnel.

EXEC SQL
SELECT COUNT(*) INTO :WS-COUNT FROM RESERVATIONS
WHERE RESOURCE_ID = :WS-RESOURCE-ID AND DATE = CURRENT_DATE
FOR UPDATE END-EXEC.

Le mot-clé FOR UPDATE est fondamental. Il demande au système de base de données de mettre cette ligne en mode verrouillé pendant la transaction, empêchant qu’un autre programme ne modifie l’état avant que notre COBOL n’ait effectué son propre COMMIT. C’est la garantie de la cohérence des données transactionnelles.

Calculs Agrégés Complexes (Reporting)

Pour les rapports complexes (ex: « calculer le total des ventes de produits X pour les clients Y ayant plus de Z transactions »), l’EXEC SQL en COBOL permet d’exploiter la puissance des fonctions d’agrégation SQL (SUM, AVG, COUNT) directement, plutôt que de forcer COBOL à effectuer des boucles de calcul coûteuses en CPU.

EXEC SQL
SELECT CUSTOMER_ID, SUM(SALES_AMOUNT) AS TOTAL_SALES
FROM SALES_TABLE
WHERE SALE_DATE BETWEEN :START_DATE AND :END_DATE
GROUP BY CUSTOMER_ID
END-EXEC.

Le résultat sera un jeu de données déjà agrégé, que le COBOL devra simplement itérer et afficher. Cette approche optimise le temps de calcul et la consommation de CPU mainframe.

⚠️ Erreurs courantes à éviter

L’utilisation de EXEC SQL en COBOL est puissante, mais elle est aussi source d’erreurs subtiles. Ne sous-estimez jamais la complexité de la gestion des ressources. Voici les pièges les plus courants rencontrés par les développeurs.

1. Oubli de la Gestion du Curseur (CURSOR Leak)

C’est l’erreur la plus fréquente. Exécuter un SELECT sans déclarer ni fermer le curseur (manquer DECLARE et CLOSE) peut entraîner un blocage de ressources de la base de données, réduisant la capacité globale du système à gérer des transactions simultanées. Toujours s’assurer que la ressource est libérée, même en cas d’exception.

2. Mauvais Mapping des Types de Données

Les types de données SQL (DATE, TIMESTAMP, VARCHAR) doivent correspondre strictement aux types COBOL (PIC). Tenter de lire un grand nombre de caractères (VARCHAR) dans un champ PIC trop petit provoquera une troncature silencieuse ou une corruption des données. Vérifiez toujours la taille et le format, surtout pour les chaînes de caractères complexes.

3. Négliger la Gestion des Exceptions (SQLCODE)

Le code de retour SQLCODE doit être vérifié après chaque appel SQL critique (COMMIT, ROLLBACK, OPEN, FETCH). Si vous supposez que l’opération réussit, votre programme ne sera pas résilient. L’enrobage du code SQL dans des blocs de test (équivalents de TRY-CATCH) est indispensable pour capter les erreurs du réseau, des permissions, ou des violences de contraintes.

4. Traitement des Paramètres Liés (Bind Variables)

Il est dangereux de construire des requêtes SQL en concaténant des chaînes de caractères COBOL (ex: SELECT * FROM T WHERE ID = ' :WS-ID'). Cela ouvre la porte aux injections SQL. On doit toujours passer par des variables liées (Bind Variables : WHERE ID = :WS-ID) pour que le pilote de base de données protège l’exécution de la requête contre les entrées malveillantes.

✔️ Bonnes pratiques

Pour garantir que votre code utilisant l’EXEC SQL en COBOL soit performant, sécurisé et maintenable, adoptez ces standards professionnels.

  • Toujours Utiliser des Requêtes Préparées (Prepared Statements) : Au lieu de recompiler l’intégralité du programme pour chaque petite modification de requête, préparez le statement une fois au début de la transaction. Ceci améliore la performance, surtout si la requête est appelée plusieurs fois dans la boucle.
  • Implémenter une Gestion Transactionnelle Exhaustive : Encapsulez tout bloc de travail (SELECT/UPDATE/DELETE) entre un START TRANSACTION implicite ou explicite, et terminez par un COMMIT WORK (en cas de succès) ou un ROLLBACK WORK (en cas d’échec). N’intercalez jamais des logs métier sans gestion transactionnelle.
  • Séparer la Logique SQL de la Logique Métier COBOL : Le COBOL doit gérer le « quoi faire » (la décision métier), tandis que le SQL doit gérer le « comment obtenir les données » (la performance du jeu de résultats). Ne jamais tenter d’effectuer des calculs complexes en COBOL si la base de données fournit des fonctions d’agrégation (SUM, AVG, GROUP BY).
  • Utiliser des Noms de Variables de Liaisons Clairs : Les variables que vous liez (Bind Variables) doivent être distinctement nommées et dimensionnées avec soin. Cela facilite la lecture et le débogage en cas de divergence de types entre le code et la base de données.
  • Établir un Schéma de Couche d’Accès aux Données (DAO/DAL) : Ne laissez jamais les blocs EXEC SQL se mélanger au reste de la logique métier. Créez une ou des sous-programmes spécifiques (ex: DS-EMPLOYEE-SERVICE) dédiés uniquement à l’interaction DB. Cela augmente l’isolation, testabilité et maintenabilité du système d »EXEC SQL en COBOL.
📌 Points clés à retenir

  • La gestion transactionnelle est primordiale : utiliser toujours COMMIT et ROLLBACK pour garantir l'intégrité des données.
  • Le CURSOR est indispensable pour un traitement ligne par ligne des résultats, évitant de surcharger la mémoire.
  • La différence entre les données logiques (SQL) et les données physiques (COBOL PIC) nécessite une attention constante au mapping des types.
  • Le pré-compilateur SQL/COBOL est un outil de compilation critique qui garantit la compatibilité syntaxique et optimise les performances.
  • Les requêtes avec <code>FOR UPDATE</code> permettent le verrouillage optimiste/pessimiste, empêchant les conflits de données en temps réel.
  • La prévention des injections SQL passe obligatoirement par l'utilisation de variables liées (Bind Variables) et jamais de concaténation de chaînes.
  • Le cycle de vie complet (OPEN -> FETCH -> CLOSE) doit être géré rigoureusement pour éviter les fuites de ressources.
  • L'utilisation de MERGE est la méthode moderne et atomique pour gérer à la fois les insertions et les mises à jour de masse.

✅ Conclusion

En conclusion, maîtriser l’EXEC SQL en COBOL n’est pas un simple ajout de fonctionnalité ; c’est une étape de modernisation critique qui permet aux applications mainframe héritées de rester compétitives et adaptables aux exigences du monde des données modernes. Nous avons parcouru le cycle de vie complet, de la simple lecture de données SELECT jusqu’aux transactions complexes avec MERGE et FOR UPDATE. Rappelez-vous que la force de cette technologie réside dans son rôle de pont fiable entre la puissance structurée du SQL et la logique métier éprouvée du COBOL.

Le succès dans ce domaine vient de la rigueur méthodologique : respecter le cycle de vie des ressources (curseurs, transactions) et adopter les pratiques modernes comme les statements préparés. Si vous avez aimé cette plongée technique, je vous recommande de vous plonger dans les exercices de réécriture de programmes Batch qui passent de la manipulation de VSAM à l’orchestration via DB2.

Pour aller plus loin, la documentation COBOL officielle de votre fournisseur est une ressource incontournable. N’hésitez pas à simuler des cas de compétition de données et à y appliquer les mécanismes FOR UPDATE. N’oubliez jamais : le code COBOL est le cerveau, mais la base de données relationnelle est la mémoire et le cœur de décision. Cultiver cette complémentarité est la clé d’un développeur mainframe de haut niveau.

Nous espérons que cet article vous a éclairé sur la complexité et la puissance de l’EXEC SQL en COBOL. Il ne s’agit pas de remplacer le COBOL, mais de l’armurer avec les capacités du monde relationnel. Au plaisir de vous retrouver pour décortiquer la prochaine merveille du mainframe !

2 réflexions sur « EXEC SQL en COBOL : Maîtriser les requêtes SELECT avancées »

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *