TOCBACKFORWARD

Charlie Calvert's C++ Builder Unleashed

- 12 -

Understanding Relational Databases

In order to make sure everyone is following the discussion in the next few chapters, I'm going to spend a few pages giving a quick-and-dirty introduction to relational databases. This discussion will also include a brief overview of the Database Desktop.

My purpose here is to give a relatively concise explanation of what it means to use a relational, as opposed to a flat-file, database. Naturally, this will be a very broad overview of a complex and highly detailed subject. I am not attempting an academic analysis of this field of study, but instead want to provide a practical guide for everyday use.

In this chapter, I will be working with Paradox tables and InterBase tables. Each database has its own unique set of rules. There is no definitive example of a relational database, any more than there is a definitive operating system or a definitive compiler. All databases have things in common, just as all compilers and all operating systems have things in common. As much as possible, I try to stress these common traits throughout this chapter. However, the specific implementation that I am referencing here is for Paradox and InterBase databases, and not everything I say will apply to Oracle or dBASE tables.

In particular, this chapter is about the following:

If you already understand these subjects, you probably won't have much use for this chapter. If you need to review these subjects, or need to be introduced to them, you should read this chapter.

Getting Started with Relational Databases

There are many different kinds of possible databases, but in today's world, there are only two kinds that have any significant market share for the PC:

1. Flat-file databases

2. Relational databases


NOTE: Emerging in recent years has a been a new system called object-oriented databases. These databases represent an interesting form of technology, but I will omit discussion of them here because they have a small user base at this time.

The subject of object-oriented databases will come up again briefly in the chapters on OOP called "Inheritance," "Encapsulation," and "Polymorphism." In those chapters you will see that OOP has some powerful features that it can bring to the database world.

Flat-file databases consist of a single file. The classic example would be an address book that contains a single table with six fields in it: Name, Address, City, State, Zip, and Phone. If that is your entire database, what you have is a flat-file database. In a flat-file database, the words table and database are synonymous.

In general, relational databases consist of a series of tables related to each other by one or more fields in each table. In Chapter 9, "Using TTable and TDataSet," and Chapter 10, "SQL and the TQuery Object," you saw how to use the TTable and TQuery objects to relate the Customer and Orders tables together in a one-to-many relationship. As you recall, the two tables were joined on the CustNo field. The relationship established between these two tables on the CustNo field is very much at the heart of all relational databases.

The Address program shown in Chapter 13, "Flat-File, Real-World Databases," is an example of a flat-file database. In Chapter 14, "Sessions and Relational Real-World Databases," you will see a second program, called KDAdd, which is a relational database.

Here are three key differences between relational and flat-file databases:

1. A flat-file database, like the address book example outlined previously, consists of one single table. That's the whole database. There is nothing more to say about it. Each table stands alone, isolated in its own little solipsistic world.

2. Relational databases always contain multiple tables. For instance, the Customer and Orders tables are both part of the BCDEMOS database. As you will see, there are many other tables in this database, but for now just concentrate on the Customer and Orders tables.

3. Tables in relational databases are tied together in special fields. These fields are called primary and foreign keys. They are usually indexes, and they usually consist of a simple integer value. For instance, the Customer and Orders tables are related to one another by the CustNo field. The CustNo field is a primary key in the Customer table, and a foreign key in the Orders table. There are also indexes on both fields.


NOTE: Indices are about searching and sorting. Keys, on the other hand, are about relating tables, and particularly about something called referential integrity.

In practice, these concepts get mixed together in some pretty ugly ways, but the underlying theory relies on the kind of distinctions I am drawing in this note. For instance, keys are usually indexed, and so people often talk about keys and indexes as if they were the same thing. However, they are distinct concepts.

One way to start to draw the distinction is to understand that keys are part of the theory of relational databases, while indexes are part of the implementation of relational databases. More on this as the chapter evolves.

Clearly relational databases are radically different from flat-file databases. Relational databases typically consist of multiple tables, at least some of which are related together by one or more fields. Flat-file databases, on the other hand, consist of only one single table, which is not related to any other table.

Advantages of the Relational Database Model

What advantages do relational databases have over flat-file databases? Well, there are many strengths to this system; here are a few of the highlights:

To summarize, a relational database offers these possibilities:

As you can see, the three concepts that stand out when talking about relational databases are referential integrity, flexibility, and conservation of disk space. In this case, the word "flexibility" covers a wide range of broad features that can only be fully appreciated over time.

The one disadvantage that relational databases have when compared to flat-file databases is that they are more complicated to use. This is not just a minor sticking point. Neophytes are often completely baffled by relational databases. They don't have a clue as to what to do with them. Even if you have a relative degree of expertise, anyone can still become overwhelmed by a relational database that consists of three dozen tables related to one another in some hundred different ways. (And yes, complexity on that scale is not uncommon in corporate America!) As you will see later in the book, almost the only way to work with big systems of that type is through case tools.

Simple Set Logic: The Basis of Relational Databases

The basis for relational databases is a very simple form of mathematics. Each table represents a simple set that can be related to other tables through very fundamental mathematics. Because computers are so good at math, and particularly at integer math, they find relational databases easy to manipulate.

One common feature of relational databases is that most records will have a unique number associated with them, and these numbers will be used as the keys that relate one table to another. This enables you to group tables together using simple mathematical relationships. In particular, you can group them using simple integer-based set arithmetic.

For instance, in the Customers table from BCDEMOS, there is a unique CustNo field in each record. Furthermore, the Orders table has a unique OrderNo field associated with it. The Orders table also has a CustNo field that will relate it to the Customer table. The terminology of relational databases expresses these ideas by saying that the Customer table has a primary key called CustNo, and the Orders table has a primary key called OrderNo and a foreign key called CustNo:

Tablename Primary key Foreign key (secondary index)
Customer CustNo
Orders OrderNo CustNo

Given this scenario, you can say "Show me the set of all orders such that their CustNo field is equal to X or within the range of X - Y." Computers love these kinds of simple mathematical relationships. It's their bread and butter. In essence, you are just asking for the intersection of two sets: "Show me the intersection of this record from the Customer table with all the records from the Orders table." This intersection will consist of one record from the Customer table with a particular CustNo plus all the records from the Orders table that have the same CustNo in their foreign key.

These CustNo, OrderNo, AuthorNo, BookNo, and similar fields might also be used in flat-file databases as indexes, but they play a unique role in relational databases because they are the keys used to relate different tables. They make it possible to reduce the relationship between tables to nothing more than a simple series of mathematical formulas. These formulas are based on keys rather than on indexes. It is merely a coincidence that most keys also happen to be indexed.

Viewing Indices and Keys in DBD or the Explorer

In the next few sections I define primary and secondary keys, and describe how to use them. It might be helpful if I preface this discussion with a brief description of how to view keys using some of the tools that ship with BCB. This is just a preliminary look at this material. I cover it again in greater depth later in this chapter in a section called "Exploring the Indices in the BCDEMOS Database."


NOTE: Right now it is not so important that you understand what primary and foreign keys do, but only that you know how to view them using the tools that ship with the product. The theory will become clear as the chapter progresses.

There are two ways to view the indexes and keys on a table. The best way is in the Database Explorer. Open up the Explorer and view the BCDEMOS database as shown in Figure 12.1.

Click the Orders table and open up the Referential Constraints branch as shown in Figure 12.2. Notice that there are two constraints on this table, one called RefCustInOrders and the second called RefOrders. The RefCustInOrders field defined CustNo as a foreign key that relates to the CustNo field in the Customer table.

A second way to view this key is in the Database Desktop. Set the Working Directory from the File menu to BCDEMOS. Open up the Orders table in the Database Desktop and select Table | Info structure from the menu. Drop down the Table Properties and select Referential Integrity, as shown in Figure 12.3.

FIGURE 12.1. Viewing the BCDEMOS database in the Database Explorer.

FIGURE 12.2. The primary and foreign fields of the Orders table.

FIGURE 12.3. Selecting Referential Integrity in the Database Desktop.

Double-click RefCustInOrders to bring up the Referential Integrity dialog shown in Figure 12.4.

FIGURE 12.4. The CustNo field in the Orders table relates to the CustNo field in the Customer table.

The fields in the left side of this dialog belong to the Orders table. On the right is a list of all the tables in the database. In the center, you can see that the CustNo field has been selected from the Orders table and the CustNo field has been selected from the Customer table. The primary key of the Customer table is related to the foreign key of the Orders table.

Now go back to the Database Explorer and open up the Indices branch of the Orders table, as shown in Figure 12.5.

Note that you can see the names of the indexes, here labeled as <primary> and as CustNo. The fields found in the indexes are also displayed. For instance, you can see that the primary index consists of the OrderNo field and the secondary index consists of the CustNo field.

FIGURE 12.5. The primary and CustNo indexes on the Orders table.

I am showing these to you so that you will begin to see the distinction between keys and indexes. The two concepts are distinct. For further proof of this, open up the IBLOCAL database in the Database Explorer. Use SYSDBA as the user name, and masterkey as the password. Now open up the Employee project table as shown in Figure 12.6. Note that there are separate listings for the index, primary key, and foreign keys.

FIGURE 12.6. The Employee_Project table has three indexes, one primary key, and two foreign keys.

In practice, almost all keyed fields will also have indexes. This leads people to think the two concepts are the same. However, indexes are about searching and sorting, and keys are about referential integrity. These distinctions will become blurred at times, but it helps if you can keep it in your mind that they are different ideas. The actual details concerning these distinctions will become clear in the next few pages.

You can also see the indexes for a table inside the Database Desktop. To get started, open up the Orders table and select Table | Info Structure from the menu. The fields with the stars beside them are part of the primary index. Drop down the Table Properties combo box to view the secondary indexes. Double-click the indexes you see to view the details of their design. If you want to change the structure of a table, choose Table | Restructure from the menu, rather than Table | Info Structure.

Most of the time, I find the Database Desktop is the right tool to use when I want to create or modify a table, and the Database Explorer is the right tool to use when I want to view the structure of a table. However, I often find myself jumping back and forth between the two tools, to get the best features of each. Later in the book I will talk about case tools, which are generally superior to either of the products discussed in this section. However, there are no case tools that ship with BCB, so I emphasize the universally available tools in this text.

Throughout the ensuing discussion, you might have occasion to use the Database Explorer to examine the structure of the Customer, Orders, Items, and Parts tables. These are the tables I use when defining what relational databases are all about.

Rule Numero Uno: Create a Primary Key for Each Table!

The last two sections have introduced you to some of the key concepts in relational databases. If there is one lesson to take out of this chapter, it is the importance of creating a unique numerical key in the first field of most tables you create. This field is called a primary key. In both Paradox and InterBase, it is impossible to create a primary key without also simultaneously creating an index.

If you want to have a list of addresses in a table, don't just list the Address, City, State, and Zip. Be sure to also include an integer-based CustNo, AddressNo, or Code field. This field will usually be both an index and the first field of the database. It is the primary key for your table, and must be, by definition, unique. That is, each record should have a unique Code field associated with it.

The primary key

As I said earlier, the distinction between indexes and keys becomes blurred at times. However, they are distinct concepts and you should endeavor to discover the differences.


NOTE: In this discussion I am taking a liberty in saying that you have to create a primary key for a table in a relational database. In fact, you can simply create a field that contains a unique integer value. It doesn't have to be an index. However, making a unique index for the field will speed up the operation of your database, and it will help enforce rules that make it easy to create robust relational databases. In particular, the restraints on a primary key make it impossible for you to create two fields in one table with the same primary key.

Just to make sure this is clear, I'll go ahead and list out the right and wrong way to create a table.

Right Method

CustNo
: Integer
LastName, FirstName, Address, City, State, Zip: string

Wrong Method

LastName, FirstName, Address, City, State, Zip: string

The first example is "correct" because it has a primary index called CustNo. It is declared as a unique Integer value. The second example is "wrong" because it omits a simple numerical field as the primary index.


NOTE: I put the words "correct" and "wrong" in quotes because there really are no hard-and-fast rules in this discipline. There are occasions when you might not want to create a table that has a simple integer as a primary index. However, ninety-nine percent of the time, that's exactly what you want to do.

At the height of a warm May spring day, there is such a thing as a rose bush that has no buds or flowers. However, the whole point of rose bushes in May is that they flower. I doubt we would feel quite the same way about roses if they did not have beautiful blooms. In the same way, relational databases without primary indexes wouldn't garner quite so much attention as they do now.

I should add that not all primary indexes are numeric fields. For instance, many tables might use alpha fields containing values such as HDA1320WW35180. I'm stressing simple numeric fields in this chapter because they are easy to work with and easy to understand.

Even if you don't yet understand how databases work, for now I would suggest automatically adding a simple numerical value in a primary index to all your tables. Do so even if you are not using the field at this time. Believe me, as you come to understand relational databases, you will see why I recommend doing this in most, though not all, cases. At this point, however, you will probably be better off creating the extra field and letting it go to waste, even if you don't understand why you are doing it. After you get a better feeling for relational databases, you will understand intuitively when the field is needed, and when you are encountering one of those rare occasions when it is going to be useless.

When people first work with relational databases, they can get a little hung up about the overhead involved in creating all these extra key fields. The point to remember is that these fields allow the database to be treated as nothing more than sets of simple integers related together in various combinations. Computers fly through integer math. Adding these extra index fields to your tables makes your data become computer-friendly. Computers love those simple integer fields; your computer will show its thanks by running faster if you add them to your tables!

Computers don't feel weighed down by the extra field any more than a car feels weighed down by a steering wheel, people feel weighed down by their hands, or a rose bush feels weighed down by a rose. Relational databases want you to add an extra integer field as a primary index to your tables!

Remember, people like beautiful paintings, eloquent words, and lovely members of the opposite sex. Computers like logic. They like numbers, they like nice, clean, easily defined relationships! They like simple, integer-based primary keys in the first field of a table!

One-to-Many Relationships: The Data and the Index

One good way to start to understand relational databases is by working with the Customer, Orders, Items, and Parts tables from the BCDEMOS database. All four of these tables are related in one-to-many relationships, each-to-each. That is, the Customer table is related to the Orders table, the Orders table to the Items table, and the Items table to the Parts table. (The relationship also works in the opposite direction, but it may be simpler at first to think of it as going in only one direction.)

Master Detail Connector (primary key and foreign key)
Customer Orders CustNo
Orders Items OrderNo
Items Parts PartNo


Read the preceding table as a series of rows, starting left and moving to the right, as if they were sentences. The preceding list shows that the Customer and Orders tables are related in a one-to-many relationship, with Customer being the master table and Orders being the detail table. The connector between them is the CustNo field. That is, they both have a CustNo field.

The CustNo field is the primary key of the Customer table and the foreign key of the Orders table. The OrderNo field is the primary key of the Orders table and a foreign key of the Items table. The PartNo field is the primary key of the Parts table and a foreign key of the Items table.

The relationship between these tables can be reversed. For instance, the Parts table could become the master table and the Items table the detail table, and so on, back down the line. The reason you can reverse the relationship becomes clear when you think in purely mathematical terms. The Customer table has a series of CustNo fields. Say the CustNo for the first record is 1000. To get the Orders associated with that customer, you ask this question: "What are all the rows from the Orders table that have a CustNo of 1000?" That is:

Select * from Orders where CustNo = 1000

Clearly, you could reverse this question. If you select a particular row from the Orders table, you could find which item from the Customer table it is related to by asking for the set of all Customer records with a CustNo of 1000. Because the CustNo field for the Customer table is a unique index, you will get only one record back. However, the way you relate the tables is still the same:

Select * from Customer where CustNo = 1000

Working with Primary Keys

The Parts, Orders, Items, and Customer tables have various keys. As it happens, these keys are also indexes. An index enables you to sort tables on a particular field. A key helps you define the relationship between two tables, or otherwise group related bits of information by a set of predefined and automatically enforced rules.

Unfortunately, sadly, and confusingly, you can still relate tables even without the presence of any keys or indexes. For instance, if there were no CustNo primary and foreign keys in the Customer and Orders tables, Paradox would still let you use SQL to relate the tables in a one-to-many relationship. However, in this scenario, performance would be slow because there is no index, and there would be no constraints on the data you could enter in the two tables because there would be no primary and foreign keys that define referential integrity. In this scenario you are back to the rosebush-without-a-rose phenomena. Yes, the tables are still part of a relational database, but they lack the features that make a relational database appealing. You need both the keys and the indexes to make a relational database appealing.

I'll draw a distinction between only two different kinds of keys. The first kind I will discuss is called a primary key. The second is called a foreign key.

Creating a primary key enables you to have two people with the same name, but with different addresses. For instance, you can list a John Doe on Maple Street who has a CustNo of 25, and a John Doe on Henry Street who has a CustNo of 2000. The names may be the same, but the database can distinguish them by their CustNo. Once again, this shows why databases love those simple integer indexes. If the database had to sort on the address fields every time it tried to distinguish these two John Does, it would take a long time for the sort to finish.

Computers can easily distinguish the number 25 from the number 2000, but it takes them longer to do a string compare on "Maple Street" and "Henry Street". Furthermore, just comparing the streets wouldn't be enough; you would also have to compare cities, states, and so on. If two entries with the same name were both missing addresses, the whole system would be in danger of falling apart altogether. The same thing would happen if two people named John Doe lived at the same address. Use those integer indexes; they make your life simpler!

Working with Secondary Indices and Foreign Keys

It's now time to move on to a consideration of foreign keys. The CustNo field of the Orders table is a foreign key because it relates the Orders table to the primary key of the Customer table. It is also a secondary index which aids in sorting and searching through data. Indices also speed up operations such as joins and other master-detail relationships.

When writing this section, I have found it difficult to totally divorce the idea of foreign key and secondary indexes. However, I will try to split them up into two categories, taking foreign keys first:

Here are some facts about secondary indexes:

If you are new to databases, you will undoubtedly be frustrated to discover that different databases have varying rules for setting up indexes, keys, and so on. In this book, I tend to use Paradox tables as the default, but I also spend considerable time describing InterBase tables. If you use some other database, such as dBASE, Oracle, or Sybase, you should be sure to read up on the basic rules for using those tools. For instance, some databases let you set up a foreign key that is not an index. In the Paradox and InterBase world, however, foreign keys are always accompanied by an index, so the two words become synonymous, particularly in the hands of people who don't really understand how relational databases work.

The good news is that you will find that overall there are certain basic principles that define how databases work. The details may vary from implementation to implementation, but the fundamental ideas stay the same.

Keys Are the Keys to the Kingdom!

Let me take this whole paradigm even one step further. When I first looked at a database, I thought of it as a place to store information. After spending a lot of time with relational databases, I now think of them primarily as a way to relate bits of information through keys and indexes.

I know this is putting the cart before the horse, but what really interests me about databases now is not the fact that they contain information per se, but that I can query them to retrieve related bits of information. In other words, I'm more interested in the logic that defines how tables relate to one another than I am in the information itself.

No one can get excited about a list of addresses or a list of books. The lists themselves are very boring. What's interesting is the system of keys and indexes that relate tables together, and the various SQL statements you can use to ask questions against various sets of tables.

When I picture a table, I see its primary and foreign keys as great big pillars, and I envision all the rest of the data as a little stone altar that is dwarfed by the pillars. Like a pagan temple, it's the pillars that you notice first; the altar is just a small stone structure you might overlook until someone points it out. Of course the temple is built around the altar, and databases are built around their data. But in practice it is easy to overlook the data. You care about the pillars, and you care about the primary and foreign keys. The rest tends to fade into the background.

Give me a well-designed database with lots of interrelated tables and I can have fun asking it all sorts of interesting questions. It's not the data per se that is important, but the way the data is related!

The act of properly relating a set of tables in a database is called, tragically enough, "normalizing" the data. Where this dreadful term came from I have no idea, but "normalizing" a database is the fun part of creating a database application.

Exploring the Keys and Indices in the BCDEMOS Database

I am now going to look again at the tools that ship with BCB, and show how to use them to view and create indexes and keys. This examination of the subject will have greater depth than the quick overview presented earlier in this chapter.

Here is a list of the indexes on the Customers, Orders, Items, and Parts tables:
Table name Primary indexes Secondary indexes
Customer CustNo Company
Orders OrderNo CustNo
Items OrderNo, ItemNo OrderNo, PartNo
Parts PartNo VendorNo, Description


Notice that the Items table has a composite primary index consisting of the OrderNo and ItemNo fields. It also has two secondary indexes, one on the OrderNo field and one on the PartNo field. The Parts table has two secondary indexes, one on the VenderNo, and one on the Description field.

If you do not have a pre-made list like this one, you could find this information in at least four ways:

I will explain all these methods and then discuss some possible alternative techniques.

If you drag the Customer table off the Explorer and onto a form, you will be able to view its Indices in the Object Inspector. If you drop down the IndexName property editor, you will see that there is one index listed there. This is the secondary index, called ByCompany. If you select this index, the table will sort on the Company field.

If you set the IndexName property back to blank, the table will sort automatically on the primary index, which is the CustNo field. In other words, BCB never explicitly lists the primary index in the IndexName property editor. I suppose that the architects of the VCL assumed that all tables have a primary index, and that if you don't specify a particular index name, you want to sort on that index. Of course, it is not an error to create a table that has no primary index, and BCB can still work with that kind of table.

You can also drop down the IndexFieldNames property, which gives you a list of the fields that are indexed, in this case the CustNo and Company fields. Here you can see the fields included in the primary index, but they are not marked as belonging to any particular index.


NOTE: To study an interesting case, drop down the Items table on a form. Recall that it has a primary index on the OrderNo and ItemNo fields, and secondary indexes on the OrderNo and PartNo fields. If you drop down the index field names, you see the following list:

OrderNo


OrderNo; ItemNo

PartNo

The first item is the ByOrderNo index--the second the primary index--and the third, the PartNo index.


The IndexName and IndexFieldNames properties give you a handy way of tracking Indices at design time. They don't, however, give you all the information you might need, such as exactly what fields make up which parts of the primary and secondary Indices. In this case, you could probably guess, but it would still be nice to get a more definitive answer.

If you open up the Database Explorer, expand the BCDEMOS node, the Tables node, the Customer node, and finally the Indices node, you get (naturally enough) a list of the Indices on the Customer table! This is a great feature, and you should use it whenever possible. Figure 12.7 shows the expanded nodes of the Indices for the Customer table. (The program kdAddExplore in the Chap14 subdirectory on the CD-ROM that accompanies this book uses the TSession object to do the same thing in a BCB program.)

While you have the Explorer open, you should also expand the Fields node, as shown in Figure 12.8. This gives a quick list of all the fields and their types. Notice that you can drag and drop individual fields onto a form.

A third way to get a look at the structure of a table is through the Database Desktop (DBD). You can open this program from the Tools menu in C++Builder. Use the File menu in the DBD to set the Working Directory to the BCDEMOS Alias. Open up the Customer table and choose the Table | Info Structure menu choice. Drop down the Table Properties combo box and look up the secondary Indices, as shown in Figure 12.9. The primary index is designated by the asterisks after the keyed fields in the Key Roster. In this case, only the CustNo field is starred, because it is the sole keyed field.

FIGURE 12.7. The Indices of the Customer table viewed in the Database Explorer.

FIGURE 12.8. The Fields view of the Customer table from the Database Explorer.

FIGURE 12.9. The Database Desktop struts its venerable features by displaying the Indices on the Customer table.


NOTE: Over time, the Database Desktop will probably be replaced entirely by the Explorer. However, there are still some things that the DBD does better than the Explorer, so both products are shipped with C++Builder.

Notice the Save As button on the Info Structure dialog. You can use this to save a table that contains the structure of the Customer table. You can then print this out on a printer using TQuickReports. Be sure to use a fixed-size font, not a proportional font:

Field Name          Type   Size Key

CustNo              N           *

Company             A      30

Addr1               A      30


Addr2               A      30

City                A      15

State               A      20

Zip                 A      10

Country             A      20

Phone               A      15

FAX                 A      15

TaxRate             N

Contact             
A      20

LastInvoiceDate     @

In the example shown here, I have printed out only the first four fields of the table because of space considerations. (The fields are Field Name, Type, Size, and Key.) If I then recursively print out the structure of the table used to house the structure of the Customer table, I get the following report:

Field Name          Type   Size  Key

Field Name          
A      25

Type                A      1

Size                S

Key                 A      1

_Invariant Field ID S

_Required Value     A      1

_Min Value          A      255

_Max Value          A      255

_Default Value      A      255

_Picture 
Value      A      176

_Table Lookup       A      255

_Table Lookup Type  A      1

This is the same information found in the Data Dictionary, and it should prove sufficient under most circumstances.

Using the Database Desktop to Create Indexes

To create a unique primary key in a Paradox table, open up the Database Desktop, and create a table with the first field declared as an Integer or autoincrement value. Place a star next to the first field, which tells Paradox to create a primary index on it, as shown in Figure 12.10.

FIGURE 12.10. Place asterisks next to the first field or fields of a table to designate the primary index.

To create a secondary index, drop down the table properties list and choose Secondary Indices. (See Figure 12.11.) Click the Define button. Select the fields from your table that you want to be part of your index. Click OK. A simple dialog will then pop up asking you to name the index. I usually give the index a name based on the fields being indexed. For instance, if I want to create an index on the CustNo field, I would call the index CustNo, CustNoIndex, or ByCustNo. If I wanted to create one on a field called Name, I would call the index Name, NameIndex, or ByName.

FIGURE 12.11. Creating a secondary index in a Paradox table.

Using the Database Desktop to Create Primary and Foreign Keys

To create a primary or foreign key on a Paradox table you need to define something called referential integrity. You cannot define referential integrity without first defining primary keys on both tables involved. There also must be an index on the foreign key, but this index will be created automatically for you when you create the foreign key.

In InterBase, the situation is somewhat different. The act of creating primary or foreign keys will automatically define indexes. As I said earlier, there are little variations on the main themes of relational databases, depending on what kind of database you use.

In the Data subdirectory from the CD that ships with this book you will find two tables called MasterTable and DetailTable. Figure 12.10 shows how to use the Database desktop to create the MasterTable. These tables look like this, with the MasterTable listed first and the DetailTable listed second:
Field name Type Size Primary index?
Code + *
Name A 25

Field name Type Size Primary index?
Code + *
MasterCode I
SubName A 25


To create referential integrity between these two tables, you should open up the DetailTable in the Database Desktop. Open the Table | Restructure menu item. Select Referential Integrity from the Table Properties combo box. Click the Define button, and set things up so they look like they do in Figure 12.12. Click the OK button and give this relationship a name, such as RefMasterDetail.

FIGURE 12.12. Defining referential integrity between the DetailTable and MasterTable.

When you are done, you will have created primary keys and foreign keys on the MasterTable and DetailTable. The best way to see these keys is in the Database Explorer. On my system I used the BDE Configuration Utility to create an alias called CUnleashed that points at the Data subdirectory. If you open this alias in the Database Explorer and go to MasterTable, you can see the primary and foreign keys, which Paradox calls Primary and Foreign Fields.

Why Use Referential Integrity?

Referential integrity is one of the most valuable tools in a database programmer's arsenal. In particular, referential integrity will help guide the user so that they do not accidentally enter invalid data, or accidentally delete needed records.

To see referential integrity in action, use the Database Desktop to enter two records in the MasterTable. The first should have the word Day in the Name field and the second should have the word Month in the Name field. You do not have to fill in the Code field, because it is an autoincrement field (+) and will be updated automatically.
Code Name
1 Days
2 Months


In the DetailTable, enter in a few names of days of the week or months of the year in the SubName field. Give the MasterCode field a 1 if you are entering a day, and 2 if you are entering a month.
Code MasterCode SubName
1 1 Monday
2 1 Tuesday
3 2 January
4 2 February
5 2 March


With this data in the tables, you could define a one-to-many relationship such that if you viewed the MasterTable record with Days in the Name field you would see only the days in the DetailTable, and if you selected Months, you would see only the month names from the DetailRecord.

Referential integrity will do two things to help make sure that these tables stay in good shape. It will prevent you from deleting a record in the MasterTable that has detail records associated with it in the DetailTable. For instance, if you select the MasterTable, set the Database Desktop in Edit mode and press Control+Delete, you will not be able to delete a record from the MasterTable. Referential integrity will prevent you from entering a value in the MasterCode field of the DetailTable that is not in the primary key of the MasterTable. For instance, if you tried to enter the number 3 in the DetailTable's MasterCode field, you would get the error message "Master field missing". This is because there is no record in the MasterTable with a Code field of 3. Of course, if you added a record to the MasterTable with a Code field that had 3 in it, the database would let you enter the data.

Needless to say, these rules are also enforced inside BCB. In your own programs, you might want to create exception handlers that would pop up messages that explained to the user exactly what was wrong, and why they could not perform a particular operation. Most users would not respond well to an exception that said no more than "Master field missing!"

That is the end of my explanation of relational databases. In the last few pages you have learned about primary keys, foreign keys, indexes, referential integrity, and how all these pieces fit together to help you create robust applications. In the next few pages I will step you through some simple examples that illustrate these points.

One-to-Many Relationships: The Code

Now that you know something about the data in the Customer, Orders, Items, and Parts tables, it's time to link them together in a single program called Relate. To get started, begin a new project and add a data module to it. Place four TTable objects and four TDataSource objects on the data module, wire each data source to a TTable object, and then wire each of the TTable objects to one of the four tables mentioned earlier. You can also rename the TTable and TDataSource objects so that they correspond with their respective tables, as shown in Figure 12.13.

FIGURE 12.13. The data module for the Relate project.

Drop four TDBGrid objects on the main form for the project. Use the File | Include Unit Header menu option to link Form1 to DataModule1. Wire the grids to the datasources on the datamodule, making sure that each grid has its DataSource property assigned to a unique object. For instance, link the first grid to the Customer table, the second to the Orders table, and so on.

Using the names visible in Figure 12.4, click the OrdersTable component and set its MasterSource property equal to CustomerSource, that is, set its MasterSource equal to the TDataSource object that is linked to the TTable object that hosts the Customer table. Set the ItemsTable MasterSource property equal to OrdersSource and the PartsTable MasterSource equal to ItemsSource.

Click the OrdersTable MasterFields property and link up the Orders and Items tables on the CustNo field, as described in Chapter 9, "Using TTable and TDataSet." In the same way, hook up the TblItems to OrdersTable ká[infinity]the OrderNo field, and PartsTable to ItemsTable on the PartNo field. If you set all the tables to active and then run the program, the result should look like what you see in Figure 12.14.

Spend a little time mucking about with this program. Notice, for instance, that if you change the selected item in the Customer table, the contents of the grids showing the Orders, Items, and Parts tables will change. In particular, notice that the CustNo in all the items in the Orders table is always equal to the CustNo in the currently selected item in the Customer table. The same thing can be said about the OrderNo field in the Orders and Items tables, and the PartNo field in the Items and Parts tables.

In general, selecting one item at any level but the lowest in the hierarchy will force many detail records to change. That is why these are called one-to-many relationships. One record in the Orders table points to many records in the Items and Parts tables.

FIGURE 12.14. The Relate program at runtime.


NOTE: In this particular example, you might notice that the Parts table is always arranged in a one-to-one relationship with the Items table. However, if you reverse the order of these tables and make the Parts table the master, the arrangement will look more like a proper one-to-many relationship. However, it is not wrong to make either table the master. The point is simply to arrange the tables so that you get the information from them that you want to obtain.

This discussion of the Relate program has given you a look at some of the important features in the Database Explorer and Database Desktop. It has also given you a quick run-down on some of the key ideas behind the construction of relational databases. The point here is that C++Builder has lots of built-in tools that help you construct relational databases. There is more that I want to say about these topics, even in this rather sketchy overview of a complicated subject. In particular, I have not yet talked about joins.

Relational Databases and Joins

In the last section, you saw how to relate the Customers, Orders, Items, and Parts tables in a one-to-many relationship that is sometimes called a master-detail relationship. In this section, you will again relate all four tables, but in a different kind of relationship, called a join.

You had a look at joins in the last chapter, "Working with Field Objects." This time the query that you need to build is a bit longer:

SELECT DISTINCT d.Company, d1.AmountPaid, d2.Qty,

                d3.Description, d3.Cost, 
d3.ListPrice

FROM "Customer.db" d, "Orders.db" d1,

     "Items.db" d2, "Parts.db" d3

WHERE (d1.CustNo = d.CustNo)

      AND (d2.OrderNo = d1.OrderNo)

      AND (d3.PartNo = d2.PartNo)

ORDER BY d.Company, 
d1.AmountPaid, d2.Qty,

         d3.Description, d3.Cost, d3.ListPrice

Though not horrendously complicated, the syntax shown here is still ugly enough to give some people pause.

The basic principles involved in this kind of statement are simple enough to describe. All that's happening is that the Customer, Orders, Items, and Parts tables are being joined together into one large table of the type you would have to create if you were trying to track all this information in a single flat-file database. The one proviso, of course, is that not all the fields from the four tables are being used. In fact, the only ones mentioned are

d.Company, 
d1.AmountPaid, d2.Qty,

d3.Description, d3.Cost, d3.ListPrice

Here the d, d1, d2, and d3 are described in the following From clause:

"Customer.db" 
d, "Orders.db" d1,

"Items.db" d2, "Parts.db" d3

The Order By clause, of course, simply defines the sort order to be used on the table created by this join. I am guilty here of using meaningless variable names. In general, you should choose identifiers more informative than d1 or d2.

You can create a program that performs this join by dropping a TQuery, TDataSource, and TDBGrid on a form. Wire the objects together, wire the TQuery to the BCDEMOS database, and set its SQL property to the query shown previously. A sample program called FourWayJoin demonstrates this process. The output from the program is shown in Figure 12.15.

If you are not familiar with this kind of join, you might want to bring up the Relate and FourWayJoin tables side by side and compare them. Look, for instance, at the Action Club entries in the FourWayJoin program and trace them through so that you see how they correspond to the entries in the Relate program. Both programs describe an identical set of relationships; they just show the outcome in a different manner.

Notice that the AmountPaid column in the FourWayJoin program has the same number repeated twice in the Action Club section, as shown in Figure 12.15. In particular, the numbers $1,004.80 and $20,108 both appear twice. This is because there are two different items associated with these orders, as you can tell from glancing at the Parts table in the Relate program.

FIGURE 12.15. The FourWayJoin program demonstrates a join between four tables.


NOTE: Unless you are already familiar with this material, be sure to run the FourWayJoin and Relate programs and switch back and forth between them until you understand why the FourWayJoin program works as it does. I find it easy to understand the Relate program at a glance, but the FourWayJoin program is a bit more subtle.

Joins and QBE

The FourWayJoin program is a good advertisement for the power of SQL. Once you had the SQL statement composed, it was simple to put the program together. All the work is embodied in just a few lines of code, and everything else was trivial to construct. SQL can help concentrate the intelligence of a program in one small area--or at least it does in this one example.

The sticking point, of course, is that not everyone is a whiz at composing SQL statements. Even if you understand SQL thoroughly, it can still be confusing to try to string together all those interrelated Select, Order By, From, and Where clauses. What is needed here is a way to automate this process.

Most of the versions of C++Builder ship with a very useful tool that makes it easy to compose even relatively complex SQL statements. In particular, I'm talking about the QBE tool in the Database Desktop. If you want, you can use the Query Builder instead, or some other third-party tool that you might favor. However, in this section of the book, I will concentrate on the QBE tool, because it will be available to nearly all readers of this book. (QBE is also built into Paradox. Furthermore, there are some third-party QBE components on the market. The Query Builder only ships with the client/server version of C++Builder or Delphi.)

Start the DBD and set the Working Directory to the BCDEMOS alias. Choose File | New | QBE Query from the menu. A dialog will appear listing the tables in the BCDEMOS database. Select the Customer table. Reopen the Select File dialog by clicking the Add Table icon in the Toolbar. You can find the Add Table icon by holding the mouse over each icon until the fly-by help comes up or until you see the hint on the status bar. You can also simply look for the icon with the plus sign on it. Continue until you have added the Customer, Orders, Items, and Parts tables to the query. You can multiselect from inside the FileOpenDialog. Resize the query window until all four tables are visible, as shown in Figure 12.16.

FIGURE 12.16. Four tables used in a single QBE example.

To join these tables together, select the Join Tables icon, located just to the right of the lightning bolt. Click once on the Join Tables icon, and then click the CustNo fields for the Customer and Orders tables. The symbol "join1" will appear in each field. Click the Join Tables icon again, and link the Orders and Items tables on the OrderNo field. Join the Parts and Items tables on the PartNo field.

After joining the tables, select the fields you want to show by clicking once in the check box associated with the fields you want to view. When you are done, the result should look like Figure 12.17.

FIGURE 12.17. The complete QBE query for joining the Customer, Orders, Items, and Parts tables.

To test your work, click the lightning bolt icon once. You should get a table that looks just like the one in the FourWayJoin program. You will find a copy of this QBE query in the Chap12 directory on the CD-ROM that accompanies this book.

To translate the QBE statement into SQL, first close the result table so you can view the query shown in Figure 12.17. Click once on the SQL icon to perform the translation. You can save this SQL to disk, or just block-copy it and deposit it in the SQL property of a TQuery object.

On paper, this process takes a few minutes to explain. However, once you understand the QBE tool, you can use it to relate multiple tables in just a very few seconds. For most people, QBE is probably the simplest and fastest way to compose all your SQL Select statements. Don't neglect learning to use this tool. It's a simple, easy-to-use tool that can save you hours of time.


NOTE: The only peculiarity of the QBE tool is that by default it saves its output in a text-based language called QBE, rather than in SQL. However, once you press the SQL button, it converts the QBE code to SQL, thereby rendering the exact same results produced by standard SQL query builders. Once again, the great advantage of the QBE tool over other SQL tools is that it ships with the DBD product that accompanies nearly all versions of C++Builder. If you have access to a more powerful SQL builder, you might want to use it instead of the QBE tool. However, QBE works fine in most circumstances, even when running against SQL data in an InterBase table.

What I have said here, is, of course, heresy to many members of the hard-core client server crowd. They tend to have a natural aversion to QBE, just as C++ and Object Pascal programmers shy away from BASIC. However, QBE ships for free with both Paradox and the Database Desktop, and it will meet the needs of ninety percent, but not all, of the programmers out there. So it's worth a look, yes?

That's it for the discussion of the basic principles of relational databases. You've seen how to build master-detail relationships, and how to construct joins. More importantly, you've seen how C++Builder encapsulates these key aspects of relational database design. There is, of course, much more to the theory of relational databases. There are whole books on this subject, particularly on the best way to design relational databases.

Which Database Should I Use?

If you are not sure of which database to use, I would tentatively suggest using Paradox to get started. It has a robust set of rules for enforcing data integrity, a rich set of types, and some nice features such as autoincrement fields. It works fine on a network, as long as everyone can attach their PCs to one centralized server and you aren't expecting a large number of simultaneous users.

If you are expecting 30 or more simultaneous users, I would bite the bullet financially and switch to InterBase or to another standard SQL server such as Oracle, Sybase, or MS SQL Server. You could have a hundred or even two hundred users hitting a Paradox table at the same time, but I wouldn't recommend it. If you have a hundred users, but only ten or fifteen are likely to be after a table at one time, I would still feel comfortable with Paradox, though I would start leaning in the direction of a real client/server database.

Client/server databases such as InterBase will

Remember that when I make suggestions about databases or about anything else, I am usually not so much trying to establish a definitive standard as I am trying to give reasonable advice to those readers who are not sure which way to turn.

Summary

My suggestion at this point is to dig into relational databases and learn as much about them as you can. Raw data sitting on a disk is boring. Rows of data in a grid are boring. Relational databases, however, are innately interesting. This is the fun part of database programming. Play around with indexes, or play around with joins and one-to-many relationships. The name of the game here is to find ways to arrange data in relational tables so that you can get at it easily. When you arrange data correctly, it's amazing to see how quickly you can locate very obscure pieces of information. In fact, a number of very fun games, such as Civilization or the Ultima series, rely heavily on databases in order to further the game play. Take some time to dig into this stuff. It's more interesting than you might think.

If you are wishing that I had spent more time on InterBase tables, don't worry, because I cover that topic heavily later in the book. Much of the material covered in this chapter will be reviewed again, in much shorter form, in the light of the InterBase server.

TOCBACKFORWARD

©Copyright, Macmillan Computer Publishing. All rights reserved.