menu
  Home  ==>  articles  ==>  bdd  ==>  interbase  ==>  interbase_tutorial   

Tutorial Interbase - John COLIBRI.


1 - Introduction

Ce tutorial va vous indiquer comment utiliser le Serveur Interbase fourni avec Delphi. Ce tutorial a plusieurs objectifs:
  • vous présenter intégralement comment réaliser des applications utilisant des bases de données Sql en Delphi. Tous les exemples ont été codés et les sources sont téléchargeables.
  • effectuer toutes les opérations en Delphi, plutôt que de présenter une série d'outils annexes dont la présentation alongerait l'exposé
  • insister sur les points qui posent problème (création d'une base, chaîne de connection, modification de dbGrid) plutôt que sur les composants visuels assez intuitifs au demeurant
  • présenter les composants réellement utilisés pour gérer des tables Sql (des tIbQuery) plutôt que des composants génériques (tIbTable) dont les traitements automatiques masquent le fonctionnement réel d'un Serveur Sql
  • limiter au maximum le nombre de composants utilisés pour manipuler les données du Serveur sans perdre en fonctionnalité
Si après ce premier contact vous souhaitez approfondir vos connaissances, nous vous proposons soit des formations ou des prestations de programmation et d'assistance.

Nous nous adressons pour ce tutorial à un programmeur Delphi:

  • ayant une idée élémentaire de Delphi: Palette, Inspecteur, OnClick. Tout le reste sera expliqué
  • n'ayant pas nécessairement de connaissance SQL. Le fonctionnement des requêtes et leur syntaxe sera présenté



2 - Installation

2.1 - Principe

Le logiciel Interbase comporte essentiellement deux parties:
  • le programme Serveur (le moteur), qui en général est placé sur un PC distant
  • le programme Client, qui dialogue avec les programme applicatifs (Delphi ou autre).
Il faut donc installer les deux parties (sur la même machine ou sur des PC séparés).

Interbase peut fonctionner selon deux modes:

  • le mode dit "local", pour lequel le moteur et le programme client sont sur le même PC. La machine n'a pas besoin d'avoir les couches réseau (TCP\IP, Novell ou autre), ni de carte réseau (carte Ethernet ou liaison série). Le programme applicatif communique avec le Client, qui appelle directement les routines du serveur (sans passer par des appels TCP\IP)

  • le mode Client Serveur
    • les communications entre la DLL client et le Serveur passent par les couches réseau de Windows: il faut que ces couches réseau soient installées (même si le Serveur et le Client sont sur le même PC)
    • le programme applicatif devra désigner le serveur en utilisant l'adresse IP du serveur.

Dans les deux cas, l'installation se fait en utilisant l'installateur du CD Delphi (ou du CD Interbase). Nous présenterons l'instalation à partir du CD Delphi.



2.2 - Installation avec Delphi

Lors de l'installation de Delphi, Installshield propose d'installer aussi Interbase.

Vous pouvez répondre oui et Interbase sera installé

Si vous avez répondu "Non", voici comment procéder:



2.3 - Installation du Mode Local

Pour installer Interbase en mode local:
   insérez le CD Delphi
   sélectionnez "Interbase Desktop Edition"
   répondez oui à toutes les questions
   vous pouvez vérifier que
  • le répertoire "c:\Program Files\Borland\" contient bien un sous-répertoire "Interbase"
  • le répertoire "c:\Windows\SYSTEM\" contient bien la DLL client "gds32.dll"
  • le menu démarrer propose à présent une option "Interbase" avec "Interbase Server Manager". Cliquez sur ce choix, et le gestionnaire du serveur apparaît:




2.4 - Installation du Mode Distant

Installez le Serveur sur le PC serveur, en procédant comme ci-dessus, mais sélectionnez "Interbase 6 Server". La partie Client installée sur le PC du Serveur sera utilisée pour les logiciels de gestion du Serveur

Installez le Client sur le PC client:
   sélectionnez "Interbase 6 Server"
   après les dialogues de copyright ou autres apparaîtra:

   sélectionnez "Interbase Client"


2.5 - Suppression d'Interbase

Pour supprimer Interbase:
   ouvrez le Serveur Manager et cliquez "Stop" (pour arrêter le moteur)
   ouvrez le panneau de configuration, sélectionnez "Ajout / Suppression de logiciel", sélectionnez "Interbase" et cliquez Oui partout



3 - Créer la Base

3.1 - SQL et la création de bases

Pour créer une base, il faut, pour tous les moteurs, utiliser des outils spéciaux, le langage SQL ne contenant pas de requête spécifique pour cette opération

Pour Interbase, ce sont des primitives de l'API native du moteur qui permettent cette création. Elle n'est pas possible depuis les composants générique d'accès aux données (tTable, tQuery). En revanche, Jeff Overcash a ajouté à tIbDatabase la méthode qui utilise les API Interbase natifs et permet la création de la base.

Les éléments à fournir sont les suivants;

  • le chemin DOS où sera placé le fichier .GDB
  • le dialecte Interbase à utiliser. En gros, le dialecte détermine le traitement de certains types de données Interbase (en dialecte 3 nous pouvons utiliser des Integer 64 bits et des tTimeStamps, minuscule ou majuscule sont reconnus etc). Nous choisirons le dialecte 3 (le plus récent).
  • le nom d'utilisateur et le mot de passe du Serveur. Ce sont les noms du serveur par défaut (la base n'étant pas encore créée, elle)
  • des paramètres d'optimisation, tels que la taille des pages du cache
Plus précisément:
  • le chemin est placé dans tIbDatabase.DataBaseName
  • le dialecte est placé dans tIbDatabase.SqlDialect
  • le nom d'utilisateur et le mot de passe, ainsi que les autres paramètres sont placés dans la propriété fourre-tout Params
  • la méthode de création est simplement:
          tIbDatabase.CreateDataBase



3.2 - Création de la base

Nous allons créer une base contenant les stages offerts par l'Institut Pascal. Nous placerons cette base dans le répertoire "..\data\" (situé au même niveau que le .EXE. Le .ZIP téléchargeable créera ce répertoire automatiquement). Le nom du fichier sera "Institut_Pascal.GDB".

Pour créer la base:
   créez une nouvelle application et appelez-la "p_ib_create_base"
   sélectionnez dans la page "Interbase" de la Palette le composant tIbTransaction:

et posez ce composant sur la tForm

   sélectionnez de même une tIbDatabase

et placez-la sur la tForm

   initialisez IbDatabase.DefaultTransaction vers IbTransaction1
   utilisez un tButton pour créer la base:
  • placez un tButton, nommez-le create_base_, et créez sa méthode OnClick
  • voici le code de cette méthode:

        procedure TForm1.create_database_Click(SenderTObject);
          begin
            with IbDatabase1 do
            begin
              DatabaseName:= '..\data\Institut_Pascal.gdb';
              SqlDialect:= 3;

              Params.Add('USER "SYSDBA"');
              Params.Add('PASSWORD "masterkey"');
              Params.Add('PAGE_SIZE 4096');

              CreateDatabase;
            end// with IbDataBase1
          end// create_database_Click


   compilez et exécutez
   cliquez Button1
   le répertoire ..\data\ contient bien le fichier "Institut_Pascal.GDB" dont la taille est d'environ 580 K
Notez que:

  • si le fichier formation.gdb existait déjà il faut tester sa présence (FileExists) et l'effacer (Erase)
  • si nous souhaitons répéter la création, il faudrait purger les Params avant tout ajout par Add (Params.Clear)
Vous pouvez télécharger ce projet "ib_create_base.zip".



3.3 - Connection à une base

tIbDatabase est utilisé par la suite pour assurer les échanges entre notre application et la base que nous venons de créer.

Ce composant doit être initialisé avec:

  • dans DataBaseName, la chaîne de connection qui est
    • si nous travaillons en local, le chemin:
              c:\programs\interbase\data\Institut_Pascal.gdb
      ou
              ..\data\Institut_Pascal.gdb
    • l'URL du PC qui héberge le Serveur:
              127.0.0.1\ c:\programs\interbase\data\Institut_Pascal.gdb
              HostName\ c:\programs\interbase\data\Institut_Pascal.gdb
              www.jcolibri.com\ c:\programs\interbase\data\Institut_Pascal.gdb
  • dans Params, les paramètres propres à Interbase. Dans notre cas, les paramètres par défaut du Serveur, plus le nom d'utilisateur et le mot de passe.
Une fois les propriétés initialisées, nous pouvons tester la connection en basculant tIbDatabase.Connected sur True.

A titre de vérification:
   posez un second tIbDataBase sur la forme
   initialisez IbDatabase2.DatabaseName avec
        ..\data\Institut_Pascal.gdb
   cliquez deux fois sur IbDatabase2 pour ouvrir l'éditeur de tIbDatabase
   Delphi présente le dialogue suivant:

   sélectionnez "User Name" et tapez SYSDBA
   sélectionnez "Password" et tapez masterkey
   supprimez la coche de "Login Prompt"
   le résultat est:

   fermez l'éditeur
   sélectionnez Connected et basculez sa valeur sur True
   au bout d'un "petit" instant, la valeur bascule sur True


Notez que:

  • la connection est le test IMPERATIF pour vérifier que nous pouvons travailler en Interbase. Nous recommandons d'effectuer cette connection systématiquement avant de poser des tonnes de composants sur la tForm ou d'écrire des milliers de lignes
  • si nous connectons la base en mode conception, Delphi ouvrira aussi la base lorsque l'exécution sera lancée. Ces deux connections sont comptabilisés par le gestionnaire de licences Interbase comme 2 connections séparées (ce sont bien deux processus Windows). Si vous travaillez en utilisant la version Delphi d'Interbase, il vaut mieux fermer la connection en mode conception, puis la rouvrir lors de l'exécution par:
            tIbDatabase.Open;

4 - Créer une Table

4.1 - Principe

Nous allons créer une table contenant pour chaque formation:
  • un code (par exemple 8)
  • un nom (par exemple "Delphi Interbase")
  • un prix (par exemple 1.400)
Pour cela nous devons envoyer une requête en langage SQL vers le Serveur Interbase.

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 Interbase citons:

  • INTEGER pour les valeurs entières 32 bits
  • SMALLINT pour les valeurs entières 16 bits
  • NUMERIC(decimales, précision) pour une valeur numérique flottante
  • DATE pour une date
  • CHARACTER(taille) pour des caractères
Pour envoyer cette requête vers le Serveur:
  • nous utilisons un tIbDataBase qui assurera la connection vers le Serveur
  • nous utilisons un tIbQuery
    • nous le relions à tIbDatabase
    • nous plaçons la requête SQL dans sa propriété IbQuery.SQL (via l'Inspecteur ou en code)
    • nous appelons tIbQuery.ExecSql
  • la création n'est visible par tout le monde que si la transaction qui est utilisée pour la création de la table est confirmée


4.2 - Utilisation de SQL

La requête à envoyer au Serveur est placée dans tIbQuery.Sql. tIbQuery.Sql est un descendant de tStrings. Nous pouvons donc utiliser, par exemple:

          IbQuery1.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:
          IbQuery1.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:

          tIbQuery.Sql.Add('CREATE TABLE');
          tIbQuery.Sql.Add('  formations ');
          tIbQuery.Sql.Add('  (f_numero INTEGER, f_nom CHARACTER(11))');
  • nous pouvons entrer la requête en utilisant l'Inspecteur d'Objet. Si nous cliquons sur Sql, Delphi affiche:

    et vous pouvez taper manuellement la requête dans la partie "SQL"

  • nous pouvons aussi créer la String en plusieurs étapes par toutes les primitives de String telles que la concaténation, Insert, le test par Pos ou autre. Par exemple:
          l_requete:= 'CREATE TABLE 'Edit1.Text' (';
          l_requete:= l_requeteEdit2.Text')';
          tIbQuery.Sql.Add(l_requete);



4.3 - Comment ça Marche

Au niveau fonctionnement:
  • lorsque nous construisons la requête par IbQuery.Sql.Add, ce texte de requête est à ce stade une tStrings en mémoire de notre PC.

    C'est une tStrings comme n'importe quelle autre. Ce qui explique qu'aucune vérification de syntaxe n'est effectuée.

  • Lorsque nous exécutons:

                 IbQuery1.ExecSql;

    alors:

    • la requête est envoyée au Serveur via le Client
    • le Serveur traite la requête et:
      • crée la table si la requête est correcte:

      • retourne une erreur transformée par Delphi en exception en cas de problème
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 (PAS en basculant IbQuery1.Active sur True en mode conception)



4.4 - Les Transactions

En Interbase, toutes les requêtes sont créées dans le cadre d'une transaction. Les transaction sont un mécanisme qui garantit que plusieurs sont réalisées en entier ou annulées. L'archétype est le transfert bancaire: si nous débitons DUPOND pour créditer MARTIN, le système doit garantir que soit les deux modifications sont réalisée ou aucune ne l'est.

En Delphi, les composants de bases de données utilisent par défaut des transactions transparentes pour le programmeur, mais nous pouvons gérer les transactions nous-même. C'est ce qui est recommandé pour Interbase.

En InterbaseExpress, nous utilisons un composant tIbTransaction que nous connectons à tIbDatabase.DefaultTransaction. Les primitives sont:

  • tIbTransaction.StartTransaction pour démarrer une nouvelle transaction avant une ou plusieurs opérations
  • tIbTransaction.Commit pour confirmer la suite d'opération, ou tIbTransaction.RollBack pour tout annuler.
  • StartTransaction et Commit ou RollBack sont souvent utilisés dans TRY EXCEPT:
          TRY
            IbTransaction1.StartTransaction;
            débite DUPOND
            crédite MARTIN
            IbTransaction1.Commit;
          EXCEPT
            IbTransaction1.RollBack;
          END;

Nous ne pouvons appeler IbTransaction1.StartTransaction si la transaction attachée à IbTransaction1 est fermée (committed ou Rolledback). Pour savoir si une transaction est active, nous pouvons utiliser la fonction InTransaction. Ainsi:

          IF NOT IbTransaction1.InTransaction
            THEN IbTransaction1.StartTransaction;

Que se passerait-il si nous réalisons le traitement sans cette IbTransaction:

  • pendant l'exécution de notre application d'autres applications (l'explorateur de bases de données, un autre de nos exe) ne "verraient" pas la table
  • un autre IbQuery de notre propre application pourrait ne pas voir la table
  • lorsque l'EXE sera fermé, la transaction sera automatiquement fermée
Le plus simple est donc d'utiliser Commit.



4.5 - L'application

Pour créer notre table
   créez une nouvelle application et appelez-la "ib_create_table"
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt":

Vérifiez la connection en basculant IbDataBase.Connected sur True, puis fermez la connection (pour éviter de monopoliser un utilisateur). La connection sera ouverte avant l'envoi de la requête

   placez un tIbTransaction sur la Forme, et reliez IbDatabase1.DefaultTransaction
   placez un tIbQuery sur la tForme

   sélectionnez sa propriété DataBaseName et initialisez-la à

    IbDataBase1

   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(SenderTObject);
      begin
        with IbQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('CREATE TABLE formations');
            Add('  ('
                +'    f_numero INTEGER,');
            Add('    f_nom CHAR(9),');
            Add('   )');
          end// with Sql

          Try
            IbDatabase1.Open;
            if ibtransaction1.InTransaction
              then ibTransaction1.Commit;
            ibtransaction1.StartTransaction;
            ExecSql;
            ibtransaction1.Commit;
          except
            on eException do
              display('pb_create 'e.Message);
          end// try ... except
        end// with IbQuery1
      end// create_table_Click


   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger le sources du projet "ib_create_table.zip".



4.6 - Vérifier la création

Nous pouvons vérifier que la création a été effectuée en utilisant l'explorateur de bases de données Delphi, ou en lisant les données ce cette table dans notre application.

Pour lire les données il suffit d'envoyer la requête

SELECT * FROM formations

au moteur. Nous verrons cette requête SELECT en détail plus bas, mais voici comment procéder pour notre test:
   ajoutez un second tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à

    IbDataBase1

   placez un second tButton sur la Forme et placez-y la requête de lecture:

    procedure TForm1.select_Click(SenderTObject);
      begin
        with IbQuery2 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('SELECT * FROM formations');

            Try
              Open;
              display('ok');
            except
              On eException do
                display('not_there 'e.Message);
            end;
          end// with Sql
        end// with IbQuery2
      end// select_Click


   compilez, exécutez, et cliquez le bouton


4.7 - Effacer une table

Pour supprimer une Table, il faut exécuter la requête:

DROP TABLE formations

Donc:
   ajoutez un troisième tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à

     IbDataBase1

   placez un autre tButton sur la Forme et placez-y la requête de suppression:

    procedure TForm1.drop_table_Click(SenderTObject);
      begin
        IbDatabase1.Open;

        with IbQuery3 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('DROP TABLE formations');
          end;

          Try
            if ibtransaction1.InTransaction
              then ibTransaction1.Commit;
            ibtransaction1.StartTransaction;
            ExecSql;
            ibtransaction1.Commit;
            display('  ok');
          except
            on eexception do
               display('pb_drop 'e.Message);
          end;
        end// with IbQuery3
      end// drop_table_Click


   compilez, exécutez, et cliquez le bouton
Notez que:
  • nous avons utilisé 3 IbQuery séparés. Nous aurions aussi bien pu utiliser le même
  • nous aurions même pu utiliser un IbSql. Pour ce tutorial que nous souhaitons minimal, nous avons préféré un IbQuery qui permet à la fois la modification de données du Serveur (CREATE, DROP) et la lecture de données depuis le Serveur (SELECT)
  • notez toutefois, que pour envoyer une requête de modification il faut utiliser tIbQuery.ExecSql, et pour la lecture c'est tIbQuery.Open.

    tIbQuery.Open est équivalent à tIbQuery1.Active:= True (mais pas à ExecSql). Comme l'Inspecteur propose Active (pour pouvoir positionner ses éléments visuels), nous pouvons ouvrir une Table depuis l'Inspecteur, mais nous ne pouvons pas créer de Table en mode conception (il faut exécuter du code)



4.8 - Automatisation de la création

Si nous avons plusieurs tables à créer, le codage en dur se révèle très vite fastidieux. Or il est très simple de paramétrer une procédure générique, et d'appeler cette procédure en lisant les paramètres depuis un fichier.

Une solution est de placer sur disque les requêtes SQL et de lire et exécuter ces requêtes les unes après les autres.

Nous avons préféré utiliser une définition plus schématique qui est analysée par la procédure de lecture.

Voici notre schéma que nous avons tapé dans NotePad et placé dans le fichier "schema_simple.txt":

formations
      f_numero INTEGER
      f_nom CHAR(23)
      f_jours INTEGER
      f_prix NUMERIC(12, 2)
dates
      d_formation INTEGER
      d_date DATE
      d_ville INTEGER
villes
      v_numero INTEGER
      v_nom CHAR(30)

Nous avons simplement fourni

  • le nom de chaque table, à la marge
  • le nom des champs et leur type Interbase, indenté de deux espaces
On peut difficilement faire plus simple !

Notre programme va alors

  • lire le fichier
  • générer la requête pour créer chaque table
Et:
  • la création de la table est réalisée par une classe dont voici la définition:

        type c_create_ib_tableclass(c_basic_object)
                                  m_table_nameString;
                                  m_c_ib_query_reftIbQuery;
                                  m_c_fieldstStringList;
                                  m_exec_sqlBoolean;

                                  Constructor create_ib_table(p_namep_table_nameString;
                                      p_c_ib_querytIbQuery);
                                  procedure drop_table;
                                  procedure create_table;
                                  procedure display_sql;
                                  Destructor DestroyOverride;
                                end// c_create_ib_table

  • la méthode de création de la table est la suivante:

        procedure c_create_ib_table.create_table;
          var l_field_indexInteger;
          begin
            drop_table;

            with m_c_ib_query_refSql do
            begin
              display(m_table_name);
              Clear;

              Add('CREATE TABLE 'm_table_name' (');
              for l_field_index:= 0 to m_c_fields.Count- 2 do
                Add(m_c_fields[l_field_index]+ ',');
              Add(m_c_fields[m_c_fields.Count- 1]+ ' )');

              if m_exec_sql
                then
                  try
                    if DataBase.DefaultTransaction.InTransaction
                      then DataBase.DefaultTransaction.Commit;
                    DataBase.DefaultTransaction.StartTransaction;
                    ExecSql;
                    DataBase.DefaultTransaction.Commit;

                    display('  ok 'm_table_name);
                  except
                    on eException do
                       display('  *** pb_create 'e.Message);
                  end // try ... Except
                else display('did_not_ask_to_create');
            end// with m_c_ib_query_ref, Sql
          end// create_table

  • cette méthode utilise la liste de définition des champs qui est chargée à partir du fichier par la procédure suivante

        procedure TForm1.create_script_Click(SenderTObject);
          var l_list_indexInteger;
              l_linel_trimmed_lineString;
              l_c_create_ib_tablec_create_ib_table;
              l_table_indexInteger;
          begin
            with tStringList.Create do
            begin
              LoadFromFile(k_script_pathk_script_name);
              l_c_create_ib_table:= Nil;

              l_table_index:= 0;

              for l_list_index:= 0 to Count- 1 do
              begin
                l_line:= Strings[l_list_index];
                l_trimmed_line:= Trim(l_line);
                if l_trimmed_line<> ''
                  then begin
                      if l_trimmed_linel_line
                        then begin
                            if Assigned(l_c_create_ib_table)
                              then begin
                                  l_c_create_ib_table.create_table;
                                  l_c_create_ib_table.Free;
                                  Inc(l_table_index);
                                end;

                            display_line;
                            l_c_create_ib_table:= 
                                c_create_ib_table.create_ib_table(''l_lineIbQuery1);
                          end
                        else begin
                            l_c_create_ib_table.m_c_fields.Add(l_trimmed_line);
                          end;
                    end;
              end// for l_list_index

              // -- the last table 
              if Assigned(l_c_create_ib_table)
                then begin
                    l_c_create_ib_table.create_table;
                    l_c_create_ib_table.Free;
                  end;

              Free;
            end// with tStringList
          end// create_script_Click


Notez que:
  • la procédure c_create_ib_table.create_table commence par appeler la procédure c_create_ib_table.drop_table. Pour la première création, ceci est inutile, mais si vous créez une seconde fois la base, il faut d'abord effacer la table, sinon le Serveur refuse la création.
  • mais dans le cas de la première création, DROP TABLE provoque aussi une exception. C'est pourquoi cette instruction est placée dans un TRY ... EXCEPT. Cette exception va interrompre l'exécution. Pour poursuivre la création:
    • vous pouvez taper F9 pour passer outre
    • vous pouvez placer Delphi en mode "ne pas s'arrêter en cas d'exceptions" en suprimant la coche de la CheckBox "stop on Delphi Exceptions" située dans:
      Tools | Debugger | Language Exceptions
    C'est le mode préféré lorsque l'on utilise beaucoup de bases de données, compte tenu des nombreuses exceptions provoquées à tous les niveaux (Serveur Sql, pilote, BDE ou dBExpress, Ibx etc).


Vous pouvez télécharger le source du projet "ib_create_tables_with_script.zip".




5 - Ajouter des Données

5.1 - Ajout simple

Ajoutons un enregistrement pour le stage

    3, Interbase Delphi

L'instruction SQL est:

 INSERT INTO formations
     (f_numero, f_nom)
     VALUES (3, 'Interbase Delphi')

L'ajout d'enregistrement va modifier les données du Serveur, donc nous utiliserons tIbQuery.ExecSql.

De façon détaillée:
   créez une nouvelle application et nommez-la "ib_insert_data"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   placez un tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions d'ajout:

    procedure TForm1.insert_Click(SenderTObject);
      begin
        IbDatabase1.Open;

        with IbQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('INSERT INTO formations');
            Add('  (f_numero, f_nom)');
            Add('  VALUES (8, ''Delphi InterBase'')');
          end// with Sql

          Try
            if not IbTransaction1.InTransaction
              then IbTransaction1.StartTransaction;
            ExecSql;
            IbTransaction1.Commit;
          except
            on eException do
              display('pb_insert 'e.Message);
          end// Try...Except
        end// with IbQuery1
      end// insert_Click

   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger les sources du projet "ib_insert_data.zip".



5.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, 'Interbase Delphi')

ou

 VALUES (3, "Interbase Delphi")

De plus si notre valeur est nichée dans une String Pascal, il faut dédoubler les guillemets

IbQuery1.Sql.Add('  VALUES (3, ''Interbase Delphi'')');

Pour simplifier cet imbroglio de guillemets, Delphi propose la méthode QuotedStr:

IbQuery1.Sql.Add('  VALUES (3, 'QuotedStr('Interbase')+ ')');



5.3 - Type NUMERIC

Pour les valeurs numériques avec décimales, nous devons batailler avec les points et les virgules:
  • aux US, la partie entière est séparée de la partie décimale par un point:

        3.1415

    alors qu'en Europe nous disons

        3,1415

  • les valeurs que nous tapons en PASCAL doivent donc respecter la syntaxe US

  • mais si nous utilisons des primitives de conversion interface utilisateur <=> Delphi, Delphi lit les paramètres de localisation dans la base de registre Windows. Pour ma machine il est dit que le séparateur décimal est la virgule. Par conséquent les fonctions telles que:

        FloatToStr

    attendent une virgule

    Nous pouvons imposer le séparateur à utiliser en spécifiant:

DecimalSeparator:= '.';

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 ','


5.4 - Type DATE

Un problème similaire intervient pour les dates:
  • Sql utilise le format US "année mois jour". EXCLUSIVEMENT
  • suivant le type de primitive, Delphi utilisera un format US ou Européanisé (via le panneau de configuration Windows, ou des primitives de formatage)
  • le séparateur doit être un slash / (et non pas un tiret ou autre)

         2004/03/29

  • les dates doivent être entourée de guillemets:

         '2004/03/29'

    car sinon Delphi effectue une division et calcule un Double, assimilé à un tDateTime, et traduit en une date

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:

IbQuery1.Sql.Add('INSERT INTO dates');
IbQuery1.Sql.Add('  (d_numero, d_date');
IbQuery1.Sql.Add('  VALUES (3, ''2004/03/29'')');



5.5 - Automatisation de l'Ajout

Les valeurs à insérer ont été figées dans notre code. Nous pouvons automatiser cet ajout
  • soit par des scripts
  • soit par une procédure amplement paramétrée
  • soit en mode interactif.
Nous allons présenter ici l'utilisation d'une procédure.

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_numberIntegerp_nameStringp_daysIntegerp_costDouble);
      begin
        with Form1IbQuery1 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
            if not IbTransaction1.InTransaction
              then IbTransaction1.StartTransaction;
            ExecSql;
            IbTransaction1.Commit;
            display('  ok');
          except
            on eException do
              display('  pb_insert 'e.Message);
          end;
        end// with IbQuery1
      end// insert_generic

Et voici un exemple de procédure appelante

    procedure TForm1.insert_batch_Click(SenderTObject);
      begin
        IbDatabase1.Open;

        insert_generic(1, 'Initiation Delphi', 3, 1400.40);
        insert_generic(2, 'Bases de Données Delphi', 3, 1.400);
        insert_generic(3, 'Interbase Delphi', 3, 1.400);
        insert_generic(4, 'Composants Delphi', 3, 1.400);
        insert_generic(5, 'UML Delphi', 3, 1.400);
        insert_generic(4, 'Initiation Pascal', 4, 1.900);
      end// insert_batch_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:= ',';




6 - Lire et Afficher

6.1 - 5.1- Principe

Pour afficher un enregistrement, nous devons d'abord récupérer ses valeurs du Serveur.

Pour lire les données contenues dans une table, SQL utilise l'instruction SELECT. Par exemple:

 SELECT f_numero, f_nom
     FROM formations

Lorsque le Serveur reçoit cette requête:

  • il vérifie sa syntaxe
  • il construit une table contenant les valeurs demandées
  • ces données sont envoyées au Client
Plus concrètement:
   créez une nouvelle application et appelez-la "ib_select"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   placez un tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions de lecture:

    procedure TForm1.select_Click(SenderTObject);
      begin
        IbDataBase1.Open;   
        with IbQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('Select * from formations');
            Open;
          end// with Sql  
        end// with IbQuery1
      end// select_Click

   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger le source du projet "ib_select.zip".



6.2 - Comment ça Marche

Lorsque nous exécutons IbQuery1.Open:
  • Delphi envoie la requête SELECT au Serveur
  • celui-ci construit une table résultat, en utilisant, bien sûr, la table FORMATIONS, mais aussi éventuellement des tables annexes (index, contraintes etc.).

  • cette table résultat est envoyée via le réseau au Client Interbase. Celui ci la transmet ensuite à l'application. Delphi place alors ce résultat dans un tampon mémoire associé à tIbQuery:



6.3 - Affichage

Une fois que IbQuery a récupéré les données nous pouvons les afficher ou les traiter en mémoire.

Pour afficher les données nous utilisons:

  • un tDataSource qui récupère les données du tIbQuery
  • un ou plusieurs composants d'affichage relié au tDataSource. Par exemple un tDbGrid.
Par conséquent:
   sélectionnez dans la page "Data Access" de la Palette le composant tDataSource:

et placez le sur la Forme

   sélectionnez dans la page "Data Controls" de la Palette le composant tdbGrid:

et placez le sur la Forme

   sélectionnez sa propriété DataSource et donnez-lui la valeur DataSource1

   compilez, exécutez, et cliquez le bouton qui lance la requête


6.4 - Affichage en mode conception

Nous pouvons aussi lancer la requête de lecture en mode conception. Il faut pour cela que:
  • tIbDatabase soit correctement initialisé
  • tIbQuery soit relié à tIbDatabase
  • la propriété tIbQuery.Sql soit initialisée en utilisant l'Inspecteur
  • la requête soit envoyée en basculant tIbQuery.Active sur True
Donc:
   sélectionnez IbQuery1
   cliquez sur sa propriété Sql
   Delphi ouvre l'éditeur de Sql. A présent les tables créées sont affichées:

   sélectionnez la table "formation"
   les colonnes de cette table sont affichées

   sélectionnez les colonnes que vous souhaitez afficher (ou "*")

   fermez l'éditeur de requête
   sélectionnez Active, et basculez sa valeur sur True
   Delphi affiche les valeurs de la table


6.5 - SELECT

 SELECT est la seule instruction de lecture de données du Serveur. Sa structure générale est:

 SELECT colonnes
      FROM tables
      WHERE conditions

et:

  • colonnes indique quelles colonnes nous voulons voir figurer dans le résultat. Nous pouvons
    • citer explicitement les colonnes souhaitées, dans l'ordre qui nous convient:

       SELECT f_nom, f_numero
            FROM formations

      Nous pouvons ne demander qu'une partie des colonnes (projection)

       SELECT f_nom
            FROM formations

      L'abréviation "*" permet de désigner "toutes les colonnes"

       SELECT *
            FROM formations

    • effectuer des sommes, des moyennes (agrégats)

       SELECT COUNT(*)
            FROM formations

      le résultat est alors une table d'une ligne, une colonne avec le nombre de formations

  • tables contient le noms des tables à utiliser pour calculer le résultat. Par exemple:

     SELECT *
          FROM formations, dates
          WHERE f_nom='UML ET DELPHI'

  • conditions permet
    • de ne retenir que certaines lignes du résultat. Par exemple

       SELECT *
            FROM dates
            WHERE f_date> '2004/05/01'

    • de trier le résultat:

       SELECT *
            FROM dates
            ORDER BY f_date

Ce qu'il faut bien comprendre est que:
  • SELECT calcule un résultat A PARTIR de tables, mais que le résultat n'EST PAS les tables. Lorsque nous demandons une somme ou une moyenne, par exemple, SELECT retourne un nombre.
  • Certes, "SELECT * FROM formations" retourne bien les mêmes données que celle de la table sur disque "formations". Mais ce n'est qu'un instantané réalisé lorsque la requête a été lancée. Il s'agit d'une copie. Si un autre utilisateur ajoute une nouvelle formation la copie que nous avons dans le cache attaché à IbQuery ne le reflète pas
  • pour rafraîchir notre cache, la SEULE façon de faire est de relancer la requête (en fermant et réouvrant tIbQuery)


6.6 - Changement de requête

Notre exemple présente une lecture dans laquelle l'utilisateur tape sa requête dans un tEdit. Rien de bien particulier, sauf que ceci permet d'explorer facilement une table (sans utiliser l'Explorateur de bases de données).



6.7 - Requête paramétrée

Chaque fois que nous souhaitons une information, nous devons exécuter un SELECT.

Lorsque la requête est complexe (plusieurs tables, sous-requête, agrégats etc) le Serveur essaye d'optimiser le calcul du résultat. Cette optimisation peut être longue.

Si nous devons exécuter la requête plusieurs fois de suite, Sql offre la possibilité de scinder la requête en deux:

  • envoyer le squelette de la requête contenant des paramètres
  • fournir la valeur des paramètres et demander le résultat
Dans le premier temps, le Serveur calcule sa méthode d'accès, mais n'exécute rien car il lui manque la valeur des paramètres. Lorsque le Serveur reçoit les paramètres, il lance le calcul en utilisant son plan d'évaluation, et retourne le résultat. Et ceci autant de fois que le squelette de la requête ne change pas.

Pour effectuer ce travail en deux temps:

  • nous rédigeons une requête qui contient des paramètres. En Delphi, ces paramètres sont désignés par un identificateur précédé de ":". Par exemple

     SELECT *
          FROM dates
          WHERE f_date= : ma_date

    Notez que:

    • l'identificateur n'a pas besoin d'avoir le même nom que la colonne
    • les paramètres peuvent seulement apparaître dans la clause WHERE (pas dans les champs ou pour le nom des tables)
    • ":" et ma_date sont collés (pas d'espace entre les deux)
  • la requête est envoyée vers le Serveur par

              IbQuery1.Prepare;

  • lorsque nous souhaitons obtenir un résultat
    • nous initialisons la valeur des paramètres en utilisant tIbQuery.Params

                IbQuery1.Params[0].AsString:= '2004/03/01';

      ou

                IbQuery1.ParamByName('ma_date').AsString:= Edit3.Text;

    • nous lançons la requête par:

                IbQuery1.Open;



En pratique:
   créez une nouvelle application et appelez-la "p_ib_parametrized"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   placez un tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions de préparation:

    procedure TForm1.prepare_Click(SenderTObject);
      begin
        with IbQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('SELECT * FROM formations WHERE f_nom= :le_nom');
          end// with Sql

          Prepare;
        end// with IbQuery1
      end// prepare_Click

   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions qui initialisent les paramètres et ouvrent la table:

    procedure TForm1.close_params_open_Click(SenderTObject);
      begin
        with IbQuery1 do
        begin
          // -- close in case has done a previous request
          Close;

          // -- set the parameter values
          with Sql do
            ParamByName('le_nom').AsString:= parameter_edit_.Text;

          // -- send the parameter to the database
          Open;
        end// with IbQuery1
      end// close_params_open_Click

   compilez, exécutez, et cliquez le bouton qui lance la requête


Vous pouvez télécharger le source du projet "ib_parametrized.zip".




7 - Modifier des Données

7.1 - Principe

Supposons que notre table FORMATIONS contienne les valeurs suivantes:

Pour changer le libellé "Interbase Delphi" en "Interbase" nous utilisons une requête UPDATE:

 UPDATE formations
     SET f_nom= 'Interbase'
     WHERE f_numero= 8

Cette requête qui modifie les données du Serveur est envoyée vers le Serveur en utilisant tIbQuery1.ExecSql



En détail:
   créez une nouvelle application "p_update_data"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   placez un tIbQuery sur la tForme
   sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions de modification:

    procedure TForm1.Button1Click(SenderTObject);
      begin
        with IbQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('UPDATE formations SET f_nom=''Interbase'' where f_numero= 3');
          end// with Sql

          Try
            ExecSql;
          except
            on e:exception do
              display('  *** pb_update'e.Message );
          end// Try...Except
        end// with IbQuery1
      end// Button1Click

   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharge le source du projet ib_update_data.zip".



7.2 - La clause WHERE

Dans l'instruction précédente nous avons spécifié la valeur f_numero à l'aide de WHERE.

Que se passerait-il si nous envoyons:

 UPDATE formations
     SET f_nom= 'Interbase'

Eh bien le Serveur modifierait le nom de TOUS les enregistrements.

Si nous souhaitons limiter les modifications à certaines lignes, il est impératif:

  • que chaque ligne de chaque table ait un identificateur unique pour que nous puissions désigner cette ligne en cas de modification
  • que nous indiquions dans UPDATE les lignes à modifier
Si en revanche nous souhaitons modifier plusieurs lignes à la fois, nous pouvons :
  • omettre WHERE. Par exemple, pour passer tous les noms en majuscule:

     UPDATE formations
         SET f_nom= UPPER(f_nom)

  • changer plusieurs lignes satisfaisant une condition. Pour effectuer une réduction promotionnelle de 5% sur les prix inférieurs à 1400 euros :

     UPDATE formations
         SET f_prix= f_prix* 0.95
         WHERE WHERE f_prix<= 1400.00

    Toutefois:

    • ne répétez pas cette requête de réduction trop souvent: il faut bien que je mange un peu !
    • nous faisons des conditions tarifaires particulières (groupes, formations intra, étudiant, particulier, réinsertion...), que nous sommes prêts à vous présenter si vous me téléphonez directement à l'Institut Pascal au 01.42.83.69.36.
Il est donc fondamental de bien comprendre qu'un UPDATE sans clause WHERE porte sur tous les enregistrements.



7.3 - UPDATE et AFFICHAGE

Ce fonctionnement de UPDATE est particulièrement important lorsque nous modifions un enregistrement affiché à l'écran dans un dbGrid: nous pensons que Interbase sait que nous "modifions l'enregistrement courant". Or ce concept d'enregistrement courant n'existe pas en mode SQL:
  • le dbGrid affiche le résultat obtenu par une requête SELECT
  • or SELECT calcule un résultat "instantané". Nous avons vu que SELECT peut supprimer des colonnes (projection), retirer des lignes (filtrage), effectuer des moyennes ou calculer un résultat à partir de plusieurs tables.

    Le résultat du calcul est envoyé par le Serveur vers le Client, et le Serveur ne garde plus aucune trace de ce qu'il a envoyé. Un autre utilisateur peut même modifier les tables sans que notre Client en soit informé

  • par conséquent toute modification du résultat ne pourait être que local.
  • en fait par défaut le résultat est non-modifiable (ReadOnly). Et si nous essayons de taper une nouvelle valeur au clavier, Delphi refuse de modifier le champ du dbGrid.
La seule façon de modifier une valeur affichée de façon interactive est de déclencher une requête UPDATE en utilisant un composant tIbUpdateSql. Le plus simple pour synchroniser les modifications est d'utiliser un composant tIbUpdateSql.



7.4 - tIbUpdateSql

Ce composant a pour vocation de contenir la requête SQL qui doit être exécutée lorsque le tIbQuery doit être modifié. Cette requête est placée dans tIbUpdaateSql.ModifySql.

Automatisons par exemple la mise à jour de f_nom lorsque nous tapons une valeur dans un tDbGrid:
   créez une nouvelle application et appelez-la "p_update_sql"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   placez un tIbQuery sur la tForme
  • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
  • sélectionnez sa propriété Sql et placez-y la requête

     SELECT *
          FROM formations

   ajoutez un composant tDataSource
  • sélectionnez DataSet et donnez-lui la valeur IbQuery1
   reliez DataSource1 à un tDbGrid:
  • placez un tdbGrid de sur la Forme
  • sélectionnez sa propriété DataSource et reliez la à DataSource1
   ouvrez IbQuery1 en basculant Active sur True
   ajoutez un tIbUpdateSql:
  • sélectionnez la page "Interbase" de la Palette, sélectionnez le composant tIbUpdateSql

    et posez-le sur la Forme

  • connectez le IbQuery1 et IbUpdateSql:
    • en initialisant IbQuery1.UpdateObject avec IbUpdateSql1
    • en basculant IbQuery1.CachedUpdate à True
  • sélectionnez sa propriété IbUpdateSql1.ModifySql, cliquez sur l'ellipse pour ouvrir l'éditeur de ModifySql (c'est un éditeur de tStrings classique):

  • tapez-y la requête de modification

     UPDATE formations
         SET f_nom= :f_nom
         WHERE f_numero= :f_numero

   compilez, exécutez:
  • Sélectionnez dbGrid1, tapez une valeur dans la colonne F_NOM.
  • Notez bien que le dbGrid permet à présent la modification de valeurs (alors que sans tIbUpdateSql ces modifications étaient impossibles).
  • lorsque nous quittons la ligne (en changeant de composant, ou en changeant de ligne dans le dbGrid), la requête de modification est envoyée vers le Serveur


Notez que:

  • pour désigner les valeur des champs à utiliser, nous avons employé une requête paramétrée, en désignant les paramètres par le nom du champ:

              f_nom= :f_nom

  • nous aurions pu modifier plusieurs colonnes

     UPDATE formations
         SET f_nom= :f_nom, f_jours:= :f_jour, f_prix= :f_prix
         WHERE f_numero= :f_numero



Vous pouvez télécharger le projet "ib_update_sql.zip".



7.5 - "Old" values

Nous avons utilisé f_numero pour identifier la ligne à modifier. Mais que se passe-t-il si nous souhaitons modifier la clé elle-même ? En effet :f_numero désigne la valeur actuelle, qui est la valeur modifiée, et non pas la valeur qui permet de désigner la ligne dans la table.

Pour résoudre ce problème, Delphi a crée le concept des "old_values", correspondant à la valeur que le champ avait lors de la lecture de la table. Et le paramètre pour désigner la valeur se note :old_f_numero

Si nous souhaitons pouvoir modifier tous les enregistrements, nous utilisons donc dans tIbUpdateSql.ModifySql une requête du type:

 UPDATE formations
     SET f_numero= :f_numero, f_nom= :f_nom
     WHERE f_numero= :old_f_numero

Ce concept de "old values" est fondamental pour la mise à jour concurrente que nous présenterons voir ci-dessous.



7.6 - Insertion, Effacement

Notez que:
  • tIbUpdateSql possède aussi des propriétés InsertSql et DeleteSql qui peuvent contenir les instructions à éxécuter lorsque nous ajoutons un nouvel enregistrement (en ajoutant une ligne au bas du tDbGrid), ou que nous en effaçons un (par Ctlr Suppr sur une ligne du tDbGrid)
  • le composant tIbUpdateSql possède un éditeur de composant permettant un écriture plus automatique des requêtes SQL. Cliquez deux fois sur IbUpdateSql1 et Delphi présentera:





8 - Effacer de Données

8.1 - Principe de l'effacement

Pour effacer l'enregistrement ayant le numéro 8, nous exécutons une requête

 DELETE
     FROM formations
     WHERE f_numero= 3

En détail:

  • créez une nouvelle application "p_delete_data"
  • placez un tIbTransaction sur la Forme
  • placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
    Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
    Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
  • placez un tIbQuery sur la tForme
    • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
  • placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions de modification:

        procedure TForm1.delete_edit_Click(SenderTObject);
          begin
            with IbQuery1 do
            begin
              Close;

              with Sql do
              begin
                Clear;
                Add(DELETE FROM formations WHERE f_numero= 3);
              end// with Sql

              Try
                ExecSql;
              except
                display('< *** pb_update');
              end// try...except
            end// with IbQuery1
          end// update_edit_Click

  • compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger le source du projet ib_delete_data.zip".



De façon naturelle:

  • comme pour UPDATE, il faut veiller à spécifier les enregistrements à effacer dans WHERE. Une requête sans WHERE peut effacer toutes les lignes. Et sans aucun "undelete". A bon entendeur...
  • nous pouvons utiliser un tIbUpdateSql en plaçant dans tIbUpdateSql.DeleteSql la requête du type:

     DELETE
         FROM formations
         WHERE f_numero= :old_f_numero




9 - Plusieurs Tables

9.1 - La Normalisation des Tables

Lorsque nous créons une application, nous répartissons les données dans des tables: les factures, les articles, les adresses des fournisseurs etc.

Cette activité de répartition peut devenir très complexe. Imaginez la modélisation de l'activité de Renault ou des Galeries Farfouillettes: faut-il placer l'adresse des fournisseurs avec les pièces du stock, ou créer un table séparée, combien de tables faut-il alors créer etc.

Pour des projets plus modestes, la répartition est souvent intuitive, et se résume souvent à adapter des structures utilisées dans d'autres projets.

Néanmoins, les données utilisées par SQL doivent obéir à un nombre minimal de contraintes:

  • nombre de colonnes
  • redondance / dépendances
Prenons le cas simple de formations dont le calendrier est le suivant (le vrai calendrier se trouve à <%dates_des_formations>):

 
Mars
Avril
Mai
Initiation Delphi:
2 au 4 - Metz
   
Delphi Interbase:  
6 au 8 - Paris
4 au 6 - Toulouse
Delphi UML:
23 au 25 - Paris
20 au 22 - Châlons
25 au 27 - Lyon

9.1.1 - Même nombre de colonnes

La première exigence est simple: chaque enregistrement de chaque table relationnelle doivent avoir le même nombre de colonnes.

Dans notre cas, toutes les formations n'ont pas nécessairement le même nombre de sessions. La première exigence SQL n'est pas respectée: le premier enregistrement aurait 2 colonnes, le second 3 etc.

La solution est simple: il suffit de créer un enregistrement séparé pour chaque date.


9.1.2 - Non redondance - Anomalie de mise à jour

Supposons que nous souhaitions modifier le nom "Interbase Delphi" en "Delphi Interbase". Il faudra veiller à modifier tous les enregistrements contenant cette valeur. Le risque est donc des modifications partielles ou incohérente (anomalie de mise à jour).

Le plus simple est alors de placer les valeurs mentionnées à plusieurs endroits dans une table séparée avec un code, et d'utiliser ce code pour référencer la valeur commune dans les autres tables.

Dans notre cas:

  • nous créons une table avec un code (arbitraire) de formation et le nom de la formation:

  • nous créons une table avec les dates et les villes, en plaçant le code de la formation pour désigner le nom des formations


9.1.3 - Anomalie de suppression

D'autre part, si le stage de Mars a eu lieu, nous effaçons la ligne "1, 2004/03/02, Metz". Ceci supprime aussi le fait que nous réalisons des stages à Metz.

Le fait que la suppression d'une ligne fasse disparaître une information est appelé "anomalie de suppression".

Si nous souhaitons présenter les villes où les stages peuvent avoir lieu, indépendamment de sessions programmées dans les trois prochains mois, il faut séparer les villes dans une table différente:

  • une table avec les villes possibles:

  • une table avec les dates et le lieu:




D'où la configuration finale:
  • les types de formations:

  • les villes où les sessions peuvent avoir lieu

  • les types de stage, dates et le lieu:




9.2 - Jointure

Ayant réparti les données dans plusieurs tables pour répondre aux exigences SQL et au souci de non-redondance / absence d'anomalies, nous avons à présent l'obligation de recoller les morceaux pour reconstituer l'information de départ: pour savoir où et quand se déroulent les formations "Interbase" nous devons:
  • cherche le code de la formation "Interbase" dans la table "formations": c'est le code 3
  • trouver les dates et lieux dans la table "dates": (3, '2004/04/06', 101) et (3, '2004/05/04', 102)
  • finalement le nom de la ville est trouvé dans la table "ville": 101 c'est Paris et 102 Toulouse
La solution standard proposée par SQL est la jointure: nous effectuons un SELECT en demandant de reconstituer une table en ajoutant les colonnes provenant de plusieurs tables:
  • pour un premier essai, nous mélangeons les deux tables "formations" et "dates":

     SELECT *
          FROM formations, dates

  • nous obtenons:

    Le Serveur retourne toutes les combinaisons de valeurs de la première table avec toutes les valeurs de la seconde. Il faut limiter le résultat aux lignes où f_numero est égal à d_formation (même code de stage)

  • voici le second essai:

     SELECT *
          FROM formations, dates
          WHERE f_numero= d_formation

  • le résultat est:

  • maintenant que les lignes sont correctement associées, il est inutile d'afficher deux fois le numéro du stage. Nous pouve effectuer une projection en supprimant la colonne d_formation du résultat:

     SELECT f_numero, f_nom, d_date, d_ville
          FROM formations, dates
          WHERE f_numero= d_formation

  • comme nous ne nous intéressons qu'à Interbase, il faut éliminer les lignes ne concernant pas interbase (filtrage) en ajoutant une condition à WHERE:

     SELECT f_numero, f_nom, d_date, d_ville
          FROM formations, dates
          WHERE f_numero= d_formation AND f_nom= 'Interbase Delphi'

  • finalement pour voir le nom de la ville, il faut faire entrer la troisième table dans la danse:

     SELECT f_numero, f_nom, d_date, v_nom
          FROM formations, dates
          WHERE f_numero= d_formation
              AND d_ville= v_numero
              AND f_nom= 'Interbase Delphi'




Notez toutefois que:
  • le résultat n'est pas très satisfaisant: le nom du stage est répété à chaque ligne
  • le calcul est de plus inefficace: le Serveur ne peut nous retourner le résultat que lorsque tout le calcul est réalisé.


L'exemple de la jointure peut être téléchargé ici "ib_join.zip".



9.3 - Relation Maître Détail

Pour éviter le calcul massif de la jointure, Delphi propose d'utiliser plutôt le mécanisme Maître / Détail.

Le principe est le suivant:

  • nous sélectionnons un enregistrement de la table Maître "formations"

  • cet enregistrement contient le numéro de formation, qui permet de retrouver dans la table Détail "dates" les dates pour ces formations

  • il suffit alors de lancer un SELECT sur ces dates, pour récupérer les dates de ces formations

9.3.1 - Maître Détail manuel

Nous pouvons provoquer le calcul Maître Détail chaque fois que nous détectons une modification d'enregistrement dans la table Maître: c'est l'événement DataSourceMaître.OnDataChange qui nous signale ce changement. Par conséquent:

Par conséquent:
   créez une nouvelle application et appelez-la "p_master_detail"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   pour le Maître:
  • placez un tIbQuery sur la tForme
    • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
    • sélectionnez sa propriété Sql, ouvrez l'éditeur, et tapez:

       SELECT f_numero, f_nom
            FROM formations

    • sélectionnez Active et basculez sa valeur sur True
  • placez un tDataSource sur la Forme
    • sélectionnez DataSet et initialisez sa valeur à IbQuery1
  • placez un tdbGrid sur la Forme
    • sélectionnez DataSource et initialisez sa valeur à DataSource1
  • les données de la table "formation" apparaissent
   pour le Détail:
  • placez un second tIbQuery sur la tForme
    • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
  • placez un tDataSource sur la Forme
    • sélectionnez DataSet et initialisez sa valeur à IbQuery2
  • placez un tdbGrid sur la Forme
    • sélectionnez DataSource et initialisez sa valeur à DataSource2
   provoquez la sélection de Détail en fonction du Maître
  • sélectionnez DataSource1 (celui du MAITRE)
  • créez son événement OnDataChange, et tapez-y la requête

        procedure TForm1.DataSource1DataChange(SenderTObjectFieldTField);
            // -- when the MASTER datasource changes, select the details  
          begin
            with IbQuery2 do
            begin
              Close;

              with Sql do
              begin
                Clear;
                Add('SELECT d_formation, d_date'
                   + '  FROM dates'
                   + '  WHERE d_formation= 'IbQuery1.FieldByName('f_numero').AsString);
              end// with Sql

              Open;
            end// with IbQuery1
          end// TForm1.DataSource1DataChange

  • petite précaution: lorsque la Forme sera créée, IbQuery1 sera ouverte, les lignes remplies, et DataSource1.OnDataChange appelé. Il est donc impératif que IbQuery2 existe AVANT IbQuery1. Pour vous assurez que c'est le cas, sélectionnez dans le menu "Edit" le sous-menu "Creation Order" et déplacez les composants pour que cette contrainte soit respectée:


   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger ce projet "ib_master_detail.zip".



9.3.2 - Utilisation de tQuery.DataSource

Nous pouvons demander à Delphi de réaliser le SELECT du détail automatiquement en initialisant la propriété tIbQueryDetail.DataSource du Détail vers DataSourceMaître:
   créez une nouvelle application et appelez-la "p_master_detail_automatic"
   placez un tIbTransaction sur la Forme
   placez un tIbDataBase sur la Forme. Cliquez deux fois sur IbDataBase1, renseignez "local", "DataBase", "User Name" "Pass Word" et "Login Prompt".
Vérifiez la connection en basculant IbDataBase1.Connected sur True, puis fermez la connection.
Sélectionnez DefaultTransaction et initialisez-la à IbTransaction1
   pour le Maître:
  • placez un tIbQuery sur la tForme
    • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
    • sélectionnez sa propriété Sql, ouvrez l'éditeur, et tapez:

       SELECT f_numero, f_nom
            FROM formations

    • sélectionnez Active et basculez sa valeur sur True
  • placez un tDataSource sur la Forme
    • sélectionnez DataSet et initialisez sa valeur à IbQuery1
  • placez un tdbGrid sur la Forme
    • sélectionnez DataSource et initialisez sa valeur à DataSource1
  • les données de la table "formation" apparaissent
   pour le Détail:
  • placez un second tIbQuery sur la tForme
    • sélectionnez sa propriété DataBaseName et initialisez-la à IbDataBase1
  • placez un tDataSource sur la Forme
    • sélectionnez DataSet et initialisez sa valeur à IbQuery2
  • placez un tdbGrid sur la Forme
    • sélectionnez DataSource et initialisez sa valeur à DataSource2
   provoquez la sélection de Détail en fonction du Maître
  • sélectionnez IbQuery2 (le DETAIL)
  • sélectionnez sa propriété DataSource et initialisez la à DataSource1 (le tDatasource du MAITRE)
  • sélectionnez sa propriété Sql, ouvrez l'éditeur, et tapez y la requête de sélection:

     SELECT d_formation, d_date
          FROM dates
          WHERE d_formation= :f_numero

  • sélectionnez Active et basculez sa valeur sur True
  • vous verrez les deux table, et en mode conception


Notez que:
  • c'est le tQuery du DETAIL qui désigne le tDataSource du Maître, et non l'inverse. Dans tout Delphi, dès que nous souhaitons associer 1 composants à n autres composants, la liaisons est toujours réalisée au niveau de l'Inspecteur d'Objets de n vers 1.
  • il arrive que par mégarde nous fassions pointer la propriété DataSource d'un tDataSet vers son tDataSource associé, ce qui est une erreur. Peut être aurait-on pu appeler cette propriété MasterSource (comme cela est fait à d'autres endroits)
  • notez que le Détail récupère la valeur du Maître par une requête paramétrée:

     SELECT d_formation, d_date
          FROM dates
          WHERE d_formation= :f_numero

    Notez aussi que le paramètre :f_numero a pour nom le NOM DU CHAMP du maître (alors qu'auparavant le nom du paramètre pouvait être arbitraire). C'est le fait que ce nom désigne la colonne de jointure qui évite l'initialisation par Param_xxx




10 - Télécharger les Exemples

Nous avons placé tous les projets dans des .ZIP qui comprennent:
  • le .DPR, la forme principale, les formes annexes eventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP contiennent des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.

Voici les .ZIP:



Comme d'habitude:
  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



11 - Conclusion

Lors de ce tutorial Interbase, nous avons présenté comment installer, créer une base, des tables, ajouter, lire, modifier et effacer des données.

Si vous avez des questions, vous pouvez:

  • vous reporter au livre Delphi dBase qui présente, par le détail, la gestion des composants visuels et les techniques de traitement des données (formatage, vérfications, calculs...)
  • pour les blobs, voyez Blobs Interbase
  • vous pouvez aussi m'adresser vos questions par e-mail à jcolibri@jcolibri.com
Vous pouvez aussi consulter sur ce site d'autres articles concernant Interbase:
  • le traitement multi-niveau qui utilise les couches dbExpress / DataSnap plutôt qu' Interbase Express (IBX): l'article Interbase dbExpress vous présente ces techniques, ainsi que des schémas faisant comprendre la différence entre les architectures API, BDE, IBX, dbExpress.
  • la présentation de l'architecture du moteur, analysée à partir de la version Open Source de Borland
  • la présentation des versions d'Interbase disponibles sous C# (VCL.NET, WINDOWS FORMS) de Delphi 8:
    • voyez les présentations à Borcon 2004
    • Interbase dbExpresss: le mode dbExpress (Delphi 6, dbExpress). Le mode qui permet le mieux de comprendre l'architecture Ado.Net qui en est directement issue
    • Interbase Ibx.Net: le portage sous .Net de la mécanique Ibx (Delphi 8, Ibx.Net). Le moyen le plus simple d'utiliser Interbase et Delphi 8. Contient aussi un comparatif de toutes ces architectures avec les schémas correspondants
    • Interbase dbExpress.Net: le portage sous .Net de la mécanique dbExpress (Delphi 8, dbExpress.Net). L'utilisation des techniques VCL pour Interbase ET pour les autres serveurs (Oracle, Sql Server, MyBase etc)
    • Delphi 8 Ado.Net: sous Windows Forms, en l'absence de couche spécifique à Interbase nous pouvons utiliser une mécanique similaire à Odbc et proche d'Ado sous Win32. Cet article présente ce fonctionnement
    • Interbase Borland Data Provider: la voie royale sous Windows Forms, et pour toutes les base, même pour tous les langages
  • et pour le développement avec Delphi 2005 en ADO.Net, nous avons un tutorial complet (similaire à celui-ci, mais en ADO.Net):
    • firebird_ado_net_tutorial: ADO .Net Tutorial, utilsant Firebird. Une introduction complète au développement ADO .Net, utilisant SqlConnection, SqlCommand, SqlDataAdapter pour se connecter à un Serveur, exécuter directement du SQL, travailler avec des tables en mémoire, utiliser des DataGrids pour afficher et modifier les données. Très nombreux schémas et code source complets
    avec une Formation Delphi 2005 .Net où 2 jours sont entièrement consacrés à ADO.Net.


Nous n'avons pas essayé d'être exhaustif dans la présentation. Lors des stages que j'anime personnellement à l'Institut Pascal, j'entre beaucoup plus dans le détail:
  • la gestion des multiples erreurs et exceptions qui interviennent lors d'applications utilisant des bases de données
  • les possibilités d'installer Interbase depuis Delphi, et d'interroger les paramètres de la Base ou du schéma depuis un programme Delphi, de lister les tables, les champs etc
  • le traitement des contraintes (clé primaires, intégrité référentielle, intégrité sémantique)
  • la manipulation détaillée des valeurs extraite d'une Table, en utilisant les tFields et les différents composants visuels tDb_xxx, ainsi que les traitements spécifiques aux chaînes, aux dates, aux blobs
  • les techniques de validation de saisie essentielles pour toute application de gestion
  • l'étude approfondie du langage SQL, qui est au coeur de tout traitement avec des Serveurs Sql
  • les triggers, les procédures cataloguées (tStoredProc), les générateurs
  • la gestion d'utilisateurs multiples, avec les paramétrages des modes d'isolation des transaction Interbase
  • la gestion de Serveur (sauvegarde / restauration, statistiques, réglage)
Pour examiner le programme détaillé de cette formation, cliquez Client Serveur Interbase. Nous proposons de plus d'autres formations présentées dans ces pages (programme, dates des prochaines sessions, conditions, transparents...).

Finalement j'interviens aussi en tant que consultant pour des missions d'audit, de réalisation de projet ou de tutorat.




12 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2020
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
      + programmation_oracle
      + interbase
        – interbase_blobs
        – interbase_tutorial
        – interbase_dbexpress
        – interbase_ibx_net
        – ib_dbexpress_net
        – delphi_8_ado_net
        – borland_data_provider
        – sql_script_extraction
        – interbase_udf
        – sql_script_executer
        – ib_blob_extraction
        – insert_blob_script
        – ib_stored_procedures
      + sql_server
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + services_web_
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Formation Mise à Niveau Consolider la programmation objet, l'écriture de composants, l'accès aux bases de données, le multi-tâche - 3 jours
Formation Perfectionnement Delphi Les techniques avancées : la programmation objet, l'écriture de composants, l'accès aux bases de données, Xml, le multi-tâche, la programmation Internet - 5 jours