|
Tutorial ORACLE - John COLIBRI.
|
- résumé : réalisation d'applications Base de Données Client Oracle, avec
connection au Serveur, création de Tables, ajout / lecture / modification de
données
- mots clé : Client Oracle - CREATE TABLE, SELECT, INSERT, UPDATE,
dbExpress, DataSnap, modèle nomade, relation maître détail
- logiciel utilisé : Windows XP personnel, Delphi 6.0, Oracle 10g
- matériel utilisé : Pentium 1.400Mhz, 256 M de mémoire, 140 Giga disque dur
- champ d'application : Delphi 1 à 2005 sur Windows, Oracle
- niveau : développeur Delphi / Oracle
- plan :
1 - Introduction
Nous allons présenter ici comment gérer une application Client Oracle en
utilisant les composants Delphi. Après la lecture de cet article, vous saurez:
- créer une Table et ses indexes
- lire, écrire et modifier les données de cette table
- travailler en mode nomade (non connecté au Serveur)
- gérer plusieurs tables liées
2 - Vérification de l'Accès au Serveur
Dans les articles précédents, nous avons présenté:
Nous utiliserons les paramètres par défaut Oracle classiques:
- nom d'utilisateur: SCOTT
- mot de passe: TIGER
Si votre administrateur vous a fourni d'autres noms d'utilisateur / mot de
passe, vous remplacerez SCOTT/TIGER par vos valeurs.
Si vous utilisez une base déjà installée, voici comment vérifier que vous
arrivez bien à communiquer avec votre Serveur Oracle:
- si vous travaillez directement sur le PC sur lequel est installé le
Serveur:
|
chargez l'outil graphique SqlPlus (démarrer | Programmes |
OraDb10g_home1 | Application Development | Sql Plus)
|
|
Sql-Plus nous demande qui nous sommes
|
|
nous tapons SCOTT, TIGER et ORCL puis cliquons "OK"
|
|
Sql-Plus nous accueille:
|
|
nous affichons la table EMP (ou n'importe quelle autres requête
correspondant à une table)
SELECT * FROM emp; Entrée
|
|
Oracle répond

|
Notez que "Host String" correspond au paramètre qui se trouve dans
"OracleHome"\NetWork\ADMIN\tnsnames.ora:
# tnsnames.ora Network Configuration File:
C:\oracle\product\10.1.0\Db_1\network\admin\tnsnames.ora
# Generated by Oracle configuration tools.
ORCL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = pol-1400)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = orcl)
)
)
|
- si vous souhaitez placer votre application sur le PC contenant le Client
Oracle (ce qui est plus classique, mais oblige à utiliser 2 PC), depuis le
PC contenant le Client:
|
sélectionnez "démarrer | programmes | OraClient10g_home1 | application
Developpments | SQL Plus"
|
|
Sql-Plus nous demande qui nous sommes:
|
|
nous tapons SCOTT, TIGER et MY_NAME_RESOLUTION puis cliquons "OK"
|
|
Sql-Plus nous accueille:
|
|
nous affichons la table EMP (ou n'importe quelle autres requête
correspondant à une table)
SELECT * FROM emp; Entrée
|
|
le Serveur renvoie le contenu de cette table
|
Notez que "Host String" correspond au paramètre qui se trouve dans
"OracleHome"/NetWork/ADMIN/tnsnames.ora:
# tnsnames.ora Network Configuration File:
C:\oracle\product\10.1.0\Client_1\NETWORK\ADMIN\tnsnames.ora
# Generated by Oracle configuration tools.
MY_NAME_RESOLUTION =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = pol-1400)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME= ORCL)
)
)
|
3 - Architecture de l'Application Client
Le fonctionnement Client / Serveur traditionnel est organisé avec un Serveur
qui répond à plusieurs Clients, chacun situé sur des PC différents:
Prenons le cas d'un Client particulier:
- lorsque ce Client souhaite communiquer avec le Serveur, il envoie une
requête via le réseau:
- et le Serveur répond, en un ou plusieurs paquets:
Du côté du Client, nous avons les couches logicielles suivantes:
- au niveau le plus bas, le PC est équipé des couches réseau. La pile TCP/IP
par exemple, qui est installée automatiquement par Windows:
- cette couche est attaquée par le Client Oracle (appelée jadis SqlNet) qui a
été installée lors de l'installation Client Oracle:
- le Client Oracle est attaqué par les composants d'accès Delphi, qui
communiqueront avec l'application utilisateur en utilisant les contrôles
Delphi:
- et finalement l'application utilisateur (comptabilité, facturation, portail
web pour un catalogue client etc):
Au niveau Delphi, les étapes d'une communication sont les suivantes:
- le Client Delphi établit la connection
- puis, pour chaque requête:
- le Client envoie sa requête
- le Client récupère la réponse
- le Client ferme la connection
Ces trois étapes peuvent être réalisées par différents types de composants
Delphi:
- le BDE (Borland Database Engine)
- les composants dbExpress
- des composants Ado (la mécanique Microsoft)
- des composants écrits spécialement pour gérer en Delphi des bases de
données Oracle
Nous utiliserons ici les composants dbExpress fournis avec Delphi. Ces
composants comportent:
- tSqlConnection qui sert à établir la connection avec le Serveur. C'est là
que nous fournissons les paramètres Oracle (SCOTT, TIGER, ORCL):
- SqlQuery qui permet d'envoyer des requêtes Sql vers le Serveur et qui
reçoit les réponses. C'est à ce niveau seul que nous envoyons les requêtes
SQL. Nous pouvons d'ailleurs travailler avec ce niveau seul, pour écrire des
données en mode batch par exemple.
- tDatasetProvider qui transforme les réponses du Serveur en paquets au
format normalisé Delphi.
- tClientDataSet qui stocke en mémoire les données reçues du Serveur et
permet des mise en forme de ces données (tri, filtrage, navigation,
sauvegarde locale etc):
- tDataSource et tdbGrid (ou autres tdbEdit, tdbListbox ...) qui
présentent les données à l'utilisateur et lui permettent de les modifier:
4 - La Connection à Oracle
Voici comment ouvrir une connection depuis le Client:
|
créez une nouvelle application Delphi, et nommez-la
p_oracle_dbx_connect.dpr
|
|
dans la page dbExpress de la Palette sélectionnez SqlConnection:
et posez ce composant sur la Forme
|
|
pour fournir les paramètres de login, double cliquez sur SqlConnection1
|
|
Delphi ouvre l'éditeur de connexion:
|
|
sélectionnez le pilote Oracle, et tapez les paramètres de connection
(SCOTT, TIGER, ORCL):
Notez que ORCL est le nom qui apparaît dans votre fichier tnsnames.ora:
Ainsi, si nous nous étions connecté depuis le PC Client, nous aurions du
utiliser MY_NAME_RESOLUTION au lieu de ORCL (cf le paragraphe 2 ci-dessus).
Et vous aurez à utiliser votre nom figurant dans votre fichier
tnsnames.ora.
|
|
fermez l'éditeur de connection
|
|
pour éviter d'avoir à taper sempiternellement SCOTT/TIGER, sélectionnez
dans l'inspecteur d'objet LoginPrompt et basculez sa valeur sur False.
Puis établissez la connection en basculant Connected sur True
|
|
au bout de quelques instants Connected bascule sur True, indiquant que la
connection est effectivement établie.
Si tel n'était pas les cas, revoyez les étapes précédentes une à une.
|
|
rebasculez Connected sur False. En effet les différents composants
Delphi forceront l'ouverture de la connection lorsqu'ils en auront besoin.
|
Vous pouvez télécharger cette application en cliquant
oracle_dbx_connect.zip.
5 - Création de Tables Oracle
5.1 - Principe
Nous allons créer une table contenant pour chaque formation:
- un code (par exemple 8)
- un nom (par exemple "Delphi Oracle")
- un prix (par exemple 1.400)
Pour cela nous devons envoyer une requête en langage SQL vers le Serveur
Oracle.
La syntaxe de cette requête est:
CREATE TABLE formations
(f_numero INTEGER, f_nom CHARACTER(11), f_jours
INTEGER, f_prix NUMERIC(5, 2) )
|
Il suffit donc de choisir un nom de table, et le nom de chaque colonne avec son
type.
Parmi les types autorisés par Oracle et correspondant aux types SQL usuels
citons:
- les nombres:
- NUMERIC(decimales, précision) pour une valeur numérique
flottante, avec 38 chiffres significatifs
- de nombreuses variantes (INTEGER, SMALLINT FLOAT,
REAL ) existent, à la fois pour des raisons de portabilité et pour
améliorer le nombre de chiffres significatifs
- les chaînes de caractères:
- CHARACTER(taille) pour chaînes avec allocation fixe, taille
maximale 2000 caractères
- NVARCHAR2(taille) pour les chaînes avec allocation dynamique,
taille maximale 32.767 caractères
- les dates:
Pour envoyer cette requête vers le Serveur:
- nous utilisons un tSqlConnection qui assurera la connection vers le
Serveur
- nous utilisons un tSqlQuery
- nous le relions à tSqlConnection
- nous plaçons la requête SQL dans sa propriété tSqlQuery.SQL (via
l'Inspecteur ou en code)
- nous appelons tSqlQuery.ExecSql
5.2 - Utilisation de SQL
La requête à envoyer au Serveur est placée dans tSqlQuery.Sql.
tSqlQuery.Sql est un descendant de tStrings. Nous pouvons donc utiliser, par
exemple:
SqlQuery1.Sql.Add('CREATE TABLE formations (f_numero INTEGER, f_nom CHARACTER(11))');
|
Pour construire la requête:
- nous pouvons utiliser Add pour ajouter une ligne de requête, Text pour
affecter une requête, Clear pour purger tout texte antérieur, ou même
LoadFromFile pour lire un fichier .txt contenant la requête:
SqlQuery1.Sql.LoadFromFile('cree_formation.txt');
|
La mise en page n'a aucune importance pour le Serveur: la requête peut être
répartie en plusieurs lignes:
SqlQuery1.Sql.Add('CREATE TABLE');
SqlQuery1.Sql.Add(' formations ');
SqlQuery1.Sql.Add(' (f_numero INTEGER, f_nom CHARACTER(11))');
|
l_requete:= 'CREATE TABLE '+ Edit1.Text+ ' (';
l_requete:= l_requete+ Edit2.Text+ ')';
SqlQuery1.Sql.Add(l_requete);
|
5.3 - Comment ça Marche
Au niveau fonctionnement:
Notez que l'envoi de toute requête qui modifie des données du Serveur (et la
création d'une nouvelle table est bien une modification) ne peut se faire que
par du code (et non PAS en basculant SqlQuery1.Active sur True en mode
conception)
5.4 - L'application
Pour créer notre table
|
créez une nouvelle application et appelez-la
"p_oracle_dbx_create_table.dpr"
|
|
ajoutez un tSqlConnection sur la Forme (comme détaillé ci-dessus):
- dans la page "dbExpress" de la Palette, sélectionnez SqlConnection et
posez-le sur la Forme
- double cliquez pour ouvrir l'éditeur de connection, sélectionnez ORACLE
puis remplissez SCOTT/TIGER/ORCL
- dans l'Inspecteur d'Object, basculez LoginPrompt sur False
- vérifiez la connection en basculant Connected sur True, puis
rebasculez la valeur sur False
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant
SqlQuery:
|
|
sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
|
|
placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les
instructions de création:
|
procedure TForm1.create_table_Click(Sender: TObject);
begin
with SqlQuery1 do
begin
Close;
with Sql do
begin
Clear;
Add('CREATE TABLE formations');
Add(' ('
+' f_numero INTEGER,');
Add(' f_nom CHAR(23),');
Add(' f_jours SMALLINT,');
Add(' f_prix NUMERIC(5, 2)');
Add(' )');
end; // with Sql
Try
SqlConnection1.Open;
ExecSql;
except
on e: Exception do
begin
display(' *** pb_create '+ e.Message);
end;
end;
end; // with SqlQuery1
end; // create_table_Click
|
|
compilez, exécutez, et cliquez le bouton
|
Vous pouvez télécharger le sources du projet "ib_dbx_create_table.zip".
5.5 - Effacer une table
Pour supprimer une Table, il faut exécuter la requête:
Donc:
|
placez un autre tButton sur la Forme et placez-y la requête de
suppression:
|
procedure TForm1.drop_table_Click(Sender: TObject);
begin
with SQLQuery1 do
begin
Close;
with Sql do
begin
Clear;
Add('DROP TABLE formations');
Try
ExecSql;
except
on e: Exception do
display(' *** pb '+ e.Message);
end;
end; // with Sql
end; // with SQLQuery1
end; // drop_table_Click
|
|
compilez, exécutez, et cliquez le bouton
|
Notez que:
- nous avons le même SqlQuery en changeant le contenu de sa propriété SQL.
Nous aurions aussi bien pu utiliser des SqlQuery séparés.
5.6 - Vérifier la création
Nous pouvons vérifier que la création d'une table a été effectuée
- soit en exécutant DROP TABLE: si la table n'existe pas, le Serveur
Oracle provoquera une exception.
- soit en exécutant SELECT * FROM formations, que nous allons présenter
ci-dessous
- soit en exécutant SELECT * FROM user_tables qui affiche toutes les
tables de SCOTT/TIGER. Nous allons présenter SELECT ci-dessous
5.7 - Création de deux autres tables
Ajoutons deux autres tables à notre exemple: une table pour les lieux des
formation, et une table pour les dates.
Nous pourrions utiliser des procédures similaires aux précédentes, mais un
examen superficiel montre que seul le contenu de la requête SQL change. Il est
donc très simple de créer une procédure do_execute_sql qui utilise cette
requête comme paramètres, et que nous utiliserons pour les autres traitements:
procedure do_execute_sql(p_sql: String);
begin
with Form1, SqlQuery1, Sql do
begin
Close;
Clear;
Add(p_sql);
Try
SqlConnection1.Open;
ExecSql;
except
on e: Exception do
display(' *** pb_create '+ e.Message);
end; // try
end; // with SqlQuery1
end; // do_execute_sql
|
Nous alors créer une table des villes, comportant un numéro de ville et un nom
de ville, et nous imposerons une contrainte d'unicité au numéro. En utilisant
la procédure précédente, la méthode utilisée est:
procedure TForm1.create_table_villes_Click(Sender: TObject);
begin
do_execute_sql(
'CREATE TABLE villes'
+ ' ('
+ ' v_numero INTEGER NOT NULL PRIMARY KEY,'
+ ' v_nom CHAR(30)'
+ ' )');
end; // create_table_villes_Click
|
Et pour effacer cette table, nous utiliserons:
procedure TForm1.drop_villes_Click(Sender: TObject);
begin
do_execute_sql('DROP TABLE villes');
end; // drop_villes_Click
|
Nous utiliserons donc la procédure do_execute_sql pour les autres instructions
de création / modification / effacement de tables.
Notre première table n'était pas dotée d'une clé unique. Nous pouvons corriger
la déclaration par une instruction SQL ALTER .
ALTER TABLE formations
ADD CONSTRAINT pk_formation PRIMARY KEY (f_numero)
|
La table FORMATIONS et la table VILLES étant dotées de clés primaires, nous
pouvons créer une table DATES qui contient des références vers les deux tables
précédentes:
CREATE TABLE dates
(
d_formation_ref INTEGER,
d_ville_ref INTEGER,
d_date DATE,
CONSTRAINT fk_formation
FOREIGN KEY (d_formation_ref)
REFERENCES formations (f_numero),
CONSTRAINT fk_ville
FOREIGN KEY (d_ville_ref)
REFERENCES villes (v_numero)
)
|
Nous vous laissons le soin d'ajouter au projet précédent les boutons avec le
code pour créer (et effacer) les deux autres tables. Le texte complet se trouve
bien sûr dans oracle_dbx_create_table.zip .
Notez que:
- le schéma correspondant aux trois tables que nous avons créé est le suivant:
- connaissant le schéma, nous aurions pu directement créer la table formation
avec sa contrainte de clé primaire (évitant ALTER )
- notez que lorsque nous créons la table des DATES qui référence les deux
autres, celles-ci DOIVENT avoir été créées et DOIVENT avoir les champs
désignés dans les contraintes FOREIGN KEY
- de façon symétrique, si nous souhaitons effacer les tables, il faut d'abord
effacer DATES, puis VILLES et FORMATIONS (si nous essayons d'effacer
FORMATIONS en premier, le Serveur signale qu'il existe une référence vers
cette table depuis DATES)
- nous avons fourni dans les tutoriaux Interbase des exemples de construction
de tables à partir d'un schéma fourni dans un fichier .TXT
- nous aurions pu de même générer les instructions de création de table à
partir de notre outil UML qui a été utilisé pour le schéma précédent.
Voici une image de cette petite application lorsque nous avons créé les trois
tables:
6 - Ajout de Données
6.1 - Ajout simple
Ajoutons un enregistrement pour le stage
3, Oracle Delphi
L'instruction SQL est:
INSERT INTO formations
(f_numero, f_nom)
VALUES (3, 'Oracle Delphi')
|
L'ajout d'enregistrement va modifier les données du Serveur, donc nous
utiliserons tSqlQuery.ExecSql.
De façon détaillée:
|
créez une nouvelle application et nommez-la "p_oracle_dbx_insert_data"
|
|
placez un tSqlConnection sur la Forme
- Cliquez deux fois sur SqlConnection1, renseignez "Driver Name",
"Connection Name", "DataBase", "User Name" et "Pass Word".
- dans l'Inspecteur d'Objet, basculez LoginPrompt sur False, et vérifiez
la connection en basculant SqlConnection1.Connected sur True, puis
fermez la connection.
|
|
placez un tSqlQuery sur la tForme
- sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
|
|
placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les
instructions d'ajout:
procedure TForm1.insert_Click(Sender: TObject);
begin
with SqlQuery1 do
begin
Close;
with Sql do
begin
Clear;
Add('INSERT INTO formations');
Add(' (f_numero, f_nom)');
Add(' VALUES (3, ''Oracle Delphi'')');
end;
Try
ExecSql;
except
on e: Exception do
display(' *** pb_insert '+ e.Message);
end;
end; // with SqlQuery1
end; // insert_Click
|
|
|
compilez, exécutez, et cliquez le bouton
|
Vous pouvez télécharger les sources du projet
"oracle_dbx_insert_data.zip".
6.2 - Type CHARACTER
Pour spécifier les valeurs CHARACTER, Sql exige que la chaîne soit
entourée de guillemets. Suivant les Serveurs, il faut utiliser un guillemet
simple ou double:
|
VALUES (3, 'Oracle Delphi')
|
ou
|
VALUES (3, "Oracle Delphi")
|
De plus si notre valeur est nichée dans une String Pascal, il faut dédoubler
les guillemets simples
SqlQuery1.Sql.Add(' VALUES (3, ''Oracle Delphi'')');
|
Pour simplifier cet imbroglio de guillemets, Delphi propose la méthode
QuotedStr:
IbQuery1.Sql.Add(' VALUES (3, '+ QuotedStr('Oracle')+ ')');
|
6.3 - Type NUMERIC
Pour les valeurs numériques avec décimales, nous devons batailler avec les
points et les virgules:
La règle est donc simple:
- les valeurs Delphi (Double ou autre) utilisent le point '.'
- les composants visuels (tEdit etc) et les primitives de conversion
(FloatToStr) utilisent la virgule ','
6.4 - Type DATE
Un problème similaire intervient pour les dates:
En supposant que nous souhaitions fournir la date du 29 Mars 2004, nous pouvons
utiliser:
INSERT INTO dates
(d_numero, d_date)
VALUES (3, '2004/03/29')
|
et:
SqlQuery1.Sql.Add('INSERT INTO dates');
SqlQuery1.Sql.Add(' (d_numero, d_date');
SqlQuery1.Sql.Add(' VALUES (3, ''2004/03/29'')');
|
6.5 - Automatisation de l'Ajout
Les valeurs à insérer ont été figées dans notre code. Nous pouvons généraliser
cet ajout
- soit en mode interactif.
- soit par une procédure amplement paramétrée
- soit par des scripts
Nous allons présenter ici l'utilisation d'une procédure paramétrée.
En fait il n'y a rien de nouveau par rapport à la technique ci-dessus, sauf que
- la chaîne de la requête est construite en fonction de paramètres de la
procédure
- la procédure appelante envoie les paramètres requis
Voici un exemple de procédure générique d'ajout:
procedure insert_generic(p_number: Integer; p_name: String; p_days: Integer; p_cost: Double);
begin
with Form1, SqlQuery1 do
begin
Close;
with Sql do
begin
Clear;
Add('INSERT INTO formations');
Add(' (f_numero, f_nom, f_jours, f_prix)');
DecimalSeparator:= '.';
Add(' VALUES ('+ IntToStr(p_number)+ ', '+ QuotedStr(p_name)
+ ', '+ IntToStr(p_days)+ ', '+ FloatToStr(p_cost)+ ')');
DecimalSeparator:= ',';
end;
Try
ExecSql;
except
on e: Exception do
display(' *** pb_insert '+ e.Message);
end;
end; // with SqlQuery1
end; // insert_generic
|
Et voici un exemple de procédure appelante
procedure TForm1.insert_generic_Click(Sender: TObject);
begin
insert_generic(1, 'Initiation Delphi', 3, 1400.40);
insert_generic(2, 'Bases de Données Delphi', 3, 1400);
insert_generic(3, 'Oracle Delphi', 3, 1400);
insert_generic(4, 'Composants Delphi', 3, 1400);
insert_generic(5, 'UML et Patterns Delphi', 3, 1400);
insert_generic(4, 'Initiation Pascal', 4, 1900);
end; // insert_generic_Click
|
Notez que:
- la procédure appelante pourrait aussi bien lire ses donnée d'une autre
source (un fichier FILE OF, un fichier ASCII ("comma separates values" ou
autre), un autre table (Oracle, Sql Serveur ou même une autre table
Interbase...)
- le paramètres p_cost est de type Double:
- la procédure appelante envoie une valeur littérale avec un point décimal
insert_generic(2, 'Bases de Données Delphi', 3, 1.400);
|
- la procédure appelée utilise FloatToStr, qui attend une virgule
décimale. Nous forçons donc temporairement l'utilisation du point:
DecimalSeparator:= '.';
Add(' VALUES ('+ IntToStr(p_number)+ ... + FloatToStr(p_cost)+ ')');
DecimalSeparator:= ',';
|
Le fichier oracle_dbx_insert_data.zip contient aussi l'ajout utilisant un
tEdit, ainsi qu'un ajout depuis un script de paramètres ASCII.
Pour vérifier que nos ajouts ont effectivement réussi, nous avons ajouté à
notre application trois boutons qui ouvrent chaque table et comptent simplement
le nombre de fiches.
const k_select_formations= 'SELECT * FROM formations';
procedure select(p_query: String);
var l_count: Integer;
begin
with Form1 do
begin
with SqlQuery1 do
begin
Close;
Sql.Text:= p_query;
Open;
l_count:= 0;
While not Eof do
begin
Inc(l_count);
Next;
end; // while not Eot
display(' ok, '+ IntToStr(l_count)+ ' lines');
end; // with SqlQuery1
end; // with Form1
end; // select
procedure TForm1.select_Click(Sender: TObject);
begin
select(k_select_formations);
end; // select_Click
|
Notez que pour lire les données nous avons utilisé SqlQuery1.Open (alors que
pour écrire des données, nous avons utilisé tSqlQuery.ExecSql).
Le véritable affichage du contenu de chaque ligne nous allons le réaliser
maintenant, et en utilisant un tClientDataSet.
7 - Affichage des Données
7.1 - Principe de la lecture de données
Nous avons vu que pour écrire des données (créer une table, ajouter des
données) SqlQuery envoyait une requête d'écriture, le Serveur effectuait
l'écriture, et ne retournait quasiment rien (au mieux un message d'erreur).
La situation est tout à fait différente pour la lecture. L'instruction
SELECT a pour seul but de récupérer côté client des données correspondant
à une ou plusieurs tables qui existent dans la base de donnée.
Ce qui change, côté Client, c'est qu'il va falloir stocker les lignes de
données correspondant à la table constituant la réponse. Certes, il arrive que
la réponse ne contienne qu'un entier (SELECT COUNT ... ), mais même dans
ce cas, le résultat est considéré comme une table d'une ligne et d'une colonne.
Et par conséquent les composants liés à SqlQuery doivent être prêts à
effectuer ce stockage.
Voici le schéma de l'exécution d'un SELECT :
- le Serveur contient une table "formations"
- le Client envoie une requête
- la requête est envoyée vers le Serveur
- le Serveur construit une NOUVELLE TABLE correspondant à la requête (même
si elle sera identique à une table existante)
- les lignes de cet "answer set" sont envoyés en un ou plusieurs paquets vers
le Client:
- le Client stocke les lignes et les traite:
Le type de stockage côté Client dépend de la suite de composants utilisés:
- si nous travaillons directement en OCI (Oracle Call Interface : le
langage de base du Client Oracle), c'est avant d'envoyer SELECT que
nous allouons un tampon mémoire pour la réponse
- si nous utilisons le BDE (Borland Database Engine: le premier moteur
de base de données de Delphi, en 1996), c'est le BDE qui stockait la
réponse dans des tampons, et le composant tQuery copiait dans un autre
tampon les lignes correspondant au tdbGrid ayant le plus de lignes:
- si nous utilisons dbExpress (ce qui est le cas dans cet article), le
résultat est stocké par le tClientDataSet:
7.2 - Lecture dans un tClientDataSet
Voici notre première application pour afficher les données:
|
créez une nouvelle application et nommez-la "p_oracle_dbx_read_data"
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant
SqlConnection et posez-le sur la tForm.
Cliquez deux fois sur SqlConnection1, renseignez
"Driver Name" -> Oracle
"DataBase" -> ORCL
"User Name" -> SCOTT
"Pass Word" -> TIGER
Allez dans l'Inspecteur d'Objet:
- sélectionnez LoginPrompt et basculez sa valeur sur False
- vérifiez la connection en basculant Connected sur True, puis fermez la
connection en basculant Connected sur False.
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery
et posez-le sur la tForm.
- Sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
- sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir
l'éditeur de chaînes de caractères et tapez-y la requête:
SELECT * FROM formations
- cliquez sur "OK" pour ferme l'éditeur
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DatasetProvider:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez SqlQuery1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
ClientDataSet:
- Posez-le sur la tForm.
- dans sa propriété ProviderName sélectionnez DatasetProvider1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DataSource:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez ClientDataSet1
|
|
sélectionnez dans la page "Data Controls" de la Palette le composant
dbGrid:
- Posez-le sur la tForm.
- dans sa propriété DataSource sélectionnez DataSource1
|
|
sélectionnez sur la Forme SqlClientDataSet1, et basculez sa propriété
Active à True
|
|
dbExpress envoie la demande d'ouverture à DataSetProvider1, qui la
transmet à SqlQuery1, qui connecte la base, recherche les données et les
propage par le chemin inverse dans la dbGrid: vous voyez les donnée
affichées:
|
Notez l'ordre de chaînage des composants:
Cet ordre provient des multiplicités possibles:
- une tSqlConnection peut être reliée à plusieurs tSqlQueries
- un tSqlQuery peut être relié à plusieurs tDatasetProviders
- un tDataSetProvider peut alimenter plusieurs tClientDatasets
- un tClientDataSet peut être relié à plusieurs tDataSource
- un tDataSource peut être relié à plusieurs composants visuels tels que la
tdbGrid
Si Delphi avait proposé au développeur programmeur de relier tSqlConnection à
ses tSqlQueries, il aurait fallu prévoir une LISTE de tSqlQueries associés,
alors que dans l'ordre inverse, chaque tSqlQuery ne peut avoir qu'une
tSqlConnection, donc une seule ligne dans l'Inspecteur d'Objet.
En conclusion, SQL comporte deux catégories de requêtes:
- les requêtes qui écrivent des données sur le Serveur (création, envoi de
données, modifications ...). Ces requêtes sont lancées en dbExpress par
SqlQuery.ExecSql
- les requêtes de lecture qui vont recevoir une table réponse du Serveur, et
qui sont lancées en dbExpress par SqlQuery.Open (ou son équivalent au
niveau de Inspecteur d'Objet: SqlQuery.Active:= True)
SqlQuery.ExecSql n'a aucun équivalent dans l'Inspecteur d'Objet et ne peut
être lancé qu'en exécutant le programme (alors que SqlQuery.Active peut être
basculé en mode conception)
Vous pouvez télécharger le sources du projet "oracle_dbx_read_data.zip".
8 - Le mode Nomade
8.1 - Principe
Nous avons vu que le tClientDataSet stockait TOUTES les lignes du résultat en
mémoire.
Ce stockage choque tout le monde au départ: y aura-t-il toujours assez de
place ?
En fait il faut se rendre à l'évidence que l'utilisateur ne travaille jamais
sur toutes les données de la base en même temps. Lorsque le préposé d'EDF vient
visiter les clients du 9ième, il n'est concerné que par les utilisateurs de cet
arrondissement. Il n'a aucun intérêt à télécharger sur son appareil de relevé
les 17 millions d'abonnés d'EDF de France: seuls ceux de sa tournée le
concernent. Il en est de même pour les autres traitements utilisateurs. C'est
donc au développeur de formuler ses requêtes pour ne rappatrier vers le
tClientDataSet que les données que l'utilisateur va réellement utiliser.
Dans ces conditions, le tClientDataset est capable de contenir toutes les
données à traiter: pour l'affichage, mais aussi pour les modifications.
De plus l'utilisateur peut:
- sauvegarder les données du tClientDataSet localement dans un fichier du PC
client
- fermer son PC
- allumer son PC plus tard (en déplacement), recharger les données du
tClientDataSet et les utiliser (lecture, modification), puis sauvegarder
les modifications:
- de retour au bureau, rallumer son PC, recharger les données modifiées et
mettre à jour les tables du Serveur
8.2 - Sauvegarde du ClientDataSet
Pour démontrer le fonctionnement nomade (BriefCase en anglais), nous allons
volontairement utiliser 3 applications séparées, pour bien démontrer que le
tClientDataset peut être utilisé sans aucune connection au Serveur Oracle
Tout d'abord, la première application qui charge le tClientDataset depuis le
Serveur, et sauvegarde sur disque. Voici comment sauvegarder le contenu du
ClientDataSet:
|
créez une nouvelle application et nommez-la "p_oracle_dbx_save_cds"
|
|
placez un tSqlConnection sur la Forme
- Cliquez deux fois sur SqlConnection1, renseignez "Driver Name"
(ORACLE), "DataBase" (ORCL), "User Name" (SCOTT)et "Pass Word" (TIGER).
- dans l'Inspecteur d'Objet, basculez LoginPrompt sur False, et vérifiez
la connection en basculant SqlConnection1.Connected sur True, puis
fermez la connection.
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery
et posez-le sur la tForm.
- Sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
- sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir
l'éditeur de chaînes de caractères et tapez-y la requête:
SELECT * FROM FORMATIONS
- cliquez sur "OK" pour ferme l'éditeur
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DatasetProvider:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez SqlQuery1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
ClientDataSet:
- Posez-le sur la tForm.
- dans sa propriété ProviderName sélectionnez DatasetProvider1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DataSource:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez ClientDataSet1
|
|
sélectionnez dans la page "Data Controls" de la Palette le composant
dbGrid:
- Posez-le sur la tForm.
- dans sa propriété DataSource sélectionnez DataSource1
|
|
posez un tButton sur la Forme et faites lui ouvrir ClientDataset1:
ClientDataset1.Open;
|
|
posez un tButton sur la Forme et faites lui sauvegarder le contenu actuel
de tClientDataset:
ClientDataset1.SaveToFile('RESU.XML');
|
|
compilez et exécutez. Ouvrez le tClientDataSet et sauvegardez son contenu
|
|
le tClientDataset sauvegarde les données
|
|
utilisez un explorateur Windows pour vérifier que le fichier est bien sur
disque:
Si vous cliquez sur RESU.XML, Windows va ouvrir Internet Explorer qui va
afficher, tant bien que mal, le contenu de cet fichier .XML (vue
partielle):

|
Notez que:
- l'affichage (tDataSource et tdbGrid) n'est pas nécessaire, mais permet une
vérification rapide avant sauvegarde
8.3 - Travail en mode déconnecté (Briefcase)
A présent utilisons le CDS sans être connecté au Serveur:
8.4 - Mise à jour du Serveur
Pour mettre à jour le Serveur, nous utilisons simplement cds.ApplyUpdates
("effectuez les mises à jour"):
|
créez une nouvelle application et nommez-la "p_oracle_dbx_update_server"
|
|
placez un tSqlConnection sur la Forme
- Cliquez deux fois sur SqlConnection1, renseignez "Driver Name"
(ORACLE), "DataBase" (ORCL), "User Name" (SCOTT)et "Pass Word" (TIGER).
- dans l'Inspecteur d'Objet, basculez LoginPrompt sur False, et vérifiez
la connection en basculant SqlConnection1.Connected sur True, puis
fermez la connection.
- créez l'événement OnAfterConnect, et placez-y l'instruction
SqlConnection1.ExecuteDirect(
'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=''.,''');
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery
et posez-le sur la tForm.
- Sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
- sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir
l'éditeur de chaînes de caractères et tapez-y la requête:
SELECT * FROM FORMATIONS
- cliquez sur "OK" pour ferme l'éditeur
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DatasetProvider:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez SqlQuery1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
ClientDataSet:
- Posez-le sur la tForm.
- dans sa propriété ProviderName sélectionnez DatasetProvider1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DataSource:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez ClientDataSet1
|
|
sélectionnez dans la page "Data Controls" de la Palette le composant
dbGrid:
- Posez-le sur la tForm.
- dans sa propriété DataSource sélectionnez DataSource1
|
|
posez un tButton sur la Forme et faites lui charger ClientDataset1:
ClientDataset1.LoadFromFile('RESU.XML');
|
|
posez un tButton sur la Forme et faites lui mettre à jour le Serveur
Oracle:
ClientDataset1.ApplyUpdates(-1);
|
|
compilez et exécutez. Ouvrez le tClientDataSet et sauvegardez son contenu
|
|
le tClientDataset sauvegarde les données sur le Serveur: si vous chargez
l'application qui affiche simplement le contenu de FORMATIONS, vous verrez
bien la nouvelle ligne 7
|
Notez que:
- l'événement SqlConnection1.OnAfterConnect est destiné à faire accepter les
décimales françaises. Si vous ne placez pas cette instruction, vous aurez
une exception "ORA-01722" (erreur de conversion d'une chaîne numérique, pour
des raisons de conflit "," et "." par exemple)
- nous avons placé dans SqlQuery la requête SELECT . En effet le .XML
ne contient pas le nom de la table
- le nom de la table est "FORMATIONS" et non pas "formations". Il s'avère que
dbExpress passe les noms de tables en majuscules, et donc si nous utilisons
"formations", le Serveur provoquera une exception "table pas trouvée"
9 - Modification de Données
9.1 - UPDATE
L'instruction SQL de base pour modifier une donnée est UPDATE.
UPDATE formations
SET f_prix= 1450
WHERE f_numero= 3
|
Comme cette instruction modifie des données du Serveur, nous utiliserons un
tSqlQuery et appellerons ExecSql.
procedure TForm1.update_Click(Sender: TObject);
begin
with SqlQuery1 do
begin
Close;
with Sql do
begin
Clear;
Add('UPDATE formations');
Add(' SET f_nom= '+ QuotedStr('ORACLE Delphi'));
Add(' WHERE f_numero= 3');
end;
Try
ExecSql;
except
on e: Exception do
display(' *** pb_insert '+ e.Message);
end;
end; // with SqlQuery1
end; // update_Click
|
|
compilez, exécutez, et cliquez le bouton
|
Vous pouvez télécharger le sources du projet "oracle_dbx_update_data.zip".
9.2 - Principe de Modification Interactive
Pour modifier les données en utilisant le tClientDataset, l'utilisateur va
travailler en deux étapes:
- toutes les modifications (ajout, changement, effacement) sont mémorisés par
le tClientDataSet
- lorsqu'il le souhaite, l'utilisateur peut envoyer les modifications vers le
Serveur Interbase
L'utilisateur travaille donc dans un premier temps uniquement avec la mémoire
du tClientDataSet. Celui-ci garde la trace intégrale de toutes les
modifications:
- si nous modifions un enregistrement, nous aurons 6 fois l'enregistrement en
mémoire (l'original et les 5 modifications)
- les ajouts sont placés dans la mémoire
- les effacements sont mémorisés comme une nouvelle ligne avec une marque
d'effacement.
9.3 - Les Modifications de tClientDataset
Le tClientDataset contient donc une suite de lignes numérotées:
- les lignes chargées lors de l'ouverture de tClientDataset
- les lignes ajoutées
- les versions modifiées de lignes précédentes
- les lignes effacées
Le dbGrid ne présente quant à lui que la dernière version de chaque ligne.
Pour visualiser les lignes originales et leurs versions successives, nous
allons simplement sauvegarder le tClientDataset dans un fichier .XML et
réafficher ce fichier .XML. Ce type d'analyse a été présenté dans l'article
Affichage ClientDataset XML. Nous nous
contenterons ici d'appeler les classes d'analyse et d'affichage.
Par conséquent:
|
créez une nouvelle application et appelez-la "p_oracle_dbx_modify_cds.dpr"
|
|
placez un tSqlConnection sur la Forme
- Cliquez deux fois sur SqlConnection1, renseignez "Driver Name"
(ORACLE), "DataBase" (ORCL), "User Name" (SCOTT)et "Pass Word" (TIGER).
- dans l'Inspecteur d'Objet, basculez LoginPrompt sur False, et vérifiez
la connection en basculant SqlConnection1.Connected sur True, puis
fermez la connection.
- créez l'événement OnAfterConnect, et placez-y l'instruction
SqlConnection1.ExecuteDirect(
'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=''.,''');
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery
et posez-le sur la tForm.
- Sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
- sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir
l'éditeur de chaînes de caractères et tapez-y la requête:
SELECT * FROM FORMATIONS
- cliquez sur "OK" pour ferme l'éditeur
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DatasetProvider:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez SqlQuery1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
ClientDataSet:
- Posez-le sur la tForm.
- dans sa propriété ProviderName sélectionnez DatasetProvider1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DataSource:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez ClientDataSet1
|
|
sélectionnez dans la page "Data Controls" de la Palette le composant
dbGrid:
- Posez-le sur la tForm.
- dans sa propriété DataSource sélectionnez DataSource1
|
|
posez un tButton sur la Forme et faites lui ouvrir ClientDataset1:
ClientDataset1.Open;
|
|
ajoutez un tButton pour afficher le contenu actuel du ClientDataset1
(optionnel):
- ajoutez u_c_cds_analyze_xml et u_cds_table à la liste des USES
- posez un tButton sur la Forme et tapez le code d'affichage:
procedure TForm1.display_xml_Click(Sender: TObject);
var l_c_table: c_table;
begin
// -- save the CDS with some temporary name
ClientDataset1.SaveToFile(k_root_path+ 'temp.xml');
// -- reload the CDS and display it
with c_cds_analyze_xml.create_cds_analyze_xml('xml',
k_root_path+ 'temp.xml') do
begin
l_c_table:= c_table.create_table('table');
analyze_xml(l_c_table);
display_line;
l_c_table.display_formatted_line_list;
l_c_table.Free;
Free;
end; // with c_analyze_xml
end; // display_xml_Click
|
|
|
posez un tButton sur la Forme et faites lui mettre à jour le Serveur:
ClientDataset1.ApplyUpdates(-1);
|
|
posez un tButton sur la Forme et faites lui mettre à jour le Serveur:
ClientDataset1.Close;
|
|
compilez et exécutez
|
A présent voici un exemple de manipulation:
9.4 - Réconciliation de Données
Si deux agences de voyages essayent simultanément de réserver sur le même vol
plus de place qu'il n'en reste, la grogne à l'enregistrement est garantie. Il
faut donc trouver un moyen d'arbitrer entre les requêtes incompatibles.
Nous n'allons pas rentrer ici dans le détail des mode de transaction et des
verrouillages. Mais voici comment fonctionne une façon de traiter le problème:
- Oracle (comme les autres moteurs: Interbase, Sql Serveur, DB2, MySql
etc) fonctionne en mode "optimiste": plusieurs utilisateurs peuvent charger
une ligne, et la modifier. C'est le dernier qui a parlé qui a raison
- nous pouvons demander à dbExpress de vérifier avant de mette à jour que les
valeur actuellement sur le Serveur sont bien les mêmes que celles que nous
avons chargées 10 minutes plus tôt (en français clair: un autre utilisateur
a-t-il effectué des modifications depuis notre chargement ?). Cela se fait
simplement en ajoutant à UPDATE une clause WHERE qui vérifie que
la valeur est celle que nous avons chargée avant nos modifications). Pour
faire simple, cette clause pourrait être:
UPDATE formations
SET f_nom= 'ORACLE DELPHI
WHERE (f_numero= 3) AND (f_nom= 'ORACLE Delphi')
|
- si UPDATE échoue (parce que WHERE ne trouve plus nos valeurs
initiale), dbExpress provoque une exception nous indiquant qu'un autre
utilisateur a modifié les données
- notre application a alors la main. Nous pouvons décider de forcer un
UPDATE (écrasant la valeur des autres utilisateurs), ignorer l'erreur,
envoyer un message à l'utilisateur en lui demandant de recommencer en
partant des nouvelles valeurs actuellement sur le Serveur etc
- pour faciliter la gestion de ces exceptions, dbExpress nous propose un
dialogue par défaut affichant à le conflit de résolution et permettant à
l'utilisateur de choisir le type de traitement à effectuer (ignorer, écraser
etc)
Voyons plus en détail comment gérer les erreur de réconciliation. Il faut tout
d'abord créer l'événement OnReconcileError du tClientDataset. Les paramètres
sont les suivants:
PROCEDURE ReconcileError(
p_c_data_set: TCustomClientDataSet;
p_c_error: EReconcileError;
p_update_kind: TUpdateKind;
VAR pv_action: TReconcileAction);
|
et:
- p_c_data_set: c'est le tClientDataSet qui contient les erreurs
- p_c_error: une exception contenant dans p_c_error.Message le texte de
l'exception
- p_update_kind indique si l'erreur provenant d'un ajout (ukInsert), d'une
modification (ukModify) ou d'un effacement (ukDelete)
- pv_action: indique ce qu'il faut faire de cette erreur (annuler, forcer la
valeur etc)
Et Borland suggère même d'utiliser un dialogue tout préparer pour proposer à
l'utilisateur de décider ce qu'il faut faire en lui présentant directement le
problème et le le laissant choisir le traitement à effectuer. Ce dialogue
appelé RECERROR.PAS se trouve dans:
C:\PROGRAM FILES\BORLAND\DELPHI6\OBJREPOS
et a l'allure suivante:
et:
- il affiche l'erreur
- présente les données ayant causé la faute
- laisse l'utilisateur décider ce qu'il souhaite faire
Pour utiliser ce dialogue:
Finalement pour comprendre les commandes que Delphi génère pour nous lors de
l'appel de tClientDataset.ApplyUpdates, nous allons employer un SqlMonitor
qui affiche les API Interbase envoyées vers le Serveur Interbase. Pour cela
il suffit de
- relier le tSqlMonitor à tSqlConnection
- basculer tSqlMonitor sur True
- créer l'événement tSqlMonitor.OnLogTrace et visualiser
tSqlMonitor.TraceList
Créons à présent l'application qui permet les modifications:
|
créez une nouvelle application et appelez-la "p_oracle_dbx_reconcile.dpr"
|
|
placez un tSqlConnection sur la Forme
- Cliquez deux fois sur SqlConnection1, renseignez "Driver Name"
(ORACLE), "DataBase" (ORCL), "User Name" (SCOTT)et "Pass Word" (TIGER).
- dans l'Inspecteur d'Objet, basculez LoginPrompt sur False, et vérifiez
la connection en basculant SqlConnection1.Connected sur True, puis
fermez la connection.
- créez l'événement OnAfterConnect, et placez-y l'instruction
SqlConnection1.ExecuteDirect(
'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=''.,''');
|
|
sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery
et posez-le sur la tForm.
- Sélectionnez sa propriété SqlConnection et initialisez-la à
SqlConnection1
- sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir
l'éditeur de chaînes de caractères et tapez-y la requête:
SELECT * FROM FORMATIONS
- cliquez sur "OK" pour ferme l'éditeur
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DatasetProvider:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez SqlQuery1
- dans sa propriété UpdateMode, sélectionnez UpWhereAll (ne mettre à
jour que si la ligne est exactement celle que nous avons initialement
chargée)
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
ClientDataSet:
- Posez-le sur la tForm.
- dans sa propriété ProviderName sélectionnez DatasetProvider1
|
|
sélectionnez dans la page "Data Access" de la Palette le composant
DataSource:
- Posez-le sur la tForm.
- dans sa propriété DataSet sélectionnez ClientDataSet1
|
|
sélectionnez dans la page "Data Controls" de la Palette le composant
dbGrid:
- Posez-le sur la tForm.
- dans sa propriété DataSource sélectionnez DataSource1
|
|
posez un tButton sur la Forme et faites lui ouvrir ClientDataset1:
ClientDataset1.Open;
|
|
posez un tButton sur la Forme et faites lui mettre à jour le Serveur:
ClientDataset1.ApplyUpdates(-1);
|
|
ajoutez à la liste des USES la forme de réconciliation:
USES .... recerror, ...
|
|
créez le gestionnaire d'erreur:
- sélectionnez ClientDataset1
- créez son événement OnReconcileError
- appelez la Forme de réconciliation et retournez le code à Delphi:
procedure TForm1.ClientDataSet1ReconcileError(
DataSet: TCustomClientDataSet; E: EReconcileError;
UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
display('reconcile '+ e.Message);
action:= | | |