meta data for this page
  •  

Import & export using IBExpert (1)

Basics for this tutorial

We read a lot about people searching in the online forums for faster Firebird import or export operations. So we have decided to show the reader ways to improve the speed using some perhaps not so well-known technologies.

All files are included in the attached zip file, so you do not need to copy all objects from this text. The demo mentions some IBExpert tools and technologies, but it can also be used with other tools like isql; however they are - in most cases - not as comfortable.

We have used IBExpert 2011.04.03 Developer Studio in this article, available here: order online and Firebird 2.5, available from www.firebirdsql.org. The attached database file db1.fdb can be registered in your IBExpert IDE after installing Firebird and IBExpert.

1. Prepare firebird.conf

Before we can use the external file technology provided by Firebird, we need to change the firebird.conf file, which is located in the firebird main directory. Open the file with a text editor and change the line with ExternalFileAcess to use the directory, where you wish to have the tutorial files. Important: Remove the comment sign # at the beginning; otherwise it will still be ignored.


   
 # External File Paths/Directories
 
 # ExternalFileAccess may be None, Full or Restrict. If you choose
 # Restrict, provide ';'-separated trees list, where external files
 # are stored. Relative paths are treated relative to RootDirectory entry
 # (see above). Default value 'None' disables any use of external files
 # on your site. To specify access to specific trees, enum all required
 # paths (for Win32 this may be something like 'C:\ExternalTables',
 # for unix - '/db/extern;/mnt/extern').
 #
 # NOTE: THE EXTERNAL TABLE ENGINE FEATURE COULD BE USED TO COMPROMISE
 # THE SERVER/HOST AS WELL AS DATABASE SECURITY!!
 #
 # IT IS STRONGLY RECOMMENDED THAT THIS SETTING BE USED TO LIMIT
 # EXTERNAL TABLE LOCATIONS!
 #
 # Type: string (special format)
 #
 ExternalFileAccess = D:\IBEImportExport_Tutorial

2. Restart the Firebird service

Restart Firebird service to be sure that the firebird.conf file is now used with the changed parameter.

3. Register and open the database in IBExpert

Register and open the database in IBExpert and have a look at the target table:

 CREATE TABLE PRODUCT (
     ID           NUMERIC(18,0) NOT NULL,
     CATEGORY_ID  NUMERIC(18,0) NOT NULL,
     TITLE        VARCHAR(50) NOT NULL,
     ACTOR        VARCHAR(50) NOT NULL,
     PRICE        NUMERIC(12,2) NOT NULL,
     SPECIAL      SMALLINT
 );

This is a very basic structure purely for the purposes of this tutorial.

4. Create an import table

Create an import table that has the same structure, but replace all binary columns by char columns, to be able to import readable text files. If you have Windows Fileformat, reserve two bytes for Carriage Return Line Feed CRLF. On Linux it is only one byte.

In the demo database the import table has already been created. If you need it on a different path, first delete the procedure sp_import_product and then delete the table import_product.

All steps necessary to recreate the objects again are taken within the next lines.

Here is the source code to create the import table, this can be executed in the IBExpert SQL Editor in the Tools menu:

 CREATE TABLE IMPORT_PRODUCT external file 'D:\IBEImportExport_Tutorial\FixedFormatData.txt' (
     ID           CHAR(18),
     CATEGORY_ID  CHAR(18),
     TITLE        CHAR(50),
     ACTOR        CHAR(50),
     PRICE        CHAR(12),
     SPECIAL      CHAR(5),
     CRLF         CHAR(2)
 );

The external file part is followed by a file name, which is used to store the table data outside the database file. An external table can be used for insert and select statements, delete and update operations are not supported.

5. Data to be imported

Have a look at the data to be imported. In this example we have used a CSV file with 100,000 lines to see the speed of this technology in the file rawdata.csv.


 ID;CATEGORY_ID;TITLE;ACTOR;PRICE;SPECIAL
 1;11;CLOSER TYCOON NATURAL;DORIS DAVIS,  AL WAHLBERG;26.31;0
 2;7;OPERATION ELEMENT HANOVER;JON MOSTEL,  DREW DICAPRIO;28.01;0
 3;9;ELEPHANT RESURRECTION GOODFELLAS;PENELOPE BULLOCK,  CHRIS STREEP;17.73;0
 4;13;PEAK BAREFOOT DUFFEL;RAY SUVARI,  TOM TAUTOU;23.96;0
 5;10;SAINTS TEXAS ENOUGH;SHIRLEY SANDLER,  ELLEN LOREN;25.40;0
 6;14;WARLOCK ANGELS VICTORY;HARRISON WOOD,  RIVER ZELLWEGER;14.82;0

This is a CSV format, which cannot directly be used for an external file, since it has no fixed length for each column. Important: you will not be able to open this in Excel, because when Excel opens a CSV file with “ID;” as the first characters, it thinks that this is a different format. OpenOffice or LibreOffice do not have this problem. Also, older Excel versions are unable to open any file with more than 65k lines. So using Excel is definitely a somewhat limited method to check if your data is correct.

6. Convert the CSV to a fixed format

There are several ways to convert a CSV file into the external file format, which can be used in Firebird. As an example you can see here a script based on IBExpert IBEBlock technology, that does exactly this job, with some comments. Important: IBEBlock commands are only supported in the IBExpert Developer Studio full versions, not in the IBExpert Personal Edition.

When you have registered the attached demo database in IBExpert, you can simply open the database, press [F12] to open the SQL Editor and copy the following part into it:

(ConvertCsvToFixed.sql)


 execute ibeblock
 as
 --Declare the variables, in ibeblock this is optional, but sometimes useful
 DECLARE VARIABLE ID CHAR(18);
 DECLARE VARIABLE CATEGORY_ID CHAR(18);
 DECLARE VARIABLE TITLE CHAR(50);
 DECLARE VARIABLE ACTOR CHAR(50);
 DECLARE VARIABLE PRICE CHAR(12);
 DECLARE VARIABLE SPECIAL CHAR(5);
 declare variable Line char(153);
 begin
   i = 0;  --just a counter 
   StartTime=ibec_GetTickCount();   --remember the starting time
   --delete the Fixedformat file if it exists
   if (ibec_FileExists('D:\IBEImportExport_Tutorial\FIXEDFORMATDATA.TXT'))
   then ibec_DeleteFile('D:\IBEImportExport_Tutorial\FIXEDFORMATDATA.TXT');  
 
   --create a filehandle for the input file, in this case the CSV file, that we want to import
   RawData = ibec_fs_OpenFile('D:\IBEImportExport_Tutorial\RawData.csv', __fmOpenRead);
   --create a filehandle for the output file, create an empty file
   FixedData = ibec_fs_OpenFile('D:\IBEImportExport_Tutorial\FIXEDFORMATDATA.TXT', __fmCreate);
   ibec_fs_CloseFile(FixedData); --close and write the empty file
   --and reopen it for write operations
   FixedData = ibec_fs_OpenFile('D:\IBEImportExport_Tutorial\FIXEDFORMATDATA.TXT', __fmOpenWrite);
 
   --all is fine, now we start the processing
   if (not RawData is null) then
   begin
     s = ibec_fs_Readln(RawData);                        --read the first line to step over the captions
     while (not ibec_fs_Eof(RawData)) do                 --now start the loop for all lines in input
     begin
       s = ibec_fs_Readln(RawData);                      --read next line  
       ValCount =   ibec_ParseCSVLine(Vals, s, '', ';', __csvEmptyStringAsNull);  --copy data to an array
       ID=          ibec_Copy(Vals[0]+'                  ',1,18);                 --we know that we get 6 columns and all must be filled with spaces
       CATEGORY_ID= ibec_Copy(Vals[1]+'                  ',1,18);
       TITLE=       ibec_Copy(Vals[2]+'                                                  ',1,50);
       ACTOR=       ibec_Copy(Vals[3]+'                                                  ',1,50);
       PRICE=       ibec_Copy(Vals[4]+'            ',1,12);
       SPECIAL=     ibec_Copy(Vals[5]+'     ',1,5);
       line=ID+CATEGORY_ID+TITLE+ACTOR+PRICE+SPECIAL;                             --combine the columns with spaces, but without delimiter
       ibec_fs_Writeln(FixedData,line);                                           --write this new line to the output file
       i = i + 1;                                                                 --increment the counter 
       if (ibec_mod(i,1000)=0) then ibec_Progress(I);                             --every 1000 operations send a message to the screen
     end
     ibec_fs_CloseFile(RawData);                         --after loop has ended, close input and output file
     ibec_fs_CloseFile(FixedData);
   end
   EndTime=ibec_GetTickCount();
   TotalTime=EndTime-StartTime;
   ibec_Progress('finished in '||TotalTime||' ms');      --display the required time
   ibec_Pause(1000);                                     --when working in ibexpert, the result stays for 1 second on the line above the sql editor
 end

After the script is loaded in the SQL Editor, execute it by pressing [F9].

And that's it. On my test machine this conversion took about 8 seconds.

What happens if you do not want to execute this in interactive mode, but in a batch mode? No problem, simply save the above script to file, for example as D:\IBEImportExport_Tutorial\ConvertCsvToFixed.sql and start it from a batch file with ibescript.exe, which can be found in the IBExpert main directory:

C:\program Files\HK-Software\IBExpert\ibescript.exe D:\IBEImportExport_Tutorial\ConvertCsvToFixed.sql

IBExpert allows also execution of the same functionality using the ibescript.dll library directly from your own application, when you have IBExpert Distribution software].

The method using IBEBlock is only one of the solutions to convert the data from CSV to a fixed format; you can use any other programming tools to do the same job, more or less comfortable and fast.

When you want to learn more about the ibec_* functions, just go with the cursor in the IBExpert SQL Editor on any keyword and press [F1], the documentation will open automatically for this keyword.

7. Next steps

We now have the data prepared for use inside an external table. In IBExpert we can now open the Data page in the table IMPORT_PRODUCT. We see the data from the external file. So now we can think about what we have to do with the data. A stored procedure is a good choice. Here is an example, again with some comments:

(sp_import_product.sql)


 create or alter procedure SP_IMPORT_PRODUCT
 as
 --declare the required variables
 declare variable ID char(18);   
 declare variable CATEGORY_ID char(18);
 declare variable TITLE char(50);
 declare variable ACTOR char(50);
 declare variable PRICE char(12);
 declare variable SPECIAL char(5);
 declare variable CNT integer;
 begin
   --start a for select  loop over all records in the import table and put the data into variables
   for
     select ID, CATEGORY_ID, TITLE, ACTOR, PRICE, SPECIAL
     from IMPORT_PRODUCT
     into :ID, :CATEGORY_ID, :TITLE, :ACTOR, :PRICE, :SPECIAL
   do
   begin
     --is this record already there? 
     select count(*) from product where product.id=:id into :cnt;
     if (cnt=0) then
     begin
       --no, so do an insert
       insert into PRODUCT (ID, CATEGORY_ID, TITLE, ACTOR, PRICE, SPECIAL)
       values (:ID, :CATEGORY_ID, :TITLE, :ACTOR, :PRICE, :SPECIAL);
     end
     else
     begin
       --yes, so update the record 
       update PRODUCT
       set CATEGORY_ID = :CATEGORY_ID,
           TITLE = :TITLE,
           ACTOR = :ACTOR,
           PRICE = :PRICE,
           SPECIAL = :SPECIAL
       where (ID = :ID);
     end
   end
   --thats all
 end

How to create this procedure? Simply copy it again to IBExpert´s SQL Editor, execute it with [F9], commit your operation and you will see the procedure on the left side in the Database Explorer.

In the SQL Editor we executed the definition of the procedure, but the procedure itself was not executed. If you double-click on the entry in the Database Explorer, the procedure is opened in the Procedure Editor. When pressing [F9] here, it is executed. Don´t forget to commit your operation.

If it does not work as expected, click on the Debugger tool button or [F8] in the Stored Procedure editor and you can debug it using all important functionalities such as breakpoints, step in, step over, etc. If you have data, for example with the wrong format such as a wrong decimal, the best way is to check it in the first conversion step. However if you prefer it in the stored procedure, there are also several possibilities to avoid import errors.

I know that Firebird also has powerful commands like UPDATE OR INSERT, but this tutorial is only intended to give you some hints for the initial steps and approach, rather than a 1,000-page reference.

8. How to execute the procedure from the script that converts the file

Just add the following lines at the end of the script:

(ConvertCsvToFixed2.sql)


    end
    ibec_fs_CloseFile(RawData);        --after loop has ended, close input and output file
    ibec_fs_CloseFile(FixedData);
  end 
  
  --this part is new
  ibec_Progress('Execute procedure sp_import_product');  --simply a hint on the screen
    DB = ibec_CreateConnection(__ctInterBase,              --define a connection to the database
                             'DBName="LOCALHOST:D:\IBEImportExport_Tutorial\DB1.FDB";
                             ClientLib=gds32.dll;
                             User=SYSDBA; Password=masterke; Names=NONE; SqlDialect=3;');
  use db;                                                --and open the connection
  execute procedure sp_import_product;                   --execute the procedure
  commit;                                                --commit the transaction
  ibec_CloseConnection(db);                              --close the connection
  --this part was new 
 
  EndTime=ibec_GetTickCount();
  TotalTime=EndTime-StartTime;
  ibec_Progress('finished in '||TotalTime||' ms');      --display the required time
  ibec_Pause(1000);                                     --when working in ibexpert, the result stays for 1 second on the line above the sql editor
end

On my machine, the import took 21 seconds for processing 100,000 records, I used Firebird 2.5×64 on Windows 7×64.

9. Conclusion

You can also use several other methods to import data into a Firebird database, but try to avoid TTable or TDataset append methods when working with Delphi or CPPB to avoid wasting time. When doing an import from a client programming environment, for example Delphi, CPPB, VS, Java or any other, try to use active transaction control and prepared queries, otherwise it will be slow. Do NOT deactivate forced writes to improve the speed, because this can result in corrupt databases, when there is, for example, an endless loop in your import process and you have to restart the Firebird server. When using active transaction control, the speed should make much difference whether forced writes is on or off.

This example can also be easily altered to perform a similar export of internal data to external CSV format. In general, an import or export with more than 5,000 records per second are standard if you use the right technology. When you do not need to convert the data from another format to a fixed format, you can also copy between 20,000 and 50,000 records per second, depending on the structure etc.

© The author of this article is Holger Klemt, IBExpert Ltd, www.ibexpert.com, first published in April 2011