Beginners’ Corner — Part 2
  The dBASE Tables
(or how to create a table, entering, viewing and indexing data)
by Jean-Pierre Martel, editor of the dBulletin

 
 
   



Introduction

A table is an organized collection of data. To make this discussion simple, let’s say a table is like an electronic spreadsheet in which each row is a single record and each of the characteristics related to it is written in the appropriate column (called a “field”). If you are a beginner, before you build your own tables, please read not only the following text but also Mike Tossy’s “Database Design and Normalization” article. The latter could save you from a lot of errors in defining your tables.

The true nature of tables

To see a table as a spreadsheet is a nice concept but this is only a concept. All the files on a computer are long strings of binary values  inside clusters, sometime consecutive, often spread all over a partition. We tend to see dBASE tables as a series of individual records or as a spreadsheet because that’s the way dBASE displays the data in its tables. Actually, these tables are just stings of zeros and ones, just like any other computer file.

The string of data from a dBASE table file has two parts: first, the header (hence its name), followed by the data. For the user, the useful part of a table is the data it contains. Not so for dBASE. The software doesn’t understand a word about the data in a table. For dBASE, the useful part of a table (and the only part it understands) is the header. In that header, dBASE finds a lot of information about the table: the number of records, the size of the header (that size changes according to the number of fields), the size of each record, etc.

When you ask dBASE to display the thousandth record, that software take note of the size of the header, multiply by 999 the size of each record, add a byte (if my memory serves) for a separator between records, and jumps N bytes further, and winds up exactly at the thousandth record. Instantly.

This is the basis of dBASE’s strength: the fixed length of the records in a given table. If dBASE had to read the long string of binary values in a table in order to find where a record stops and where another one begins, it would have the speed of a word processor looking for a word. That speed is acceptable in a short text. But in an article exceeding a megabyte in length, that search could take a very long time. On the contrary, when you ask dBASE to go to the millionth record, it finds that record instantly because it doesn’t have to find it: it knows where it is.

Even when you are looking for some data (e.g., Smith’s address) and don’t know the record number, dBASE is clever. Without an index, it will jump from record to record, accessing exactly and precisely to the family name in each record, not looking further than the first letter when it is not the right one, until it finds Smith’s record. dBASE neither knows what “Smith” means, nor that "Smith" is a family name. The only thing it knows is that the header is telling it where it could find the right information; if not, to jump N bytes further, and so forth.

This is one of the reasons why dBASE is an outstanding piece of technology.

The level and language of a table

The BDE

As bizarre at it might seem, no version of dBASE running under Windows can directly access the data in its own tables. They do it through the Borland Database Engine (BDE). The reason for that is part of dBASE history. At the end of the eighties, through acquisitions, Borland International ended up with two database management software packages: dBASE and Paradox. Rather than maintaining their own separate database engines, Borland had the idea of creating a common engine which would be able to be used by all Borland software: that was the BDE. Many years later, when dBASE was acquired by its present owner, the latter found itself with a flagship product that depends upon a foreign technology to do some of its essential tasks.

The table level

This explains why we have to go through the BDE to change the current table level. Unless the table structure is changed, the table level is always kept as it is. The level setting in the BDE just defines which level new tables are getting. That doesn’t affect the ability of the BDE to read any kind of dBASE tables. Thanks to the BDE, a dBASE application can obtain data simultaneously from tables having different table levels.

But what is the level of a table? The level means its version. The dBASE table format is a standard that has evolved over time. When a new version of dBASE made some improvements to that format, a new format level number was given, identical to the new dBASE version. For example, we have levels 3, 4, 5 and 7 corresponding to dBASE III, dBASE IV, dBASE 5, and Visual dBASE 7. There is no level 6 because there was no Visual dBASE 6.

Level 7 brought many improvements. The field names can have up to 31 characters (from a maximum of 10 before). Some new fields types have appeared (for example, the AutoIncrement field that makes nearly impossible to give the same number to two records in the same table). If your tables have to be used by other software, you might have to sacrifice these advantages for the sake of compatibility, as few applications can use a level 7 table.

The BDE Administrator is used to set the current dBASE level. Go to the menu item Start|Settings|Control Panel and double-click the BDE Administrator icon.

In the left pane of the BDE Administrator, select the Configuration tab, then open the treeview until you see the dBASE table driver (represented by a  steering wheel). In the right pane, click at the right of Level to reveal the down arrow button that gives access to the drop-down list and select the appropriate table level.

The table language driver

Just above Level, you can choose the Language driver (LangDriver): that language driver defines how records will be indexed in a table. More precisely, it decides how accented vowels will be ordered. Personally I am using 'WEurope' ANSI. It indexes the accented vowels in the following order: a A á Á à À â Â å Å ä Ä ã Ã. That Language driver is the one recommended by dBASE, Inc.’s technical support.

But Germans have some problems with it since they expect letters carrying diaeresis (umlaut) to be indexed differently from what English people expect. Danes expect words beginning with the letters AA, Aa, aA and aa to appear at the end of the index, after letter Z. It seems that they should use the Language driver called 'Borland DAN Latin-1'. That driver doesn’t appear in the list but the BDE Administrator will accept that name if it is typed in the LangDriver entryfield.

Near the bottom of the left pane, under System|INIT, a Language driver can also be selected. To be honest, I couldn’t find in Borland’s documentation what the purpose of this driver is. I presume that it is better to have the same language driver in both places. Also in the System|INIT section, be sure to set the BDE to create dBASE tables rather than Paradox tables by default (in the Default driver item). Note: While we are in the System|INIT section of the BDE, we should note that some experts believe that the default settings in that section are very conservative for todays machines. For example, on June 30, 2000,  Dan Howard [dBVIPS] revealed the settings he was using then:
 

  Setting
Default
Dan’s Setting
  MaxBufferSize
2048
8192
  MaxFileHandles
   32
  200
  MemSize
   16
   32
  SharedMemSize
2048
4096
       

His suggestion is to set MemSize equal to 16 (as the minimum) or to one fourth of the quantity of RAM on your computer.

Once you have set the language driver and the table level, quit the BDE Administrator. If you made some changes, you will encounter two dialog boxes when you exit. In the first one, you will be asked if you want to save your changes. The second will inform you that all applications that depend upon the BDE have to be restarted for these changes to take effect. In fact, these changes will have consequences only when a new table is created or when you change the structure of an existing table.

If you change the Language driver in the BDE Administrator and that affects the way accented vowels are displayed in your code, that means that no Language driver is set in dBASE’s .ini file. When this is the case, the BDE’s language driver is used by default.

In order to limit the effect of the BDE’s Language driver to your tables and avoid the troubles you could get into when a change of the Language driver affects the execution of your code (especially if there are accented vowels in your field names), load vdb.ini or db2k.ini in an editor (e.g., Notepad). In the [CommandSettings] section, add the following line: LDriver=Windows . Since we are not yet at the stage where you will be creating programs, I would suggest to you that you would check now if your .ini file contains that line. If not, please add it. You will avoid yourself a lot of troubles.

Current vs Specific language or level

Once a table is created, it is impossible to change its language driver. This means that if you develop applications aimed at the some international markets, you could create tables with different language drivers for different markets. When these tables are used, their data will be read and written according to their specific language, not according to the current one. On the other hand, if you modify a table’s structure, the table level will be changed to the table level currently called for by the BDE.

If you want to stop dBASE from reporting an error message each time it opens a table with a language driver different from the current one, go to the menu item Properties|Desktop Properties: under the first tab, Country, be sure to have the Alert on mismatch checkbox unchecked. You could get the same result if you type set LDcheck off from the Command window. (Note: You can also add that line of dBL code to your applications to avoid your clients from getting that error message when they use your applications.)

When you want to know what the current Language driver is without having to open the BDE Administrator, push the F6 key. In the Results pane, on the line that starts with Language Driver, you will get the name of the current Language driver. (We set it in blue in the right hand part of the image below.) If you see something like DBWINWEO ('WEurope' ANSI) instead of WINDOWS (L) — where L is a language — that means there is no LDriver entry in your vdb.ini or db2k.ini and that dBASE is using by default the BDE Language driver. To see the current table level without opening the BDE Administrator, you have to create a new table.

To know what the level of a table is, open it from the Command window with the command use "My_Table" and push the F5 key. Note: Replace "My_Table" with the name of the table. The quotes are needed only if there is a space in the name of the table.

To find out which Language driver is set, push the F6 key. The result is shown hereunder in red, at right, on the line Table Language Driver (which must not be confused with the line Language Driver (the latter is showing dBASE current Language driver). From the Command window, type use (with nothing else) to close the table.

The BDE limits

Up to 512 dBASE tables can be opened at the same time on a computer. Theoretically, a dBASE table can have a billion records. In fact, a table can’t be bigger than 2  gigabytes. This size is the real limit of dBASE table. A record can have up to 1024 fields. The field name is limited to 31 characters. A character field can hold up to 254 characters (letters and/or numbers) while a memo field is unlimited.

The index file has the same name its the table except that its suffix is .mdx (Multiple Indexes). As this suffix suggests, each of these files might contain many indexes. The limit is 47 indexes per index file. Even if one can create an unlimited amount of secondary .mdx (under other names), I don’t recommend that you do so because the dBASE language, dBL, cannot deal with a multiple index file that doesn’t have the same name as the .dbf.

If a multiple index file can contain 47 indexes, only one of these can be active at a given time. Another index can be applied at any time. Moreover, all of the entries of the evaluated index must have the same width. This width is limited to 100 characters. The formula needed to create an index (index on…) must not be longer than 220 characters.

The BDE can deal with requests from a maximum of 48 executables at a given moment. On a Web site, that means that if the BDE takes an average of three milliseconds to answer a request, thousands of people can visit a site but only 48 of them can receive some data contained in dBASE tables in a three millisecond period of time. If we accept that three millisecond average, a Web site based on dBASE is limited to a little bit more than 21 billion BDE requests a year. If they take an average of three seconds (which is unlikely), that limit drops to 21 million BDE requests a year. Over that limit, your site should be fed by a second computer.

How to create a table

There are two ways to create a table. It may be done either by using the dBASE interface or programmatically. The dBASE interface offers many ways to create a table:

The last command (create) will always create a table directly without calling the Wizard. The next to last command (create wizard) will always call the wizard directly (without first going through the image below). For all the other commands, it depends. In the Properties|Desktop Properties, under the Application tab, there is a checkbox called Table in the “Prompt for Wizard” box. When that checkbox is unchecked, the methods listed in the first six bullets (above) will go directly to the Table designer. When checked, the following dialog box will appear.

Spaces should be avoided in the names of dBASE tables. Otherwise, you will have to place those names between quotes each time you want to apply a dBL command to these tables.

The Table Wizard

Serious books about dBASE programming usually avoid talking about the Table Wizard. Why? Because it is just for beginners. But hey! This series is aimed directly at beginners, so why not have fun?

The Wizard has two steps. In the first one, you will be presented with a list of available table templates: e.g., an Artwork Collection template, a Billing Info template, etc. As soon as one of them is selected, a list of suggested fields will appear in the left of the Fields box. At this stage, you cannot change any field names or add new ones. (Be patient: you will be able to do that very soon.) Using the pushbuttons, you can move some or all the items from the list of available fields (on the left side of the Fields box) to the list of Selected fields (on the right side of the Fields box). When at least one field is selected, the Next pushbutton is enabled. It allows you to go to the next step of the Wizard.

The last step is simple. Near the top, a drop-down listbox presents you with the choice to create a dBASE, a FoxPro, or a Paradox table. Then, if you push the Run Table button, you will save that table and see that it’s empty (it’s boring!). If you push the Design Table, your table will be loaded in the Form designer where you will be able to put the finishing touches on it (e.g., rename fields, change field type or width, create indexes, etc.) . This module allows you to access the Form designer with most of the job already done.

The Table Designer

Let’s drop what we did in the Table wizard (it was for fun, wasn’t it?) and go back to the time we had to choose between the Table Wizard and the Table designer. This time, we shall use the Table designer in order to do a small exercise: to create a Phonebook.

When it opens, the Table designer looks like a spreadsheet (see the next image). In a table, a field doesn’t have a number. Yet in the Field column, numbers are displayed. These numbers are just there to help you to take advantage of the Ctrl–g shortcut, which is used to move the cursor to a precise field number. When a table has a lot of fields, that shortcut might come in handy.

When we create a database, we often need to know the name and width of fields in other tables. Because of their small size, a few instances of the Table designer can be opened at the same time, showing the structures of several tables at once.

When you create a table, the principles that should guide you are simple. A field should stock the smallest unit of coherent data. You should avoid putting first names and last names in the same field. Nor should you put the city and province or state in a single field. On the other hand, you should put the entire street address in a single field even if it is composed of a number, a street name and an apartment number because it is unlikely that you will have to analyze any of these data separately. If you had to, you would have to create a separate field for it.

If the table will be part of a database, it should have a unique field (a field without duplicate values) that will make it possible for that table to be linked to another table on an identical field. For that reason, the data in a field should not be duplicated elsewhere in the table.

The field name

It’s in the Name column that fields are named. Try entering a field name of First_Name using both upper and lower case characters. If it’s impossible for you to enter lower case letters, that means that you are creating a table with a table level lower than 7. If this is the case, don’t save anything, close dBASE and go to the BDE Administrator and set the table level to 7 and load dBASE anew.

Under Visual dBASE 5, field names were limited to ten characters, couldn’t contain a space (we had to use the underscore character instead) and had to start with a letter. In a level 7 Visual dBASE table, a field name can be up to 32 characters long, may contain spaces, and can start with just about anything. Having said that, I don’t recommend that you start a field name with a number unless you are absolutely sure you will never use SQL (a database management language recognized by the BDE).

The field types

Once your first field has been named, push the Tab key or the Enter key to go to the next column or click on it. The cell will become a drop-down list. In this list, you will have to choose from among twelve field types. Since some text will have to be written in the First_name field, we shall select character as the field type. If you don’t mind, let’s give a look at the different field type available for level 7 tables.

A dBASE table can store many types of data: text, number, image, sound file, OLE (Object Linking and Embedding), etc. In order to satisfy the wide range of needs from its developers community, dBASE has a large number of field types.

Before we review these field types, I would like to make a short comment about the meaning of “binary”. All data in a computer are made of values that could be qualified as binary (which means two numbers in Latin) because they have only two possible values: zero or one. Any of these zeros or ones is called a “bit” of data. Grouped by eight, these bits will constitute a byte. When a file contains bytes that represents not only letters, numbers and signs found on a keyboard, but also any of the other non-printing signs among the 256 possible values of a byte, that file is called “binary”. A text file created with Notepad is always a text file. The same text done in Word or Wordpad is a binary file (that can be read properly only by those applications that have an import filter). So a binary field is made to accept any of the 256 values of a byte.

The field width

Let’s go back to creating the structure of your phone book table. Set enough room to Width to be able to enter all the data the field must contain. For example, to enter the number 12.50, you need five bytes: two for the decimals, one for the decimal point, and two others for the numbers left to the decimal point. If it is possible that the number could be negative, you would need six bytes.

If these numbers are monetary values on which you will make calculations other than additions and subtractions, use five or six decimals in order to get the precision needed and let dBASE round the results and display two decimals.

To index or to sort

You can create indexes on any field except Binary, _dbaselock, Logical, Memo and OLE. When the cursor is placed in the Index column, the cell is transformed into a drop-down list with three choices: None, Ascending and Descending. It allows you to create an index on ascending order, descending order, or to delete an index. Later we will see how to create complex indexes, i.e., an index on something other than a single field, for example.

In an indexed table, the records stay in the same order in which they were entered into the table. When an index is created, a new file (if it doesn’t already exist) is created bearing the same name as the table except that its extension is .mdx instead of .dbf. It contains a value based on the value of the indexed field and a pointer (usually the record number) to the position of the corresponding record in the table.

Just as the person in charge of Protocol at the White House will know who can sit to the left or the right of the President at an official dinner, regardless of the order the guests arrived for the occasion, an index knows the order in which the records should be displayed in browse mode when the table is indexed, regardless of the order the records were added to the table.

Why not simply reorder the records in the table so that they would be natively in the order we want, rather than having an index interpose itself between the table and us? Because an index has many advantages :

  1. The .mdx file can contains many indexes. We can look at the data ordered by names, by states, etc. Without an index,  we would need to create many copies of the data, ordered differently.
  2. Sorting a table physically rearranges the records in a file. Compared to modifying an index, sorting is a very time-consuming process.
  3. Each time a new record is added to the table, we would have to sort it. Sorting would take more and more time as our table became bigger. On the contrary, to refresh an index is a lot faster.
  4. Sorting a table anew must be explicitly requested. Reindexing is done automatically by dBASE each time a new record is added to a table.
  5. Certain tasks are possible only through indexes. For example, a link between two tables can be achieved only when they are based on a common field. Moreover, that field must be indexed in the child table.
  6. Finding a specific piece of information is instantaneous when the search is based on an indexed field. It will take significantly longer to find that same information is in a field which is not indexed (even when the table is naturally in the right order).
If the dBASE engineers had really wanted it, a search on a sorted field would be as fast as a search on an indexed field. But nobody cared to do that because dBASE was made to work on dynamic databases, constantly changing, in which new records are added at the end of the table.

That being said, there could be very good reasons to sort a table. For example, when an archived copy of the members of an association is wanted for a specific year. In that case, the command sort is used. This command creates a new table, identical to the original table, under a new name. There are two main differences though: the new table is ordered on one of its fields and no indexes are transferred to the new table.

In the following example, we create a new table (called Members_2001) from an existing table called My_Table. Those records in My_Table whose Year_of_membership field contains a value of 2001 will be copied to the new table, ordered by name.
 
 
use My_Table
sort on Name to "Members_2001" for "Year_of_membership" = "2001"
   

When a table is opened from the Command window, a list of its indexes can be seen with the command display structure (F5). An index will appear in the Results pane only when the following conditions are met :

dBL offers two ways to create an index: through xDML or through OODML.
 
 
xDML:
use My_Table exclusive
index on Field_Name tag Index_Name
use
 
 
OODML:
 
  index1            = new DBFIndex()
index1.indexName  = "Index_Name"
index1.expression = "Field_Name"
_app.databases[1].createIndex("My_Table", index1)
// we create an index object
// it is given a name
// it is defined as a simple index built on a field
// '_app.databases[]' is the array containing the names
// of all the tables in the current folder
     

If you wish to create an index from the Command window on a field with a space in the field name or on a field that starts with a number, you must place the field name between colons and the name of the tag, between single or double quotes. For example, if a field name contains a space: index on :Last name: tag "Last name".  With OODML, one would write:
 
 
:Index Name with Spaces:            = new DBFIndex()
:Index Name with Spaces:.indexName  = "Index Name with Spaces"
:Index Name with Spaces:.expression = "Field Name with Spaces"
_app.databases[1].createIndex("My_Table", :Index Name with Spaces:)
   

From the Command window, an index can be created on a Logical field. The trick is to base that index not on the actual value in the logical field but on a character value created on the fly.
 
 
use My_Table exclusive

index on IIF(Logical_field = true, "Yes", "No_") tag "Logical_field"
// or
index on IIF(:Field Name with Spaces: = true, "Yes", "No_") tag "Logical_field"
// but not
index on IIF("Logical_field" = true, "Yes", "No_") tag "Logical_field"
// nor
index on IIF("Field Name with Spaces" = true, "Yes", "No_") tag "Logical_field"

use

   

With OODML, an index based on a logical field could be created the following way:
 
 
Yes_No_            = new DBFIndex()
Yes_No_.indexName  = "Yes_No_"
Yes_No_.expression = [iif(Logical_field = true, "Yes", "No_")]
_app.databases[1].createIndex("My_Table", Yes_No_)
   

In the code above, the conditional command IIF (which is different from the IF command) will write Yes as the key value when the field named Logical_field is true and No_ when it is false.We could have chosen Y and N, respectively, but we wanted to stress the importance of fixed length index: this is why we added an underscore to “No”.

In the code above, the part that says = true is superfluous in an IF or an IIF statement. We left it for clarity. Likewise, the quotes around the field name Logical_field are superfluous since the field neither contains spaces nor starts with a number. Finally, when an indexed table is displayed in a browse view or in a grid, the vertical scrollbar doesn’t work properly. That’s the only shortcoming of dBASE indexes.

The command display status (F6) reveals all the indexes, even the ones which are not displayed by display structure  (F5).

To delete an index from the Command window, we have once again the choice between xDML and OODML:
 
 
xDML:
use MyTable exclusive
delete tag My_Index
use

OODML:
_app.databases[1].dropIndex("My_Table", My_Index)

   

To add other fields

In the Table designer, when the cursor is in the column reserved for the indexes, we skip to the next line by pushing the Enter key. When the cursor is in the Field column, it becomes an open hand. This is a reminder that the column order can be modified through drag’n drop.

The Table designer has the following shortcuts:
 
  Shortcuts   Effect
  Up Arrow   to go to the previous field
  Down Arrow   to go to the next field or, if on the last field, to create a new field
  Right Arrow   to go to the next character in the field
  Left Arrow   to go to the previous character in the field
       
  Enter key   to go to the next column or, if in the last column of the last field, to create a new field
  Tab key   to go to the next column or, if in the last column of the last field, to create a new field
  Shift – Tab   to go to the previous column or to go to the last column of the previous field
       
  Ctrl – a   to add a new field at the end of the list
  Ctrl – n   to insert a new field
  Ctrl – u   to delete the field in which the cursor is located
  Ctrl – g   to move the cursor to a specific field number
       

The shortcuts to add or delete a field correspond to the toolbuttons   on the Toolbar and correspond also to some items in the Structure menu.

Let’s complete the table structure in order to get something like the one shown in the following image. Adapt it to your needs. If one of your field names is quite long, the width of the Name column might be too narrow to display it completely. In order to widen that column, place the mouse cursor over the line separating that column from the next one and drag it to the right.

It is possible to change the column order in the Table Designer window using drag’n drop. When you place the mouse cursor over the header of any column (except the Field column — which can’t be moved), that cursor will be changed to an open hand. Drag the header to move the column where you want it to be. This process has no effect on the table structure. If you close the table and open it anew, the columns will be in exactly the same order as they were originally.

More important is that in the Table Designer you can change the order of the fields in a table using drag’n drop. When the mouse cursor is placed at the far left over a field number, the mouse cursor is changed to an open hand. Drag the field up or down to the place you want it to be. If you save the table and open it anew, the changes you made will be reflected in the new structure of that table.

In the case of phone numbers and zip codes, you should not make any provision for number separators. For example, if you would like the phone numbers to be displayed as (123)456-7890, don’t make room for the parenthesis nor the dash. dBASE has all the tools to format a field: the user of your application will thus not have the burden to type these separators when the data is entered.

To save a table, select the item File|Save from the menu or use its Ctrl—s shortcut. A dialog box will ask you to give a name to that table before saving it. That name could be anything, as long as it is not a dBL command, nor contain a dash (in such a case, you would have problem making SQL queries on that table), nor contain accented vowels (as the BDE will have problem dealing with its indexes).Using Ctrl-w will save the file and close it automatically.

How to modify the structure of an existing table

From the Navigator, one can use any of the following means to call the Table designer to change the structure of a table. Under the Table Files tab in the Navigator:

From the Command window, we can get the same result if we type:
 
     
  use My_Table exclusive // replace 'My_Table' with the name of the table to open
  modify structure  
     

If the table already contains data and you would like to rename many fields, it would be more prudent to make the changes one at the time (and save the table between the changes) rather than doing all these changes in one step, specially if these fields are in succession in the table structure.

The On-line Help states the following under Modify structure: “You shouldn’t change a field name and its width or type at the same time. If you do, Visual dBASE won’t be able to append data from the old field, and your new field will be blank. Change the name of a field, save the file, and then use modify structure again to change the field width or data type. Also, don’t insert or delete fields from a table and change field names at the same time. If you change field names, modify structure appends data from the old file by using the field position in the file. If you insert or delete fields as well as change field names, you change field positions and could lose data. You can, however, change field widths or data types at the same time as you insert or delete fields. In those cases, since modify structure appends data by field name, the data will be appended correctly.”

If you have to add or to delete multiple fields, this can be done without saving in between each change as long as you don’t rename fields at the same time. The Ctrl—s shortcut saves the changes while the Ctrl—w saves the changes and closes the Table designer in a single operation.

How to copy a table

Even if the 32-bit versions of the BDE are a lot more reliable than the one used by Visual dBASE 5, it is more prudent to make a back-up copy of a table before making changes to its structure.
 
   
  copy table My_Table to Back_up_copy
   

To make a copy of a table, we have the choice between two xDML commands. Copy table "Old_Table" to "New_Table" allows you to create a table without having to open it first. If there are .dbt and .mdx files associated with the table being copied, those files will automatically be created also. The new table will have the same language, the same indexes, and the same level as the original table. In a nutshell it will be a perfect copy. The second command, copy to "New_Table" requires that the table be open. The new table will have the same language driver and the same level as the original table. The copy to  command doesn’t copy the _dbaselock field. If you start with the original table sorted according to an active index, the records in the new table will be sorted in that order. But, in this instance, no .mdx file will be created. If we add with production to the latter command, the new table will be sorted according to the active index in the original table and will have its own .mdx file.
 
 
use "Old_Table" order tag "Index_Name"
copy to "New_Table" with production
  use
   

OODML has a command similar to Copy table in the sense that it doesn’t need the table to be open and it makes a perfect copy of the table (with its .dbt and .mdx) as long as that table is in the current folder. That OODML command is:
 
 
_app.databases[1].copyTable("My_Table", "Copy_of_My_Table")
   

How to add records

By default, when dBASE adds a new record to a table, it does it in Column mode ( i.e., with all the fields aligned one under the other). Nevertheless, a table can be displayed in three different modes: in the Column mode, in Form mode, or in Table mode (i.e., in a spreadsheet). You can toggle from one mode to the next by pushing the F2 key. In Browse mode, it is possible to change the column width or the column order. When their order is changed, that doesn’t modify the structure of the table (unlike what happens in the Table designer).

It is possible to add new records to a table displayed on screen by using any of the following means:

When in the Command window, we have another mean to add a new record: the append command. It adds a new record at the end of the table. In Visual dBASE 5.x, the insert command is used to insert a new record at the position of the table pointer: that command is not supported in the 32-bit versions of dBASE, probably because it is never used in professional database management.

The vast majority of the records added to dBASE tables are added through dBL applications. When we have to use the dBASE interface to add new records, it’s to create a few records in order to test a user interface or a specific program. If this is the case, here are three tips that will make your job easier.

When a table contains a memo field, and if that field is empty, the field will be represented by the icon of a white page. If it contains data, it will be represented by an uppercase “A” on a white page. It is possible to ask dBASE to display those fields in a small editor in order to have a preview of their contents. dBASE can do that when a table is displayed in Column mode or in Form mode, but not in Browse mode. Select the menu item Properties|Desktop Properties, and, under the Table tab, click the Associate Component Types… button. In the dialog box that appears, beside the memo field type, select the Editor radiobutton. If the table is already displayed, close it and open it anew to make that change effective. For now on, that change will also affect the Table designer. By default, editors will be proposed to display the content of memo fields in all modes except Browse mode.

Again at the menu item Properties|Desktop Properties, this time under the Data Entry tab, if the CUA Enter checkbox is empty, you will be able to move to the next field with the Enter key (as you can with the Tab key). The only exception is when the cursor is in a memo field. In such a case, the Enter key will behave once again like the ‘carriage return’ of a typewriter. In Column mode or in Browse mode, the Tab key or the use of a mouse will be the only ways to get out of the memo field.

Finally, above the CUA Enter checkbox, there is a checkbox called Confirm. When the Confirm checkbox is empty, the cursor will automatically move to the next field as soon as a field is full (a bell sound will tell you when the end of a field has been reached). If you have to enter the answers to a survey, that tip can save you from pushing the Enter key thousands of times. Once you have become skillful, you might want to shut down the bell sound by unchecking the Bell checkbox.

You can get the same results from the Command window or from dBL applications that we just got from Desktop Properties. We can do this by entering the following commands:
 
 
set CUAEnter off
set Confirm off
   

In the dBASE for DOS versions, once a record was completed, the Enter key allowed us to create a new record (if we were appending records) and to move to the beginning of that new record. Under the 32-bit versions of dBASE, the Enter key works only in Browse mode. In Column or Table mode, the easiest way to add a new record is with the Ctrl—a shortcut.

Special cases

From the dBASE interface, the data entry doesn’t cause any problem except for certain field types. A _dbaselock field is always invisible.  When you are creating a new record, if it contains an AutoIncrement field, this field will be empty since its number will be added by dBASE at the precise moment that record will be saved. Finally, dBASE deals with OLE fields like any Windows application.

Memo fields

In Column mode or in Form mode, the Tab key allows the user to access and to quit a memo field when it is displayed in an editor. In Browse mode, we can access the memo field by any of these means:

Binary fields

Many of these methods will also work for binary fields. I would like to explain the only method that works whenever the binary field contains an image or a sound. The table has to be open in Browse mode. (Push the F2 key if the table is open in Column mode or in Form mode.) Double-click on the binary field to open the Specify Binary Field Subtype dialog box. In this dialog box, you will have to specify if you want to store an image or a sound in that field. dBASE will offer an empty image viewer or a sound player. Right-click on it and select Import Image… or Import Sound… Go to the folder where the image or sound is located and select it. Once the viewer or player is closed, the image or sound will be copied in the .dbt file and registered in the binary field. When the binary field contains data, the eight zeros that represents an empty binary field will be replaced with the icon of a sunny landscape or with the icon of a musical note. Double-click on it to view the image or play the wave file.

The 32-bit versions of dBASE support a wide range of color formats:

When dBASE displays an image that seems to be a gray-scale image, it’s because it’s a color image that contains only shades of gray, not a real gray-scale image. The size of the Image Viewer can be set to fit the size of the image to be displayed or can have the size you want. If you type set from the Command window, you will get the Desktop Properties dialog box. Under its Application tab, check (or uncheck) the Size window to content checkbox, in the center, at right, to obtain the behavior wanted from the Image Viewer.

Anything can be stored in a binary field. For example, one could hide the commercial plans of his company. Contrary to OLE fields, that can be visualized in Column mode or in Form mode, and which content can be double-clicked and loaded in an application able to read them, a binary field remains a total mystery when it is used to store anything else but an image or a wave file. In order to store or to extract the file “Secret_plans_of_the_Penta.gon”, we just have to do:
 
 
xDML:
replace binary Binary_field from Secrets_plans_of_the_Penta.gon type 10  // to store
copy binary Binary_field to Secrets_plans_of_the_Penta.gon // to extract the stored file
 
OODML:
form.rowset.fields["Field_Name"].replaceFromFile("File_Name") // to store
form.rowset.fields["Field_Name"].CopyToFile("File_Name") // to extract the stored file
   

At the end of the first line of code above, the type number can be any number between 1 and 32767. Believe me, if your data is encrypted, it will be impossible, even to the KGB, to find the secrets hidden in your table. Note: In Browse mode, when a binary field contains something other than an image or a wave file, it will be represented by the black numbers ‘0100 0010’, while an empty one will be represented by eight pale gray zeros.

If you don’t want to type the name of the file to store in a binary field, you just have to type replace binary Binary_field from ?. The question mark will call the OpenSource File dialog box (but the only possibility will be to choose among .bmp files). Similarly, for an OLE field, the command will be replace OLE OLE_field from ?.

From the Command window, if you want to delete the content of a binary field or an OLE field, use the command blank field Binary_Field_Name.

Append From… vs AutoIncrement fields

When a table has an autoIncrement field, a flag in the table’s header keeps track of the autoInc number by storing the hexadecimal value of the next AutoInc number. If we do a simple append using the xDML Append command (not the Append from command), that flag is updated.

However, when multiple records are added en masse using the Append from command, that flag is not updated. The target table inherits all the autoInc values from the source table. The records are copied as such. The autoInc flag in the table’s header is neither used nor updated. It is simply ignored. Actually, the Append from command usually doesn’t update the autoInc flag. Why “usually”? Because if we add the clause for true to the Append from command (Append from … for true), then the flag will be used and updated. Complicated? Yes, but there is more.

When all the records in a table are deleted (using the zap command), the autoInc flag is not reset. Nor is it decreased when the last saved record is deleted. Let’s see some practical examples, just to see if everything is well understood.
 
 
use Source_Table   // Source_Table has an autoIncrement field
copy structure to Target_Table
use Target_Table   // which is empty for now
append from Source_Table
   

In the above example, the autoInc numbers in the Target_Table are those found in the Source_Table. The first autoInc number given to the next record added to the table with a simple Append command will be 1. This is because all the autoInc numbers that were already in the table were ignored since they were written without changing the table’s header autoInc flag. Moreover, if the target table is appended en masse once again, but this time from another table that has some identical autoInc value, we would end up with duplicate values. This is where the for true clause is valuable. If we had used the following code:
 
 
use Source_Table   // Source_Table has an autoIncrement field
copy structure to Target_Table
use Target_Table   // which is empty for now
append from Source_Table for true
   

In this case, the BDE will increment the autoInc number correctly in Target_Table, starting from number one. Any record added individually will bear the next autoInc number available.

Does it mean that the for true clause should always be used? Not necessarily. There might be situations where we would want the autoInc number in the source table be retained in the target table. Our problem is that the first new record added thereafter will not get the following autoInc number. How can this problem be avoided? Prior to the Append from command, we just have to create (then delete) the number of records needed to set properly the autoInc number in the table’s header. For example, if the autoInc number in the source table stops at 500 and thus we want the next autoInc number to be 501 when a simple Append command is issued, we would use the following code:
 
 
use Source_Table   // Source_Table has an autoIncrement field
copy structure to Target_Table
use Target_Table exclusive
// the following line of code will increment
// the autoInc flag in the table’s header
generate 500
zap  // this command needs exclusive use of the table
append from Source_Table
   

If we start with a source table that doesn’t have an autoIncrement field and append its records into a target table that does have such a field type, what will happen? In that case, the autoIncrement field will be empty for all the records added en masse. Thereafter, the first record to be added to the target table with a simple Append command will bear the next autoInc number, as if the records added en masse never existed. That problem would have been avoided with the for true clause.

If one of your tables is at risk of duplicate values because you made some of the mistakes we’ve spoken about, you could renumber the values in your autoIncrement field. If you don’t care about the starting autoIncrement value, the easiest solution is to use SQL:
 
 
Alter table Target_Table drop Field_Name
Alter table Target_Table add  Field_Name AutoInc
   

In the dUFLP is a file called DBF7File.cc which has the ability to change the value of the autoincrement field. For example, the code needed to set the next autoInc number to 325 in a table called “Target_Table”, would be:
 
 
set procedure to DBF7File.cc additive
oFile = new DBF7File("Target_Table.dbf")
oFile.setNextAutoIncValue("Field_Name", 325)
oFile = null
   

The null values

At the moment a new record is created, all its fields look empty. When using level 7 fields under a 32-bit version of dBASE, these fields are not really empty. They contain a “null” value. This null value can be seen as a proof of the virginity of a field. For example, when the user writes some data in a field and erases everything afterwards, the field is empty but has lost its virg… I mean its null value. In the case of a numeric field, when it contains a null value, nothing can be seen in that field. If the null value has been lost, a number (zero, for example) is seen in that field. Note: Simply passing through a field with the Tab key is not enough to cause it to lose its null value status. These are a few examples of the advantages of null values:

On the other hand, null values can be a problem when we forget to exclude them from certain calculations. Two exceptions are sums and averages: dBASE will always exclude null values when it has to calculate sums or averages. Under Visual dBASE 7, if null values are causing some problems, you have to change your code accordingly. In the following example, one should note that null is used without quotes, as if it were a number.
 
 
if City = null
  replace City with ""
endif
   

Under dB2K, it is easier to deal with null values:

How to delete a record

When a table is displayed in the dBASE interface ( i.e., not by a dBL application), one can use any of the following methods to delete some of its records:

From the Command window, it is possible to delete records with the delete command. When the table is displayed in Browse mode, that command seems to be ineffective. This is not the case. If you move the table pointer and try to find that record, you will see that it has been deleted.
 
 
use My_table
browse       // to display the table
delete 20    // to delete the 20th record
go top       // to make that suppression visible
   

At the bottom of the dBASE window, you should see the Statusbar. If you don’t, just type the following command in the Command window: _app.statusbar = true. On its right end, the Statusbar indicates the record number. (For example, if it says Row 6/2500, it indicated that you’re on the sixth record in a 2500-record table). With the arrow keys, if you “navigate” in the table (i.e., if you move the record pointer to another record), the table pointer should skip over the deleted record number. The explanation is simple: deleted records still exist because they have been “marked” for deletion, not physically removed. From the Command window, if you type the following command: set deleted off and if you move the record pointer, the deleted records will reappear. Under a 32-bit version of dBASE, the Statusbar will display something similar to Row 6/2500, Deleted when the record pointer is over a deleted record. Once you are through, type set deleted on to reset dBASE to its normal behavior.

In Visual dBASE 5, it is easy to see the deleted records in a Browse window: when set deleted is off, the deleted records have a red “x” in the second column (the one immediately after the record number). The user just has to click the checkbox to delete or to recall a record.

In order to physically remove deleted records from a table, use the pack command or the packTable() function. Though this process can be slow when run on huge tables, it is necessary to eliminate the burden of carrying unnecessary records in a table, thus speeding up applications that use that table.
 
 
xDML:
use My_table exclusive
pack
use

OODML:
_app.databases[1].packTable("MyTable")

   

To “unmark” records in a table that have been marked for deletion, one has only to use the command recall all. This command works only if dBASE was allowed to display the deleted records with the command set deleted off. If you want to recall only one record, once you have taken note of its record number (that number appears in the Statusbar when the record is selected), the command recall N (where N is the record number) will recall it. The ability to recall deleted records is an unique characteristic of dBASE tables.

How to view the records in a table

To consult a table, the user can:

When either of the first two methods is used, the File Open dialog box opens. Make sure that the View Table Rows (the default radiobutton) is pushed. Click on the name of the table and then on the Open pushbutton (Note: The resources borrowed by dBASE from Windows can sometimes be revealed when the OS has another language, as it is the case below).

Note: The Open File dialog box (above) will look different according to the language and the version of your Operating System.

When the user is working in dBL, dBASE offers the programmer many means to protect tables against data corruption. Under a 32-bit version of dBASE, however, this is not the case when we view a table from the dBASE interface. There is no security net (e.g., just an Undo or its shortcut Ctrl–z). So be careful not to modify your data inadvertently. For example, when a Browse window opens, its first cell is selected. If a key is pressed its value will replace the highlighted value. Under Visual dBASE 5, full protection is provided with the command browse noedit (or browse noedit noappend) when the Command window is used.

Note: In the Phonebook table, don’t be surprised if one of its fields has a colored background. We will see how this is possible near the end of this article.

Once a table is displayed, dBASE will update its Toolbar with a new set of tools made to deal with tables. Look at the enabled buttons in the image below. From left to right, the first two buttons allow the user to view a table or to modify its structure, respectively.The next three buttons allow you to select between the three viewing modes of a table: Browse mode, Column mode, or Form mode. The next five buttons are used to add, delete, save, to cancel, or find a record. With the next two buttons, the table  will be ordered in ascending order or in descending order, but only if indexes have been set up to allow that. The last four buttons are VCR-type buttons that allow you to navigate within the table. From left to right, the VCR buttons take you 1) to the first record, 2) to the previous record, 3) to the next record, and 4) to the last record in the table.

The following shortcuts can also be used to navigate in a table displayed:
 
  navigation in browse mode other modes
  First record of table
Previous page
Previous record
– First field or Beginning of current field
– Previous field
– Next field
– Access a memo field
– Quit a memo field
– Last field or End of current field
Next record
Next page