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
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. 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:
NOTE: Indices are about searching and sorting. Keys,
on the other hand, are about relating tables, and particularly about
something called referential integrity. 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
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:
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.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. 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. 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 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. 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.)
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
Computers can easily
distinguish the number 25 from the number 2000, but it takes them longer to do a string compare on "
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:
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.
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. 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. 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:
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.
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
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. 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.
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.
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
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. 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. 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. 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.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.
|