Le guide Oracle Forms 9i/10g


précédentsommairesuivant

IX. Les blocs de données

IX-A. Les blocs basés sur table ou vue simple

IX-A-1. Définition

Un bloc est dit basé sur une table ou une vue lorsqu'il est physiquement rattaché à une table ou une vue existante de la base de données.

Un bloc ne peut être basé que sur une seule table ou une seule vue.
En d'autre termes, un même bloc ne peut pas contenir d'items (colonnes) provenant de plusieurs tables.

Un bloc est constitué d'enregistrements correspondant aux colonnes de la table ou de la vue sur laquelle il est basé.

Forms gère automatiquement les interactions avec la base de données lorsque vous insérez, modifiez ou supprimez un enregistrement du bloc.

IX-A-2. Concept

Pour pouvoir baser un bloc sur une table ou une vue, celle-ci doit préalablement exister dans la base de donnée.
Elle peut appartenir au schéma sur lequel l'utilisateur est connecté ou à n'importe quel autre schéma de la base pourvu qu'un synonyme existe.

Les tables relationnelles contenant des colonnes objets ou non ainsi que les tables objets sont supportées par Forms 9i/10g.
Cependant, Forms ne gère pas nativement les colonnes de type collection (NESTED TABLE, VARRAY).

Un bloc ne contient pas d'objet graphique mais seulement des items qui établissent la correspondance avec les colonnes de la table liée.

C'est lui qui gère l'interaction avec la base de données en ramenant les enregistrements depuis la table et en exécutant les ordres d'insertion de mise à jour et de suppression.

IX-A-3. Mise en œuvre

Ajout d'un bloc

Pour ajouter un bloc à la forme, double-cliquer sur le nœud Blocs de données ou faire un simple clic sur le nœud Blocs de données puis cliquer sur l'icône Image non disponible

Utilisation des assistants

le bloc sera basé sur une table ou une vue

Image non disponible

L'écran suivant permet de sélectionner la table ou la vue grâce au bouton Parcourir…

Image non disponible

Choisissez la table EMP puis cliquez sur le bouton OK

Image non disponible
Image non disponible

La fenêtre de gauche (colonnes disponibles) affiche les champs disponibles dans la table.

Sélectionner un ou plusieurs champs pour les faire passer dans la fenêtre de droite (Eléments base de données) avec les boutons appropriés

Image non disponible

puis cliquer sur le bouton Suivant

Image non disponible

Donnez un nom au bloc (30 caractères maxi)

Image non disponible

La définition du bloc étant achevée nous passons ensuite à l'étape de présentation

Création de la présentation

Aucun canevas n'étant disponible, laissons l'option (Nouveau canevas)
Puis dans la zone Type, choisissons un canevas de type Intégral

Image non disponible

De la même façon que nous avons sélectionné les champs du bloc, nous indiquons également la liste des champs qui seront positionnés sur le canevas

Image non disponible

Il s'agit là d'indiquer la liste des items qui apparaîtront sur le canevas.
C'est pourquoi il est conseillé, même si vous n'affichez que certaines colonnes de ramener dans le bloc l'ensemble des colonnes de la table dans l'étape précédente.
Même si vous n'en avez pas l'utilité immédiate, cela pourrait changer dans le temps.

Image non disponible

L'écran suivant permet de paramétrer un certain nombre de propriétés de chaque champ, comme l'invite, la largeur et la hauteur. (ces propriétés pourront être modifiées ultérieurement dans la fenêtre de propriétés du bloc et des items)

Image non disponible

Ensuite, nous indiquons si nous souhaitons présenter un ou plusieurs enregistrements

Une présentation de type Formulaire affiche un seul enregistrement dans le bloc.
Une présentation de type tabulaire affiche un tableau de plusieurs enregistrements.

Image non disponible
Image non disponible

Enfin, un titre est donné à l'encadrement, le nombre d'enregistrements affichés, la distance entre chaque enregistrement ainsi que l'ajout d'une barre de défilement verticale pour naviguer dans les enregistrements

Image non disponible

Voici le résultat de la création de notre bloc.
Forms a créé un item pour chaque colonne de la table avec le type correspondant.

Affichons la liste des propriétés du bloc en sélectionnant son nom dans le navigateur puis en pressant F4

Image non disponible

Description des principales propriétés relatives au bloc de données

Lorsque la propriété est modifiable pendant l'exécution, la fonction native associée est indiquée

Général

Nom indique le nom du bloc. Il accepte jusqu'à 30 caractères selon la norme Forms.
Information de référence et Commentaires sont des zones libres de saisie permettant de documenter le bloc.

Navigation

Type de navigation indique comment se déplace le focus lorsque l'on quitte le premier ou le dernier champ de l'enregistrement.
3 valeurs sont possibles:

  • Même enregistrement indique que l'on boucle sur le même enregistrement
  • Changement d'enregistrement indique que l'on saute à l'enregistrement suivant en quittant le dernier champs (Tab) ou à l'enregistrement précédent en quittant le premier champs (Shit+Tab)
  • Changement de bloc de données indique que l'on saute au bloc suivant lorsque l'on quitte le dernier enregistrement (Tab) ou au bloc précédent lorsque l'on quitte le premier enregistrement (Shit+Tab)

Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, NAVIGATION_STYLE)).

Bloc de données de navigation précédent indique quel bloc précèdera dans la navigation.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…,PREVIOUS_NAVIGATION_BLOCK)).

Bloc de données de navigation suivant indique quel bloc succèdera dans la navigation
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…,NEXT_NAVIGATION_BLOCK)).

Enregistrements

Groupe d'attributs visuel de l'enregistrement permet de désigner le nom d'un attribut visuel qui sera appliqué à l'enregistrement courant (l'enregistrement courrant est celui que l'utilisteur est en train de visualiser/modifier).
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, CURRENT_RECORD_ATTRIBUTE)).
Taille de tableau d'interrogation indique le nombre maximum d'enregistrements ramenés de la base à la fois avant affichage
Une valeur de 1 permet un affichage instantané puisque Forms affichera l'enregistrement dès qu'il sera ramené de la base
Une valeur de 10 indique que Forms ramènera 10 enregistrements de la base avant de procéder à l'affichage
La valeur par défaut de 0 indique que Forms ramènera tous les enregistrements avant de les afficher. Dans la plupart des cas, cette valeur par défaut conviendra.
Dans le cas d'interrogation de tables très volumineuses, il faudra cependant positionner cette valeur à 50 ou 100 afin de ne pas imposer à l'utilisateur un temps de recherche trop long avant affichage.
Noter également que plus cette valeur sera petite, plus l'on génèrera de trafic réseau.
Cette propriété ne peut être fixée qu'au moment du design.
Nombre d'enregistrements en tampon permet d'indiquer combien d'enregistrement seront lus et stockés dans le tampon disque. Cela permet de réduire le nombre d'accès à la base et donc également le trafic réseau en stockant les enregistrements dans un buffer disque.
La valeur par défaut 0 indique le minimum permis soit le nombre d'enregistrements affichés + 3.
Cette propriété ne peut être fixée qu'au moment du design.
Nombre d'enregistrements affichés indique combien d'enregistrement seront affichés sur le canevas
Cette propriété ne peut être fixée qu'au moment du design.
Interroger tous les enregistrements indique si, lors de l'interrogation, Forms ramènera la totalité des enregistrement en un seul fetch (si Oui) ou seulement le nombre spécifié dans la propriété Taille de tableau d'interrogation (si Non).
Cette propriété ne peut être fixée qu'au moment du design.
Sens des enregistrements détermine l'orientation des enregistrements dans le bloc (Horizontal ou Vertical).
Cette propriété n'est valide que pour un bloc multienregistrements (Nombre d'enregistrements affichés > 1) et ne peut être fixée qu'au moment du design.
Enregistrement unique indique si le bloc ne contient qu'un seul enregistrement (si Oui) ou plusieurs (si Non). Cette propriété est différente de Nombre d'enregistrements affichés.
Cette propriété doit être valorisé à Oui pour un bloc de contrôle (non basé) dans lequel se trouve un item de totalisation.
Il n'est pas possible de spécifier Oui pour un bloc basé.
Cette propriété ne peut être fixée qu'au moment du design.

Base de données

Bloc de base de données indique si le bloc est basé (Oui) ou s'il s'agit d'un bloc non basé ou dit « bloc de contrôle » (Non)
Cette propriété ne peut être fixée qu'au moment du design.
Imposer clé primaire indique que chaque enregistrement inséré ou mis à jour doit contenir une clé primaire
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, ENFORCE_PRIMARY_KEY)).
Interrogation autorisée indique si l'utilisateur peut exécuter une interrogation sur ce bloc.
Si la propriété est fixée à Oui, au moins un item du bloc doit avoir la propriété équivalente positionnée à Oui.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, QUERY_ALLOWED)).
Type de source de données indique la source des données et peut prendre l'une des 5 valeurs suivantes :

  • Aucun (bloc non basé)
  • Table (table ou vue)
  • Procédure (procédure stockée en base)
  • Déclencheurs transactionnels (pour des sources de données non-Oracle)
  • Interrogation de clause From (ordre SELECT)

Seul, le type de source de données Table (ou vue) autorise les opérations du DML (INSERT, UPDATE et DELETE) avec un traitement automatique natif dans Forms
Le type Procédure nécessite l'écriture d'une procédure stockée spécifique pour le verrouillage, l'insertion la mise à jour et la suppression d'enregistrements.
Le type interrogation de clause FROM n'autorise que le mode SELECT. Il représente une alternative intéressante à la vue puisqu'il ne nécessite aucun droit de création au niveau de la base.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, QUERY_DATA_SOURCE_TYPE)).
Nom de source de données indique le nom de la source de donnée (valide uniquement pour les blocs basé sur table/vue, sur procédure ou Interrogation de clause FROM
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, QUERY_DATA_SOURCE_NAME)).
Colonnes de source de données désigne la liste des colonnes (champs) associées à la datasource
(valide uniquement pour les blocs basé sur table, Procédure ou clause FROM
Cette propriété ne peut être fixée qu'au moment du design.
Arguments de source de données spécifie les nom, type et valeur des colonnes.
Cette propriété ne peut être fixée qu'au moment du design.
Alias désigne l'alias de la table utilisé pour les requêtes sur des tables contenant des colonnes objets ou des références
Cette propriété ne peut être fixée qu'au moment du design.
Inclure l'élément REF permet de stocker pour chaque enregistrement du bloc l'OID d'une ligne objet
Cette propriété ne peut être fixée qu'au moment du design.
Clause Where permet de spécifier une clause WHERE par défaut appliquée lors de l'interrogation du bloc. Le mot clé : WHERE n'est pas nécessaire dans la clause.
Exemple :
EMPNO > 10 AND DEPTNO <> 40
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, DEFAULT_WHERE)).
Clause Order By permet de spécifier une clause Order by par défaut appliquée lors de l'interrogation du bloc. Le mot clé : ORDER BY n'est pas nécessaire dans la clause.
Exemple :
DEPTNO ASC, DEPTNO DESC
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, ORDER_BY)).
Message d'aide d'optimisation permet d'indiquer un hint d'optimisation qui sera transmis à l'optimiseur
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, OPTIMIZER_HINT)).
Insertion autorisée spécifie si l'insertion dans la table est autorisée ou non
Si cette propriété est fixée à : NON, l'utilisateur ne pourra pas insérer de nouvel enregistrement dans le bloc.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, INSERT_ALLOWED)).
Mise à jour autorisée spécifie si la mise à jour la table est autorisée ou non
Si cette propriété est fixée à : NON, l'utilisateur ne pourra pas modifier d'enregistrement dans le bloc.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, UPDATE_ALLOWED)).
Mode de verrouillage spécifie à quel moment Forms tente de verrouiller l'enregistrement lors d'une modification dans l'interface.
Automatique ou Immédiat force le verrouillage dès que l'enregistrement est modifié dans la forme
Différé n'obtient le verrouillage de l'enregistrement que lors de la phase de Commit.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, LOCKING_MODE)).
Suppression autorisée spécifie si la suppression d'enregistrements est autorisée.
Si cette propriété est fixée à : NON, l'utilisateur ne pourra pas supprimer d'enregistrement dans le bloc.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, DELETE_ALLOWED)).
Mode clé cette propriété ne doit être modifiée que si le bloc n'est pas basé sur une table Oracle, car Forms ne peut pas obtenir de ROWID dans ce cas.
Mettre à jour colonnes modifiées seulement indique à Forms de ne spécifier dans l'ordre UPDATE que les colonnes dont la valeur a été modifiée. Si la Taille du tableau d'interrogation est supérieure à 1, cette propriété est ignorée.
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…, UPDATE_CHANGED_COLUMNS)).
Imposer sécurité sur colonne demande à Forms de placer la propriété du champs à UPDATABLE FALSE si une contrainte de base de donnée interdit à cet utilisateur de modifier cette colonne.
Cette propriété ne peut être fixée qu'au moment du design.
Durée maximum d'interrogation permet d'indiquer un nombre de secondes au delà duquel l'interrogation est stoppée. Cette propriété n'est utilisée que si la propriété Interroger tous les enregistrements est positionnée à Oui.
Cette propriété, selon la documentation peut être fixée à l'exécution (Set_Block_Property(…, MAX_QUERY_TIME)). En réalité il n'en est rien !
A l'exécution, cette instruction génère une erreur de propriété inconnue (erreur de documentation).
Nombre maximum d'enregistrements ramenés permet de spécifier le nombre maximum d'enregistrements ramenés par l'interrogation.
Cette propriété, selon la documentation peut être fixée à l'exécution (Set_Block_Property(…,MAX_RECORDS_FETCHED)). En réalité il n'en est rien !
A l'exécution, cette instruction génère une erreur de propriété inconnue (erreur de documentation).

Image non disponible


Base de données évoluée

Type de cible de données DML indique le type de source de données sur lequel est basé le bloc.
Les valeurs possibles sont:

  • Aucun
  • Table
  • Procédure
  • Déclencheur transactionnel

Cette propriété peut être fixée à l'exécution (Set_Block_Property(…,DML_DATA_TARGET_TYPE)).
Nom de cible de données DML indique le nom de la source de données sur lequel est basé le bloc.(nom de la table, la vue ou la procédure stockée).
Cette propriété peut être fixée à l'exécution (Set_Block_Property(…,DML_DATA_TARGET_NAME)).

Image non disponible


Précalculer récapitultifs indique si Forms doit valoriser les items calculés avant de remplir le bloc.
Cette propriété ne peut être fixée qu'au moment du design
Valeur de retour DML indique si Forms doit utiliser la clause spécifique RETURNING INTO disponible depuis Oracle 9i pour rafraîchir les données du bloc.
Positionner cette propriété à OUI permet de faire l'économie d'un execute_query après la modification des données.

Restrictions:
Une valeur égale à OUI n'est prise en compte que si Forms est connecté à une base 9i ou supérieure.
La clause RETURNING INTO est utilisée pour les insertions et les mises à jour mais pas pour les suppressions.
Cette clause est sans effet lorsque le bloc contient une colonne de type LONG.


Barre de défilement

Ces propriétés permettent d'indiquer si une barre de défilement est associée au bloc de données et quelles sont ses caractéristiques.

IX-B. Les blocs liés par une relation

IX-B-1. Définition

Il est possible de lier plusieurs blocs entre eux à l'aide d'objet Forms appelé : relation.

Cette relation permet de réaliser une « jointure » entre les blocs afin de créer des associations maître/détail, détail/détail ou maître/détail/détail.

IX-B-2. Concept

La relation peut être construite pendant la création du bloc détail, à l'aide de l'assistant création de bloc, mais aussi manuellement une fois que les blocs sont créés.

Une relation Forms utilise les clés primaires et étrangères des tables pour les joindre.

IX-B-3. Mise en œuvre

Nous allons créer une relation maître/détail entre un bloc basé sur la table des départements (DEPT) et la tables des employés (EMP).

Créons un nouveau module Forms dans le navigateur d'objet et double cliquons sur le nœud : Blocs de données

Image non disponible

créons le bloc sur la table DEPT

Image non disponible
Image non disponible
Image non disponible

Puis le bloc détail basé sur la table EMP

Image non disponible

Cliquez le bouton : Créer une relation… pour définir la relation établie entre les deux blocs

Image non disponible

Puisqu'il s'agit de tables relationnelles standard, cliquons l'option : Fondée sur une condition de jointure

Image non disponible

puis indiquons le nom du bloc maître de la relation

Image non disponible

La fenêtre de récapitulation de l'assistant indique alors le nom du bloc maître ainsi que deux listes déroulantes permettant de constituer la condition de jointure.

La liste : Elément détail affiche la liste des items du bloc détail (EMP en l'occurence).
La liste : Elément maître affiche la liste des items du bloc maître (DEPT).

Image non disponible

Nous lions la colonne DEPTNO des deux tables.
Cette relation permettra, lorsque le curseur est sur un enregistrement du bloc maître d'afficher dans le bloc détail uniquement les employés appartenant au département sélectionné.

Puisque nous avons choisi d'afficher les deux blocs dans un style tabulaire, voici l'écran final tel qu'il a été construit par Forms.

Image non disponible

Forms a créé un objet de type relation (DEPT_EMP) dont voici les propriétés

Image non disponible

Comme n'importe quelle propriété, celles-ci sont modifiables.

La condition de jointure ne peut pas excéder 255 caractères et il ne faut pas saisir le caractère : devant les noms de blocs.

La propriété : Comportement d'enregistrement supprimé peut prendre l'une des trois valeurs suivantes :

  • En cascade permet, lors de la suppression de l'enregistrement maître de supprimer en cascade les enregistrements détail au moment du commit
  • Isolée permet de supprimer un enregistrement maître sans affecter les enregistrements détail
  • Non isolée indique qu'il est impossible de supprimer un enregistrement maître si un détail existe

Lors de la création de la relation, Forms a créé 3 triggers automatiquement:

- Un trigger de niveau forme:

  • ON-CLEAR-DETAIL

- Deux triggers au niveau du bloc maître:

  • ON-POPULATE-DETAILS
  • ON-CHECK-DELETE-MASTER

Ainsi que deux unités de programme:

  • PROCEDURE Clear_All_Master_Details()
  • PROCEDURE Query_Master_Details()

Si la relation est de type Isolée, le trigger ON-CHECK-DELETE-MASTER est supprimé de la forme.

Si la relation est de type Cascade, un trigger PRE-DELETE est ajouté au bloc maître.

Remarque:
Dans le cas de relations maître/détail/détail, la suppression automatique en cascade n'est appliqué que sur le premier bloc détail. Pour les sous-blocs détail, il faudra gérer manuellement la suppression en ajoutant les triggers PRE-DELETE.

La propriété Différé en liaison avec la propriété Interrogation automatique indique la façon dont Forms réagira lorsque l'utilisateur changera d'enregistrement maître.

IX-C. Les blocs basés sur une vue complexe

IX-C-1. Définition

Lors de la création d'un bloc, Forms tente de récupérer, pour chaque enregistrement, le ROWID de la ligne concernée. C'est grâce à ce ROWID qu'il génère les ordres d'insertion, de mise à jour et de suppression.

Lorsqu'une vue est basée sur plusieurs tables, Forms ne peut pas identifier chaque ligne par un unique ROWID.

Dans ce cas, il ne peut plus assurer la gestion automatique des instructions du DML.
C'est donc au développeur d'assurer cette gestion.

Une vue dons la requête ne porte que sur une seule table est dite « vue simple » et n'entre pas dans le cadre de ce chapitre.

IX-C-2. Concept

Il existe deux façon de baser un bloc sur une vue :

  • Baser le bloc sur une vue existante en base
  • Baser le bloc sur une vue fictive (SUB-QUERY)

Dans le cas d'une vue fictive, le bloc est dit basé sur une clause From (SUB-QUERY)
Le code de la requête SQL décrivant une telle vue est stocké directement dans la forme.

L'avantage de la vue fictive est qu'elle peut être mise en œuvre même si vous ne possédez pas le droit de créer une vue en base, d'une part, et que la requête SQL qui la compose peut être changée dynamiquement, d'autre part.

Puisque Forms ne sait pas déterminer le ROWID de chaque enregistrement de la vue, il vous incombe de gérer explicitement les ordres d'insertion, modification et suppression dans des déclencheurs de niveau bloc.

L'insertion dans une telle vue est gérée au niveau d'un déclencheur ON-INSERT
La mise à jour au niveau d'un déclencheur ON-UPDATE
La suppression au niveau d'un déclencheur ON-DELETE


Rappel : Les déclencheurs de type ON-xxx remplacent le fonctionnement standard de Forms.

IX-C-3. Mise en œuvre

Dans le script d'installation livré avec l'article, se trouve la création de la vue : EMP_DEPT, dont l'ordre SQL est le suivant :

 
Sélectionnez

CREATE OR REPLACE VIEW EMP_DEPT 
( 
  EMPNO, 
  ENAME, 
  DEPTNO, 
  DNAME 
) AS 
SELECT 
  e.empno, 
  e.ename, 
  d.deptno, 
  d.dname
FROM 
  emp e, 
  dept d
WHERE 
  e.DEPTNO = d.DEPTNO
;

Cette vue est la mise en commun de colonnes de la table EMP et de colonnes de la table DEPT

L'écran de démonstration TEST_BLOC_VUE.FMB livré avec les exemples est basé sur cette vue.

Image non disponible

L'insertion dans les tables EMP et/ou DEPT s'effectue dans le code PL/SQL du déclencheur ON-INSERT

La mise à jour des tables EMP et/ou DEPT s'effectue dans le code PL/SQL du déclencheur ON-UPDATE

Ces deux déclencheurs appellent l'unité de programme : ins_upd_emp_dept()

 
Sélectionnez

-------------------------------------------------
-- Gestion manuelle du DML sur la vue EMP_DEPT --
-------------------------------------------------
PROCEDURE ins_upd_emp_dept IS
  LN$Dummy PLS_INTEGER := 0 ;
BEGIN
  -- Table DEPT --
  Begin
  Select 1
  Into LN$Dummy
  From DUAL
  Where exists( select deptno from dept where deptno = :EMP_DEPT.DEPTNO ) ;
    -- Trouve, MAJ --
      Message('Update de la table DEPT');
      UPDATE DEPT
      SET    DNAME = :EMP_DEPT.DNAME
      WHERE  DEPTNO = :EMP_DEPT.DEPTNO ;
  Exception
      When no_data_found Then
    -- Insertion --
      Message('Insertion dans la table DEPT');
      INSERT INTO DEPT ( DEPTNO, DNAME )
      VALUES ( :EMP_DEPT.DEPTNO, :EMP_DEPT.DNAME ) ;
  End ;
  
  -- Table EMP --
  Begin
  Select 1
  Into LN$Dummy
  From DUAL
  Where exists( select empno from emp where empno = :EMP_DEPT.EMPNO ) ;
    -- Trouve, MAJ --
      Message('Update de la table EMP');
      UPDATE EMP
      SET    ENAME = :EMP_DEPT.ENAME
      WHERE  EMPNO = :EMP_DEPT.EMPNO ;
  Exception
      When no_data_found Then
    -- Insertion --
      Message('Insertion dans la table EMP');
      INSERT INTO EMP ( EMPNO, ENAME )
      VALUES ( :EMP_DEPT.EMPNO, :EMP_DEPT.ENAME ) ;
  End ;
  
END;

Un clic sur le bouton : Requête de la vue… permet d'afficher le code SQL de la vue.

Dans cet exemple, le bloc EMP_DEPT est basé sur une requête de type : clause FROM

Image non disponible

l'ordre select est donc stocké directement dans la propriété : Nom de source de données d'interrogation.

Il est alors très facile de rendre la vue dynamique en modifiant à l'exécution les termes des clauses WHERE et/ou ORDER_BY.

Il est même possible de modifier le nom de la table source du bloc, si la nouvelle table contient les même colonnes que la table précédente.

Ce type de manipulation dynamique est implémenté dans l'écran de démonstration : TEST_DATA_SOURCES.FMB.

Image non disponible

Dans cet exemple, le bloc principal est basé sur la table : JANVIER (dont le script de création et d'alimentation est livré avec l'article).

Deux autres tables FEVRIER et MARS partagent une structure identique.

La liste déroulante : Choisir un mois permet de sélectionner le mois (et donc la table) souhaité.
La manipulation dynamique consiste à changer dynamiquement la propriété du bloc : Nom de source de données avec la fonction native Set_Block_Property().

Code du déclencheur : WHEN-LIST-CHANGED
Sélectionnez

If :CTRL.CHOIX is not null Then
    :global.choix := :ctrl.choix ;
    clear_form ;
    :ctrl.choix := :global.choix ;
    Set_Block_Property('TEST2', QUERY_DATA_SOURCE_NAME, :global.CHOIX );
    go_block('TEST2');
    execute_query;
End if ;


Correspondance des colonnes entre la vue et les items Forms

Lorsque votre bloc est basé sur une vue existante en base, la propriété du bloc : Colonnes de source de données est automatiquement renseignée par Forms.

Par contre, si votre bloc est basé sur une vue fictive, vous devez renseigner cette propriété en indiquant, pour chaque colonne de la vue le nom, le type et la longueur ou la précision.

L'éditeur de présentation

Ajoutez manuellement au bloc les items correspondant en veillant à leur donner les mêmes caractéristiques.

IX-C-4. Techniques avancées

L'utilisation d'un tel type de vue offre, au moins, l'un des deux avantages suivants :

  • Vous utilisez une vue même si vous n'avez pas les droits suffisants pour la créer en base
  • Vous souhaitez trier les enregistrements dans votre bloc sur une colonne qui n'appartient pas à la table principale.

Par exemple, la maîtrise d'œuvre vous demande d'afficher les employés de l'entreprise avec un tri sur le nom du département.

Les données relatives aux employés sont stockées dans la table EMP ainsi que le code du département. Mais le nom du département appartient à la table DEPT !

La vue permettant de combler cette demande bien gênante contient le code suivant :

 
Sélectionnez

CREATE OR REPLACE VIEW EMP_NDEPT 
( 
  EMPNO, 
  ENAME, 
  DEPTNO, 
  DNAME 
) AS 
SELECT 
  e.empno, 
  e.ename, 
  e.deptno, 
  d.dname
FROM 
  emp e, 
  dept d
WHERE 
  e.DEPTNO = d.DEPTNO
ORDER BY
  d.dname,
  e.ename
/

si vous devez utiliser un bloc basé sur une clause FROM, le code inséré dans la propriété : Nom de la source de données de votre bloc sera donc:

 
Sélectionnez

SELECT 
  e.empno, 
  e.ename, 
  e.deptno, 
  d.dname
FROM 
  emp e, dept d
WHERE 
  e.DEPTNO = d.DEPTNO
ORDER BY
  d.dname,
  e.ename
;

Remarques:

Au moins un item du bloc doit avoir la propriété : Clé primaire positionnée à Oui.

Forms ne pouvant pas verrouiller une ligne de ce type, vous devez implémenter au niveau bloc un déclencheur : ON-LOCK muni d'une simple instruction : Null ;

Vous devez gérer les ordres de manipulation (Insert, Update, Delete) vous-même dans les déclencheurs ON-INSERT, ON-UPDATE et ON-DELETE.

Puisque le champ DNAME n'appartient pas à la table EMP, veillez à le rendre non modifiable au niveau du bloc.

IX-D. Les blocs basés sur procédure stockée

IX-D-1. Définition

Un bloc est dit basé sur des procédures stockées lorsque le code PL/SQL de manipulation des enregistrements est déporté dans la base de données.

Les mécanismes standard de Forms ne sont plus utilisés comme lorsque le bloc est basé sur une table ou une vue simple.

Se sont des procédures stockées, généralement regroupées au sein d'un paquetage qui assurent les fonctions de manipulation (SELECT, INSERT, UPDATE, DELETE, LOCK).

Un bloc basé sur procédure stockée peut être alimenté via un REF CURSOR ou un tableau PL/SQL.

Cette technique permet de déporter l'implémentation des règles de gestion, généralement incluse dans la forme, au niveau de la base et donc de dissocier les parties techniques et fonctionnelles.
Elle est mise en œuvre lorsque les règles de gestion sont particulièrement complexes ou simplement lorsque l'on souhaite pouvoir adapter ces règles en dehors de l'écran.

IX-D-2. Concept

Comme son nom l'indique, ce type de bloc est basé sur un ensemble de procédures stockées en base.

Il nécessite cinq procédures distinctes:

  • Sélection des enregistrements
  • Insertion
  • Suppression
  • Mise à jour
  • Verrouillage

La procédure de sélection des enregistrements doit retourner à l'application Forms les enregistrements souhaités, sous la forme d'un REF CURSOR ou d'un tableau PL/SQL.
Cette procédure est donc alimentée par un ordre SELECT avec sa clause WHERE et son éventuelle clause ORDER BY.


La procédure d'insertion reçoit de Forms une variable de type enregistrement (RECORD) ou un tableau PL/SQL d'enregistrements (TABLE INDEX BY).
C'est cette procédure qui implémentera l'insertion des enregistrements en base.

La procédure de suppression et de mise à jour fonctionne sur le même principe que pour l'insertion.

La procédure de verrouillage permet de verrouiller les enregistrements dans le cas d'une mise à jour.

Lorsque la procédure stockée retourne un REF CURSOR, il est possible d'interroger le bloc par « paquets ».

Lorsqu'elle retourne un tableau PL/SQL, l'ensemble des lignes est retourné à Forms.
Il est évident qu'une procédure qui transmet un tableau PL/SQL est inadaptée aux requêtes qui retournent un très grand nombre de lignes.

IX-D-3. Mise en œuvre

Avant de baser un bloc sur des procédures stockées, celle-ci doivent bien évidement exister en base.

La première étape consiste à créer le paquetage et ses cinq procédures.

Le paquetage EMP_PKG livré avec le script d'installation met en œuvre ce mécanisme.

Il contient deux jeux de procédures :

- Un jeu permettant de mettre en œuvre un bloc basé sur un REF CURSOR
- Un jeu permettant de mettre en œuvre un bloc basé sur un tableau PL/SQL

Voici les types nécessaires à l'exécution de ces procédures:

Nous définissons un enregistrement constitué de colonnes de la table EMP

 
Sélectionnez

  -- Record de type EMP --
  TYPE emp_rec IS RECORD(
    Empno   emp.empno%TYPE,
    Ename   emp.ename%TYPE,
    Job     emp.job%TYPE,
    Sal     emp.sal%TYPE,
    Comm    emp.comm%TYPE);

Nous définissons une variable de type REF CURSOR typée sur l'enregistrement préalablement créé.

 
Sélectionnez

  -- Si le bloc est basé sur un REF CURSOR --
  TYPE emp_cursor IS REF CURSOR RETURN emp_rec;

Nous définissons un tableau PL/SQL qui sera transmis aux procédures par Forms.

 
Sélectionnez

  -- Tableau d'enregistrements pour les procédures --
  TYPE emptab IS TABLE OF emp_rec INDEX BY BINARY_INTEGER;

Les procédure d'insertion, mise à jour, suppression et verrouillage doivent recevoir un argument de type tableau PL/SQL (TABLE INDEX BY).


Procédure retournant un REF CURSOR

Sélection des enregistrements

Elle utilise la procédure EMP_PKG.EMP_REFCUR()

 
Sélectionnez

  ----------------------------
  --  Select avec REFCURSOR --
  ----------------------------
  PROCEDURE emp_refcur
   (
      emp_data IN OUT emp_cursor
     ,p_num IN VARCHAR2
     ,p_nom IN VARCHAR2
     ,p_job IN VARCHAR2
     ,p_sal IN VARCHAR2
     ,p_com IN VARCHAR2
   ) IS
  BEGIN
    -- sauvegarde clause where --
    GC$Num := p_num ;
    GC$Nom := p_nom ;
    GC$Job := p_job ;
    GC$Sal := p_sal ;
    GC$Com := p_com ;

    OPEN  emp_data FOR SELECT empno, ename, job, sal, comm
    FROM  emp
    WHERE EMPNO LIKE Nvl( p_num || '%', EMPNO )
    AND   ENAME LIKE Nvl( p_nom || '%', ENAME )
    AND   JOB LIKE Nvl( p_job || '%', JOB )
    AND   ( SAL LIKE Nvl( p_sal || '%', SAL ) OR p_sal is null )
    AND   ( COMM LIKE Nvl( p_com || '%', COMM ) OR p_com is null )
    ORDER BY ENAME ;
  END emp_refcur;

Cette procédure accepte une variable IN OUT du type du REF CURSOR déclaré dans les spécifications du paquetage et 5 autres variables (p_num … p_com) pour gérer le mode ENTER-QUERY.

Cette procédure ouvre et retourne le curseur alimenté par l'ordre SELECT.

Insertion

Elle utilise la procédure EMP_PKG.EMP_INSERT2()

 
Sélectionnez

  --------------------------------------
  -- Insertion trigger transactionnel --
  --------------------------------------
  PROCEDURE emp_insert2(t IN emptab) IS
  BEGIN
    FOR i IN t.first..t.last LOOP
    INSERT INTO emp (empno, ename, job, sal, comm)
    VALUES(t(i).empno, t(i).ename, t(i).job, t(i).sal, t(i).comm);
    END LOOP ;
  END emp_insert2;

Cette procédure reçoit une variable de type tableau d'enregistrements.


Mise à jour

Elle utilise la procédure EMP_PKG.EMP_UPDATE2()

 
Sélectionnez

  ----------------------------------------
  -- Mise à jour trigger transactionnel --
  ----------------------------------------
  PROCEDURE emp_update2(t IN emptab) IS
  BEGIN
    FOR i IN t.first..t.last LOOP
        UPDATE emp
        SET  ename  = t(i).ename,
             job    = t(i).job,
             sal    = t(i).sal,
             comm   = t(i).comm
        WHERE empno = t(i).empno;
    END LOOP ;
  END emp_update2 ;

Cette procédure reçoit une variable de type tableau d'enregistrements.


Suppression

Elle utilise la procédure EMP_PKG.EMP_DELETE2()

 
Sélectionnez

  -----------------------------------
  -- Delete trigger transactionnel --
  -----------------------------------
  PROCEDURE emp_delete2( t IN emptab ) IS
  BEGIN
    FOR i IN t.first..t.last LOOP
        DELETE FROM emp
        WHERE  empno = t(i).empno;
    END LOOP ;
  END emp_delete2;

Cette procédure reçoit une variable de type tableau d'enregistrements.


Verrouillage

Elle utilise la procédure EMP_PKG.EMP_LOCK2()

 
Sélectionnez

  ---------------------------------
  -- Lock trigger transactionnel --
  ---------------------------------
  PROCEDURE emp_lock2( t IN emptab ) IS
    v_rownum NUMBER;
    verrou exception;
    pragma exception_init ( verrou, -54 ) ;
  BEGIN
    FOR i IN t.first..t.last LOOP
        SELECT empno
        INTO   v_rownum
        FROM   emp
        WHERE  empno = t(i).empno
        FOR UPDATE OF ename NOWAIT;
    END LOOP ;
  EXCEPTION
    When verrou then
        Raise_Application_Error( -20100, '^Verrouillage de l''enregistrement impossible^' ) ;
  END emp_lock2;

Cette procédure reçoit une variable de type tableau d'enregistrements.

Ces procédures acceptent en argument un tableau PL/SQL, permettant à Forms d'envoyer en une seule fois tous les enregistrements nécessitant un traitement.

Nous pouvons maintenant construire dans Forms un bloc basé sur ces procédures

Créons un nouveau module ainsi qu'un nouveau bloc avec l'assistant

Image non disponible

Cliquons le bouton Procédure stockée.

Dans la zone Procédure, nous indiquons le nom de la procédure de sélection (EMP_PKG.EMP_REF_CUR) puis cliquons le bouton Régénérer.

La liste des colonnes du REF CURSOR s'affiche dans la case Colonnes disponibles afin de sélectionner celles qui apparaîtrons dans le bloc.

Nous les sélectionnons toutes:

Image non disponible

L'assistant demande ensuite d'entrer le nom de la procédure d'insertion.
Saisissons EMP_PKG.EMP_INSERT2 et cliquons le bouton Régénérer.

Répétons cette opération pour les procédures de mise à jour (update2), suppression (delete2) et verrouillage (lock2).

Choisissez à la fin un bloc de type tabulaire avec affichage de 5 enregistrements.

Lorsque le bloc est créé, vous pouvez afficher de nouveau l'assistant bloc de données via le menu : Outils -> Assistant bloc de données.

Cette fois, il est possible de visualiser (modifier) chaque procédure via son onglet spécifique.

Image non disponible


Que s'est-il passé ?

Ouvrons la fenêtre de propriétés du bloc

Image non disponible

La propriété : Type de source de données a été valorisée avec : Procédure
La propriété : Nom de source de données a été valorisée avec le nom de la procédure stockée qui retourne le curseur.

Le bouton : Suite… de la propriété : Colonnes de source de données permet d'afficher les colonnes ramenées depuis le curseur.

Image non disponible

Le bouton : Suite… de la propriété : Arguments de source de données permet d'afficher les arguments transmis au curseur.

Image non disponible

l'argument EMP_DATA est de type REFCURSOR est doit être en mode IN OUT.
Les autres arguments, nécessaire à la recherche en mode interrogation sont de mode IN.

La case : Valeur permet de transmettre une valeur au paramètre.
Cette valeur peut être un littéral ou une référence à un item (:BL_EMP_REFCUR.EMPNO).
C'est cette valeur qui sera transmise au curseur, notamment en mode interrogation.

Voyons maintenant les propriétés : Base de données évoluée

Image non disponible

Les divers noms de procédures ont été valorisé, ainsi que les propriétés Colonnes et Arguments.

On voit que les arguments sont de type TABLE d'enregistrements (EMP_PKG.EMPTAB).

Vous pouvez dès lors exécuter la forme et lancer l'interrogation sur le bloc, puis ajouter, modifier, supprimer les enregistrements.



Procédure retournant un Tableau PL/SQL

La construction d'un bloc basé sur une procédure ramenant un tableau PL/SQL est identique à celle précédemment utilisée.

Dans l'assistant bloc de données, dans le nom de la procédure d'interrogation, entrez EMP_PKG.EMP_QUERY

 
Sélectionnez

  -------------------------
  -- Select avec tableau --
  -------------------------
  PROCEDURE emp_query(emp_data IN OUT emptab) IS
    ii NUMBER;
    CURSOR empselect IS
      SELECT empno, ename, job, sal, comm FROM emp
      ORDER BY ename ;
  BEGIN
    OPEN empselect;
    ii := 1;
    LOOP
      FETCH empselect INTO
        emp_data( ii ).empno,
        emp_data( ii ).ename,
        emp_data( ii ).job,
        emp_data( ii ).sal,
        emp_data( ii ).comm;
      EXIT WHEN empselect%NOTFOUND;
      ii := ii + 1;
    END LOOP;
  END emp_query;

Les procédures d'insertion, mise à jour, suppression et verrouillage sont identiques.

IX-D-4. Techniques avancées

L'écran de démonstration TEST_BLOC_PROC.FMB livré en exemple met en œuvre deux blocs basés sur procédures stockées (EMP_PKG).

Ces procédures sont extrêmement simplifiées, mais elles pourraient mettre en œuvre des règles de gestion beaucoup plus élaborées.

Dans cet écran, le bloc : EMP n'utilise pas les déclencheurs relationnels. A la place, l'appel des procédures stockées est effectué explicitement dans les déclencheurs de type ON-xxx.

Cet exemple n'a pour seul but que de démontrer l'appel d'une procédure stockée dans un déclencheur de type ON-xxx, avec passage de l'enregistrement courant à la procédure.

Cette technique peut également être très élaborée, puisqu'elle permet de pousser le raisonnement du traitement centralisé en base à l'extrême.
En effet, il devient possible d'externaliser les règles relatives au contrôle d'un enregistrement avant son insertion ou sa mise à jour par l'appel d'une procédure sur un déclencheur WHEN-VALIDATE-RECORD par exemple.

Procédure de verrouillage

Si la tentative de verrouillage d'un enregistrement (EMP_PKG_EMP_LOCK2) échoue, Forms remonte un message laconique de type :

FRM-40735 : le déclencheur LOCK-PROCEDURE a détecté un exception ORA-00054 non traitée

Afin de ne pas laisser l'utilisateur dans un état de totale incompréhension, il convient alors de capturer correctement cette erreur dans un déclencheur ON-ERROR

Déclencheur ON-ERROR
Sélectionnez

Declare
    LC$Msg Varchar2(2000) ;
Begin

If DBMS_ERROR_CODE = -20100 Then
    -- Récupération du message inséré dans Raise_Application_Error --
      LC$Msg := substr(DBMS_ERROR_TEXT, 
            instr(DBMS_ERROR_TEXT,'^')+ 1 ,
            ( instr(DBMS_ERROR_TEXT,'^',-1) - instr(DBMS_ERROR_TEXT,'^') - 1) ) ;

    message(LC$Msg, acknowledge ) ;
Else
    message('erreur : ' || DBMS_ERROR_TEXT, acknowledge ) ;
End if ;

End ;

L'erreur devant être également capturée au niveau de la procédure stockée

 
Sélectionnez

  ---------------------------------
  -- Lock trigger transactionnel --
  ---------------------------------
  PROCEDURE emp_lock2( t IN emptab ) IS
    v_rownum NUMBER;
    verrou exception;
    pragma exception_init ( verrou, -54 ) ;
  BEGIN
    FOR i IN t.first..t.last LOOP
      SELECT empno
      INTO   v_rownum
      FROM   emp
      WHERE  empno = t(i).empno
      FOR UPDATE OF ename NOWAIT;
    END LOOP ;
  EXCEPTION
    When verrou then
      Raise_Application_Error( -20100, '^Verrouillage de l''enregistrement impossible^' ) ;
  END emp_lock2;

Afin de pouvoir récupérer facilement le texte du message transmis à Raise_Application_Error(), nous encadrons notre message avec des accents circonflexes.

Gestion générale des erreurs

Lorsqu'un bloc est basé sur procédures stockées, Forms génère automatiquement les déclencheurs suivants :

  • QUERY_PROCEDURE
  • INSERT_PROCEDURE
  • UPDATE_PROCEDURE
  • DELETE_PROCEDURE
  • LOCK_PROCEDURE

Il est inutile d'essayer d'y ajouter du code et notamment un bloc exception puisque ces procédures sont automatiquement régénérées à chaque compilation du module.

Il convient donc, dans chacune de vos procédures stockées de capturer les erreurs et de les remonter vers Forms via la fonction RAISE_APPLICATION_ERROR().
Ces erreurs devant être capturées dans un déclencheur : ON-ERROR.

Attribuez un numéro particulier pour chaque type d'erreur :

  • Erreurs insertion : à partir de -20200
  • Erreurs mise à jour : à partir de -20300
  • Erreurs suppression : à partir de -20400

Et gérez-les dans le déclencheur Forms : ON-ERROR.

IX-E. Les blocs basés sur une collection

IX-E-1. Définition

Une table Oracle peut contenir une ou plusieurs colonnes de type collection (NESTED TABLE, VARRAY)

Une colonne de ce type peut être intégrée dans une table relationnelle comme dans une table objet.

Forms 9i ne gère pas les collections en natif.

Toutefois, il est quand même possible de travailler avec ces objets dans une application Forms.

IX-E-2. Concept

Puisque Forms ne sait pas gérer nativement les collections contenues dans les tables, il faut assurer cette gestion explicitement dans la forme.

Pour cela nous utiliserons deux blocs.

Un premier bloc sera directement basé sur la table.
Il affichera les colonnes « standard » de la table

Un second sera basé sur une Clause FROM et permettra l'affichage des lignes contenues dans la collection attachée.
Il faudra gérer les insertions, mises à jour et suppressions dans la collection via les déclencheurs de niveau bloc :

  • ON-INSERT
  • ON-UPDATE
  • ON-DELETE

Enfin, comme il n'est pas possible d'ajouter des lignes à une collection NULL, nous devrons gérer l'insertion de la table principale dans un déclencheur ON-INSERT du premier bloc basé afin que l'insertion d'un nouvel enregistrement soit accompagné de la création d'une collection non NULL.

IX-E-3. Mise en œuvre

Dans le script d'installation livré avec l'article, se trouve la création d'une table : ARTICLES, contenant une collection d'objets. Cette collection défini, pour chaque article les emplacements de stockage ainsi que la quantité en stock pour chaque emplacement.

 
Sélectionnez

-- Type d'un emplacement --
CREATE TYPE TYP_CASE AS OBJECT
 (
   EMP   VARCHAR2(10),
   QTE    NUMBER
 )
/
 
-- Table d'emplacements --
CREATE TYPE TAB_TYP_CASE AS TABLE OF TYP_CASE
/

-- Table des articles --
CREATE TABLE ARTICLES
 (
   CODE    VARCHAR2(20),
   LIBELLE VARCHAR2(100),
   PRIX    NUMBER(8,2),
   QTETOT  NUMBER(8),
   CASES   TAB_TYP_CASE
 )
 NESTED TABLE CASES STORE AS CASES_NT
 STORAGE
 (
   INITIAL 5K
   NEXT 5K
   PCTINCREASE 0
   MINEXTENTS 1
   MAXEXTENTS 500
 )
/

Chaque article peut donc pointer sur plusieurs emplacements indiquant la quantité stockée dans l'emplacement.

La forme d'exemple TEST_COLLECTION.FMB livrée avec l'article permet de gérer la table ARTICLES.

TEST_COLLECTION.FMB

Le premier bloc (ARTICLES) est naturellement basé sur la table du même nom.

Le second bloc (CASES) doit être constitué manuellement.

Deux items (de même type que les attributs de la collection) sont ajoutés à ce bloc:

  • EMP de type Varchar2(20)
  • QTE de type NUMBER

La propriété du bloc : Type de source de données d'interrogation est positionnée à : Interrogation de clause FROM

La propriété du bloc : Nom de source de données d'interrogation est positionnée avec la requête temporaire : Select 1,2 from dual

Cette requête sera adaptée dynamiquement à chaque fois que l'enregistrement du bloc principal changera.


Particularités des blocs

- Le bloc principal (ARTICLES)

Pour ne pas insérer dans la table ARTICLES une collection NULL, nous devons programmer l'insertion explicitement dans un déclencheur de niveau bloc : ON-INSERT

Déclencheur On-Insert
Sélectionnez

------------------------------------------------
--  l' insertion est effectuée explicitement  --
--  car le nouvel enregistrement ne peut pas  --
--  contenir une collection NULL              --
------------------------------------------------
INSERT INTO ARTICLES
  (
    CODE,
    LIBELLE,
    PRIX,
    QTETOT,
    CASES
  )
  VALUES
  (
    :ARTICLES.CODE,
    :ARTICLES.LIBELLE,
    :ARTICLES.PRIX,
    :ARTICLES.QTETOT,
    TAB_TYP_CASE() -- insertion collection vide
  )
  ;

Notez que nous insérons une collection vide, qui est différent de l'insertion d'une collection NULL.
En effet, il n'est pas possible d'ajouter des éléments à une collection NULL.

Pour afficher les lignes de la collection du second bloc, nous devons adapter dynamiquement la requête, en codant un déclencheur : WHEN-NEW-RECORD-INSTANCE

Déclencheur When-New-Record-Instance
Sélectionnez

Declare
   LC$Req  Varchar2(256) ;
Begin
   If :ARTICLES.CODE Is not null Then
      -- requête dynamique du bloc secondaire --
      LC$Req := '(SELECT cases.EMP, cases.QTE FROM TABLE ( SELECT cases FROM articles WHERE code = ''' 
        || :ARTICLES.CODE || ''') cases)' ;
      Go_Block('CASES' );
      Clear_Block ;
      Set_Block_Property( 'CASES', QUERY_DATA_SOURCE_NAME, LC$Req ) ;
      Execute_Query ;
      Go_Block('ARTICLES') ;
   Else
      Go_Block('CASES' );
      Clear_Block ;
      Go_Block('ARTICLES') ;
   End if ;
End ;


- Le bloc secondaire (CASES)

Gérons manuellement les ordres du DML en ajoutant les déclencheurs nécessaires :

ON-INSERT

Déclencheur : ON-INSERT
Sélectionnez

-- Insertion d'une ligne dans la collection --
INSERT INTO TABLE
 (  
   SELECT
      cases
   FROM
      articles
   WHERE
      code = :ARTICLES.CODE
 )
 Values
 (
   TYP_CASE( :CASES.EMP, :CASES.QTE )
 );


ON-UPDATE

Déclencheur : ON-UPDATE
Sélectionnez

-- Mise à jour de la ligne de la collection --
UPDATE TABLE
 (  
   SELECT
      cases
   FROM
      articles
   WHERE
      code = :ARTICLES.CODE
 ) cases
 SET
   VALUE(cases) = TYP_CASE( :CASES.EMP, :CASES.QTE ) 
 WHERE
   cases.emp = :CASES.EMP
 ;


ON-DELETE

Déclencheur : ON-DELETE
Sélectionnez

-- Suppression de la ligne de la collection --
DELETE FROM TABLE
 (  
   SELECT
      cases
   FROM
      articles
   WHERE
      code = :ARTICLES.CODE
 ) cases
 WHERE
   cases.emp = :CASES.EMP
 ;


ON-LOCK

Déclencheur : ON-LOCK
Sélectionnez

   Null ;

IX-E-4. Techniques avancées

Suivant le même principe, il est possible de gérer des collections multi niveaux (collections de collections) en créant autant de blocs basés sur une clause FROM que nécessaire.

IX-F. Les blocs basés sur une table objet

Cette section traite particulièrement de blocs basés sur une table objet contenant une collection de références.

IX-F-1. Définition

Une table Oracle peut contenir une ou plusieurs colonnes de type collection (NESTED TABLE, VARRAY)

Une colonne de ce type peut être intégrée dans une table relationnelle comme dans une table objet.

Forms 9i/10g ne gère pas les collections en natif.

Toutefois, il est quand même possible de travailler avec ces objets dans une application Forms.

IX-F-2. Concept

Puisque Forms ne sait pas gérer nativement les collections contenues dans les tables, il faut assurer cette gestion explicitement dans la forme.

Pour cela nous utiliserons deux blocs.

Un premier bloc sera directement basé sur la table objet.
Il affichera les colonnes « standard » de la table

Un second sera basé sur une Clause FROM et permettra l'affichage des lignes pointées par les références contenues dans la collection attachée.
Il faudra gérer les insertions, mises à jour et suppressions dans la collection via les déclencheurs de niveau bloc:

  • ON-INSERT
  • ON-UPDATE
  • ON-DELETE

Enfin, comme il n'est pas possible d'ajouter des lignes à une collection NULL, nous devrons gérer l'insertion de la table principale dans un déclencheur ON-INSERT du premier bloc basé afin que l'insertion d'un nouvel enregistrement soit accompagné de la création d'une collection vide et non pas NULL.

IX-F-3. Mise en œuvre

Dans le script d'installation livré avec l'article, se trouve la création d'une table : ARTICLES_OBJ, contenant une collection de références. Cette collection définit, pour chaque article les emplacements de stockage ainsi que la quantité en stock pour chaque emplacement sous la forme de références (OID) pointant sur la table objet : EMP_OBJ.

Script de création des objets
Sélectionnez

-- Type Emplacement --
CREATE OR REPLACE TYPE TYP_EMP AS OBJECT
 (
   EMP   VARCHAR2(10),
   ART   VARCHAR2(20),
   QTE   NUMBER
 )
/

-- Type référence emplacement --
CREATE OR REPLACE TYPE REF_TYP_EMP AS OBJECT
(
  ref_emp REF TYP_EMP
)
/  

-- Table de références emplacements --
CREATE OR REPLACE TYPE TAB_REF_TYP_EMP AS TABLE OF REF_TYP_EMP
/

-- Type objet Articles --
CREATE OR REPLACE TYPE TYP_ARTICLES AS OBJECT
(
  CODE     VARCHAR2 (20), 
  LIBELLE  VARCHAR2 (100), 
  PRIX     NUMBER (8,2), 
  QTETOT   NUMBER (8), 
  REMP     TAB_REF_TYP_EMP
)
/

-- Table objet ARTICLES --
 CREATE TABLE ARTICLES_OBJ OF TYP_ARTICLES 
 NESTED TABLE REMP STORE AS REMP_NT
/

-- Table objet EMPLACEMENTS --
 CREATE TABLE EMP_OBJ OF TYP_EMP 
/

Chaque article peut donc pointer sur plusieurs emplacements indiquant la quantité stockée dans l'emplacement.

La forme d'exemple TEST_OBJETS.FMB livrée avec l'article permet de gérer la table ARTICLES_OBJ ainsi que sa collection de références.

TEST_OBJETS.FMB

Le premier bloc (ARTICLES) est naturellement basé sur la table ARTICLES_OBJ.
Le second bloc (CASES) doit être constitué manuellement.

Deux items (de même type que les attributs de la table objet EMP_OBJ) sont ajoutés à ce bloc:

  • EMP de type Varchar2(20)
  • QTE de type NUMBER

La propriété du bloc : Type de source de données d'interrogation est positionnée à : Interrogation de clause FROM
La propriété du bloc : Nom de source de données d'interrogation est positionnée avec la requête temporaire : Select 1,2 from dual

Cette requête sera adaptée dynamiquement à chaque fois que l'enregistrement du bloc principal changera.


Particularités des blocs

- Le bloc principal (ARTICLES)

Pour ne pas insérer dans la table ARTICLES_OBJ une collection NULL, nous devons programmer l'insertion explicitement dans un déclencheur de niveau bloc : ON-INSERT

Déclencheur : ON-INSERT
Sélectionnez

------------------------------------------------
--  l' insertion est effectuée explicitement  --
--  car le nouvel enregistrement ne peut pas  --
--  contenir une collection NULL              --
------------------------------------------------
INSERT INTO ARTICLES_OBJ 
VALUES
(
  TYP_ARTICLES
   ( 
      :ARTICLES.CODE, 
      :ARTICLES.LIBELLE, 
      :ARTICLES.PRIX, 
      :ARTICLES.QTETOT, 
      TAB_REF_TYP_EMP() - collection vide
   )
) ;

Notez que nous insérons une collection vide, qui est différent de l'insertion d'une collection NULL.
En effet, il n'est pas possible d'ajouter des éléments à une collection NULL.

Pour afficher les lignes de la collection du second bloc, nous devons adapter dynamiquement la requête, en codant un déclencheur : WHEN-NEW-RECORD-INSTANCE

Déclencheur : WHEN-NEW-RECORD-INSTANCE
Sélectionnez

Declare
   LC$Req  Varchar2(256) ;
Begin
   If :ARTICLES.CODE Is not null Then
      ------------------------------------------
      -- requête dynamique du bloc secondaire --
      ------------------------------------------      
      LC$Req := '(SELECT emp.ref_emp.emp EMP, emp.ref_emp.qte QTE FROM ' ;
      LC$Req := LC$Req || 'TABLE( SELECT REMP FROM articles_obj WHERE CODE = ''' ;
      LC$Req := LC$Req || :ARTICLES.CODE || ''') emp WHERE emp.ref_emp.art = ''' || :ARTICLES.CODE || ''')' ;
        
      Go_Block('CASES' );
      Clear_Block ;
      Set_Block_Property( 'CASES', QUERY_DATA_SOURCE_NAME, LC$Req ) ;
      Execute_Query ;
      Go_Block('ARTICLES') ;
   Else
      Go_Block('CASES' );
      Clear_Block ;
      Go_Block('ARTICLES') ; 
   End if ;
End ;

Les informations de la table EMP_OBJ sont ramenées par l'intermédiaire des références stockées dans la collection REF_EMP.


- Le bloc secondaire (CASES)

Gérons manuellement les ordres du DML en ajoutant les déclencheurs nécessaires:

Déclencheur : ON-INSERT
Sélectionnez

-- Insertion d'une ligne (REF) dans la collection --
Declare
    LC$Req Varchar2(256) ;
Begin
    LC$Req := 'INSERT INTO TABLE
    ( SELECT remp FROM ARTICLES_OBJ WHERE code = ''' || :ARTICLES.CODE  || ''')
    VALUES
    ( REF_TYP_EMP ( (SELECT REF(a) FROM EMP_OBJ a WHERE a.ART = ''' 
      || :ARTICLES.CODE || ''' AND a.EMP = ''' || :CASES.EMP || ''') ) )' ;

    Forms_Ddl( LC$Req ) ;
End ;

Pour ajouter une référence à la collection (bloc CASES), une LOV est disponible sur le champ EMP. Celle-ci affiche les enregistrements de la table EMP_OBJ dont le code article est égal à celui du bloc maître.

Déclencheur : ON-DELETE
Sélectionnez

-- Suppression de la ligne (REF) de la collection --
DELETE FROM TABLE
( SELECT remp FROM ARTICLES_OBJ WHERE code = :ARTICLES.CODE ) emp
WHERE 
   emp.ref_emp.art = :ARTICLES.CODE
And
   emp.ref_emp.emp = :CASES.EMP 
;
Déclencheur : ON-LOCK
Sélectionnez

   Null ;

Lors de l'enregistrement, la quantité totale d'articles est calculée à partir des références stockées dans la collection, puis la table ARTICLES_OBJ est mise à jour.

Déclencheur : KEY-COMMIT
Sélectionnez

Declare
   LN$Cpt pls_integer ;
   LN$Cur pls_integer ;
Begin
   -- Enregistrement --
   Commit_form ;

   -- Mise à jour de la quantité totale
   -- pour l'article en cours
   UPDATE ARTICLES_OBJ
   SET   QTETOT = (
   SELECT 
      SUM(emp.ref_emp.qte)
   FROM 
      TABLE( SELECT REMP FROM articles_obj WHERE CODE = :ARTICLES.CODE ) emp
   WHERE 
      emp.ref_emp.art = :ARTICLES.CODE
   )
   WHERE CODE = :ARTICLES.CODE    
   ;

   Forms_Ddl( 'commit' ) ;

   -- Raffraichissement de l'écran --
   Go_block('ARTICLES');
   LN$Cur := :SYSTEM.CURSOR_RECORD ;
   Execute_query ;
   Go_Record( LN$Cur ) ;

End ;

IX-F-4. Techniques avancées

Suivant le même principe, il est possible de gérer des collections multi niveaux (collections de collections) en créant autant de blocs basés sur une clause FROM que nécessaire.

Les limites de Forms 9i/10g

Nous voyons ici les limites de Forms 9i/10g concernant la manipulation des objets.
S'il est possible de baser un bloc sur une table objet et d'en gérer les références, l'implémentation du PL/SQL est encore limitée.
Comme on le voit sur le code du trigger ON-INSERT, il n'est pas possible de gérer une sous-requête ramenant une référence ou une variable de type REF au niveau client, d'où l'utilisation de l'instruction Forms_Ddl().

Les instructions suivantes:

 
Sélectionnez

INSERT INTO ARTICLES_OBJ VALUES
(
  TYP_ARTICLES( 'ART02', 'Article 2', 12, 10, 
  TAB_REF_TYP_EMP
  ( 
     REF_TYP_EMP ( (SELECT REF(a) FROM EMP_OBJ a WHERE a.ART = ... AND a.EMP = ...) )
  )
  )  
) ;

ou

 
Sélectionnez

Declare
  LR$Ref  REF EMP_OBJ ;
Begin
  Select
     Ref(a)
  Into
     LR$Ref
  From
     EMP_OBJ
  ...

Ne sont pas acceptées par le moteur PL/SQL de Forms


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2005 SheikYerbouti. 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.