Você está na página 1de 347

-

Included in This Chapter: \


The Relational Model n What is a Database?
Along Came Codd n Data Structure m Data
Integrity q Data Manipulation
n
n

T he database is ubiquitous in modern information processing; data is,

stored in collections of some type in nearly every application of note. Ic


was the automation of mammoth collections of data and the commen- J
surate ability to sort, search, and maintain those records that led tu the :I*:
advancement, at breakneck speed, of the powerful computer hardwartr,z:j;
that has become common today. Data is power, and organizations hav+
.;
come to recognize that the ability to harness these information
resources is not only a competitive edge, but critical to their survival.
Initially modeled after the hierarchical structure of the paper files that -!: L
it replaced, the computerized database has been through numerous
,%& &
incarnations in a search for the most efficient method of maintaining ,.I+$**
the integrity and efficiency of the data sets. In the 1970s Dr. E.E Codd
proposed a database model based on mathematical objects known as ::sj
3;;
relations and the processes that can be applied to them. This intellectual treatise became known as the Relational Database Model, and it
has revolutionized the design and development of data management
,~.i_,.I.
software.
,$
Delphi, with its tight binding to the Borland Database Engine, allows
r
the developer unprecedented freedom in the design and creation ot

software that works with database resources on nearly all platforms.


This freedom, however, comes at a heavy cost. To reap the benefits of
this powerful environment, it is up to the developer to create a design
that combines the beneficial aspects of the freedom with the discipline
of the relational model. Before diving headlong into the nuts and bolts
of Delphi coding, it is important for you to form a solid foundation of
knowledge in the theory and reasoning that drive this specialized area
of programming. Database development is easy to do and even easier
to do badly. The first three chapters of this book are designed to provide you with the background skills needed to make effective design
choices, decisions that let your software derive the maximum benefits
from the wide variety of tools and implementation choices provided by
Borland.

The Relational Model


The initial step to take in this foray is to establish clearly what the relational model is. Based on the certainty of mathematics, the defining
rules for the relational model describe a system that provides consistent
and accurate results in all conditions while maintaining the integrity of
the data elements as a highest priority An important note to make at
this point is that the relational model is not a blueprint for the physical
implementation of data storage. How closely a particular vendor
chooses to apply the rules is up to them. The RDBMSs (Relational Database Management System) and development tools available vary
widely in the measure of how closely they are bound to the relational
rules in their efforts to generate greater performance from their storage
and retrieval mechanisms. Delphi, as a development tool, and the
Borland Database Engine (BDE) in particular, allow a great deal of freedom in how a database is built using the product. Understanding the
rules of the road that follow will allow your efforts to benefit from the
data integrity offered by the relational model and the internal power of
the physical engine driving your database.

What is a Database?
The term database has been applied in a number of different ways,
many specific to the development context in which the word is mentioned. Ignoring the marketing driven terminology for the moment, a
database is best described as simply a collection of related data. The
relationship is defined by some natural or forced affinity between the
items, or records, that make up the collection. Figure 1.1 shows a file of

Chapter I -Introduction to the Relational Database


/ , ielm

,.
i

invoices, all related by what they represent and the shared informati4
contained within each invoice.
INVOICE

A computerized database is an electronic representation of this file. It is a.%


repository for electronically storing da1
records, each record being a set of the
individual data elements that describe
each item that the records are mode&
upon.

1098

INVOICE1322
INVOICE1779

figure 1.1
The paper file
system was
emulated in
the first database systems.

The simplest form of a computerized,


database is a single file that contains.&.-.
of the data. Similar to a paper file con-?!
taining all of the records, there are
=:
certain actions that will commonly be
performed with the file:
*;
.,:
.
,
,
n Adding new, empty records to the database
._;
H Inserting records into a specific position within the database
w

Deleting

n
n

unneeded

records

Retrieving specific records from the database


Modifying the records contained in the database

The flat file database

in Figure 1.2 contains the names of our friends. :+

.:g

FRIENDS

Figure I.2
The FRIENDS
f/at file
database

Geurii
Sharon
Rollins Hank

722 Boston Ln
PO Box 89

818 986-2091

Geurii
Wayne

722 Boston Ln
Warhawk Dr

909 221-4456
303 458-8095

Tom
Marion

909 221-4456

Each record in this table contains one entry about each of the peo@e
Yj
that we know. Despite the advantages provided by its simplicity, the fk#
file poses a number of different problems for the developer and user ;,,*t
alike. For example, because all instances of a friend must be con:.;
tained in a single file, the possibility of update anomalies affecting the-_
integrity of the data is great. Notice that two records are needed to ,G

6 W Part I-The Relational Database


_,-:,,
, -. _., ,/,..*,, ._i

-l.i(

/?.sV

maintain the telephone number for the Geurii family members; two
records are needed because both Tom and Sharon are our friends and
this is the only way of representing both of their names. Modifying the
telephone number that they share requires two discrete operations, one
to modify Toms record and a second to modify Sharons. Due to the
duplication of data, the possibility exists that both entries may not be
updated correctly, or at all, leading to problems with the integrity of
the data. The next time that the phone book database is used to telephone Sharon, we might discover that a disconnected telephone
number has been stored rather than the correct exchange.
A common problem encountered with paper-based files also appears as
a glaring weakness in the flat file. If the records are filed according to
the spelling of the last name, a simple misspelling causes otherwise
matching records to be separated. Tom might be filed correctly as
Geurii but Sharons record could easily be misspelled as Guerii. While
this problem is certainly not limited to the flat file paradigm, the
record-by-record methods that are required when updating data compound this problem. If the update action relies on a search of last
names, only one of the Geurii family records will be updated with their
new address, causing a billing to be sent to the old address and not
received.
Flat file database implementations are usuall>- tightly bound to the
physical storage method used to place the records on disk. Because of
this, the user access methods provided for working with the table
closely match their actual implementation methods. The user perceives
that flat files are being accessed with a record-b>--record approach. The
database access methods are coded in such a ]\a!- that each record is
approached discretely, not recognizing a connection between the
related records.
The flat file database was a logical intermediate solution when the leap
was made from paper-based record keeping to the electronic storage of
data. The problems that plagued paper records followed the data to
disk and the speed at which the technology brought the problems to
the surface made it clear that a new approach was necessary.

Along Came Codd


In 1968, while working as a researcher for IBM, Dr. E.E Codd began to
examine the possibility of applying the uniformity and consistency of
mathematics to the undisciplined field of database management. His
research resulted in the now-classic paper, A Relational Model of Data
for Large Shared Data Banks, which was published in 1970 and

Chapter I -Introduction to the Relational Database


( ,s*s*Y

Columns are Single Valued


This first property is of primary importance, as it sets the stage for
many of the later properties and methods of the relational database. .$
This rule implies that there are no repeating groups in the table, a
;z?:s
repeating group being duplicate entries in the table representing the
j
same entity. The power, flexibility, and integrity of the relational data- :i;
. ;.$<
base model are derived from the strict application of this standard.
-e@
Application of the single-value rule ensures that each row in the table
unique and data manipulation and retrieval operations can be designi!
)j
to operate with mathematical precision. No gray areas need to be
:;gj
accounted for.
--; $
The VENDOR table displayed in Figure 1.3 shows a spreadsheet
approach to managing the items supplied by each vendor.
1 Kinafich

Inr

Smelt

Tidepool Company

Crab

Clam

The Crab Walk

Tuna

Shark

Swordfish

Devil Ray Foods

Oyster

Abalone

Clam

Figure I .3 VENDOR table with repeating columns

Vendors have a single row


devoted to their inventory. $
This design is problematic; li
attempting to design access 3
logic to take into account th+$J
number of possible item sol-.:$
umns would be a nightmare+!!
Locating a specific item a@$$
the vendor that supplies it ,f,i+$
would be difficult at best. &I..+
better approach, enforced ?$j
through the relational par&$
digm, is displayed in Figk$$
14

Figure 1.4 VENDOR table with


columns removed

repeating

In this relation, none of the columns are repeated and it is now a si


ple matter to associate an item with a vendor.

..*::
? CL

I-The Relational Database


_

I A;/

_ _ _ I n^.

I _i

j
__ -414~1~~-~ ,T

Entries in Each Co/umn are from the Same Domain

;.$
A domain is the defined set of values from which the data in a column 3
can be drawn. This pool of values describes the entire range of accept- $2
->:;jc*
able data elements. For example, the domain of the column
Employee-ID is the range of valid employee ID numbers. Items not
belonging to that collection of values, such as the employees last name ,::I
or an ID number beyond or below the valid range, fall outside of the
A
,I
domain for that column.
f*
With a domain for each column defined, the user is rewarded with con- 134
sistency in their data. Since the columns make up the attributes, or
I$
description, of each row added to the table, the user knows that each j$
2J
row retrieved will have the same description. Applying this rule also
simplifies data validation. Because you will always apply this rule, busi- $j
q
ness rules can more readily be applied to each data element.
. ;$,;
.3

Each Row

is

Unique
No two rows in a relational table are identical; at least one column
uniquely identifies the contents of each row. The power of this property $4
cannot be underestimated. In a non-relational data collection, it is pos- -5$
sible, and likely probable, that there is a considerable duplication of -1
information. Retrieval operations must then be designed to seek and
f&
compare, record by record, the retrieval request against all of the col- .&j
$
umns in the table in order to satisfy the request. Modification and
manipulation requests are equally at risk; slight differences in a num$
ber of conceptually matching records leads to the possibility that they
::
will not be selected for updating.
The uniqueness of each row is enforced by the presence of a key value.
A more extensive discussion of key values is presented in Chapter 2,
but for now, the definition of a primary key is a column, or a minimal
set of columns, that uniquely describes each row. Two simple rules
apply to the primary key of a relation: First, it cannot be a null. A null
value breaks the rules immediately; it does nothing to describe the row.
Secondly, the primary key must be unique within the table. A duplicate
key value would indicate duplicated data in other fields, destroying the
integrity of the relational operations.

-j
,!
:-:!
,y :x
.&a
;
;I
:
1;
.?

The existence of a key value gives the designers of the data access and
manipulation operations the freedom to build their routines around
fast and simple search methods. With the uniqueness characteristic
guaranteed, the access method can be confident that once it has
located a key value that matches the retrieval request, all of the rows
matching the request have been located and returned to the user.

:
2
.I
.i
1

Ino

Chapter I --Introduction to the Relational Database


dn_d..
s_**I .
._
. *_

(.

-.*_.

Referring back to Figure 1.3, we see that the Vendor column uniquely -,;.
identified each of the rows. With the conversion performed to create . +
the table in Figure 1.4, the database now is at risk of containing dupli-5:.
cate rows. As we will see in Chapter 2, its easy to design a solution to $
meet both criteria.

,_.;
,

The Sequence of the Columns is Insignificant

By not placing any importance on the order of the columns, the relational table is ensuring that there is no hidden meaning in the order o&j
the data in each row. Without concern for the sequence of the columnq;
the database user is free to retrieve the data in any combination or .i $
order, viewing the data as they see fit, not as enforced by the databas$s
:i<! ;..::
The VENDOR table in Figure 1.5 is shown in two configurations.

Kingfish, Inc.
Tidepool

366 Old Salt Lane

Company Keiko Avenue

Coos Bay

OR

Reedsport

OR

9a(

OR

90*3r;;

OR

903 f &$j

The Crab Walk

El Perro Blvd

Lincoln

Devil Ray Foods

Navy

Rockaway

Way

City
Beach

O R

Coos Bay

366 Old Salt Lane

Kingfish, {nc.

90421

OR

Reedsport

Keiko Avenue

Tidepool

90231

OR

Lincoln

El Perro Blvd

The Crab

OR

Rockaway

9 0 3 3 3

9031

City

Beach Navy Way

Company

Walk

Devil Ray Foods

Figure I .5 Insignificance of column order

::
The sequence of columns in version two is reversed, but the underlyin$$
data remains the same. Though the columnar sequence has changed, <$
each row still represents the same entity. In your database and code ,. .:i
designs it is important to maintain this condition. Designing data accee
routines that rely on an artificial sequencing of the columns in your
-:
data tables will result in maintenance difficulties prevented by the reEa:j
tional database design.
: ;

The Sequence ofthe Rows is Insignificant

(>
.::.*

Of even less consequence is the ordering of the rows in a table. PI&t


encumbrance placed on the user by the order of the rows, the user is
able to resequence the data to be meaningful to them. The sort order

I2 n Part I--The Relational Database


I(

*a*-*

any resulting table can be determined by the data contained in any of


the columns.

Each ofthe Cohnns is Uniquely Named


The user will refer to the columns of a relational table by name rather
than by column position; because of this, each of the columns in a table
must have a unique description. The benefits from this characteristic
are twofold. First, this will ensure that the data requested by the user is
the data that is received by the user. Secondly, since the physical positioning of the column is unimportant, the table can be easily modified
without destroying the work done by the user in the past. These same
benefits are not achieved using access and manipulation methods that
are dependent upon a column being located in a specific position
within the table.

Data

Integrity
One of the most critical aspects of any data management scheme is the
integrity of the data and how well its internal methods ensure it. The
integrity rules of the relational database model focus on constraining
the values contained within the columns of each table; without the constraints, values in the columns would be free to assume incorrect
values. Simplicity is the key in this regard; there are only two rules that
encompass the whole of the integrity constraints, the entity integrity
rule and the referential integrity rule.
The two relational integrity rules represent business rules, not technical
considerations. The relational database definition requires that the
tools needed to maintain the integrity of the database be an integral
part of the implementation. At the same time, the user should never
need to be concerned with them from any technical standpoint; relational integrity, once set through technical means, should be
transparent to the end user.

Entity integrity
The first rule is simply a repeat of the earlier structure requirement that
each row in a table be unique. The entity integrity rule requires that no
part of the primary key of a table be allowed to accept null values.
Blank data fields do not lend themselves to an adequate description of
the row, so requiring data elements in the column or columns that
make up the key to the row makes perfect sense. Requiring that the key
for each row be unique fulfills the mission of the relational model.

,,_=

Chapter I -Introduction to the Relational Dat


,_ ,.in_\_

.1

Referential Integrity
The rule regarding referential integrity introduces a new topic to the
discussion, the foreign key. Aforeign key is a column or a combinati&$
of columns that serve as the primary key in another table. The reasa i_
exists in the table of focus is to provide a method of linkage betvveea 2
the two tables. The relationship between the tables is often describ+$i
as a parent-child relationship, and, as shown in Figure 1.6, this rela- :$
*I . _
tionship must be maintained at all times.

Kingfish, Inc.

8213

Tidepool

947 I

The

Crab

Devil

Company
Walk

7790

Ray Foods

9022

V E N D O R (Parent)

I30

82 I 3

Smelt

230

947 I

Crab
PRODUCTS

(Child)

Figure 1.6 The parent-child relationship

The parent-child relationship is displayed in Figure 1.6. The VENDOR


table contains one entry for each of the suppliers that we do business . _
with; each row is unique. The number of products purchased from f?
supplier usually is greater than one. Each of the items in the
-2
PRODUCTS table is linked to its parent record, which contains the.4
ping information for each vendor, through the Vendor ID field. As &.?
3
real life, no child can exist without a parent. No products can be
entered without having a vendor ID, which can only be generated &I&$
entry in the VENDOR table. Likewise, referential integrity will prevq
a vendor from being deleted while there are still items in the prodx@, jl
:;
table using its vendor ID.

Data Manipulation
The third aspect of the relational database model concerns itself tin.=*
the manipulation of data. The model defines two categories of ope *
tions that can be performed using the relations:
a

14 n Part I- -The Relational Database

,.

,,/.

.__

1. Assignment of relations to other relations


2. Manipulation of the data using eight defined operators
Both categories are in reality intertwined. Data manipulation opera
tions such as Select result in the selected data being placed into a n
table.
The eight relational database operators share two characteristics. Fi@
the relational operators are set processing commands; they apply to .%!
and result in relational tables. The second characteristic is that the :ig
operators are unaffected by how the data is physically stored. Reme&#
ber that the relational model calls for a strict separation of the logic&-3
. .>L:
and physical implementation. The operators to be discussed are:
n Select
n
Project
H Product
H Join
n Union
m Intersection
n Difference
n Division
These operators are not intended to be a specification for a language.
Rather, the intention of defining this set of operations is to protect the
user from the burden of having to be familiar with the technical details
of data manipulation and retrieval. SQL is covered extensively in Chap- :.;,
ter 3, which also focuses on the operations supported by Delphi and
BDE. The following
are meant to act as an introduction and to
-- pages
round out the relational database definition.
Select
The select operation retrieves a set of rows into a new relation. This set
is composed of rows in the base relation in which the column values
match the criteria provided in the query.
Project
The project operator retrieves a subset of columns from a relational
table, placing them into a new relation. In the process, it also removes
duplicate rows from the result. The necessity for this initially seems
odd, but consider the situation in which the key columns are not
retrieved as a part of the operation. Without these values, it is realistic

_._

Chapter I --Introduction to the Relational Datgd


**-em>_. ,*~~m

to expect that there may be some duplication among the value&


non-key columns of the resulting table. The project operation i
unique values in the new table.
Product
The product operation puts two rows from separate tables to&
the resultant table. The new relation is now twice the column 1
the original base tables.
I oin
The join operation combines the Product and Select operations
duce the new relation. Rather than simply combining the columi
two tables, the rows to be combined are defined through the Q
a Select operation.
Union
The union operation vertically combines the data in the rows of8
relation with the rows in another table. removinn duDlicate r
resulting table. Vertical combination requires that the columns
for each of the base tables be defined exactly alike.
intersection
The intersection of two tables is a relation containing those row
are common to both tables. The intersection operator evaluates
contents of matching columns in each table to determine if the
are a match.
Difference
The difieerence of two tables is a relation that contains those rows
exist in one of the two tables but not the other. The unique rows
each relation are selected after a comparison of column values

Division
The division operator results in a relation that contains colu
from one table for which there are other matching column
sponding to every row in another table. In other words, a r
going to be divided by another relation with the quotient b
relation. Consider the example shown in Figure 1.7. Dividing the !T

INGREDIENT relation by the RECIPE relation results in a new R


The resulting table answers the question: Who can provide all Or!-, ,,
JS.ti,
ingredients for this recipe?
r i;,.
,
A/ TI-

SUPPLIERS

Kingfish, Inc.

33c

Crab

100

Kingfish, Inc.

4T

Tuna

200

The

c22

Crab

300

Devil Ray Foods

CRXXI

Crab

100
t:

Kingfish. Inc.

v3

Onion

300

Devil Ray Foods

VGXX9

Onion

200

The

BR

Bread Cr

300

Crab

Crab

Walk

Walk

Devil Ray Foods

1 Fuo<67

Bread

Crumbs

RECIPE

RESULTS OF DIVISION

.$g
&

Devil Ray Foods

Figure I. 7 The division operator

The relational database model is the de facto standard for data man:,
agement software and development tools. As a developer, it is criti+S
that you understand how closely your choice of tools follows the
rules of the relational paradigm. By understanding the theory be
the implementation, you are in a much better position to exploit
power of the database. The following two chapters continue to p
the bedrock knowledge that is the mark of the experienced databe;_
developer.

Chapter 2 discusses the practice of designing the relational database. -;


Careful design, as in all software projects, pays off in the measure of ,.
integrity and performance evident in the final database.

Included in This Chapter:


H Diagramming the Database 4 Case Study 8 Normalization n Normalization Case Study n Translating
the Logical Design into a Physical Design

As with any programming effort, taking the time to plan and

create a.1
proper design pays off when the rubber hits the road and the time corr *
to develop a database application. The application creation process is 1gL-a
simplified, as numerous problems and issues have already been
addressed up front, before the first Begin statement is coded. If you dont
care to spend the time to carefully consider your database up front, you
can simply pay the price in stability, accuracy, and programming effort
needed to make it right on the back end.

Logical database modeling, the subject of this chapter, is very similar to


the practice of object modeling; in both processes you are attempting to
identify the real-life items, relationships, and processes that youre going
to emulate with your software. The model that we create is going to
meet all of the requirements of the relational database model discussed
in the preceding chapter, and because of that, it will reap all of the benefits. There are going to be three areas of activity described, and in the
course of examining them, we will become familiar with the Redwood
Fish Foods Company, our case study.

28

_-%p.zd
.i
F

20 n PaT-t

I- -The

Relational

Database

First, the design for the database will be mapped through the creation
of entity-relationship diagrams, a standard tool that makes the database members crystal clear and puts the information into a format that
can be quickly converted into relations. Secondly, the process of normalizing the relations described in the ER diagrams will be examined in
detail. Normalization is the process by which the relations you have
designed are tested against the rules of the relational database, and
through multiple design iterations they are manipulated into place.
Finally, the process of mapping the design to a physical data structure
will be explored, setting up your design to be converted to a physical
implementation.

,j

..!,
$
2
52
;
,>
;
S
:i

Diagra.mt ning the Database


.,$

To arrive at any destination, the most efficient process is to follow a


;ii
map. Developing a database is no different. The mapping used for this $3
type of development effort is called an entity-relationship (El?) diagt-unz;$j
its a graphical representation of all of the items that will be contained 2
in the database. The diagram completely describes the database to the 3
level of detail necessary to transfer the logical design directly to a php 5%
,$Z
ical implementation.
$
In nearly every instance, the diagram will be composed of representa- .g
:*;
tions of the following items:
w Entities
w Relationships
n Primary keys
8 Alternate keys
n Foreign keys
w Business rules

Entities

In reviewing the ER diagram, it is easy to see that the two most critical ;;i

components of the database model are the entities and the relation-: i -4;;
ships between them. Before applying these diagramming tools,
.::1/
establishing some definitions is in order.
.v
;?,I.
, ;:;s
An entity is any object that the user wants to represent in the databae- i--i
and which she wants to record facts about. Examples of entities are
..3
VENDORS or ITEMS or CDs; any people, places, or things involved wi$$&$
the subject of the database. Entities are composed of attributes, facts
%$1

_-- -.

.-

F _ (_

..->

-_

Chapter 2-Logical and Physical Database Design


I ,, -. _ .i- .i -_ I;_.d-, ..s I.. __=n a ._,*-v_e

that describe the object. For example, a VENDOR will have a name,::
street address, and city attributes that serve to make that instance (
VENDOR different from all the rest. Attributes should be simple, disg
Crete pieces of data such as the city or state, atomic in nature, mea&
that they describe only one element of the object.
1.r

Relationsh

ips

Relationships are the linking agents between the identified entities. d

the case of two entities, VENDOR and ITEMS, we can identify a rel&?!$
tionship as supplies, as in the VENDOR supplies ITEMS. Thrt:e br
categories, generally described by verbs or prepositions, are general
indicators of the existence of a relationship:
w Existence relationship-a VENDOR has EMPLOYEES
w Functional relationship-an EMPLOYEE fi&Hs ORDERS
n Event relationship-a VENDOR delivers ITEMS
The entity-relationship diagram displayed in Figure 2.1 demonstratd _ _
.i
pronertv of the defined relationships; each has a cardinalin/.

One-to-One I : I

INVOICE

LINE

ITEMS

One-to-Many I :M

Figure 2. I
Cardinality of
relationships

PRoDUCTS

l-----l
Many-to-Many

PARTS

M:M

Cardinality is the number of expected occurrences of the relationship


between two entities. A one-to-one relationship means that for each .7aa
record (set of attributes) in the first entity, there is exactly one
-j,A

22
#..

n Part I--The Relational Dotabase


_-_

occurrence of a related record in other entity in the relationship. In Figure 2.1, the first example says that for each instance of WAREHOUSE
there is exactly one WAREHOUSE MANAGER.
rip

There are many accepted methods for the graphical representation of an entity-relationship diagram. For purposes of simplicity,
we are going to document the cardinality degree of our relationships with one of two symbols. One-to-one relationships will be
denoted with a single arrowhead (-+). Relationships with multiple
cardinality will be shown with two arrowheads on the relationship line (++).

The second example, a one-to-many cardinality, is demonstrated by the


invoice to line items relationship. In this relationship, one record in the
first entity is related to one or more records in the other entity. The
example diagrams a relationship in which each INVOICE contains one
or more LINE ITEMS.
The last relationship in the example is the most complex. A
many-to-many cardinality represents that one or more records of one of
the entities has relationships with one or more records of the other
entity. The many-to-many relationship is difficult to diagram and even
more difficult to translate into a logical and physical database design.
The recommended course for handling relationships of this type is that
they be further decomposed, if possible, into a pair of one-to-many
relationships. Figure 2.2 is an example of this process.
The original relationship had two programmer entities assigned to one
or more of the project entities. Creating a new entity called TASKS and
then establishing two one-to-many relationships between the relations
creates a clearer representation of the relationship. The first, between
TASKS and PROGRAMMERS, states that one PROGRAMMER is assigned
to one or more TASKS. A second relationship is then established that
says that the PROJECT entity has one or more PROGRAMMER entities
assigned to it. No information is lost in this process and you end up
with a much more understandable design.

Chaster 2-Logical and Physical Database Design


.__.**~

PROGRAMMER
JO+

/
(Yp,,,

PROJECT Y

-i

Figure 2.2

A manyto-many
relationship
decomposed

TASKS
III

Relationships also have direction, unlike some of the programmers. The


direction of the relationship describes which entity is the from entity
and which is the to. The from is also referred to as the parent while
the to entity takes the label of child. The direction of a one-to-one
relationship is for the most part arbitrary. A one-to-many cardinality
requires a little more thought to discover the direction, although often
the verb or preposition that describes the relationship provides a strong
indicator. For example, an INVOICE has LINE ITEMS; the INVOICE
entitv has the LINE ITEMS, making it the oarent to the LINE ITEMS
child.

Primary Keys
The singular nature of items in a relational database system is the key
concept that drives the paradigm. By ensuring that each record is
unique in some way, the results of either a query or data manipulation c,;
operation can be guaranteed to be correct by the database system. A
.:
primary key is an attribute value on which the non-redundancy rules
.
are heavily enforced. Though attributes in the remainder of the record r

,t,

24 n

fart I -The Relational Database


---.w^_wI_p
,I=.- 4181118-1
il s. .sda,-.n... , I. , _

_,

may match similar attributes in other records, in tandem with the key
value, they are unique.
The first step taken in this part of the design process is to identify an
attribute or a minimal set of attributes that can be used to uniquely
identify a record to become the primary key Sometimes it may become
necessary to introduce a new attribute to the relation if a single attribute or a set of attributes (a composite key) cannot be identified. As an
example, in an EMPLOYEE relation, this may take the form of an identification number that is applied to each record. A key may also be
composed from a minimal set of attributes, the smallest number of
attributes that are necessary to uniquely identify a record. Testing to
determine if it is truly the minimal set of attributes involves the
removal of any one of the attributes to determine if it results in a loss
of the uniqueness. Any attribute or attributes that are selected as candih
date keys must meet the requirements of the primary key, uniqueness
and always being non-null.

.,?
-j4
.,$
,g:;
:$iC-3
jj:,$

In the VENDORS entity, the name of the company alone may serve to
uniquely identify each record. An ITEMS table might select the item
number as a candidate key since no two products will share the same
identifying number. On the other hand, if we deal with a number of
divisions of the same vendor, the name alone will not suffice to ensure
uniqueness. In this case we will need to create a composite key of multiple fields. Combining the vendor name and zip code creates a unique
identifier that can be used.

:
;
~
2
:?
1:~
?;

$
,j
*ii
Y

Alternate Keys
The candidate keys that were not selected as the primary keys become
;
:;:,::
alternate keys. These keys will become tools to help implement easier
access to the data for your users, perhaps becoming indexes. The pur.:j
pose of identifying the alternate keys is to provide substitute access
_- 3
~$. t<
paths.
*
Foreign Keys
Aforeign key serves a critical purpose in a relationship. It is an attribute ,Pw2:y
or set of attributes that identifies the parent record. In other words, the $j
foreign key is the attribute that links the child occurrences to the par~~4
2
ent entity occurrence through matching key values. The foreign key is
L.z
:. -j
artificially present in the child entity and is the primary key of the
parent.

i._

/*;1

l
_
_
* r .~*r_s.Bcs)~

Chapter 2-Logical and Physical Database Design

_c. eie._*_ ,. __

).

For example, in the relationship VENDOR supplies ITEMS, VENDOR8


is the primary key in the VENDORS entity. This attribute appears in
ITEMS entity as a foreign key, linking each item instance back to a sp#
cific vendor. Figure 2.3 shows the resulting relation between the two
. y$$rs
entities.
::;a
VENDORS

ITEMS

901

Disk

902

CD-ROM

903

Book

904
.

I
.

Disk

200

200

I
I

Figure 2.3 The VENDOR-ID attribute is the primary key in the VENDORS em
und a foreign key in the /EMS entity.
Tip

Do not select alternate keys as a foreign key. Alternate keys


allow null values, leading to linkage problems not present if the
primary key is used. In addition, the use of alternate keys leads to
unnecessary complication of the database structure because their
use requires indirect referencing back to the primary key anyway.

- .,.~
,x
3
,:;
2
;?
:?
.,-d

Bush qess RI lies


Recall that one of the major benefits of the relational database modei ii
its increased data integrity. The main tools for implementing the inte@$
rity constraints are called business rules. These constraints are just th
rules that govern the data and the transactions that are modeled by.@
database. For example, the Waytag Appliance service center will only;..;
make service calls to cities in the state of Colorado. Because of this;?
area code other than 303, 719, 720, or 970 would be acceptable on d
work order. When written as a business rule, this constraint is desc
as follows:

26

fart/ -The

Relational

Database

The customers area code must be equal to 303 or 719 or


720 or 970.

,-.$
: &$f
I

Each attribute in every table should be reviewed with the user to deter-$
mine the constraints necessary for maintaining the integrity of the data.Gg
Acceptable ranges of data as well as format and type are all items that I?$
work together to implement a business rule. The rules will become a
t?
part of the physical database implementation, either directly imple:$
mented through the data structure or handled by the supporting code. 2;
r;
There is a second aspect of the rules that needs to be discussed, the
1:
issue of triggering. Business rules become a part of the field definition ii;
for each relation. Obedience to the rule is tested during specific opera- ;
tions on the database. These actions are called triggering operations.
.-I!
Three database operations trigger the business rules tests:
- .
l Adding data to a database
>*:
m Deleting data from a database
.:I
*.w Updating the data in fields of a database
Triggering operations cause the relational system to validate that the
~.;
database is being changed according to the rules defined for the partic- 3
2~1
ular field. For example, when adding a record to the SERVICE CALL
table, the data being entered for the Area Code field is validated
against the four acceptable codes that were defined. Entering 415
would generate an exception and the RDBMS control mechanisms
would prevent the user from entering this data.
Likewise, a deletion operation should trigger a more complex validation
procedure. Take the example of an INVOICES table that draws the ven- I ji
dors address from the VENDOR table. A business rule applicable to this :J
situation is:

Every vendor ID that appears on an invoice must be a verifk .d


.$$j
able and valid vendor ID.
a .2:G

This indicates that as long as open invoices contain a vendors ID num- $i


ber, there must be a record in the VENDOR table that matches that ID. .,
If the user tries to delete a vendor from the VENDOR table, a validation -procedure must examine all open invoices to determine if that ID is still
-:i
in use. If it is, an exception must be raised to alert the user to that fact
and prevent her from removing the record.

,_-

Chapter 2-Logical and Physical Database Desigrp


._j.j / .**v

i~--4-,*n.i_-*-,

Case Stu dY
The best way to apply these conceptual tools is to work our way
:
through an example. To do this well examine a common database LB
application, the order entry system. This business model is easy to .S
understand: A company sells items to customers. The customers
the company and provide their name and address and payment i
mation, which the company retains for future reference. The custd
selects the items that they would like to purchase and orders the
company, in turn, pulls the item from inventory, prepares an in
the offsetting entry to the customers payment, and ships the it
dors ship the items to us that we sell to the customers. The Re
Fish Foods Company will be the company that opens its doors to
exploration.
The Playerl S
In this example, it is relatively easy to pick out the entities. Reme
that the entities are those people, places, or things about which we4
want to record facts in the database. Figure 2.4 collects the ident
entities for Redwood Fish Foods and, as shown, we will be using
standard rectannular remesentation of an entitv in all of the dia

CUSTOMERS

Figure 2.4
The entities of
the Redwood
Fish Foods
Company

/ INVOICES

/ CREDITTERMS

Iii

Five entities were identified from the case description:


n Vendors
n Customers
H Items
n Invoices
w Credit terms

Part I -The
2 8 H p*l*El__._

Relational Database
*..lrimjXa
1-

. .- _* . _ .-Ic*e,,,s_L

~ eI *,_ _._ss

In reality, the process of identifying entities is a laborious one and


would result in the identification of numerous other entities. The practice of interviewing users, reviewing reports, examining business
practices, and looking at the current database would yield many more
items for us to work with, In the interest of simplicity, we will limit our
discussions to these few entities.

*. ..gj
hl;.:
. , ::
$
z

Their Attributes
Finding the positive attributes of an object (person, place, or thing) is a .:i
good exercise in any walk of life, but it is an especially important skill
$:
in database design. Remember that the attributes of an entity are the
.?
smallest component that describes the entity. In other words, the attrib-
utes are the individual items that make up each record and will direct& i
translate into the fields of the database records. In reviewing the enti,2;-_-I
ties that you have identified for the database, the next task is to
identify the components of each of them.
Well start the process with the VENDORS entity. Without worrying
about the normalization of the data (that step will arrive soon enough), -:
list all of the attributes you can identify that describe the vendors that .--:I
will inhabit the table. In considering each attribute, you should be sure .j+;
that the element cannot be decomposed further. If it can, it should be X~2:
reduced into two or more attributes. For example, the Address attribute S:!fq
of a vendor entry should not contain the street, city, state, and zip codes ::$J
of the vendor. This attribute can be decomposed further into its individ- .??g
ual elements and should be represented by individual fields for the
.,:i$: .2;
.x-s
street, city, state, and zip code.
.g;
_ 1. ~$
The VENDOR entity is composed of the following attributes:
n Name
n Street
n City
n State
n Zip Code
n Telephone
n Fax
n Contact Last Name
H Contact First Name
This set of attributes adequately describes the vendors in the table. We
must now consider which of the items uniquely describes each entity
The fields that we select will be the candidate keys, and nearly all of

.
$sTPd

.va-ilw. v ;_willnsl-._* . ,

Chapter 2-Logical and Physical Dcrtabase Design

==,~,>a~,_

b-q

the fields in the VENDOR entity could stand a reasonable chance of


being the primary key. In one way or another, each of them could be .,I
expected to be unique in each record. As we review them, lets first
consider if any of the values of the attributes is likely to change as the 1:
database is put to use. Primary key fields should be the most stable iIi I
the database since so many items are linked by and through them. The!
Street, City, State, Zip-Code, Telephone, Fax, and Contact fields coulft- c<
all be susceptible to change during usage, causing all manner of update:;2;
repercussions. They will be removed from consideration, leaving the Y*
vendor Name field as the only candidate.
This field would be a natural choice for a primary key. Very few cornpa- ,
::
nies will share business names, so it meets the requirements for
uniqueness, and it isnt very often that a company changes its name, .,;.3
making it relatively stable. One last consideration is whether or not it .;i
will ever be a null value. All of the vendors that we deal with will have
a business name, so we are okay on this front as well. However, what
;
will happen if the companies begin to provide products to us from mul- :.
tiple locations such as different warehouses or processing plants? To ?
keep our books in order, it is necessary to maintain the source of the
goods information for each order. In this case, the names are now in
danger of being duplicated. This destroys the uniqueness of the Name
field.
In situations such as this, it is best to introduce an additional attribute .:i
to the entity that will ensure the uniqueness of each record. The easiest :;
method of producing this result for the VENDORS relation is to add a :$
Vendor ID attribute to each record. This will serve two purposes, the . z; $
first bemg that each record will now be guaranteed to be unique by the,,.
Vendor-ID attribute. Secondly, each vendor record can now be freely -, .:;$
;, .:?a
modified without fear of harming any relationships within the
database.

I Can Relat :e to That


As we seek to identify the entities to be represented in the database, , .y,t
there should also be some idea forming about how they relate to one 2
another. The relationship describes transactions, communications, and fz4
ownership between the entities. As we identify and examine the rela,;;r/
tionships, the database designer is looking to identify the cardinality,
the type of participation, and the degree of participation. The fundamental relationships are shown in Figure 2.5.

30 n

Part I -The Relational Database

VENDORS

CUSTOMERS

I-

r-----l
CUSTOMERS

t-

PURCHAsf--u

GENERATE--b

ITEMS

r-----l
INVOICE

:2
:;;

-,?
1 Y-2

Figure 2.5
Relationships
between the
entities
Identifying the relationships and the details of each is again perform&j
through an analysis of the existing database, interviews with users, a&$
your own understanding of business practices. Examine the entities on@ 3;
by one and try to determine how many of the other tables are related ,,T.
to it. Often, stating the relationship in sentence form helps to identify .G
the nature of the relationship. In general, a verb or preposition will be
j
the connecting word between the two entities and will readily identify :l;S:i
ii!
a relationship. For example, VENDORS supply ITEMS or CUSTOMERS
-.g
a
generate IhWOlCES.

Once you have established that a relationship exists, one of the primary .I
:,i$
.d
aspects of the affinity that you must identify is which of the flavors of
cardinality you will be implementing. The type of relationship determines the use and extent of foreign keys or linking tables in your
database design.
A one-to-one relationship is one in which a single record in one table is
related to only one record in another table. In addition, a record in the , . :
second table can only be related to a single record in the first table. TIN, :?.%
example shown in Figure 2.6 is a relationship in which each Customer .,
record relates to only one record in the CREDIT TERMS table. Likewise,-S:
each entry in the CREDIT TERMS relation is offset by a single record in , :
the CUSTOMER table.

__

C U

i-

-i

_I

Chapter 2-Logical and Physical Database


....ii L * _
_.I-iiilP^
I
,_,
..,__*(
_.

STOMER

b CREDIT TERMS

Figure 2.4 A one-to-one relationship

Figure 2.7 demonstrates a one-to-many relationship between the


INVOICE and the ITEMS tables: An invoice contains one or more 1
items. Though the items themselves may be duplicated in th
table, each instance is unique because of the addition of the
key, Invoice Number, in the list of attributes for the table.
b+

INVOICE

ITEMS

Figure 2.7 A one-to-many relationship

The one-to-many relationship allows that a single record in one t


the parent, or dominant, table, is related to one or more records
child, or subordinate, table. The relationship also states the inve
that one or more records in the subordinate table is related to a sin
record in the dominant table.
The many-to-many relationship is the most complex of the three
both design and implementation. A many-to-many relationship c
of two tables in which a single record in the first table is related
or more records in the second table while at the same time a s
record in the second table is related to one or more records in
table. The example shown in Figure 2.8 demonstrates this con

32 n

fart I -The Relational Database

VENDORS
7

ITEMS

100

Coos Bay Fish

SQO I

Squid

200

Portland Sea Food

SQ02

Cuttlefish

300

Pacific

Coast

CR10

King Cral

400

Maine

Lobster

Crab
Shack

Figure 2.8 A many-to-many relationship

CR50

Blue Cral

OY36

Oyster

.&
,,y :$s&
~<$s
< y

* .;3
? ;:,?
L

,g
Each vendor listed in the VENDORS table could supply one or more of:;<*
the products that we carry in the ITEMS table. Additionally, our crab l
could be purchased from one or more vendors. Due to the amount of -$
redundant data that this relationship requires when implemented with.@
y;,&2-&2
two tables alone, a third table is going to be introduced to the
.ri,<:;*
-d
database.
This third table is called a linking table. The linking table is created byj$$
taking the primary key from each of the two tables involved in the rei@;3
:$g,
tionship and making these the only attributes of the linking table.
: ,:9

VENDORSITEMS

Chapter 2-Logical and Physical Database Design

COOS Bay Fish

SQO I

Squid

Portland Sea Food

SQ02

Cuttlefish

Pacific Coast Crab

CR10

King Crab

Maine

CR50

Blue Crab

OY36

Oyster

Lobster

Shack

Figure 2.9 The many-to-many relationship is decomposed and simplified by the addition
ing table.

The structure of the VENDORSITEMS linking table is now:


Vendor-ID

Item -ID

In looking at this new table you are now wondering how this fu
primary rule of the relation: no redundant data. The reason that t&
table works is that the key for the table is a composite key. The k
composed of both fields together, which serves to make each ret
unique. In examining the new relationships created in Figure 2.9
find something completely different. The many-to-many relations&,&S
now composed of two one-to-many relationships. This makes the dg
base much easier to understand and implement.
Your goal to this point in a project is have a complete ER diagram foryour project. This should include the entities, their attributes, and tl
relationshios that have been identified. The next sten in the orocess
the normalization of the data.

Normalization
Normalization is the process of decomposing relations to ensure rn&<$$

mum stability and minimal data redundancy. The process is one in !


which relations and their structures are refined in such a way that 4
data is lost and no artificial structures are introduced. A fully
:;:

34 n Port I
ii

--The

Relational Database

~;L-;

normalized relation is one that most closely matches the guidelines &
the relational model and exhibits correctness, consistency, stability, iL_non-redundancy.

I
A table is said to be in normal form when its structure and data meet.:;;
the requirements of one of the stages of normalization. There are
.:
stages labeled first through fifth normal form, Boyce/Codd normal
form, and domain-key normal form. For purposes of our discussion,
will limit our detailed discussion to the first through third normal
forms, as this level of normalization is sufficient for the vast majority
database programs. We will also step away from the Redwood Fish :Tg
example to explore other relations.
; .gy2-i
.;*$< 1!
First Norm ral Form (INF)
To be in first normal form, a relation must have no repeating groups ti
multi-valued attributes. The class and grade fields in Figure 2.10 are.&
good example of a non-relational design.

GRADE
i

Figure 2. IO
Decomposition
to first normal
form (I NF)

Each student record maintains the names of up to four classes and


grades. To be a relational table, each attribute, or field, must represent::.;1
a unique, discrete fact about the entity. The fields in the example rela- I!$.A
-.$
tions represent duplication of the data.
1.:
It is a simple matter to reduce this relation to first normal form by
,
merely introducing a child relation to the database called GRADES. 37
new structure of STUDENTS will now contain only unique records MI&,.~:
the entries in the GRADES relation will be made unique by the combi- -$$
. .- :;I ;+j
nation of the Student-ID and Class fields into a composite key. The
connection between these two tables will be a one-to-many relationsh

._i*i) .? . -_. . . . i

^ _.

Chapter

I (,

2--Logical and Physical Database Design a?i


/ /__sr*-*
i /(_
. .1 .~>_

based upon the Student ID being the primary key in the STUDENT&
table and a foreign key L the GRADES table.
,.g
,J
The benefits of a table being in first normal form are easy to enume&$
ate. First, you are now working with simpler data structures. Second&$
the tables are now in a state from which further normalization can .;,*
occur, and finally, these structures are much easier to move from the-:
:a
logical data model to a physical one.
. $4
Second Normal Form (ZNF)
Y$
Second normal form further refines the database structures. To be in i,
2NF a table must in 1NF and all of the attributes must be fully depeaz
ent upon the whole primary key. Every non-key attribute must be fu@
dependent upon the primary key. The TRANSCRIPT relation in Figur&#
2.11 is probably not in 2NE
,,;.~ 1

The TRANSCRIPT table is in 1NF; there are no repeating groups or


multi-valued attributes. Your next step is to then consider the de
ency of the fields on the primary key of Student-ID + Class. Th
STUDENT-NAME, STREET, CITY,, STATE, and ZIP are fully dependent it:
upon the STUDENT ID attribute for their meaning. On the other haq$%,*
these attributes certainly have no dependency on the field Class. The 2
reason for the second-normal-form refinement to the relation becony;
clearer when you consider a situation in which the student moves an+-,4
needs to update his address. Each instance of the student in the
,.
TRANSCRIPT relation would have to be updated, a tedious and poten;:l
tially error-introducing operation.
6
Figure 2.12 shows the decomposed TRANSCRIPT table that has
.removed the non-dependent fields into another entity called STUDEkj

3 6 w P a r t I---The Relational Database


...~
STUDENT ID
STUDENT NAME
STREET
CITY
STATE
ZIP
CLASS

r-

I
STUDENTID

Figure 2.12
Decomposition
to second normal form

WW

I
1
L--

I---

1
STUDENT NAME
STREET
CITY
STATE
I
ZIP
-~~~~~_I

STUDENT ID
cLAss

rGRADE
I
~--

>I ->
; rf
-5
.:,
.I:
G-a
tl

:
Each record in the TRANSCRIPT relation remains unique based on the $
primary key values and the relationship is set by the values in the Stu-:i
dent-ID fields in both TRANSCRIPT and STUDENTS. The update
,.
advantages are now apparent; changes to the student record will only. :z
$
involve a single operation, leaving far less opportunity for error.
Tl?ird NomJOI Form
Third normal form is achieved by first massaging the relation into setond normal form. In 3NF, each non-key attribute must now be fully
dependent upon the whole primary key 3NF introduces the concept of
transitive dependency. This type of dependency indicates a functional
dependency between two or more non-key attributes. The FRESHNESS
relation in Figure 2.13 has a transitive dependency in the Hours
attribute.
FRESHNESS

Coos Bay
200

Portland

300

Seattle

Denver
Los Angeles
Portland

!:f
c.;;
.-?
,-{
,,:i::
.pi
yh v
I)

.,

Chapter

2-Logical and Physical

Database Design
^*lrvdtl

VENDOR-DISTANCE

Coos Bay

Denver

Portland

Los Angeles

Seattle

Portland

FRESHNESS-INDEX

Coos Bay

Denver

Portland

Los Angeles

Seattle

Portland

Figure 2. I3 The FRESHNESS relation contains transitive dependencies.

i:

The values contained in the Hours field are dependent on the Origin, -.
and Destination fields but not the Vendor-ID value. Origin and Destil
tion are non-key attributes of the FRESHNESS relation. Third normat!:
form removes these transitive dependencies to a child relation in wl$
:..:,j
the attribute is fully dependent upon the primary key.
The FRESHNESS relation has been decomposed into two new relatio
VENDOR DISTANCE and FRESHNESS-INDEX. Now, each of the attrig
utes in the new relations is fully dependent on the primary key of the
table. Why go to this much trouble? This normalization exercise
.
removes update anomalies such as the deletion anomaly surrounding
the removal of vendor 100. Because of the transitive dependency we 1,
lose the freshness index between Coos Bay and Denver that is useful f$ I
7
other vendors in that location.

Though we have examined the normalization process in detail throq


third normal form, that should not be taken as an indication that this.4
where it ends. 3NF is sufficient for most applications, but there is sti1i.Z
the possibility of some specific anomalies being found in your databa
The other normalization steps are summarized in the following
_.
paragraphs.

38 n

Part I -The Relational Database


~..
*
_.
._.l../
/.

Boyce/Cod

/;_e

.__

xI--sa-*

Norma/ Form (BCNF)

A table in third normal form is in Boyce/Codd normal form when every


determinant is a candidate key. A determinant is the attribute on the
left-hand side of the arrow in a functional dependency. Dr. Codd and
RX Boyce were responsible for identifying the anomalies that occurred
with some tables in 3NF and suggested this stronger form as a solution.
Fourth Normal Form (4NF)

A relation is in fourth normal form if it is in BCNF and it contains no


multi-valued dependencies. A multi-valued dependency is a type of
dependency that exists when there are at least three attributes in a primary key. To best explain this normal form requires an example. An
entity represents a student who takes many classes and has many educational objectives, both of which are independent of each other.
Redundancy is rampant in this structure because the objectives will
have to be entered for each class. To normalize this relation requires
that the STUDENTREASONS relation be decomposed into two separate
relations, one containing the attributes Student and Class and the other
composed of Student and Objective.
fifih Norma/ Form (SNF)

A relation is in fifth normal form if it is in fourth normal form and does


not have a join dependency A table that has a join dependency cannot
be decomposed into two tables and then have the resulting tables be
recombined to form the original table.
Domain-Key Normal Form (DKINF)

Domain-key normalization is a simplification of the entire process that,


unfortunately, lacks a methodology for achieving it. Its theorist states
that if a relation is in DWNF then it is automatically in 5NE 4NE etc. A
relation is in domain-key normal form if every constraint on the relation is a logical consequence of key constraints and domain constraints.
The normalization of the relations in a database marks the difference
between a professional developer and those who will wantonly lash
together a set of tables to meet an end result. The application may initially function correctly, but over time, the anomalies will begin to
surface and beat the program down. The well-designed and normalized
set of relations will encounter far fewer problems when placed into service. One caveat to follow during the normalization considerations is
not to over-normalize. It is possible to zealously decompose relations to

j_ ___ll .=1_ =* .- ^^ i -=_ I ..e . . I_ ~.

Chapter 2-Logical and Physical Database Design


,. _i, .s/ _ i,- 1 _ .^-FICLIP.-a -4.-s ,I*I,LIx-esv~~
,I

the point at which YOU must artificially manipulate data in your apd,,
-).y;
cation in order to make the relations fit together.
~:*:g.pcq
2

Normalization Case Study

Back to the Redwood Fish Foods Company and the entities that were
identified during the earlier processes. The normalization process
through first normal form and second normal form is usually intuit@.j
to an experienced database developer. Rather than a formal process c: .~
examining dependencies and repeating groups, experience will tell th&
developer when the attributes of a relation dictate that it is appropriaa
-.a-ax:
to decompose the relation into multiple tables.
Lets review one of the entities to determine if it meets the require1;:_
.:*
ments of the normalization process. Earlier we decided on the
following attributes for the VENDORS entity:
w Name
n Street
n City
W State
n Zip Code
w Telephone
n Fax
n Contact Last Name
w Contact First Name
This structure works on a limited basis. Figure 2.14 has placed the
..-a-SE*
attribute list in table format and filled it with some representative da&?
VENDORS

Figure 2.14 The VENDORS entity before normalization

Notice the entries for Deep Sea Foods; this repeating group breaks the
first normal form rules. A primary key field needs to be added because

40

H Part I -The Relational Database

none of the candidates identified met the requirements for unique@


In Figure 2.15 we have added the field Vendor-ID to the table, en&@
ing that each record has a unique identifier.
VENDORS

Deep Sea

2 I2 Flounder

Seattle

WA

99999

(9991999.9999

OR

99999

(999)999-9999

OR

99999

(999)999-9999

99999

(999)999-9999

Foods
Deep Sea

67 Captan

Bligh Cannon

Beach

Foods
Deep Sea

3934 Maple

Florence

Foods
PKlflC Coast

Rockaway O

I2 Semde

Figure 2. I5 The VENDORS entity transformed to INF

Deep Sea Foods has informed us of a change in their sales ranks. They$$
have broken out their product lines into Finned and Non-finned and :y
assigned a salesman to each, assigning them to cover all of the su
locations. This causes a new issue within the VENDORS table; the
mans data is going to be repeated and the phone number, fax, and ~:f
;r,
name are not directly dependent on the primary key of the VEND0Rs ~.ii
.7
table. This tells us that we need to decompose the table further.
;
The new rable structures are displayed in Figure 2.16:
VENDORS

Chapter 2-Logical and Physical Database Design li


.*-me&

,+

.:_J
,

SALESMAN

Cunningham

S298

Richie

s300

Krista

Shea

S299

Linda

Bates

s574

Martin

Blank

Figure 2. I6 Decomposing VENDORS to 2NF

Removing the non-dependent fields from the VENDORS table require


that a new relation be created. This is the SALESMAN relation, which::
now contains the following attributes:
:,;j.-
n Sales ID
. ic.
H Sales First
m Sales Last
n Sales Phone
n Sales Fax
H Sales Vendor ID (foreign key)
The tables are now normalized to second normal form.

Trans dating the Logical Design into a Physical Design

;r

The final step in all of this work is to create the tables that will make
up the database. The entities and their attributes will directly map to a
table structure and, if the design and normalization steps were care;F&%
fully applied, you should end up with a stable, simple, optimal
database. There are two steps remaining in the process that lead up tO:
the use of Delphi to create the data structures: determining the data 1
types of the structure of the table and creating the field and file nam
Fields
Each attribute that you have identified will become a field in the tabkg
In creating a field in a table, some new aspects of the attribute must ti
considered. The name, data type, and size of the field are the most Q ---.
ical elements of any field. Turn your attention to the name first. It m
be an identifier that fully describes the contents of the field so that it -1:
will be clear and easy to work with within your application. In a
.

42 n

Part I --The

Relational

Database

complicated database, it is also a good idea to indicate the parent td


(the main table that the field exists in) in the name of the field.
3$
+;
~.-a
;$s
Tip Prepend any identifier that indicates the name of the table that
owns the field to the field name. For example, the field Street is a
part of the VENDORS table. Name it Ven-Street. When this field
appears in the program code for your application, it will be crystal clear as to the source and the contents of the field.

*i
-y.
.:j,+1

.;,-;z
IL?;
.;3Jf
Iz
~.1:_jl

Once the field is named, the data type must be set. The database tool Ti
with which the relation will be implemented will determine the set d &
choices. Delphi supports a wide variety of data types, all of which are -$
shown in Figure 2.17. Choosing the appropriate type may take some :;
consideration of the final use of the data. A primary consideration is ..a
the differentiation between an Alphanumeric and a Numeric data nq-&-.:
.

Alpha

Character

Number

Float

Money

Number

Date

Date

Short

Number

Memo

Memo

Binary

Memo

Formatted Memo

Memo

OLE

OLE

Graphic

Binary

Long

Number

Time

Character

DateTime

Character

Boo1

Booi

Autolnc

Number

Bytes

Memo

BCD

N/A

figure 2. I7 Delphi supported data types

If the data will not be used in any kind of computation, it should b$


stored as an alphanumeric field. This allows numbers to be form&g
in standardized ways such as telephone or social security number@.j
also to contain numeric sequences (such as the zip codes of the Ez+
United States) that are not numerically correct. Also, pay attention
fields that represent Boolean, or true-false, data. Many users are q
fortable with Y/N or A/B type data that is more correctly represend
with a Boolean data type. The field should contain the correct dat&
type, leaving permutations such as Yes/No to the user interface. i
Through the process of normalization, the VENDORS table has uz&
gone considerable change. At the last normalized level, the table 6:
designed in such a way that a database table can now be constructa
through the Database Explorer and used in a program. We will di&4
this relation-to-table creation process in more detail when we expi@
the Database Explorer and create some tables.

Summa1Y
This chapter covered the process of designing the relations that wi$
make up a database. As stated many pages ago when this chapter
began, when performed conscientiously and correctly the design ph
will result in a stable, consistent database. A good database will mi
the user interface much easier to design and implement, as the pro-i3
grammer wont be required to cover for any flaws in the underlyi
database structure.

Looking Forward
The next chapter introduces you to the SQL language, the primary .I
method of manipulating data in a relational database. Though the 1
Delphi SQL implementation is limited in scope, the knowledge of th@,
query aspects alone will make your database development much mf
productive.

DQL, Structured Query Language, is the linguafranca of the relational database environment. SQL is the driving force behind nearly
every database product available to developers and end users today
.i
Depending on the audience for which the product is designed, the SgL , ;.G
7
may be hidden behind the scenes, doing its work through a point and
,:
.^:
click interface rather than the typing of a long SQL statement. One of
the major benefits of SQL is its portability between vendors. Though ;
there are numerous, vendor-specific extensions and dialects that color $
the language, the core concepts and principles of SQL remain constant.

g
6.
B

&

This chapter presents the concepts of SQL in a reference fashion, focusing on Borlands Local SQL implementation. Upon completion of this
chapter you will be well versed in this implementation of SQL and be
prepared to work in multi-tier environments with other SQL dialects.

.?
;

SQL is an evolving standard language that closely parallels the evolution of the relational database model. Early implementations were
developed in the 1970s and an ANSI committee developed a first Stanf
dard around 1986. The most recent standard from this group was
!
published in 1992 and most vendors have settled on support for the
::$
ANSI92 SQL standard. SQL does not ensure adherence to the relational $

:.

46

n Part I -The Relational Database


model but it at least implies it with any database implementation that
advertises SQL support.

.,:J3~2
The structure of the language is based on the relational concept of all :I 2
data being in table form rather than a flat file. Files have a specific
j
structure and order while tables are unordered sets of items. Languages.:;
that address files must explicitly rely on the structure and the sequence
of the file itself and thus become inextricably linked to the data structure. One of the most important precepts of the relational model is the
separation of the logical and the physical implementation of the database. The SQL language is not directly connected to the underlying
storage mechanism and is therefore not affected by the data structure
or the storage mechanisms.
Note The first thing to learn about SQL is how to say it! There are
two camps, old guys and young bucks, warring over this topic
every day. Many in the older camp will pronounce the acronym
sequel, causing great consternation among the young bucks.
The youth movement insists that its pronounced IBM style, one
letter at a time, as in es-queue-et. Im not getting in the middle
of this one. Pronounce it however you feel comfortable.

Procedural Versus Declarative Languages


An important aspect of the SQL language that makes it ideal for working with databases and lends to its continued growth is the declarative
nature of the language. The difference between this type of language
and the procedural languages that most programmers are familiar with
is a crucial point to understanding the structure of SQL.3 syntax. A procedural language builds a program from a series of step-by-step
instructions. Each line of code is designed to tell a program how to perform one of the tasks needed to achieve a goal. A declarative language
is focused on what YOU want to achieve and relies on the languages
knowledge of the relational database and its own abilities in deciding
how to go about achieving the task.
Consider the task of a music store clerk who relies on a procedural
method to respond to customer inquiries. We tell the clerk that we
would like to purchase all of the stores Black Flag compact disks. To
complete this task, he follows a step-by-step plan for locating the items.
First, he goes down one aisle of the store, stopping at each CD to determine if it meets the criteria. If it does, he drops the disk into a basket
and moves on to the next bin. He continues this process, up and down
each aisle, until he has covered the entire store and reviewed each

Chapter 3-Structured Query

Language n

47

item. This methodology works fine until the store manager comes in
one night and rearranges the layout of the store. Suddenly, our friend
finds that his instructions no longer work and he needs to develop a
new set of instructions to respond to the changed conditions.
The procedural clerk tires of this exercise and decides to become a
declarative agent. A declarative method requires only that we reiterate
our request to the clerk. Being smarter now, he has inside structural
knowledge of the store and he knows precisely where an item is and
how many he has. He also understands the rules by which the manager
runs the store. There are no duplicated item storage spots; each item
exists in precisely one spot and is addressed by its name, not its storage
location.

Leuming SQL
To effectively learn SQL, there are a couple of items that must become
ingrained to prevent a procedural-thinking block. The first is that SQL
is set based. Just like in mathematics, a set is a non-sequenced collection of items of the same type. All SQL statements act upon and result
in sets of rows, or records. This is a primary feature of the relational
database; all data in the database is presented to the SQL language
processor in the form of tables and any action upon them results in a
new table.
The structure of these tables, being composed of rows and columns, is
the second item to remember. Though the data appears to the SQL programmer in the form of a matrix, the order and sequence of the rows or
columns of the data set are irrelevant. SQL will address the columns by
their names, not their position within the table, making the order of the
columns unimportant. SQL processors also know that once they have
located an item that meets the criteria of a statement, there is not
another one like it. This is driven by the non-redundancy rules of the
relational paradigm.

SQL. Subsets
The SQL language is subdivided into three subsets that represent separate, functionally related tasks:
n

DML-Data Manipulation Language


n
DDL-Data Definition Language
H DCL-Data Control Language

48

n Part I -The Re/crtiona/ Datubase

The third category is not commonly found in SQL implementations, as


most packages provide its functionality-internal security for the database (user permissions, etc.)-at a much higher level in the package.
We will not be discussing this category in this book.
DML is made up of SQL statements that are used for retrieving, inserting, updating, and deleting table data. An example of this is the
SELECT statement that executes a query against the database. DDL is
the SQL subset responsible for creating, altering, and deleting tables
and indexes. As stated earlier, this chapter references the SQL implementation of the Delphi/BDE environment, Local SQL. Local SQL is a
subset of the SQL-92 ANSI specification and it supports most DML and
some DDL statements.

Locd SQL
There are a number of items that are specific to Local SQL that should
be mentioned prior to discussing the statement syntax. The sections
that follow highlight the syntax rules that are specific to the formulation of the SQL statements using the Local SQL implementation. When
developing in a multi-tier environment, refer to the other product documentation to determine its specific requirements.
fib/e Names

Local SQL is enhanced to handle multiple-word table names. Table


names can include the full path and file name and can include the
tables file extension. When the file extension is included, it must be
surrounded by single or double quotes. You may also use BDE aliases as
a part of the table name.
When no file extension is provided as a part of the table name, the BDE
configuration is referenced to determine the driver type of the table.
Column Names

Local SQL is enhanced to support multiple word column names.


Multi-word column names with embedded spaces must be surrounded
by quotes, as should column names that include the table name or alias
preface.
Dates

Dates used as a part of a SQL statement must conform to U.S. date format, MM/DD/YY or MM/DD/YYYY. All dates must be enclosed in
quotes to prevent confusion with arithmetic expressions. If the year is

Chapter 3- Structured

Q luery

Language W

provided in two digits, the FOURDIGITYEAR setting in the BDE config- j


uration is referenced to determine its action.
..
If the FOURDIGITYEAR setting is False, years 49 or less are considered
to be in the 2000s. Years of 50 or greater are considered a part of the
1900s.
Time formats
Time data supplied as a part of a SQL statement must conform to the
HH:MM:SS format and be enclosed in quotes. You may append AM or
PM to indicate morning or evening. The AM/PM indicator is optional. ff
you use times from a la-hour clock, you should specify the period of
,:
the day. You can substitute 24-hour times, and all hours past 12 will be
considered to be in the afternoon.
Boo/em
Boolean data must be spelled out, True or False, and can be included
with or without quotes.
fib/e Correlation

Table correlation is used when explicitly linking a column name with a


table. This is necessary when the same column name exists in one or
more of the relations included in the SQL statement. It also allows you
to provide a shorter alias for use throughout the statement.
If the table name is surrounded by quotes in the FROM clause, it must
also be quoted when used in a correlation context. You can also assign
an alias by adding a token directly after the relation name as in
(VENDORS V). V will now be recognized as the alias for VENDORS
throughout the SQL statement.
Column Corf elation
You use the AS keyword to assign an alias to a cohunn name for use in
the rest of the query The alias cannot contain embedded spaces and
cannot be in quotes.
Embedded

Comments

Comments are often helpful to document a lengthy SQL statement.


Comments can be embedded in statements by surrounding the comment with /* and */.

:i

1:
J,.
!;.

Chapter 3-Structured Query Language I 5 1


The asterisk wildcard character represents all columns to the SELECT
statement. When used, every column from the original table is found irk :
the result set. To specify a set that is composed of specific columns, a
comma-separated list of column names replaces the asterisk:
SELECT VendorID,

VendorName

FROM Vendors

The technical term for the resulting data set is a projection.

I oins
Another common SELECT statement task is to select rows from multiple tables; this process is called a join. The SELECT statement allows
you to use a comma-separated list of table names in the FROM clause
to specify the table or tables that the statement should act against:
SELECT * FROM Vendors, Items

The resulting union contains every column and every row from both of
the tables. The statement given as an example points out the importance of carefully considering your goals for the query. Such a blanket
query as this may generate results that are far removed from what you
were expecting. You would be much better off limiting the scope of
your result set by adding additional clauses to the statement.
Join types are closely related to their mathematical counterparts and
are named accordingly The joins supported through Local SQL are:
H
n
n

W
n

:: ;;

Equi-join
Inner
Outer
Cartesian
Union
Heterogeneous

Equi-join

The purpose of an equi-join is to denormalize tables that were originally normalized into separate relations. Through the normalization
process, new relations were formed from a single original and both
contain one or more columns in common. It is through these common
values that the equi-join rejoins the tables into a single result set.
The WHERE clause of the SELECT statement determines on which columns the relations will be joined, the rows being selected when the
values in columns of the WHERE clause are equal. The values used in
the comparison expression can be single columns or concatenated

_, pG.33
GG
<:,P,
.._A
i
.?
.:.<I$
x,j

52

,.,,
i
.

Part I --The ReIationa/ Database

iL

columns. Multiple column values are concatenated through the use of


the concatenation function, 1 1.

**

Inner Join

in inner join is the same as an equi-join; it just uses a different syntax


to achieve the same result. The inner join allows you to define the join
condition in the FROM clause by adding the INNER JOIN and ON
keywords. The syntax for an inner join is:

,rJg
Y;:
::

SELECT <column list> FROM <table>


INNER JOIN <table to be joined> ON <join expression>

The result set from this join will contain only rows that meet the equality conditions of the join expression.
Outer Join

in outer join generates a new relation from two relations that have one
or more columns in common. The resulting data set does not exclude
rows from the source table-the table in the FROM clause-when they
do not match a row in the joining relation. This concept may be best
illustrated with an example. Suppose that we have VENDORS and
ITEMS tables and we perform the following outer join on the tables:
SELECT * FROM Vendors
OUTER JOIN Items ON Vendors:Vend-ID = Items:Vend-ID

The base relations and the resulting data set are shown in Figure 3.1:
ITEMS

VENDORS

Blue Moon Foods

Crab

200

Sea Foods Inc.

Oyster

300

CrabwalkSuppliers

loo

RESULT SET

100

Blue Moon Foods

Figure 3. I An outer join

Crab

ID0

and result set

The resulting relation contains the columns from both of the tables. In
the third row, where there was no match between VENDORS and
ITEMS, an outer join will fill the columns with null values.

.(.
.I:.
,_

*_ia

Chapter 3-Ctructor Fed

very Lung

This is representative of a left outer join where all of the rows from t&$
relation on the left of the expression are included in the result set. TiW{
modifier BIGHT will create exactly the opposite result; all rows from ;;i
the table on the right of the expression will be included. A full outer ,i:
join wiI1 contain all rows from both of the tables with the expression
:qa
null filled in as appropriate.
I
,;:jS
Cartesian Join

Approach the Cartesian join cautiously, as it can quickly get out of


hand. A Cartesian join is the result of all possible combinations of lows
from all of the tables included in the join. The join will match each TOW
in table A with each row in table B without comparison. The result set
is built from simple association. If table A has 10 rows and table B has
10 rows, the resulting data set will contain 100 rows.

r 3i/
! I

.?
$!
.j
T:
,?;

~sTf:,.q
~g

Union Join

The union join is used when you want to add the rows of one relation ;:
to the end of another relation of similar structure. The two relations
must be very close in structure, including data type, for the union joti,
to be successful. The BDE will attempt to do some data conversions
during the process, but up-front work will pay off if this type of join is :
,$., y
your goal.
I. .g
Heterogenous

join

A heterogenous join is a join in which the tables involved are from two
different databases. A Local SQL requirement is the databases involved
in the join must be accessible through the BDE.

The first delimiting word that we can add to a SELECT statement is


DISTINCT The DISTINCT keyword limits a resulting data set so that it
contains only singular rows. The uniqueness of the rows is determined
by the column list that is provided in the statement. The SELFXT
statement:
SELECT DISTINCT Vendor-Name, VendorCity

from Vendors

will result in a new relation composed of these two columns. The SQL
,;i!
processor will evaluate each combination of columns for uniqueness,
and redundant rows will be removed from the final result set. You are -i.::
probably questioning the need for this clause, given the relational rules i,$
,>j$
that we have discussed in the previous two chapters. In general, the
database should take care of this for us. If the column list that we a&~&-~;
includes the primary key, then we can be assured that a unique data set ;.
will result. However, many times we will want to retrieve a subset of .zs:Tjg

54

Part I -The Relational Database

the table that does not necessarily include the primary key, as shown in
the above SELECT statement. The DISTINCT keyword acts upon the
non-repeating values rule and ferrets out the duplicate rows.
Limiting the Result Set

Most of the time, you will not want to retrieve an entire data set into
another, new data set. More often, the SELECT query that you formulate will be based upon a set of criteria. The SQL WHERE clause is the
keyword that filters the resulting data set based on a Boolean evaluation of each record. The optional clause results in a subset of the base
table being selected.
SELECT <column list> FROM <table
WHERE < predicates >

list>

The WHERE Cluuse

The WHERE clause uses a defined set of predicates, or logical expressions, to define the filtering conditions. The following predicates are
supported:
Comparison-Compares two values
BETWEEN-Compares a value to a range of values
EXISTS-Compares a value to a lookup list
IN-Determines if a value exists in a list of values or a table
LIKE-Compares one value with another
IS NULL-Compares a value with Null
SOME/ANY/ALL-Performs a quantified comparison
Comparison

Comparison predicates simply compare two like values based on the


Boolean operator that separates them. The following SELECT
statement:
SELECT VendorName
WHERE VendorZipCode

FROM Vendors
= '80437'

retrieves a result set that is composed of rows containing the vendor


names for those vendors that have a zip code of 80437. You can use all
of the Boolean operators available to Delphi ( <, < =, =, > =, > ) in a
comparison predicate.
SELECT * FROM Salesman
WHERE AnnualSales
>= 10000

Chapter 3-Structured Query

Language

%$$$
.:;,

You can add the logical operators AND or OR to your WHERE clause t@:5
create more complex comparison statements. The SELECT statement: -$
,<a3
SELECT * FROM Vendors
WHERE ((VendorCity
= 'Coos Bay') OR (VendorCity

= 'Portland'))

will evaluate each row from Vendors to determine if either of the


VendorCity conditions is met. The logical NOT operator can also be
used to immediately negate the predicate:
SELECT * FROM Vendors
WHERE NOT VendorCity
= 'Coos Bay'

This statement will retrieve a result set containing all vendors that are
not located in Coos Bay.
Tip The CAST function can be used in SQL operations that require
like data types such as the comparison predicates. The CAST
function converts a specified value into a different data type.
Using this function can allow you to create a comparison
between two operators such as a number and a string. For
example,
SELECT * FROM Biolife
WHERE CAST("Species
No" as Char(6)) >= '90200'

.,

casts the field Species No as a character string rather than a


numeric field. This might be just the ticket for performing some
of your parameter-driven queries.

i
^i3

BETWEEN

The BETWEEN comparison operator compares the column value in


your WHERE predicate to a range of values. If the column value is
greater than or equal to the low end of the range and less than or equal
to the high end of the range, a TRUE result will be returned. You may
also modify this operator with the NOT logical operator to achieve the
opposite result and identify those rows that fall on the outside of either
side of the range. The values that are being compared must be of the
same data type or of a compatible data type. The SELECT statement:
SELECT Description,Freshness FROM Items
WHERE (PurchaseDate
BETWEEN 10/31/98 AND 11/03/98)

will return a result set that includes any inventory purchased on or


between the range of dates.

-i
-?i
>::
I$
~5
5:
i
<*.

56 n

Part I -The Relationa/ Database


EXISTS

The EXISTS comparison predicate lets you take an entirely new


approach to developing your query. The addition of EXISTS to a SQL
statement indicates that you are performing a subquery, or nested
query, within your outer query. The comparison action is then performed against the result set from the inner query
The tables shown in Figure 3.2 are related by the vendor ID contained
in each, once as the primary key and again in the ITEMS table as a foreign key We want to develop a query that will tell us who our suppliers
are for a specific product, crab. Performing this requires two different
questions: Which vendors supply crab and what is the vital information
that we need about them? The EXISTS predicate performs this operation for you. It filters a table to retrieve those values that exist in a
subquery table.
VENDORS

ITEMS

Figure 3.2 The VENDORS and ITEMS relations are related through the Vendor-/D field, making
an

EXISTS

predicate easy to implement.

The subquery uses a WHERE predicate that includes one or more fields
from the outer query and, presumably, additional filtering conditions.
The SQL statement:
SELECT * FROM Vendors V
WHERE EXISTS

(SELECT IT-VENDOR-ID FROM ITEMS I


WHERE IT-ITEM_DESC
= 'Crab' AND
V.VS VENDORID = I.IT-VENDOR-ID)

performs the subquery (shown in italics) once for each row in the table
of the outer query. If the conditions in the subquery are met and a
TRUE is returned, the current row in the outer query is returned in the
result set.

Chapter 3--Structured Query Language n

57

The subquery requires table correlation in order to work correctly


There are a couple of different ways that this can be handled. The first
is demonstrated in the example query. Notice that in each FROM
clause, an alias is appended to the entry in the table list. This is known
to the query as the tables alias. You can then use this shorthand to
refer to the table as the owner of a field by prepending this alias to the
column name, as in VVS-VENDORID
and I.IT-VENDOR-ID.
If you
dont want to use the alias approach, you can use the full table identifier to identify the table, VENDORS.VS-VENDORID
and
rIEMS.ITVENDOR-ID.
IN

The IN predicate allows you to select the result set based on the comparison of a column value with a specified set of values. The IN
predicate works on the same principle as the Pascal IN Boolean operator: If value X is a part of set S, a TRUE value is returned. The IN
predicate works the same way. If a column value matches an item in a
set that is defined in the body of the query, then a TRUE is returned
and the row containing the column value is retrieved as a part of the
result set. The following SQL statement will retrieve only those rows in
ITEMS that have an item description that is included in the set
(Crab,Shad) :
SELECT * FROM Items
WHERE IT-ITEM-DESC IN

('Crab','Shad')

The items being compared, as in all SQL statements, must be of the


same data type or cast to be of the same data type.
The IN predicate can also use the results of a subquery to determine
the members of a set. For example, to determine all of the items that
can be purchased quickly from a supplier in Portland, the query:
SELECT * FROM Items I
WHERE ( I.IT VENDOR ID IN
( SELECT V-k-VEiiORID FROM Vendors V
WHERE V.VS-VENDORCITY
= 'Portland'))

will create a set based on the criteria VS-VENDORCITY = Portland.


The set will contain the vendor ID numbers and will be a dynamic comparison set for the IN predicate. Each row in ITEMS will be retrieved
based upon its Vendor ID column being a member of the set.

58

H Part I -The Relational Database


LIKE

Selections for the result set using the LIKE predicate are based on the
similarity between a column value and a comparison value. The fuzzy
comparison is implemented through a set of substitution characters and
allows you to match anything from the first letter of a column all the
way out to the entire length of the value.
The wildcard substitution character % represents any number of characters in a comparison. A row is retrieved in a query in which a WHERE
comparison using the LIKE predicate matches any portion of the comparison value not corresponding to the wildcard character.
SELECT * FROM Vendors
WHERE VS-VENDORNAME
LIKE 'C%'

Running this SQL statement against the VENDORS table will retrieve
rows that contain Crabwalk Suppliers and Conglomerated Foods
but not Blue Moon Foods.
If the substitution is limited to a single character, you can use the substitution character _ at any position within the comparison value.
SELECT * FROM Items
WHERE IT-ITEMNUMBER LIKE lo-

This SQL statement will retrieve any Item Number that falls between
100 and 109, performing the substitution on the last character in
the string. As with the wildcard substitution character, the single-character substitution token can appear in the beginning, middle,
or end of the comparison value. For example, the comparison value in
the SQL statement could be modified to be U 0 . This would return a
result set that included the first ten values in-e&h hundreds set
(101..109, 201..209) but would not include any above those values
such as 110 or 233.
The keyword ESCAPE is a modifier to the substitution characters used
in a LIKE predicate. You will use ESCAPE when the characters O/6 or
- appear as a part of the data in the column values. An escape character designates a symbol which indicates to the LIKE predicate that the
(yo,, or ,, character immediately following is to be taken as a literal
value. For example, @ is the escape character in the comparison value
%lOO@%%. The retrieval selections will be filtered for values that are
like 100%. The SQL syntax for this statement is:
SELECT * FROM Inventory
WHERE InStock LIKE %lOO@%%

ESCAPE @

Chapter 3-Structured Query Languuge n 59


The LIKE predicate only works with alphanumeric data types or types
that can be cast as character data. The comparison performed is case
sensitive, following standard Pascal conventions.
IS NULL

The IS NULL predicate simply retrieves rows based upon a TRUE


response to an IS NULL comparison.
SELECT * FROM Vendors
WHERE VS-FIRSTORDERDATE

IS NULL

This SQL query, performed against the VENDORS relation, returns a


result set for all Vendors from which we have never ordered. The comparison value must be a true Null, not blanks or zeroes.
SOME/ANY/ALL

The quantified comparison predicates SOME, ANY, and ALL retrieve


rows based on the quantified comparison with the results from a
subquery This predicate combines with a simple comparison predicate
to select the rows to be retrieved. The SOME and ANY predicates are
functionally the same and the rows are selected if the accompanying
simple comparison predicate evaluates to TRUE for any row in the
subquery. For instance, the SQL statement below will return any row
from the ITEMS relation in which the Item Cost is less than ANY of the
Wholesale Costs returned by the subquery.
SELECT * FROM Items I
WHERE (I.Item-Cost < ANY
(SELECT WholesaleCost
FROM Wholesale))

The subquery may retrieve multiple rows but it can only contain one
column. The column selected must be of the same data type as that to
which it is being compared in the outer query You may also cast the
values if necessary to make them comparable. The ALL predicate
requires that every simple comparison between the column value and
the column values in the subquery evaluate to TRUE.
SELECT * FROM Items I
WHERE (I.Item-Cost > ALL
(SELECT WholesaleCost

FROM Wholesale))

This SQL statement modifies the original query to retrieve rows from
ITEMS in which the Item Cost is greater than every row in the
WHOLESALE relation.

Put-t I - T h e Relationd D a t a b a s e
Predicate

Summary

The predicates supported for the WHERE keyword determine the filtering of the query and any subqueries. If you review these in terms of the
relational database discussion in earlier chapters, you can see that the
SQL statements all work on the surety of the design. There can be only
a true or false response to any query of the database; all gray areas
have been removed through careful design. This confidence makes all
of your up-front work worthwhile.
The ORDER BY Chuse
The ORDER BY clause is used in a SQL SELECT statement to order the
retrieved rows in the result set. The order is determined by the values
in a comma-separated list of one or more columns.
SELECT * FROM Vendors
WHERE VS-VENDORCITY = 'Seattle'
ORDER BY VS-VENDORNAME

This statement will retrieve all vendors located in Seattle and then will
order the output by the value contained in the column
VS-Vendorname. By default, this will be in ascending order. You may
control the listing direction by adding either the ASC or DESC modifier
to the ORDER BY colunm list.
SELECT * FROM Vendors
WHERE VS-VENDORCITY = 'Seattle'
ORDER BY VS-VENDORNAME DESC

This statement will present the retrieved rows sorted in descending


order. A comma-separated list of column names can each be set to a
different sort direction as shown in the following example.
SELECT * FROM Items
WHERE IT-VENDOR-ID = '100'
ORDER BY IT-VENDOR-ID ASC,

Tip

IT-DESCRIPTION

DESC

You can use some handy shorthand to build your ORDER BY


(and GROUP BY) column list. Remember column correlation?
Local SQL allows you to refer to the column names in three different ways. You can use the full column names but this makes
for some very long statements. You can also refer to the columns
as they are enumerated, for example, in the column list Name,
Street, City, State, Zip, Name is referred to as position I, Street
is position 2, etc. Also, much as we have used single letters to

Chapter 3-Structured Query Language

6 1

create table aliases (i.e., VENDORS V) you can create column


aliases by listing the alias directly after the column name. The column list would now be Name N, Street S, City C, State ST, Zip
Z, and your ORDER BY clause can now use the aliases to shorten
up the overall statement.

The GROUP BY Cfuuse


The GROUP BY clause is used in conjunction with the aggregate functions to combine rows with the same column value into a single row.
The criteria for combining rows is based on the column list specified in
the GROUP BY clause. The following SQL statement groups the average
totals by the Item Name.
SELECT Item-Description, AVG(
FROM Sales
GROUP BY Item-Description

Item-Cost ) As Average

Figure 3.3 shows the original relation and the rows that are retrieved
from this query

563

563

I
I

I
I

I Smelt

563

I Smelt
I Devil Ray
I Smelt

666

IDevil Ray

921

I Oyster

666

I
I
I

I
I

Result of SQL statement using the aggregate fixxtion

IOysteC
I Smelt

I
I

8.77
5.77

3.07

9.88

.99

3.56

I .os

8.77

I
I

Average.

I
I

Figure 3.3 SQL results using an aggregate function and the GROUP BY clause.

Part I -The Relational Database


Aggregate Functions

Local SQL supports a number of aggregate functions for use in SQL


statements. The functions supported are:
n

H
H
W
H

AVG-Averages all non-null numeric values in a column


COUNT-Counts the number of rows in a result set
MAX--Determines the maximum value in a column
MIN-Determines the minimum value in a column
SUM-Totals all numeric values in a column

As shown in the GROUP BY example above, the functions are used as a


part of the SELECT column list. Each of them results in a new column
being created that will either be named by you using the AS Name
modifier or will default to a description of the function as in COUNT
OF ITEMCOST.
AVG

In its simplest form, the AVG (average) function will compute the average of all values in a column.
SELECT AVG( ITEM-COST ) FROM Sales

will result in a single row result set. When the GROUP BY clause is used,
an average is computed for each group determined by the column list.
COUNT

Use the COUNT function to return the number of rows retrieved by a


statement. The statement:
SELECT COUNT(

ITEM-DESCRIPTION ) FROM Items

will return a count equal to the total number of rows in the Items table.
You can modify this function by adding the DISTINCT modifier to the
statement:
SELECT COUNT(

DISTINCT ITEM-COST ) FROM Items

This will result in the function ignoring duplicate column values in its
total.
MAX

The MAX function returns the maximum value encountered in all of the
rows of the identified relation.
SELECT MAX{ ITEM-COST ) From Items

Chapter 3-Structured Query Language H 63

When this SQL statement is processed against the Sales table in Figure
3.3, the row returned will contain 9.88. By modifying the SELECT
statement with a GROUP BY clause, the maximum cost of each of the
item types can also be returned.
MIN

The MIN function returns the minimum value of a column in all of the
rows selected. It works identically to the MAX function.
One item not mentioned to this point is that you can combine these
functions in a single statement to produce a set of useful statistics from
a single query The following statement will produce a result set that
shows the average cost for each item and the highest and lowest costs
as well.
SELECT ITEM-DESCRIPTION, AVG( ITEM-COST
MAX( ITEM-COST ), MIN( ITEM-COST )
FROM Items
GROUP BY ITEM-DESCRIPTION

),

SUM

The aggregate function SUM totals all numeric values in columns


across the entire set of rows in a dataset. As in all of the functions,
using the function alone will result in a single row being returned that
contains the sum of the columns selected. The GROUP BY clause will
result in a sum being computed for each group.
The HAVING Clause

The HAVING clause is used in a SQL statement to limit the rows


retrieved to those in which the aggregated columns meet the HAVING
clause criteria. To utilize the HAVING clause, both a GROUP BY clause
and one or more aggregated columns must be present in the statement.
For example, the following SQL statement will retrieve only those
groups that have an average cost of greater than $5.00.
SELECT ITEM-DESCRIPTION, AVG( ITEM-COST
GROUP BY ITEM-DESCRIPTION
HAVING ( AVG( ITEM-COST ) ' 5.00 )

FROM

Sales

Multiple predicates may be used in the HAVING clause to further knit


the roivs that are retrieved by using the logical operators AND or OR.
You may also utilize subqueries in a HAVING clause, causing it to act
like a search condition. Though they appear to be similar in function,
the WHERE and the HAVING clauses perform two separate functions.

61

Part 1 -The Rekational

Database

The WHERE clause limits the data to be aggregated, using columns that
are not a part of the aggregate functions as the criteria for selection.
The HAVING clause filters the rows after aggregation, using the columns that are part of the aggregation functions to limit the result set.

The DELETE Statement


DELETE
[WHERE

FROM table-name
predicates]

You can use the DELETE SQL statement to delete one or more rows
from a relation. When used without the WHERE clause, all rows in a
table will be deleted. The WHERE clause is used in conjunction with
selection predicates to limit the rows that are deleted. The WHERE
predicates supported match those detailed in the SELECT section in
previous pages.
The statement:
DELETE

FROM

Sales

will result in all rows in the relation Sales being selected for deletion.
Be cautious when issuing such a blanket command. More common will
be a DELETE statement that utilizes a WHERE clause to control the
deletion selection. The following example deletes rows from the Sales
relation based upon the value of the SaleDate column.
DELETE FROM Sales
WHERE SaleDate <= 10/15/98

Note When deleting records from a relation, it is important to know


the deletion scheme used by the database driver that you are
using. Some will not remove and clear the space in your data
table, while others will. This process could end up leaving holes
of unused space in the table, slowing your program down. You
may want to follow a DELETE with a process that packs the
table.

Chupter 3-Structured Query Language n

65

The NVSERT Stutement


INSERT INTO table-name
[ (column list) ]
VALUES ( update values )

You can use the SQL INSERT statement to add new rows of data to a
relation. The syntax for this DML statement changes a bit. The keyword
FROM is replaced by INTO before declaring the target relation name.
You may include a comma-separated list of column names that is surrounded by parentheses to the statement to explicitly define the data
targets. If you do not include the column list, as in the following
statement:
INSERT INTO Sales
VALUES (1231,Smytheson,ll/l5/60,455.68)

the data contained in the VALUES clause is inserted into the columns of
the relation on a positional basis, as the columns appear in the relation
definition. Without the column list, you must supply the identical nurnber of values as columns in the relation definition.
Specifying a column list gives your SQL statement much more control
over the insertion process. When a column list is included, the VALUES
clause now corresponds one to one with the column list, inserting the
values into the same enumerated slot.
INSERT INTO SALES
( ITEM-NUMBER, ITEM-DESCRIPTION)
VALUES ('833','Lobster')

In this statement, 833 will be entered for the Item-Number value and
Lobster for the Item-Description. Any columns not included in the
column list will have a NIL inserted.

The UPDATE Statement


UPDATE
table-name
SET column name = update value
[, column name = update value ]
[ WHERE predicates ]

66 n

hrt 1 -me Relational Database

The UPDATE statement modifies column values in one or more rows of


a relation. By default, all rows in the relation will be modified according to the column/value list that follows the SET clause. Each
expression in the SET clause is composed of a column name, the equal
sign, and an appropriate data value. This list is comma-separated as we
see in the following example:
UPDATE Sales
SET ProfitMargin

= 0.10

The value part of the expression may be a literal value, as shown, or


can be passed by parameters, returned from functions, or be the result
of a subquery that returns a singleton select. (A singleton select is a
query in which a single column and row are returned.)
Because there is no WHERE clause in the sample SQL statement, every
row will have the update applied to it. To limit the rows that will be
affected by the update, you can include the optional WHERE clause
and all of the associated predicates to the statement. For example, the
following SQL statement will only modify those rows that have an
Item-Cost of greater than $5.00.
UPDATE Sales
SET ProfitMargin
= 0.15
WHERE ( ITEM-COST > 5.00 )

Data Definition Language Statements


Data Definition Language (DDL) statements are used to create, delete,
and modify objects in a database. DDL statements do not create data or
search it in any way; they only create the structures for these actions.
Local SQL supports the following DDL statements:
n

a
n

H
n

CREATETABLE
ALTERTABLE
DROP TABLE
CREATEINDEX
DROP INDEX

The DDL language set is more concise than the Data Manipulation Language but no less important. In fact, outside of database tools that take
all of their direction through SQL statements, these statements are little
used by programmers. Many programmers, not surprisingly, prefer to
use interactive tools to create and manage their data objects. After this

Chapter 3-Structured Query Language

67

review, you might see these commands in a new light and realize
opportunities for expanding your application development skill set.

CREATE TABLE Statement


CREATE TABLE table name
( column-definition [, column-definition, . . ]
[, primary-key-constraint ]

The CREATE TABLE statement is used to create a new, empty table,


defining its columns and setting a primary key for relation. Local SQL
supports the creation of dBASE and Paradox tables through this statement. The table and column names must meet all of the requirements
defined for these identifiers, and if they contain embedded spaces, each
must be surrounded by quotes.
A column definition is composed of a column name, a data type, and a
size, if applicable. There is no punctuation between these elements, but
multiple column definitions are comma separated. Though the native
BDE databases support a wide range of data types in their proprietary
tables, Local SQL supports a common subset as shown in Figure 3.4.

precwon

optlona

No scale or precision specified.

$4
..z
:;

68 H Part 1 -The Reiationai Database

BLOB column: Memo (I), Binary (2),


ted Memo (3), OLE (4). or Graphic/Binary (5).
ength of a Paradox BLOB column must be
between 0 and 240. This represents the amount of
BLOB data that is stored in the .DB file itself; for
dBASE tables the length must be between 0 and

Figure 3.4 Column

types supported by Local SQL.

The following SQL statement creates a Paradox table called STUDENT:


CREATE TABLE student.db
( LastName
CHAR
(201,
Fi rstName
CHAR
(lo),
CHAR
Major
(5) 3
GPA
NUMERIC (6,2),
PRIMARY KEY (LastName,
FirstName))

The PRIMARY KEY keyword defines a primary index for the relation
composed of the items contained in the column list, in this case
LastName plus FirstName.
The type of table that is created is defined by the filename extension
that is a part of the table name. .DB will build a Paradox table; .DBF
builds dBASE tables. If the extension is omitted in the table name, the
default driver setting in the BDE is referenced for the type of table to
build.

The ALTER TABLE Stutement


ALTER TABLE table-name
DROP [COLUMN] co1 umn-reference[,
co1 umn-reference, . .]
ADD [COLUMN] co1 umn-reference [,col umn-reference, . .]

Chapter

3-Structured

Query

Language n 69

You

use the ALTER TABLE SQL statement to restructure a table in a


database. The ALTER TABLE statement can either add new columns to
a table or delete an existing column. Both of these actions can be combined into a single ALTER TABLE statement.
To delete a column from a table, you only need to provide the name of
the column. The following statement deletes the GPA column from the
STUDENT table:
ALTER TABLE 'student.db'
DROP gpa

When adding a column, the column definition must follow the same
structure described in the CREATE TABLE section. The statement below
drops the column FirstName from the STUDENT relation and replaces
it with a new column named FirstInitial.
ALTER TABLE 'student.db'
DROP FirstName,
ADD FirstInitial

CHAR (1)

It is important to note that if a dropped column is a part of the primary


key for the table, the primary index will be deleted. You should then
rebuild the primary index before going forward based on a new or
existing column.
The DROP TABLE Statement
DROP

TABLE

table-name

DROP TABLE is simple enough to understand and use. Its purpose is to


delete an existing table from a database. No questions asked. No waming. No caution dialog. You get the idea.

The CREATE INDEX Statement


CREATE (UNIQUE] [ASC 1 DESC] INDEX index-name ON
table-name (column name [,column
name,...])

The CREATE INDEX statement is used to build secondary indexes for


existing Paradox and dBASE tables. The value used for the index name
may not contain embedded spaces. Paradox indexes may be composed
of multiple columns but dBASE indexes developed through the SQL
statement are limited to using a single column. When the UNIQUE keyword is used, the index will raise an exception if duplicate values are
encountered. The statement:
CREATE INDEX DESC students ON Student (LastName,FirstInitial)

70 n

Part I -The Relationa/ Database

creates a Paradox index built with the LastName and FirstInitial columns of the STUDENTS table. The keyword DESC is used to sort the
index in descending order.

The DROP INDEX Stutement


DROP

INDEX

table-name.index

name 1 PRIMARY

You can use the DROP INDEX SQL statement to delete a primary or secondary index from an existing table. The notation used to perform this
drop is different according to the table type and the type of index you
are working with. To delete a Paradox primary key, the SQL statement
will read:
DROP

INDEX

Students.PRIMARY

A Paradox secondary index is deleted by using an identifier that


appends the index name to the table name using dot notation:
DROP

INDEX

student.gpa

dBASE primary or secondary indexes are deleted in the same manner.


DROP INDEX "sales.dbf".territory

Summary
SQL is the point at which careful relational design pays off. As you have
seen with both the DML and DDL statements, the primary rule of relational databases, non-redundancy, creates an environment in which
there can be only one correct set of responses to any query The depth
of the SQL constructs supported by Local SQL makes this language
within a language, the Delphi development environment, an important
skill to obtain. We will discuss these statements again when the components that use them are explored.
This is the last chapter on the theoretical aspects of database development. You have reviewed the theory behind the relational database
paradigm and, hopefully, have gained some new insight into the importance of following this model in your development efforts. You have the
tools and the knowledge to effectively design a relational system; now
we will explore the tools that Delphi provides to do so.

Chapter 3-Structured Query Language I 7 1

Looking

Forward
The next chapter will explore the Borland Database Engine. This is the
database behind Delphi and it sometimes works in unexpected ways.
We are going to delve into the engine itself and the API through which
many commands can be entered.

he Borland Database Engine (BDE) is the motor that drives all of


the database functionality in Delphi as well as C + + Builder, Visual
dBASE, and Corels Paradox. Originally brought to life as IDAPI (Independent Database Application Programming Interface), an open
database connectivity product, its popularity in this context has been
usurped in recent years by ODBC. Borlands decision to focus the
engine back on its core tasks has been very beneficial to the BDE and
developers who utilize it. The engine is a collection of DLLs and utilities that create a nearly universal interface layer between the Delphi
code and components, and the low-level requirements of the specific
database that the developer wishes to address.
Knowledge of the internals of the engine is important to all Delphi
developers, regardless of experience level. At first blush, this topic
would appear to be geared towards the low-level developer. While the
occasion to directly address the engine through the API is rare, there
will be times when this is the only avenue available to accomplish a
specific task. By developing a depth of understanding about the inner
workings of the engine, the developer is better able to integrate the

. .-z

*,c;j
-.
-,j

76

H Part II-The Debhi Database Tools

complex calls into their application. This chapter will seek to develop
this understanding while leaving the API discussion to later chapters.
We will examine the architecture of the BDE first to surface the efficacy
of the driver-based design. The drivers and the system of aliases critical
to utilizing the engine follow, rounding out the engine discussion in
abstract. Putting it to work is next on the agenda, so the utilities including the Database Desktop and the Database Explorer are examined along
with a brief discussion of distribution needs of projects using the BDE.

Architect we,
The Borland Database Engine utilizes a driver-based architecture. Discrete software drivers provide the specific support and translation
layers needed by the specific database product. The engine then uses
these drivers to provide a standard set of functions among a disparate
variety of database products. Drivers can be specific to a single database, such as the Paradox driver, or a single driver that can support a
range of similar database structures such as the dBASE/FoxPro family
The fundamental task of the driver is to implement the specific
low-level commands required by the file system of the specific database. In doing so, it serves as a translator between the underlying file
structure and the high-level structures and commands needed by the
BDE. The database engine, in turn, then provides a transparent interface to the Delphi components that support database access. It is this
type of architecture that makes it possible to utilize a single component
to access the wide variety of database structures that are available to
the developer. Though the architecture seems cumbersome and
unwieldy, it is well implemented and highly optimized, giving Delphi its
world-renowned flexibility and multi-tier capabilities.
Figure 4.1 is an architectural diagram of the BDE layers. Each layer is
responsible for performing a specific function as the database call traverses the engine. A Delphi database access path through the BDE
follows these steps:
The application calls a VCL component.
2. The component passes the call to the BDE.
3. The BDE in turn calls the specific database driver.
4. The driver enables direct access to the data tables, performing the specific actions requested.
I.

t
~
i

Chapter &The Bar/and Database Engine and the Database


Sinde-Tier

Two-Tier

Utilities n n

Multi-Tier

CONNECTION
COMPONENT

Figure 4. I
Borland Database Engine
architecture

Having the database access functions enabled separately from the


;g
Delphi code brings numerous benefits. Primarily, the access methods
.:
required by various types of data relations are transparent to the Delphi ,+$i
components. This enables the developer to concentrate on building her :
expertise in areas such as interface issues and manipulative code rather ,.
than spreading her attention among the various requirements of the
data manipulation commands required by the table. In addition, the
command transparency allows a common command set to address a
combination of table types, both local and remote simultaneously, in a i
client/server
environment.

tabase Drivers
The Borland Database Engine ships with a collection of database drivers, allowing access to a wide variety of data sources. The standard
BDE drivers include those for Paradox, dBASE, Microsoft Access, ASCII
text, and a new file specific to FoxPro. Each driver is implemented as a , -i
DLL and each is similar in function; they encapsulate the specific

78 n

Part I/-The Delphi Database Tools

low-level instructions needed by the data source while presenting a


common interface back to the BDE. Any ODBC 3.0 driver can also be
used by the BDE through the ODBC connectivity socket.
A further benefit of the architectural design is exposed when the database drivers are pointed at a SQL database. In their native state, SQL
databases do not support the navigational abilities that have come to
be expected of desktop data tables. This includes simple tasks such as
forward and backward navigation. The BDE masks this omission and
presents the data tables as having addressable rows and the ability to
navigate them in all directions.
A database driver is called by the BDE at initialization. This occurs
when a Delphi component first calls for service. At initialization, any
configurable settings for the drivers are read from either the BDE configuration file, IDAPI.CFG, or the Windows registry. Once the driver is
loaded, all BDE clients then have access to it.

Configuring the Borland Database Engine


The BDE is fully configurable through the use of the BDE Administrator,
a utility that ships with the BDE. The utility accessible through a shortcut that will appear in the Windows Control Panel, provides a simple
interface through which you can add and configure ODBC, standard,
and SQL drivers, as well as configure the base options of the BDE. The
Administrators main window is shown in Figure 4.2.

Figure 4.2

BDE hministrator f main


window

Chapter 4-The Borland Database Engine and the Database Utilities

79 1

The BDE Administrator interface is split into two panes. The left-hand
pane displays a hierarchical listing of the applicable objects. This pane
contains two tabs, the Databases tab that shows the aliases of all avail.$L-4
able database objects and the Configuration tab that provides access to
;&I
the configuration of the database drivers and the BDE options. When
an item is selected in the left-hand pane, a Definition tab will be shorn, .1
in the right-hand pane containing the options applicable to the selected 2
object.

Configur ing u Standurd Dutabuse


A standard database, which includes the Paradox, dBASE, ASCII, or
FoxPro drivers, will expose a small set of configurable options.
Exploring the standard database configuration options is best performed by creating a new object. To add a new alias:
Click on Object 1New from the menu or right-click on the Databases tab and select New.
2. The New Database Alias dialog will be displayed from which the database driver is selected. This option determines the type of alias that
will be created. STANDARD is the default selection.
3. A new Definition tab is displayed in the right-hand pane of the Administrator. The type value at this point in the process is fixed. It will
show STANDARD as the value. Three user-configurable values remain
available:
a. DEFAULT DRIVER-You must select from Paradox, dBASE,
FoxPro, or ASCII as the database driver for the alias.
b. ENABLE BCDThis option specifies whether or not the BDE
translates numeric or currency data into BCD (Binary Coded Decimal) values. BCD storage eliminates the errors caused by the
rounding of decimal numbers. Setting this value to True will enable
BCD conversion.
c. PATH-The complete path to the database is entered here. Standard databases in the BDE parlance use the directory that contains
the database as the alias.
I.

;
4
2
<
:

:
i&i
l
:
;

4. Click Object1 Apply to save the configuration. The alias will be saved :
with a default name such as Standardl. You will most likely want to -?
change this to something more meaningful to your application.

Configuring an ODBC Dutabase


The BDE is equally adept at supporting standards such as ODBC (Open
Database Connectivity). Recognizing that it would be impractical to write
drivers for every possible database themselves, Borland instead opted to
include the ODBC connectivity socket. By doing do, the BDE turns any
ODBC driver into an IDAPI driver, making the ODBC databases accessible
through Delphi. The BDE interface also adds the native functionality and
benefits to ODBC data sources.
The easiest way to successfully connect to an ODBC database is to
establish an ODBC data source name prior to creating a BDE alias for it,
This process is best accomplished through the use of the Microsoft
ODBC Data Source Administrator (shown in Figure 4.3), often located
on the Windows Control Panel.

Small Business Financial


Xtreme sample data

Manager

Maosoft
MIcrosoft

Access Driver
Access Driver

r.m
r.m

Figure 4.3

ODBC Dato
Source Admin-

istrator dialog

To establish the sample MEDINFO data source, the following steps were
taken:

I. Select the System DSN tab. Creating a System data source gives your
application multi-user access to the data source. Click Add to start the
process.
2. The first dialog asks that you select a driver for the data source. For
the sample, the Microsoft Access Driver was selected. Click Finish.

A dialog, specific to the driver, is displayed as shown in Figure 4.4.


iP

3. Name the data source as appropriate to your application, followed by


a description if needed. Click on Select to create a path to the database, Using the file browser, navigate to your database file and select
it. When successfully complete, the fully qualified filename will appear
after the word Database in the Database panel.
4. You have a fully operable data source at this point, but you may want
to consider further modifications. You may want to set the login name
and password if applicable to your application to save your users the
trouble of being DrOInDted for this connection information at each
access.
5. Save your data source before exiting the Administrator.
Once a data source has been established, an alias must be created for it
in the BDE. The process for creating an ODBC alias is nearly identical
to the steps used to create a standard database alias:
I . Select Object 1 New from the menu and then choose the appropriate
driver name. For MEDINFO, select the Microsoft Access Driver. Click
on OK,
2. Enter MEDINFO in the ODBC DSN field. The majority of the configuration has already been done in the Data Source configuration dialogs
stepped through prior to this process. Click Apply to save your work.
The alias that has been created can now be accessed and manipulated
through Delphi as though it were a native database source. The ease
with which databases, other than the Standard databases, can be integrated into Delphi through the BDE is a tangible but downplayed
benefit tc

82

H Part II-The Delphi Database Tools

Configuring the Dutabuse Engine


As stated earlier, configuration information for BDE is stored in both
the Windows Registry and a configuration file. When the engine is
installed, a default configuration file named IDAPI.CFG is created. The
configuration file information that determines this is stored in the Registry key:
HKEY-LOCAL-MACHINE/SOFTWARE/Borland/Database

Engine/CONFIGFILEOl

Because of the number of applications, other than Delphi alone, that


utilize this file, you may find that it resides in a totally unexpected location. This key ensures that all relevant configuration changes are
written to the same file.
Only one configuration file may be used at any time but you may establish different configurations if appropriate. The naming rules for the
file include the requirement that it have an extension of .CFG and
that the characters V:*?< > 1not become a part of the filename. If
your application needs to use a configuration file other than the
default, it can call the BDE API function DbiInit to initiate the process
and point to the other file. If another application is running using the
default file and this action is attempted, an exception will be raised.
The Windows Registry contains information regarding the drivers, various pieces of system information, and the size of the Buffer Manager
cache. Information that is stored in the configuration file includes the
current NET DIR entry for Paradox tables and all alias information.
Base configuration changes can be made through the BDE Administrator. The engine is configured through changes made on the
Configuration tab of the Administrator. Option categories available
include:
n

INIT
n Date Formats
n Time Formats
n Number Formats

The system INIT settings are the initialization options used when starting an application. All of this information is stored in the registry
Figure 4.5 is a table showing the parameters accessible through the
INIT branch.

lapter 4-The Borland Database Endne and the Database Utilities

AUTO ODBC

q -

Determines if the ODBC drivers and data soup

DATA REPOSITORY
DEFAULT DRIVER

I If the table name provided has no extension or t~c~::r


TYPE is of FILE, this is the driver that is tried first,,

LANGDRlVER

e driver matching the version I


ascii ANSI (DBWlNUSO)

LOCAL SHARE
shared between BDE
applications at the same time.
LOW MEMORY USAGE
LIMIT
MAXBUFSIZE

The

maximum amount of low memory

that tne

I The maximu

MAXFILEHANDLES
MEMSIZE
MlNBUFSiZE
cache.
MTS POOLING
SHAREDMEMLOCATION
SHAREDMEMSIZE

When True, MTS pooling is enabled. This improvua


the initial opening-time of a database.
This determines the preferred address of shared
memory manager and shared buffer mana]
This is the maximum amount of memory that the
BDE will utilize for shared resources.

SQLQRYMODE

The selected method for handling queries to


data sources.

SYSFIAGS

Do not modify.

VERSION

] Do not modify

Figure 4.5 The IN/T options accessible through the BDE Administrator

Darte Formats
The options in the FORMATS 1DATE branch of the configuration option,
modify the handling of data strings in BDE applications. The parame- . ~li
ters surfaced here, shown in Figure 4.6, affect the way in which string ,:
values are converted into date values.

84

n Part //-The Delphi Database Tools

FOURDIGITYEAR

When True,
digits.

all year values are expressed in four

LEADINGZEROD

When True, single-digit days (I ..9) contain a leading


zero.

LEADINGZEROM

When True, single-digit months (I ..9) contain a


leading zero.

MODE

This controls the order of the month, day, and year


components of a date.
0 - MMDDYY
I - DDMMYY
2 - WMMDD
Defaults to the order used by the country selected
in the Windows Control Panel.

SEPARATOR

This is the separator character used to demarcate


the month, day, and year components of a date.

YEARBIASED

This parameter determines whether or not the


BDE application will add I900 to two-digit years.

figure 4.6 The date format options accessible through the BDE Administrator

Time

Formats

The parameters accessible through the FORMATS 1TIME branch determine how string values are converted into time values acceptable to the
BDE. The parameters are listed in Figure 4.7.

AMSTRlNG
MILSECONDS
PMSTRlNG

This is the string used to indicate morning. It defaults to


AM.
When True, time values include a milliseconds
component.
This is the string used to indicate evening. It defaults to
PM.

SECONDS

When True. time values include a seconds component.

TWELVEHOUR

When True, a I2-hour clock is used to express time


values. When False, a 24-hour clock is utilized.

Figure 4.7 The time format options accessible through the BDE

Administrator

Chapter 4-The

Borland Database Engine and the

Database Utilities n

85

Numeric Formats

Much as the TIME and DATE format parameters control the string conversion into time and date values, the NUMBER parameters determine
how string values are converted into numeric values. Figure 4.8 lists
the parameters accessible.

number

of

e maximum
decimal places in the string converted

numeric values less than I have


at-ate the thousands components of large

Figure 4.8 The number format options accessible through the BDE Administrator

The Database Desktop


The Database Desktop is a utility that ships with the Borland Database
Engine and gives the developer the ability to create, delete, modify, and
query database files. It supports the standard driver databases: Paradox, dBASE, and some SQL implementations. In fact, the trained eye
will recognize the Database Desktop for what it is, a reduced function
version of the Paradox database.

Creating Tables

One of the most common uses for the Database Desktop is creating
tables for use in your Delphi projects. The Database Desktop can create
a wide variety of table types. Depending on the type of table that you
choose to build, a varying array of capabilities will be available for the
modification of the table. Aside from naming the columns and defining
the constraining sizes and data types, you may also find the ability to:
H Specify a table language to control sort order and the available character set.
n Assign columns for indexes.
w Use (borrow) the structure of another table to create a new one.

I Assign the primary key for a relation.


W Create secondary indexes for a table.
n Define domain constraints for the columns of a relation.
n Establish relationships between tables.
n Set the security constraints for a table.
ilable through
through this method of
To demonstrate the range of options ava ilable
establishing tables, we will build a sample Paradox table to be used in a
Delphi project. The table will make use of the Paradox validity check
features to bind the domain constraints to the table. The ti ible
- - will represent basic registration information for an elementary school student.
Start the Database Desktop program to begin the process.
I. Select File 1New from the menu. The Create Table dialog will be displayed, allowing you to choose the base format for your table. For this
exercise, we will use the default type of Paradox 7 as shown below.

Figure 4.9
The Create
Table dialog

2. When the file type is selected, you will be presented with the Create
Paradox 7 Table dialog as shown in Figure 4.10. This is where all
memaining design work on the table will occur.

Create
! all

3. Figure 4.11 contains the schema for the REGISTER table that we are
creating. It describes the field name, data type and size, and domain
constraints for each column in the table.

SOCIAL
IRI-I-V

II
Ml

IM

1S T R E E T
STATE
ZIP

Default value to CO.


A

Required field. The value must equal


80439 to ensure that the student lives
in the district.

Figure 4. I I The schema of the Register tab/e

4. Begin by entering the name of the first field, Social Security Num.
Tab to the Type column and select a data type. Pressing the space bar
will drop down a list of possible data types supported for the database
type. For this field, use the Alpha data type. Tab again and enter 11
for the field size. Finally, this field is the primary key for the table so

88

I Part I/-The Delphi Databose Tools

this must be indicated in the Key column. Press any key to mark this
as the primary key. Your design should appear as shown below.

Figure 4. I2

Adding inform a t i o n for t h e


Social Security
Nurn field to
table

5, You will now set the domain constraints for this column. The only constraint for this field is that it is required, being the primary key To
indicate this in the Paradox .VAL (validity check) file, click on the
Required Field check box. Add the Last Name and First Name fields in
this same way.
6. Add the Date of Birth column to the table. The validity check value for
this column requires a little more work, The constraint requirements
for this field state that the child being registered must be five years old
or older on the first day of school. For this exercise we will establish
the first day of school as September 1, 1999. Therefore, to be old
enough, a childs birthday must be prior to or equal to August 31,
1994. Implementing this constraint in a Paradox table utilizes a Maximum value validity check. For this school year all children should have
been born no later than August 31, 1994. This constraint is shown in
Figure 4.13.

7. To simplify the data entry process, another validity check that can be
added is a default value. For this database, the vast majority of entries
will come from a single town and state. When the column City is
entered, add the value Evergreen (minus the quotes) to the Default
value field. Perform the same operation for the State Column, entering
CO as the default value. When an empty row is added to the database, these values will have already been entered.
8. Finally, add the Zip column. As a further verification of the students
district status, the zip code should be validated. A narrow range of values, sometimes as low as one, can be used to validate an entry For the
Zip column, the highest and lowest value that should be allowed in
this field is 80439. Setting this validity check is simply a matter of
entering this value in both the Maximum value and Minimum value
fields of the validity check.
9. Save the table with an appropriate name in the work directory for
your application. Remember that an alias will be needed to locate the
table for use in Delphi.
Using the Database Desktop, you can immediately test the results of
your modifications. Open the RFGISTER table and click on the Edit
Data button to open the table for data entry Experiment with an entry
to see the errors that are generated when incorrect values are entered.
The messages till appear in the desktops status bar. The validity
checks you created will prevent poor quality data from being added to
the database. These same validity checks will raise exceptions in your

Delphi code, making it much easier to trap errors in the data input by
your users.
Another benefit of using the Database Desktop for table creation is the
ability to add Picture clauses to each column, helping you to format the
data in the most presentable manner. For example, you can modify the
presentation of the column that contains the Social Security number so
that entries appear in the accepted format of ###-##-####. To add
this picture string using the desktop:
I. Select File 1Open 1Table from the menu and navigate to the alias
under which you saved the REGISTER table. Open the table.
2. Click on the Restructure button, opening the Restructure Paradox
table dialog. This will contain the current structure of the REGISTER
table. Alternately, you can get to this point by selecting Tools 1Utilities 1Restructure and then selecting the table.
3. Select the Social Security Num column. Click on the Assist button
below the Picture field. This opens a dialog that helps you to format
the picture appropriately or allows you to experiment with different
formatting strings. Figure 4.14 is the Picture Assistance dialog. In the
Picture field, the string *3{#)-*2{#)-*4(#}
represents the standardized format for a Social Security number.

Figure 4. I4
The Picture
Assistance
dialog

The chart in Figure 4.15 shows the recognized picture string characters.
These characters are substitutes for the data that will be entered into
the field. The formatting is positional, meaning the character that is
input in a particular location is formatted according to the picture
string character found in that spot. Any characters other than those

Ar*

Chapter 4-The Borland Mabase m and the Database utilities

9 1

shown in the chart &at are added m he string will be considered constants and will appear b be final dg , as the dashes do in the

example.

Requires a numeric digit in this position.

Any letter (uppercase or hcase)

allowed

in this

position.
&

@
I

Any letter in this position is converted

to uppercase*

Any letter in this position is converted

to lowercase.

Any chmcter allowed in thb position.


@ character in this position is converted to
upper==Interpret the next &wmer a a literal, not as a special

Figure

4.15 The Picture string characten

recognized by the BD~z

4. Save your changes to the structure of the table.


Other candidates for formatting in he REGISTER table would be the .
Zip and name colt- mere are a number of additional options avafiable through the Data&e Desktop for he construction of or
modification of the tables used in your Delphi pro.iects. Becayse the
changes can be immediately tested md verified, the desktop 1s a Valuable tool to learn and u&e.

Distribution of a BDE-Based Application


When your application k written uw he features of the BDE, you
must incorporate &e -ssary engine files inK, your installadvn routine. A great majority of the database engine is redistributed Wth the
applkatbn executab&
ad Borland has specially licensed YOU t0 do
SO. Fortunately, the BDE an be either p&ally or completely redistributed, depending on the needs of your application. As an example, your
application may on& srrpport dBASE fle ws and therefore, YOU do
not want to distribute Nippon for paradox or SQL filesTO meet this requw t, Borland reqd &at you utilize an Borland
certified installation program. me InstallShield installation builder that
ships with the Delphi pa&age is an mple of this. This installation

:.

92 n

Part I/-The Delphi Database Tools

builder knows which files from the BDE can be distributed per the
license granted by Inprise. You cannot use your Delphi installation disk
to install the BDE on a client machine. During the development of the
installation script, the installation program will determine if you want
to perform a complete or partial redistribution of the BDE.
Borlands reasons for this requirement are sound, even beyond simple
protection of their property Careless installation of the Borland Database Engine can easily cause other programs to fail. Remember the
earlier mention of the number of applications that utilize the BDE for
their core database functionality An installation of a later numbered
version of the BDE can overwrite or otherwise destroy references to the
other installations of the engine, effectively destroying the other applications database access. Use of a certified installation program
prevents these entries from intersecting. Inprise now also includes a
.CAB file for distribution with ActiveX controls.
The only BDE utility that can be distributed with an end user application is the BDE Administrator, and it is included with a certified
distribution as outlined above. None of the other utilities, including the
Database Desktop and the Database Explorer, are redistributable.

Database Explorer
The Database Explorer is a utility shipped with Delphi that incorporates
a hierarchical database browser with data editing capabilities. The utility is also the gateway to the creation and manipulation of data
dictionaries for Delphi datasets. A data dictionary is a special database
that contains the attribute sets for field components. The tool ships in
three different flavors with two different names, depending on the version of Delphi in use. The Desktop and Developer versions of Delphi
receive the Database Explorer while the Client/Server version is
shipped with the SQL Explorer. Their capabilities differ commensurately with the database access capabilities offered with each version.
The Database Explorer that accompanies the Desktop edition accesses
local databases only, while the Developer edition gains access to the
Local Interbase Server and ODBC-compliant databases. In addition, the
data dictionary is enabled. The Client/Server version of Delphi ships
with the SQL Explorer, giving the developer extended capabilities wit&
access to a full range of SQL databases, both local and remote.

Chapter I-The

Borland Database Engine a n d the Database Utilities

n 93

fbtabase Explorer Interface


The appearance of the Explorer closely matches the format seen in the
BDE Administrator. Figure 4.16 shows the dual-paned window used for
all actions in the Explorer. The left pane contains two tabs: the Databases tab listing all database aliases defined to the BDE and the
Dictionary tab showing the contents of the data dictionaries that have
been defined.

:.
T F&we

4.16
; The Database

T
DBDEMOS
&--#i DddtDD

The contents of the right-hand pane vary according to the object


selected in the left-hand pane. The number of tabs and their descriptions will change with the choice of more detailed selections. Status
bars are displayed above each of the panes, giving you a summary
description of what you are currently viewing.
The hierarchical viewing capabilities are constrained to the left pane. In
this window, the developer can drill down to increasingly detailed levels of information about the database selected. In Figure 4.17, the
DBBDEMOS database has been expanded from the root of the hierarchy by clicking on the plus sign next to the name. When a database is
selected, the detail in the right pane will be the alias information
defined when it was established. As shown, the next items listed are the
tables that make up this database. Each of the tables is also prefaced
with a plus sign, indicating that there is further detail about each of
them available.

II
.H

94 H Part II-The Delphi Database Tools

Figure 4. I7
The hierarchical view of the
Explorers
Oatabases tab

The detail for the REGISTER table, built earlier in this chapter, has
been expanded. The number and names of the nodes that appear will
differ for each of the supported table types. The Fields node in the
example is expanded to show all of the fields that were defined for the
table. Selecting the field narrows the information down to the field definition. Information also available for the Paradox table includes the
Indices, Validity Checks, Referential Constraints, Security Specifications, and Family Members. Perhaps most importantly, the data that the
table contains can be modified and examined.
The Database Explorer can also be used to establish a new alias. The
methodology is the same as that used for the BDE Administrator.

Data

Dictionary
The ability to create and utilize a data dictionary is a distinct advantage
of the BDE/Delphi development tool. A data dictionary is a database
that is used to store attribute sets for field components. Attribute sets
determine the field type, properties, and the data-aware component
that should be generated whenever a TField object is dragged to a form
from the Fields Editor. A data dictionary containing an attribute set
means that you will only need to set properties once to have them
propagated throughout your project.

rhapter

I---The B o r l a n d Database Engine and the D a t a b a s e Utilities q

95

This tool displays its use when you have several columns in a database
that share common formatting properties. An extended attribute set
can be set once for such things as the alignment, picture string, and
minimum and maximum values, and then be associated with multiple
fields in your application. If a change is necessary it can be made once
through the Explorer and it will then flow to all of the components that
use it at once.
Attribute sets can be changed both through the Database Explorer and
the Fields Editor. The editor will be discussed in conjunction with the
components it services in a later chapter; right now we will focus on
creating an attribute set through the Explorer. The first thing to be
done is to create a new dictionary Start the Database Explorer, either
from the Start menu or from within Delphi, using the Tools menu.
Select the Dictionary tab in the left-hand pane.
Bight-click on the Dictionary entry and select New. Alternately, you
may select New from the Object menu. The dialog shown in Figure
4.18 will be displayed.

Elgure 4.18
lnfirmation fir
I- 0 new dictio$wfyin the
4Create a New
$XcGonafy
I. .

3.

r...

Enter the data shown in the example dialog. The Description field is
optional. Click OK to save the new dictionary.
You can create multiple dictionaries as needed to support your development needs. Only one dictionary is displayed at a time; the Database
Explorer does not extend its hierarchical display to this feature. To
work with alternate dictionaries, select Dictionary 1Select from the
menu and choose the dictionary name from the drop-down box.
Establishing the attribute sets that make up the contents of the new dictionary is the next step. Attribute sets can be created manually or
imported from databases or SQL constraint sets. Before establishing an
attribute set, it is helpful to view an exceptional example provided by
Borland. This dictionary is linked to the examples provided with the
Delphi package so they must be installed prior to exploring this item.

96 n kwt /l--The Delphi Datubase Tools

Select Dictionary1 Select from the menu and choose the Borland
Data Dictionary; click OK to open it. Expanding the Databases node
informs you that this dictionary is used for the DBBDEMOS database,
the demonstration tables included with Delphi. Theres not much of
interest under that node but plenty to look at by expanding the Attribute Sets node. You will discover a number of attribute sets defined.
Expand the VendorNo node to find two nodes that follow: Referencing
Fields and Referencing Attribute Sets. Select the VendorNo field itself to
open the attribute definition shown in Figure 4.19.

_-.-.-

Transliterate

--.-----{---I~~~.
I___ _I ___._ ---.-_-----I

Edit Mask
Displ~ofmat
EditFormd
____I_~-

Figure 4. I9
The Definition
tab for setting
attributes

MinValue
MaxValue
Garency
PWiSiOtl
DiS&Vdues
BlobTYpe

IVN ooo0

_____

DefaultExpressim
customconst1aint
_~____._
Enoc Strii
Based On

I
I
ilOO
;9999
/False115

---.

__.._...

I
_____

i - (VendorNo
< 99%)
- - - > 1000)
- - and (VendotNo
%ndof No has to be between 1000 and 9399
,<nOne)

A daunting number of possibilities present themselves for modification


of the attribute set. Each of the choices is explained in Figure 4.20.

TFieldClass

This determines the type of field to create when a field is


added to a dataset. When left blank, the physical data type
determines the type of field to create.

TControlClass

This determines the type of control to create when a fi& is


dragged to a form. When left blank, the physical data type
determines the type of control to create.

Alignment

Determines the alignment of the data in an edit or grid


control. Options are center, left, or right alignment.

Chapter 4-The Borland

DisplayLabel

Database Engine

and t h e Database Utilities

97

When the field is displayed in a grid control, the field name is


the default column heading. The entry in this parameter
replaces the default value.

DisplayWidth

This determines the number of characters used to display a


field in a grid control.
This determines if a field can be edited or if it is read only.

ReadOnly

Values are True/False.


This determines if the field requires an entry. Values are

Required

True/False.
This determines whether or not a field can be displayed in a

Visible

grid control. Values are True/False.


Transliterate

This determines whether or not a field type is translated

EditMask

This value contains the Edit mask, the formatting character

between types in different databases. Values are Tme/False.

string that determines how data can be entered into an edit


bOX.

DisplayFormat

The value in this field determines how the field value is


disolaved.

EditFormat

This determines how numeric values appear when edited in

MinValue

This sets a minimum allowable value for the field.

MaxValue

This sets a maximum allowable value for the field.

Currency

When set to True, this indicates that the values entered in

a data-aware control.

this field are currency values.


Precision

This determines the number of decimal positions that are

DisplayValues

This determines the translation of values in a Tboolean field

formatted prior to being rounded off.

to and from the display format.


BlobType

This field specifies the type of blob associated with a memo

DefaultExpression

This specifies a default SQL value for the field when it is left

or graphic control.

empty during data entry.


CustomConstraint

This specifies a SQL string that enforces a value constraint


on the field.
This determines the custom message that is displayed when

Error String

the CustomConstraint is violated.


Based On
~, : -

This specifies a different attribute set from which the


:,

current set is based. The settings in the current set override


the originals.

Figure 4.20 Extended attributes for the TField

objects

98 n

Purt II-The Delphi Database Tools

The entry for the field VendorNo contains some interesting examples of
the level of control that can be exercised through the data dictionary.
Note first the DisplayFormat. The entry contains the value VN 0000,
which translates into a literal string of 70-J followed by the vendor
number that you want to assign. It is important to remember that this
differs from the EditFormat entry, especially when you are entering
data. When a new value is entered for the vendor number, the VN
will not appear until after the value is accepted.
The value acceptable for this field is constrained by the
CustomConstraint SQL string. This field contains any valid SQL string
that you wish to use to validate or otherwise constrain the data for this
field. Values entered that violate this constraint will cause an exception
to be raised. When the exception occurs, the error message contained
in the Error String field is displayed, overriding the default exception
message.
Below the entry for the VendorNo field are two more nodes. Expanding
the Referencing Fields node demonstrates the usefulness of the data
dictionary Notice that two fields in entirely separate tables reference
the extended attributes set for the vendor number. By setting the
parameters for a field once, we are able to reuse the attribute set for
any similar field. Whether the data item is the same, as is the case with
the vendor number, or different data items with similar formatting, a
single entry in the data dictionary can format multiple fields. When the
settings need to be modified, expanding the range of allowable values
for the vendor number for example, the modification is made at a single point rather than at each field in the database. This saves enormous
time and makes for much cleaner code.
The REGISTER database is a comfortable place to establish our first
attribute set. The attributes that are going to be set will be used for verification of the Date of Birth field (renamed DOB for simplicitys sake).
After reopening the dictionary DemoDictionary created earlier in this
chapter, follow these initial steps to establish a new attribute set:
I. Expand the nodes of the dictionary by clicking on the plus sign. You
should have two empty nodes: Databases and Attribute Sets.
2. Right-click on the Attribute Sets node and select New from the context menu. A new attribute set called EXTFIELDl will be opened.
3. While the set name is highlighted, rename it DOBField.
4. Three items in the attribute set are going to be modified for this field.
Figure 4.21 is the right-hand pane of the Database Explorer showing
the completed items.

Chapter 4-The Borland Database Engine and the Database Utilities W 99

-_~-__.-

Aliint
DisplayLaM
Displawtih
Readonly
Rmuired
Vile
Transliterate
E&Mask
Disph$FC4mat
ECUFormd
Minvdue
Math/due

I
_
_
1 mmmm dd, yyyy

-.___ --...--~.---.-----l.-.-

I
!

__._. ---- -.... .-._ .-

Currency
PIecision
DisplayValues
BkbTVpe
DefatAtEwpression

Figure 4.2 I
The Definition
tab for setting
uttributes

-____-

customcQrlstraint
ConstraintErrwhiessage

(REGISTERDOE >= U8/3l/93]AND (REGISTER.DOB


i Student birthdate is out of ranse. Please ve&

Based On

I <none>

<= 08i31/94]

5. The first entry will be to modify DisplayFormat. We are going to


change the way the date is displayed. This will not affect the way in
which data is entered. Change this parameter to read mmmm dd,
yyyy by typing this directly into the property area. When this field is
displayed in a grid or edit control, this attribute will cause the date of
birth to be displayed as the full name of the month followed by the
day, a literal comma, and the year displayed with the century. Because
we have not modified EditMask or EditFormat, the field will still
accept the default MM/DD/YY format for input purposes.
6. Next, we are going to turn our attention to the CustomConstraint
parameter. This field allows us to enter a SQL statement that will validate any data entered into this field. For the start of the 1999 school
year, a student should have been born prior to August 31,1994 and no
earlier than August 31, 1993, which would make him too old to be
just starting school. Enter the SQL statement that will verify this in the
CustomConstraint field:
(REGISTERDOB > = 08/31/93) AND (REGISTER.DOB < =
OS/3 l/94)
7. Finally, add an explanatory error message to the attribute that is displayed when the CustomConstraint is triggered. Enter the message
Student birthdate is out of range. Please Verify. in the field.
8. Click Apply or select Object 1Apply from the menu to save your
changes. Close the Database Explorer.

In order to apply these attributes to a field object, the dictionary must


be associated directly with the TField object. The project DBTEST
(included on the CD) will provide the basis for testing the attribute set
just created.
The main form for the

ect is shown in Figure 4.22,

Figure 4.22
Database
Breadboard
dialog

Invoke the Fields Editor from the Table1 component by double-clicking


on it. If the columns of the REGISTER table do not appear in this form,
right-click on the Fields Editor to invoke the context menu. From the
context menu, select Add All Fields, Your next step in utilizing the
attribute set from the dictionary is to associate the definitions with the
fields of a table.
I. Click on the DOB field, Notice in the Object Inspector that a different
set of properties has been exposed. These are the TFieid specific
properties.
2. Right-click on the DOB field and select Associate attributes from
the context menu The Associate Attributes dialog will appear as
shown in Figure 4.23. The single attribute set that we defined,
DOBField, will appear for selection. Click OK.

Figure 4.23
The Associate
Attributes

dialog

Chapter &The &w/and fhtufmse

Engine and the Databuse Utilities W

IO 1

3.

The properties in the Object Inspector that relate to the attribute set
properties will now be filled with the parameters from the data
dictionary
4. Close the Fields Editor and compile the project.
Test the project by entering various dates in the DOB field. The error
message defined earlier will appear for each date that fails outside of
the range. You will also discover that the validity checks set in earlier
sections are in place. For example, the row will not be accepted without
a Social Security number.
One critical caveat must be remembered when updating your attribute
sets. The changes will not automatically be associated with the TField
objects. You must associate the new attribute sets with the objects and
recompile your project before the changes will appear in the executable-a small price to pay for the flexibility offered through a data
dictionary
The Dictionary Menu
The Dictionary option available on the main menu provides access to a
number of different functions pertaining to the management of data
dictionaries. Each of the selections is detailed in the following
paragraphs.
Select
As discussed and utilized earlier, the Select option is used to select an
existing data dictionary from the Select a Dictionary dialog.
Register

The Register option is utilized to register a new data dictionary in the


Object Repository, making it available for selection. The Register an
Existing Dictionary dialog box appears when this command is issued.
You can then provide details about the dictionary to be registered.
Unr egister
Unregister removes the registration of a data dictionary in the Object
Repository The dictionary itself is not affected by this command; it will
simply not appear in the Select a Dictionary dialog.

102 H Part H-The Delphi Database Tools


(

New

;:

The New option is used to create a new dictionary As seen earlier, a


dialog box requesting the details of the new dictionary is displayed.

,._
-.,

Delete
The Delete option is a destructive command that will delete the specified data dictionary file. The deletion is performed through the Delete a
Dictionary dialog box.
Import from Dutabase
The Import from Database option allows you to import table schema
information from an existing database into a new or updated data dictionary The dialog box that appears, Import Database, is used to
determine the column attributes that you want to import into the data
dictionary as attribute sets.
Import from File
Similar to the previous command, the Import from File option imports
table schema information. This command works with information contained in a flat file rather than a data-dictionary file format. The flat
file format that it recognizes is the Borland Dictionary Export file extension (.BDX). This type of file is created using the Export to File
command from the same menu.
Export to File
This command exports a data dictionary to a flat file format. As
explained above, the flat file can be imported using the Import from
File command.

importing SQL Constraints


The Database Explorer can import SQL server-based constraints and
defaults into a data dictionary to be used in Delphi. This action is performed through the Import from Database command described earlier.
The constraints that are imported differ by server. Any items that are
not supported by the BDE are commented to prevent errors from being
generated.

.i

Chapter AThe Bodand Database Engine and the Database Utilities n 103

Before performing this action, you should be very familiar with the SQL
database and the types of constraints that it supports. This information
will determine where you will find the different types of constraints
after importing them.

The discussion in this chapter provided a survey of the functionality of


the core of Delphis database access, the Borland Database Engine. The
driver-based architecture of the BDE makes it, and your applications
that utilize it, infinitely extensible. This design allows the BDE to support your application needs from the desktop all the way to complex
multi-tier applications supporting a wide variety of SQL databases. The
ODBC socket extends the BDE support to cover nearly every desktop
and midrange database currently on the market. The transparency of
the engine and the low-level requirements of each database type allow
the Delphi developer to concentrate on interface and functionality
issues rather than the database access components of a project.
The Borland Database Engine is not a single executable file but rather
an entire suite of executables, drivers, and utilities. We have covered
the majority of the engine level functionality in this chapter. If your
application needs focus on an individual file type, it is recommended
that you explore further the unique capabilities of that database
through the engine documentation.

Looking Forward
The next chapters will finally get into the meat and potatoes of Delphi
database programming. The data access components will be examined
first since they are the ones that actually link the program to the data
files. Then the data controls are put to work, creating the outstanding
interface that Delphi is famous for.

Included in ahis Chapter:


w The Dataset Objects n The Table Component (TTable)
n The Query Component (TQuery) n The Datasource
Component (TDataSource) H Putting the DataSource,
Table, and Query Components to Work H The Stored
Procedure Component (TStoredProc) n The Database
Component (TDatabase) n The Session Component
(TSession) n The BatchMove Component (TBatchMoye)
n The UpdateSQL Component (TUpdateSQL)
H The
NestedTable Component (TNestedTable)

F inally, we arrive at the Delphi promise of RDAD, Rapid Database

Application Development. Dash out a form, slap some components on ._ 3


that surface, point the components towards your database, and voil$-ii
a database application is born. Building database applications in Delp&j$$
can be that easy but only after spending the requisite time exploring ;
the power of the tools. Like everything else in Delphi, the database
components are rich with functionality and adaptability, and the effort: ji
expended to build your knowledge on these classes will result in
2-t?
greater user satisfaction and measurably higher quality in your
applications.
The data access components, available to the Delphi developer from $
the so-named tab of the component palette, are non-visual componenti.
that expose the BDE database capabilities to the Delphi development :f
environment. All database access and manipulation is performed
through these components. This chapter wil 1 cover all of these base
components, their properties, and events.

IO6 n Part II---The Delphi Database Tools

^.i

The Dataset Objects


A dataset is the base building block of any database application. The&&
are the objects that contain or access the data; the facts that we am,$
attempting to manage. The most immediately familiar dataset objet+
the table, a structure that we have discussed at length in the precec
pages. There are four components on the data access palette that a
derived from the TDataSet base class: Table, Query, Stored Proc
and NestedTable. Before examining the individual classes, the
ing class from which they inherit the majority of their functiona
. 1;
be laid open.
, $ .-~$

The DataSet Object (TDataSet)

*: _(i

The TDataSet object (Latin name, TDataSet) is the core unit that .:*
enables access to tables through the BDE or other database access !$
points. A TDataSet object is the ancestor class from which all oth&#
dataset objects are derived and models such real-life items as a ta
a database or a query that presents a subset of this data. The clas
defines a set of fields, properties, methods, and events that are in
ited by all of the descendants. You cannot utilize the TDataSet ob
directly because many of the properties and methods are virtual ~$2
abstract. A virtual method is one that is defined in the base clas
can be overridden in descendant classes. An abstract method in
class provides a function prototype but no implementation; descen,i
dants are required to provide the implementation. This object modei &
perfect for implementing these database components: Each is built F4$$
the cask of accessing data; they just use different methods of accom-l*
plishing the job.
There are three classes that are direct descendants from TDataSet.
TBDEDataSet enables the TDataSet class to work with the BDE to
enable database access (TDataSet itself is database engine indepen
ent). The TDBDataSet component is a descendant of the BDE-eni
TDataSet that adds additional session and database features. I~Jx&~
were to develop your own data access components, you would m
likely create a descendant from the TDBDataSet class. The last cl&$
also a direct descendant of TDataSet, TClientDataSet. This class
designed lo implement multi-tier database applications using di
uted databases.

Chapter S--Data Access with Delphi

Dataset

St

ates
Before we talk about what can be done with a dataset, it is
to understand under what condition we are addressing the table. The
state of the dataset is a read-only flag that determines what actions u
and cannot occur at any specific moment. The table in Figure 5.1 SU$$
marizes the DossibIe states that are communicated bv the tables.
;?4

dslnactive

Inactive

This state indicates that the data in the

tab&

not available. The dataset is closed.


dsBrowse

Browse

&Edit

Edit

Dslnsert

insert

DsSetKey

SetKey

DsCalcFields

Calcfields

This is the default state for a dataset; indicates

::

5 are
recessed and prevents changes to

DsCurValue

CurValue

DsNewValue

NewValue

DsOIdValue

OldValue

Dsfilter

Filter

Figure 5. I

Possible

states of a TDataSet

descendant

p*-i
Each of the state values shown breaks down into one of two basic
$5
conditions-the table is either open or closed. A closed dataset object
does not expose any of the data contained within to either browsing or :f
modification. In order to perform these operations, the dataset must be $$
opened. Delphi has two methods of performing this function. You can :i:k$
set the Active property of the dataset object to True, either at design
time or programmatically at run time:
Vendors.Active

:=

true;

You can also call the Open method at run time:


Vendors.

Open;

Many operations, such as setting the current index, also require that
the dataset be closed and reopened for the effects to be seen. Delphi
provides two methods for performing this task as well. The Active property of the dataset can be set to False at design or run time:
Vendors.Active

:=

False;

or, you can utilize the Close method of the dataset:


Vendors.Close;

When the dataset state falls in the open category, the developer must
be aware of two factors: what actions can be performed against the
database and how the dataset arrived at the current state. For example,
a dataset object linked to a data-aware control can be displaying the
data but the user may find that he is unable to edit the current record.
The developer should be aware that the default state of a dataset,
Browse, will allow browsing of the data values but no modifications.
The state of the dataset will have to be changed to Edit before any
changes can be applied. Depending on the dataset component used,
certain combinations of properties can be set to automatically set the
state to Edit upon accessing the table. Awareness of these conditions is
important in your design considerations and in testing your application
prior to rollout.

The Table Component Fable)


The Table component is generally the first data access control that you
will pair with a DataSource control to construct a database application.
It has been designed to provide uniform access and navigability to each
row in the table types supported by the BDE regardless of the underlying rules of the dataset source. Despite the wide range of properties, it
can be quite simple to use. Place a table component on a form, set the
Database and Table properties to point to the relation you want to
access, and the data flows immediately to your application.

Key Properties
The Object Inspector displays the wide range of properties published
for the Table component and the chart in Figure 5.2 provides a brief
definition of each of them. Many of these will be utilized when we
arrive at the first sample application, but there are a number of properties whose values are not self-explanatory or immediately put to use.

Chapter S--Data Access with Delphi I

Active

False

AutoCalcFields

True

CachedUpdates

False

Enables or disables cached


This contains the record-level constraints

Constraints

for

this table.
DatabaseName

The name of the database to which this table

Defaultlndex

When True, the data will be ordered by the

belongs.
True

primary key or index of the table.


Exclusive

False

current

Setting this property to True gives the

application exclusive access to Paradox or


dBASE tables, preventing other users from

for
unless ..:

This property contains the field definitions

FieldDefs

the table. This should not be modified


you are creating a new table.

The text string defining the current filtering

Filter

condition.
Filtered

False

This activates or deactivates filtering

i
,jl:
.34.

for the

.;

table.
FilterOptions

The set of options affecting the filter process Q


:-;j
the table.

IndexDefs

This set contains information about the


available indexes for this table.
This property lists the column names

IndexFieldNames

fo be

used in building an index for the table.


IndexFiles

dBASE only. This property specifies index files

IndexName

The property contains the name of a

secondary

index for the table.

I.

The column names from a master table

MasterFields

that

links to this child table are contained in this

. 2
*F
j :z

property.
The name of the DataSource

MasterSource

component

linked to the master table in a parent-child

-:t
,

relationship.
Name

Table I

ObjectView

False

IThe Internal name of this comoonent.


Determines the method of storing the column 3
names. Included for compatibility reasons.
::

ReadOnly

1 False

of the

table.

SessionName
StoreDefs

This determines the read-only status

Associates a session name with this


False

table.

This property determines whether or not

the

Field and Index definitions are stored

TableName

TableType

I 1
ttDefault

persistently with the form or data module.


The name of the table associated with
component.

this

This determines the database structure of

..

(ri
5

the

table.

Tag

This property is for the developers use.

UpdateMode

UpWhere

This BDE setting determines the method used

All

to locate updated rows.

UpdateObject

Specifies the update object used to update a


read-only

Figure 5.2 The TTable

2
_; 0

dataset.

component properties

Aura Cc&Fields

This table property controls the firing of the OnCalcFields event that .!$
updates the calculated fields in a row. If the property is set to True, cat%j
culated fields will be updated under the following conditions:
;
n The table is opened.
n The state of the table is set to dsEdit.
n ii column is modified through a grid or other data-aware control
and the focus changes.
n A row is retrieved from the database.
Depending on the complexity of the calculation and the number of columns with which it works, auto-calculation may slow data entry or
retrieval considerably. For this reason, it might be desirable to set this
property to False during periods of intense data entry When set to
False, the event will not be fired and the columns will not be updated.

$
,. :g.-a
! sQ
(4
_,::

.,-*-s
r,iys

Chapter S-Data Acces


~m*b^*s.cI_^*- x1 _-~*_d_j_ij_*s_lyl-_t~*~-~~~~-_*s.__*_*__

Tip

Be sure to fire the OnCalcFields


event prior to displaying
updated data to the user. Without firing the event, the contents
of the columns will not contain current data. The event does not
need to be fired explicitly; you can also cause one of the triggers
listed above to occur after setting the property of AutoCalcFields
back to True.

.~;.i

.:
..f

CachedUpdates

Another table feature available for improving performance is the ability 3


J
to utilize an internal cache to temporarily store updates to a dataset
and then write them to the underlying dataset as a single transaction.
The CachedUpdates property value determines whether or not the
-i,,
cache is used. When the value is set to True, the internally cached rows ,,I
will be updated rather than sending the updates to the remote dataset. -I
When the default value of False is selected or remains, all modifications
are written directly to the dataset when they are entered. Note that in
all situations, client data sets are always cached.
The advantage of caching is in the minimization of traffic to the
dataset, increasing the apparent speed of database updates. Using the .
cache is not without its drawbacks, however. Without the records or
table being locked, other users can modify the underlying row data
prior to the updates being committed, causing possible anomalies when ,~i
-9
the current sessions data is written to the table.
-.
Enabling CachedUpdates at run time is a matter of setting the Boolean
variable:
Vendors.CachedUpdates

:=

True;

When caching is turned on, the user will work with a local copy of the
data held in memory All updates will be applied to the in-memory data
and written to the source table in a single transaction. Disabling cached
updates without writing the data back to the table will result in a loss
of data as the updates will be discarded without notification.
Constraints

Record-level constraints can be set to control the data that can be


entered in the fields of a table. Constraints at this level of control generally relate one field to another to control the data.

2
,ii
1 .;
.$
i

.7*
_ ?&

.I4;I
:,s
,+j
,.-+

Part /l-The Delphi Database Tools


.,..m-.
,(,I
i
____,
i

__

^...

/_

.._

i_.*

_.s~u.pI~-~~~~
.:

Filter

--

Filtering is the method by which some data from the table is exclude from view by displaying only those rows that meet specific criteria. ::
These criteria can be set through the Filter property of the Table
ponent. The property contains a string that defines the filter criteria.
For example, if we want to limit the displayed data to those custome __
who owe more than $10,000, a filter such as the following will only-g
,. 4.<
display records that meet the condition:
I
TotalDue > 10,000

This condition can be set either at design or run time. In addition to--this string, the values in the Filtered and FilterOptions properties ati ,_r
how the filtering occurs. The Filtered property is a Boolean value th@$
determines if the filter string is ignored or acted upon. When set to -.::
False, the filtering is ignored and the complete dataset is displayed.4
ting the property value to True will enable the filter string in the Fi&
property. The FilterOptions property is a set of two separate prop&j
that further control the filtering process. The first, foCaseInsensit
makes the comparison between the literal in the Filter property
column value case-insensitive. When foNoPartialCompare is set.tu
False, strings in the Filter property that end in an asterisk signify a
tial match with the asterisk acting as a wild card. If the value of thk,,> _~
item is set to True, the asterisk is taken literally.
lndexFie/dNames

This orooertv allows vou an alternate method of defining an index for r:;j
the I:able. The value entered in this property is the column (
that you want to use to index a table. Entering a value in this propeM
overrides the value set in IndexName and vice versa.
I

MasterFields

The parent-child relationship is fundamental to the proper functionh


of a relational database system, and this property is one part of ma&
that a reality in your Delphi application. The value in the MasterFi&
property specifies the field or fields in the master table on wh
ate a link with corresponding fields in this table. The property
MasterSource works in conjunction with this field to define the
DataSource component that is linked to the master table. Lets exam
the links for a moment.

:.$
-.?
tj;/*

er 5-Data Access with Delphi II 1 13


/ _
*,x_xH -. > <*

Field Link Designer


Delphi has a tool for use at design time to aid in setting your relationships correctly. Its called the Field Link Designer and the main window
is shown in Figure 5.3. The designer is an easy-to-use visual tool that
shows all of the participants in the relationship. It will be invoked
through the MasterFields property of the child [detail) table. The
MasterSource property is set to point to the DataSource component of
the master table. Click on the MasterFields property, selecting it, then
click on the ellipsis button to invoke the link designer.

Figure 5.3
The main window ofthe
Field Link
Designer

/ *

The Field Link Designer has done some quick background work and has
already determined the salient data for you when the program starts.
The indexed columns from the detail table have been placed in the
drop-down box; you simply choose the linking field in the detail table
by selecting it from your list of choices. In the example, the column
CustNo has been selected; it then appears in the Detail Fields box.
Select this field and move your attention to the Master Fields edit box.
To create the linkage, the value in the detail field must match the value
in the master field. Select the CustNo field in the Master Fields box and
click on the Add button. The join will now be displayed in the Joined
Fields box. Clicking on OK finishes the process.
SessionName
The Borland Database Engine uses a Session object to wrap all of the
database connection, drivers, cursor, queries, etc., under a single name
and separate them from other applications use of the engine. By
defaulr, every database application creates an object called Session and
the BDE maintains a list of all of these objects called Sessions. The
property SessionName allows you to associate a new session name with

n
-

this table. You will create new session objects only under exceptional
circumstances, such as concurrent queries against the same database,
for two reasons. First, the default Session object can manage a wide
range of conditions and needs and rarely needs to be overridden on
low-tier applications. Secondly, the complexity of managing multiple
sessions can quickly overwhelm an application and its developer.
Table Type

The TableType property specifies the underlying data-table structure for ,.


the table associated with this component. The default value, ttDefault,
.:,7:
allows the BDE to determine the table type based on the file extension
that it finds. The associations that will occur with this component are:
3
:$
,,i$
H DE or no extension-Paradox table
:
w DBF-dBASE table
n

TX-T-ASCII table

This default association can be overridden by explicitly selecting the


table type for your file. The drop-down list offers the following-choices:
H ttDefault-the
w ttparadox-uses
n ttDBase-uses
H ttFoxPro-uses
n ttASCII--uses
quoted strings

file extension determines the driver type


the Paradox driver
the dBASE driver
the FoxPro driver
the ASCII driver. The file is comma delimited, with
for each field.

b/e Events
The events for dataset objects are somewhat different from those for
other visual components. The events are triggered by movement and
modification within the underlying data rather than the changes to
control itself. The Table component events are:
H AfterCancel
n AfterClose
n AfterDelete
n AfterEdit
W AfterInsert
n AfterOpen
W AfterPost
W AfterScroll

Chupter 5-b
-_l*-__l

De
-_I

@hi

BeforeCancel
n BeforeClose
W BeforeDelete
H BeforeEdit
H BeforeInsert
n BeforeOpen
H BeforePost
w BeforeScroll
w OnCalcFields
w OnDeleteError
n OnEditError
n OnFilterRecord
w OnNewRecord
n OnPostError
w OnUpdateError
w On UpdateRecord
The before and after events for TDataSet descendants surround a
corresponding interaction with the dataset. For example, we have
BeforeCancel and AfterCancel events. The event that will trigger both
of these events is a request to cancel changes to a row. The
BeforeCancel event is fired after the cancellation message is sent but
before it is executed. The AfterCancel event is fired after the request
has been executed. By utilizing the before and after events, you can
wrap and control nearly every transaction against your database.

.g-4
( A :
-i
,;*
;.;
i ii

CIose Events

These events fire when a message to close the dataset has been issued ,.X:
and immediately after the dataset has been closed.
cfj
.,$.$
,;c;
,/

Delete Events

The delete events fire when the message to delete a record is issued
and immediately after the record is deleted.

Edit Events

This set of events fires immediately before the application enters edit
mode for the current record. The AfterEdit event fires after the
of the record begins.

3
.*

Part I/--The Delphi Database Tools


.,~~oI _-_- bilk ,- _ )/

Insert Events
These events fire before the application enters Insert mode and after a
new record is inserted.
Open Events
The BeforeOpen event fires when a request to open a dataset is
received and before the dataset is opened. The AfterOpen event occurs
after the dataset has been opened to the application and prior to any
data access.
Post Events
After changes have been made to a record, the modifications must be
posted to the dataset. The BeforePost event is fired before the update
request is executed. AfterPost is triggered after the changes have been
posted to the dataset but before the dataset is returned to the Browse
state.
Scroll Events
These events fire before the application scrolls from one record to
another and after the scroll action has occurred.
OnCalcFields
The OnCalcFields event fires when an application recalculates calculated fields within a dataset. This action occurs, in the default mode,
when a dataset is opened, the dataset is put into Edit mode, the cursor
moves from one column or field to another, and when a record is
retrieved from a database.
OnDeleteError
This event fires when your application fails at an attempt to delete a
record. Your event handler can issue corrective instructions and then a
retry attempt for the delete action.
OnEciitError

Similar to the previous event, OnEditError will be triggered when an


attempt to modify or insert a record raises an exception.

Chapter 5-Data Access with Delphi

-__

.i

.__*-

-._

_n_((-~qs

OnFilterRecord

?sj

The OnFilterRecord event fires each time a new record becomes the
1-j
current record and filtering is enabled. This event handler is included -
so that you can set a filter condition that cannot be set in the Filter
..$
property due to its complexity. Each record is tested against this code %&
q
determine if the data is visible to the application.
4
OnNewRecord

This event is fired when your application adds a new record to the
dataset.
OnPostError

Another of the error handling events, OnPostError is triggered when an ,,;$


L..,.-*
exception is raised by an insert or modification action.
?.I

OnUpdateError

The OnUpdateError event is triggered by an exception that is generated:


by cached updates being applied to the database. The event handler
can take corrective action based on the error and then request that the :$
:
updates be reapplied.
OnUPdateRecord
.

This event is fired when cached updates are successfully applied to a


dataset. The event handler is used to perform more complex updates
than those that can be handled by a simple update action.

~7

The Query Component (TQuery)


The Query component is similar in nature to the Table component; it
provides a method of accessing table data from a wide variety of
sources and making it available to the application. What makes it dif<
ferent from the table is that it uses SQL SELECT statements to build tk..~$
dataset that it displays.
_.
.*

Key Properties
The property list for the Query component is detailed in Figure 5.4 and
as you will notice, it looks remarkably similar to the property set of the
Table. This, of course, is because both the Table and Query components .!
1
descend from the TDataSet class and inherit its properties, methods,

rrt l/-The Delphi Databose

Tools
.i *
,b.

and events. The changes to tllis property list reflect the differences ia
the components approach to da ta access.

Active

False

Determines if the database table is OP

AutoCalcFields

True

This setting determines when the


OnCalcFields event is fired.

CachedUpdates

False

Enables or disables cached updates.

Constrained

False
statement that
This contains the

record-level

The name of the database to which this


table belonns.

.y:j
II
:
-I

This activates or deactivates filtering for f,.,

.?j

the mhlc

The set of options affecting the filter


process of the table.

IQ uery I

The

FilterOptions
Name

internal name of this comoonent.

Determines the method of storing the


column names Included for compatibility
reasons.

ObjectView

This value determines whether or not tb


Params list for the SQL statement is
regenerated during run-time changes.
The parameter

RequestLive

values

to be used in the

False

SessionName

Associates a session name with this tab&.:

SQL

This property contains the SQL string to:-


be executed.
-1
This property is for the developers use.

Tag
UniDirectional

0
False

Determines the navigational


the querys result set.

capabilities

mines the metha-

Figure 5.4 The TQuery component properties


Constrained

Constrained property value determines if, when using a live result set,
rows can be inserted or modified that would violate the parameters of
the WHERE statement that built the result set. For example, if the origi- .?
nal result set was created using the following SQL statement:
SELECT * FROM Vendors WHERE State = 'CO'

"CO".

DataSource

Parameterized SQL statements build enormous flexibility into your


-.$
application by allowing users to define the specific boundaries of their
dataset. Many times the parameters are filled by an interactive session z3
with the user or by internal values within the application itself. Another
method of defining the parameter values is through retrieval of the
data from another dataset. This is the domain of the DataSource property. The value in this property names the DataSource component from
which the values to fill the parameters are drawn. If the parameters
have the same name as columns in the DataSource dataset, the values
ate parameter.
ParamCheck

The ParamCheck property determines if the parameter list for the SQL
statement is cleared and rebuilt when the SQL property is modified
during execution of the application. The SQL property is easily modified during execution to run different SQL statements. If the statement

fort II-The
.^,

Delphi Database Tools


,_.
_
,,

is modified, it is important that the corresponding parameter list ma&&:{


the new query. To ensure that this occurs, the parameter list is cleared ,$4
and rebuilt based on the current contents of the Params property.
.$!

The Params property is an array that contains the parameter values


needed for the SQL statement. At design time, the collection editor wiiI
aid in setting the name and data type of each of the parameters.
RequestLive

There are two types of result sets that can be obtained through the use z.~
of a Query component: read-only and live. A read-only result set con- Gi.-:
tains the results of the SQL statement but cannot be modified by the
--.A
application. If the value of the RequestLive property is True, a live
i
result set open to modification is returned when possible. The user CSQ+~
modify the contents of the result set and have those modifications -:
propagated to the underlying dataset.
I.
.,g!
SQL
c$;+a
.:;:
The SQL property is the heart of the Query component. This string IiW:$
contains the text of the SQL statement that is executed to provide the !:
result set to the application. This property can be set at design time or
built dynamically at run time. The BDE and the underlying capabilities
of the target database determine the syntax supported.
UniDirectional

The BDE supports bidirectional cursors, meaning that both forward and .i
backward navigation are allowed regardless of the rules of the undetly-$2
ina database. If the value of the UniDirectional oropertv is False,
. __;.e
bidirectional cursors are returned in the result set. Setting this value to ;:i
True will result in a unidirectional cursor that allows only forward
:;i 8.,
navigation.
.z ,

I DataSource Componel It (TDataSource)


The DataSource component is the pipeline between a dataset and the
data-aware components. This intermediary component is required anyLti
time that data-aware components and dataset components are to be
utilized on a form. The DataSource component requires a matching
f:
DataSet component in every case. The DataSource control also enabMS:$
I, -,k;r;
the linking of datasets in master-detail (parent-child) relationships.

,.

I,i

Chapter 5-Data

.-

-^- ;I-.

^.

Access with Delphi

.s..

_.*%a*

The DataSource component surfaces a limited number of published


properties for use at design or run time; these are summarized in Figure 5.5. Since the number of published properties is so small, the
maioritv
_I
+ of them are kev to extracting the best performance from yet
application.

rtoEdir rue /Edit mode.

:-

Determines if the data-aware components


connected to this control automatically enter 6~

Specifies the dataset


gets its data.

DataSet

1 Enabled
Name

Tag

1 True
DataSource

10

Figure 5.5 TDataSource

from which the compone&..l?

\i

(Determines if the data-aware controls associate&l:


with the DataSource component display data.
4
The internal name of the component.
This property is for the developers use.

component properties

Auto Edit
When the value of this property is set to True, the associated dataset
automatically enters the Edit state when a connected data-aware con- :!j
trol is updated. When it is set to False, the application must explicitly 2
change the state of the dataset to Edit through a call to its Edit metk#
-;
before the user can update the data.
The convenience of defaulting to the Edit state is a good choice for i-3
most programs but should be considered carefully for more critical
applications. You may want to exercise more control over exactly wlI&
updates can be performed by either having the user explicitly turn on :;$
the edit functionality or having the program review the conditions
-*2
under which the update is being performed.
:*
_...i$>

DataSet

This property defines the dataset to which the DataSource compone


is connecting the interface components. The value of the property is
selected either through use of a drop-down list of available dataset
.,.J
components or through manual entry of the name. The DataSet comg
nent must be added to the form prior to attempting to enter this val
attempts to enter a nonexistent dataset name will result in an error
message.

122

#.,%. _

_
Enabled

This property is the switch that controls the visibility of the data contained in the dataset. When this property value is True, the DataSourc$f
is connected to the dataset and any controls that reference the
:
DataSource will display current data. (This is provided that the Table;::
etc., is in an active state). A value of False will serve to disconnect the 2
data-aware controls from the dataset.
I:
<:
The value of the property can be modified at run time in order to tern-%
porarily disconnect the controls from the dataset, although this is not --?
recommended. The preferred method of performing this operation is ..jf!: .t
c;
through the DisableControls method of the dataset.
. :;
7

Tip

The component name seems to be the simplest of all of the


properties, needing no explanation. Why not take advantage of
this property to make your own work easier? Name the
DataSource component something similar to the name of the
DataSet component that it is connected to. Grouping controls
like this in a complex application will make your life a lot easier.

TDU tdource Events


There are three event handlers exposed by the TDataSource class:
n OnDataChange
H OnUpdateData
n OnStateChange

,;g
_, .A
., .-(
A<
.,
7

Each of the event handlers fires on a change in the dataset to which it -2


is connected. It does not reflect any change in the DataSource; the con--{ :
trol remains a simple intermediary
OnDataChange

X.

The OnDataChange event is triggered by the cursor moving to the N+:$


or previous record; any method that results in a change in the cursor *l!?
position will trigger this event. This event is especially useful for ma
ally synchronizing components.
. .*.t,
OnStateChange

. .i.
--;:

Whenever the state of the dataset associated with the DataSource


: .:i
changes, the OnStateChange event is triggered. Since a state change 9:
the dataset can be a significant event, this event is useful for managin@
:,
these changes.
I.

OnUpdateData
Whenever the data content in the current record of the dataset is about?!!
to be updated, the OnUpdateData event is fired. This handler will pro- :;I
vide your application with the opportunity to update non-data-aware .,--TZ-$+
components and keep them synchronized. For example, when data ina
field is modified and the user tries to move on to the next record, the 1.
Post method of the dataset is called. Prior to actually changing the
record, the OnUpdateData event is called.

putting the DataSource, Table, and Query Components 1


Work

,.

The best way to understand the interaction between the database corn--$
ponents is to put them to work. A simple project that uses the
minimum number of components will allow us to explore the
DataSource and DataSet components without the interference of interface issues. The first project will zero in on the DataSource componen&,:j:
On its own, it is singularly unspectacular and cannot function in a vat- .k
.,;sj?;
uum without the support of other components. Remember that its
purpose is to provide a conduit between the interface components and :
the database. The Datasrc project will examine the control that it provides to your application. All of the projects described in these pages
are on the CD-ROM in the Chapter5 subdirectory.

The Dutasrc Project


I. Start a new application and caption the form Datasrc Project. Save the project, naming the unit Dsrcu.pas and the project Dsrc.dpr.
.*t
2. On the form, place a DataSource and a Table component. It doesnt

matter where you place these components. They are non-visual and
will not appear on the form at run time.
3. Click on the Table component and set the properties as follows:
H DatabaseName = DBDEMOS
. 3.. 21
W TableName = CUSTOMER.DB
,-I,
4. Select the DataSource component and set the value of the DataSet : .;s
property to Table 1.
5. From the Data Controls tab, select the DBGrid component and drop it
onto the form. The DBGrid control provides a table-like display of the
contents of a dataset. Select the DataSource property and change it to
DataSourcel. You will notice that all of the database components

I/-The Delphi Database Tools


pi
a_.-

6.

7.

I.

are aware of what other components are a part of the project and their dg;
names will appear in the drop-down lists for properties such as this.
Select the Table component again. We are going to turn on the spigot
and start the data flowing to the application. Change the Active prop,jj!
erty to True. When you do, data from the CUSTOMER table will be
_ f$
displayed in the DBGrid control.
~~3
One last step for neatness sake. Drop a BitBtn control on the form and
.i
_
A.?$
modify its Kind property to bkclose.
ihs,$A
You can run the application at this point. It does not do anything specd:
tacular but you should be able to navigate the dataset with the arrow
4
i
keys and browse all of the data. Click on Close to continue.
; ;-.* #
Recall that the state of the dataset determines what actions can occur
::$
against or with the dataset. Using the simple database application that
..&j
we have constructed there is an excellent opportunity to learn more
-.::
about the complexity of the state sequence. The Datasrc application
will be modified in the following steps to pop up a message box each
time the state changes in the database.
Select the Events tab for the DataSource component and add the code
shown in Listing 5.1 to the OnStateChange event handler.
procedure TForml.DataSourcelStateChange(Sender:
TObject);
var
tempstr
: string;
begin
tempstr := 'I;
case Tablel.State of
dsInactive
: tempstr := 'Inactive';
dsBrowse
: tempstr := 'Browse';
dsEdit
: tempstr := 'Edit';
dsInsert
: tempstr := 'Insert';
dsSetKey
: tempstr := 'SetKey';
end;
messagedlg('Current
state changed to '+tempstr,
mtInformation,
[mbOk], 0);
End;
Listing 5. I Modifying the OnStateChange event handler

2.

Compile and execute the project.

r S-Data Access with Delphi II 125


. , , _.,_ _) _. ,*. _ _,,. __. ,. , * ,. _ _,. _I, ..j,_ _..

As shown in Figure 5.6, your application should notify you of every single state change.
- * *

1354 Cayman Dwers World Unlmted

Pfl Box 54

Notice that the dataset is originally in Browse mode and will automatitally switch to Edit mode when a change is made to the data. You may
discover that the state changes more often than you ever suspected.

Wall Street Watch


Next, we will put the Query component through some simple paces so
that you can get a good idea of what its job is. Putting this component
to work will also require the use of possibly nascent SQL skills. An
advantage of this small program is that you can also use it to experiment with SQL statements while building some experience.
Begin with a new form, captioning it with the string Wall Street
Watch. Save the application, naming the unit wswU.pas and the
project file wsw.dpr.
2. Add a DataSource, Query, and DBGrid component to the form. Set the
properties as follows:
Query:
DatabaseName:
DBDEMQS
DataSource:
DataSet:
Query1
DBGrid
DataSource:
DataSource
3. For good measure, add a BitBtn component and set the Kind property
to bkclose.
I.

126 w fart II-The

Delphi Database Tools


.-1~~1^^121p~~~-.^~~i~._w.1
~.. ,~.xs

i _.,_ .~ *a _Y -slr_*_.*lil-__/,__llI_~

4. Unlike the Table component that starts the data flowing to the app,,,
tion as soon as it is made active, Query sends data to the applicatiu@$$
only as a result of a successful query. The SQL statement must be

:.:;>
entered into the SQL property prior to activating the component. Th&,;$
easiest way to enter code at design time is to invoke the String List :?$
Editor by double-clicking on the SQL property value column. The fi
statement that will be used is a simple SELECT statement that will
retrieve all of the rows from the MASTER.DBF table. Enter the follovjx;,, i
ing code into the String List Editor:
.>B
,<$
SELECT * FROM
5.

:
:y

MASTER.DBF

Set the Active property of the Query component to True. If the SQL
i
statement was correctly formatted, the grid should fill with data. If a a!
problem is found with the statement, an exception will be raised and :$
the Query component will not be activated. Save the project after c
piling it.
>q
A simple query but useful in learning to utilize the components. The. L&
muscle behind the Query component is the complexity that is allows
in the SQL statement. Local SQL supports a wide variety of construe
for building static statement but it also has another advantage-it SI
ports the placement of replaceable parameters into the clauses of a i., Jstatement. This allows you to build a statement into the Query co
nent into which your users can place values to manipulate the
statement at run time.
MASTER.DBF has a column containing the risk assessment of each of
the stocks that we are tracking. Each of the instruments is rated as low,
medium, or high risk. To add functionality to the Wall Street Watch
application, we will use the Query component to allow the user to categorize the stocks displayed by risk.

:.p
-8
, .,
,I

i.*,.Aq
A SQL statement can be parameterized so that the values in the param- fi:.
eters can be modified at either design time or run time. The parameters ::
replace persistent values in a SQL statement. One very useful place to ii
utilize parameterization is in the WHERE clause in which the selection,. 3:s2
criteria is based on the values found in specific columns. Parameters I f?J
are represented in a SQL statement by their label and are always pre- :$j
2 I>,2
ceded by a colon.
-.d
53
The SQL statement in the Query component will be modified with a :T$
parameter to represent one of the operands in the WHERE clause. The-:$
new statement will read as follows:
-I 2
SELECT * FROM

master.dbf

WHERE

risk

:RISKCODE

'-3

RISKCODE will contain a value that will be compared against the column Risk when the SQL statement is executed.
When the SQL statement is modified, it will share the parameter values 8
with the Params property of the Query component. Modifying this
property at run time is another dynamic method of adding new parameters to a SQL statement that enables you to build the statement on the 8
fly.
Listing 5.2 shows the code used to modify the RISKCODE parameter
value at run time.
procedure TForml.ButtonlClick(Sender:
begin
with Query1 do
begin
Close;
Unprepare;
Params[O].AsString

TObject);

:= Radiogroupl.Items[Radiogroupl.ItemIndex];

Prepare;
Open;
end;
End;
Listing 5.2 Modifying the RISKCODE

parameter

When using parameters in a SQL statement, there are a number of preparatory and cleanup steps that must be executed. Notice that the first
8
thing that occurs is that the Query controls Close method is used to
close the dataset. Modifications to the SQL or Param properties must be
executed on a closed dataset. Next is the statement Unprepare. This
8
method works in conjunction with the Prepare method to set up the
environment and bind the values to the parameters prior to executing
the SQL statement. The Unprepare method cleans up after the SQL
8
statement and releases resources back to the application.
The value that we want to use in the WHERE clause of the SQL statement is set in the line that reads:
Params[O].AsString

:=

Radiogroupl.Items[Radiogroupl.ItemIndex];

Remember that the Params property is an array with each of the parameters in the statement indexed to their position. Therefore, the first and
only parameter in our statement resides at subscript 0 in the Params
list.
I

128 q

Part II---The Delphi Databose Tools


-.
I/
-.-.

_,._

,. il. _I.IIN,lihL ,j_

Finally, the statement is executed by calling the Open method. The


Onen method is used to execute SQL statements when the SOL will
result in a dataset being returned such as our SELECT statement.
Another method, ExecSQL, is utilized when no result set is returned
such as in an INSERT, DELETE, or CREATE TABLE statement.
Modify rhe Wall Street Watch application by performing the following
steps:
Modify the SQL statement as shown above, if you have not already
done so.
2. Add a Radiogroup control to the form. Caption it Risk and add three
items to the Items property: LOW, MED, and HIGH. The strings that
are used should match the expected values in the Risk column so that
a match can be made.
3. Add a button captioned Check Risk to the form. The code in Listing
5.2 is the OnClick handler for this component.
4. Compile and execute the application. It should appear somewhat similar to Figure 5.7.
I.

HIGH

Figure 5.7
The Wall
Street Wutch

project
Try selecting the different risk values. For each one, a query will be executed and a new dataset displayed on the grid. (The Risk column is on
the far right-hand side of the table and may not be immediately visible
for you to verify your results.)

I 29
,. ,.

iota Access with Delfhi q

simple components that we


_**-_ - -__
cation that uses a parent-child/master-detail relationship between two or more tables. To
explore this topic, we will use the OrderForm project.
I.

Start a project and create a form that is similar to that shown in Figure
5.8. You are going to use two Tables, two DataSources, and two
DBCrids. Save the project.

2. Prior to building your application you will have decided which table
will be the master, or controlling, table in a relationship. Navigation in
this table determines the contents of rhe detail table when linked in
this manner. The Table1 and DataSource components will link to the
master dataset in this relationship. Their properties are the same as
those used up to now. Point the DataSource control to Table1 as the
DataSet property. Table1 will use DBDEMOS as the Database property and fill TableName with CUSTOMER.DB.
Activate the dataset.
3. Select DataSource and set the DataSet value to Table2. Select the
Table2 component and again, set the DataBase property to
DBDEMOS. The TableName property for the detail table will point to
ORDERSDB.

I Fart /I-The Delphi Database looh

4. The detail component is used to set up the relationship at the application level. Select the MasterSource property and select the DataSource
control linked to the master table, DataSourcel. Select the
MasterFields property and click on the ellipsis button to invoke the
Field Link Designer. (This was referenced earlier in this chapter. Please
refer back to those paragraphs for usage information.) The primary
key for the ORDERS table is the default index displayed in the
designer but this will not work as a linking field. Click on the Available
Indexes drop-down box and select a secondary index, CustNo. Click
on the CustNo field in both the Detail Fields and Master Fields lists
and click Add. Select OK to finalize the relationship.
Activate the Table2 component. Review the orders shown in the detail
grid. They are now limited to those orders matching the customer
number of the currently selected record in the master table. Compile
and execute the application. As you navigate the master table, notice
how the contents of the detail dataset change. The miracle of the relational database is revealed!
Filtering is a dataset-based method of limiting the rows that are displayed. Afilter is a constructed statement that compares a value to a
column value. Only those rows that match these criteria become available to the application. The OrderForm application can be modified to
utilize the filtering properties of a table to improve the usability. A filter
will be built to utilize the contents of the State column to limit the size
of the dataset geographically
1.

Add an Edit and Button component to the form as shown in Figure


5.9. We will use the Button OnClick handler to manipulate the
filtering.

Figure 5.9
Addrng an edit
box and button
to the
OrderForm
project

2. Add the following code to the OnClick handler procedure for Buttonl,

Carefully examine the number of quotes:


Tablel.Filtered
:= True;
Tablel.Filter
:= 'State = "I + Editl.Text

+ I"';

Chapter S--Data Access with Delphi


.., i __; _ _ _ l.-_ 114 u-.P.l?^len _.-___, ,<ial*lsbp

Execute the OrderForm application and scroll through the CUSTOM,


table to see the range of values that have been entered in the State&,
umn. Enter a value in the Edit box and click the Filter button. The .*i
Customer List grid will display only those items that match the Stati%
value. None of the other functionality is affected; the relationship &$
still intact.
Another limiting tool is the Range. Similar in function to filtering, a
range allows you to bracket a range of values that are available to t
application. The filter offers more flexibility than the range, as the
range requires an indexed Paradox or dBASE column to work. SQL 2
umns listed in the IndexFieldNames property can also support a ran,Consider both options when deciding how to present data to your .:,$.
,..t,t?
applications users.
7 Tip

One last thought prior to moving on to the remaining components: If your application plans include a requirement for
scalability, you may want to use Query components for all table
access rather than simple Table components. This will make the
task of scaling up much easier to accomplish with fewer changes
to the application.

Stored Procedure Component PStoredProc)


Stored procedures are mainly the domain of the remote server world. d
stored procedure is a set of SQL statements or other subroutines sup,:
ported by the server environment that reside and are executed on the 1
remote server. They serve two purposes: The independent code packa
can be used on multiple databases to ensure consistent modification t
and retrieval actions and they relieve network traffic by not requiring ?
long SQL statements to be sent to the remote server.
Stored procedures come in two types. The first class manipulates the 5:
data contents of a remote dataset and the other returns a result set
d
from a SQL SELECT statement. The type of the stored procedure and i$
the remote database determine the type of component that you will UZ@
to execute the stored procedure. In some cases, when a stored proce- ..:i
dure returns a result set, the Query component is used to execute the %
procedure rather than the StoredProc control. Stored procedures sup- :
port both input and output parameters, often utilizing the input
z,parameter as a parameter to the SELECT statement. The results of a ?
statement are returned to the calling application through the output
i
parameters. A thorough understanding of the stored procedure that YE&I,
want to utilize is important so that you can make an accurate decision

as to what type of object is needed to receive any results returned from


the execution of the procedure. In Delphi, the StoredProc component is
the interface to this functionality.
Key Properties
The StoredProc component surfaces a familiar set of properties, all of
which are detailed in Figure 5.10. Since the functionality is based on
the remote server, there is little for the component to do except point to
it and pass the appropriately formatted parameters.

Active

Determines if the stored procedure is

False

to be executed.
AutoCalcFields

This setting determines when the

True

OnCalcFields
CachedUpdates

Enables or disables cached updates.

False

The name of the database to which this

DatabaseName

stored

procedure

belongs.

The text string defining the current

Filter

filtering
Filtered

event is fired.

condition.

This activates or deactivates filtering for

False

the results of the stored procedure.


The set of options affecting the filter

FilterOptions

process of the stored procedure.


Name

StoredProc

ObjectView

False

The internal name of this component.


Determines

the method of storing the

column names. Included for


comoatibility
Overload

reasons.

This property represents the Oracle


overloaded stored procedure to be
executed.

ParamBindMode

PbByName

Determines the order that the


parameters are assigned to the
parameter

Params

fist of the procedure.

The list of input and output parameters


for the stored procedure.

SessionName

Associates a session name with this

StoredProcName

The name of the remote procedure


represented by this component.

stored

procedure.

Chapter S-Data Access with Delphi n

13

This property is for the developers


UpdateObject
Figure 5. IO The TStoredProc

Specifies the update object used to


update a read-only dataset.
component properties

Overload

The Oracle database supports overloaded procedures, which are stored


procedures that share a name but differ in function based on the context. Oracle differentiates between them by assigning each a numeric
identifier, and this property passes that identifier to the BDE. When
selecting from among the overloaded procedures, it is crucial to
remember that the parameter lists for the overloaded procedures can
be different and that the appropriate parameters must be passed and
received.
When addressing other server types, this property should be left at its
default value, 0.

kq

: v$
r

1::
rilr-l

Param BindMode

The ParamBindMode property determines the method that will be used


to match values from the Params parameter list to the parameter list of (,
the stored procedure. Two assignment strategies are supported: By
1
Name and By Number. The preferred method is to utilize By Name,
which explicitly matches the identifiers of the parameters in both the
parameter list and the procedure. The By Number method assigns the
parameter values in a sequential method, i.e., the first parameter from
the Params list is assigned to the first parameter slot at the procedure.
Par ams

Stored procedures support the return of values through the procedures -3


parameters, similar to the Pascal variable parameter. The Params list for ,_::
a stored procedure is used to set and examine the values of both input :.
~?
and output parameters.
TStoredProc Events
All of the events surfaced through the StoredProc component are
derived from the TDataSet and TBDEDataSet. Because they have been
discussed in previous paragraphs, you are referred back to those
descriptions for more information.

134 II Port Ii-7%e Delphi Database TOO/S

Putting Stored Procedures to Work


We will make use of the Local InterBase Server that ships with some
versions of Delphi to explore the functioning of the StoredProc component and stored procedures in general. The project that we will build is
called the Project Manager, and it works with the sample InterBase files
in the IBLocal darabase. If you have not installed the LIBS, this database will not appear in the Database Explorer. Remember that the
stored procedures are a part of the metadata of the database, so all of
the information pertaining to the structure of the procedures can be
viewed through the Database Explorer.
The running application is shown in Figure 5.11.

MAPCJB

MapBrowser porl
Translaior blipgrade

Figure 5.
Figure
5, I I
The Project
Manager
Manager moin
mum
screen

Two different operations are being handled by stored procedures running on the InterBase server. When the Programmers dataset is scrolled
from row to row, a procedure is used to extract the programmers current project assignments and display them in the Assignments grid.
Secondly, a procedure is used to insert new rows into EMPLOYEEPROJECT To begin building this project, start with a new application.
I. Caption the form Project Manager and save the project. Name the
unit file stprocuZpas and the project stproc2,dpr.
2. Add three DBCrid components to the form: DBGridl in the top left,
DBGrid2 in the top right, and DBGrid3 in the lower left-hand corner. So that the following instructions will have some measure of
clarity, add the labeling as shown. We will refer to the grids by their
closest label in the instructions.

Chapter

S-Data Access with Delphi q

135

3. On the Programmers grid place a DataSource and a Query component.


Set the DBGrid DataSource property to DataSourcel. On
DataSourcel, set the DataSet property to Queryl. Select the Query
component and set the DatabaseName property to IBLocal. (Remember, this will only show if LIBS has been installed.) Double-click on the
SQL property to open the String List Editor and enter the following
SQL statement:
SELECT

emp-no,

first-name,

last - name

FROM

employee

Click OK to save the statement to the control.


4. Be sure that the LIBS is running and double-click the Active property
on the Query1 component to execute the SOL statement. Enter the
password &asterkey. If it is correctly form&ted, the statement will
be processed and the grid will fill with the result set. Your projecr
should be similar to Figure 5.12 at this point.

Figure 5. I2
Modifyrng the
Project Managerproject

.,,
, .. .~....,. . >.. . > .
. . . _.
.:.*--:a>
._ ._.I.
.
-,.

5. To the Current Projects grid, add both a DataSource and Query component. The grids DataSource property should be set to
DataSource2. The DataSet property of DataSource should be
pointed to Querya. The Query components
components DatabaseName
DatabaseName property
property
will be IBLocal and the following SQL statement will be entered for
the SQL property:
SELECT

proj-id,

proj-name

FROM

project

6. Activate this query to fill the dataset with the results from the SQL
statement.

I36 n

Part II-The Delphi Database Tools

__,. ,.-

7.

. *%;--

Finally, add the DataSource and Query components to the Assignments


grid. Set the appropriate DataSource and DataSet properties to link
these three components together. In the SQL property, the SQL statement that we are going to enter will execute a stored procedure. We
are using a Query component because the GET-EMP-PROJ stored procedure returns a result set. This procedure has an input parameter
through which we will pass the employee ID number for which to display the current projects. Enter the SQL statement as follows:
SELECT * FROM get-emp-proj(:Emp-Num)

8. This query will not be activated at design time. The application is

going to execute this query each time a new employee is selected. To


do this we will utilize the OnDataChange event of the DataSource
control. Add the following lines to this event handler procedure:
procedure
TForml.DataSourcel.DataChange(Sender:
TField);
begin
Query3.CLose;
Query3.Prepare;
Query3.ParamByName('Emp-Num').AsInteger
:=
Queryl.FieldByName('EMP_NO').AsInteger;
Query3.0pen;
end;

TObject;

Field:

9. Add a BitBtn control and set the Kind property to bkclose. Compile

and save the application. Execute the application and scroll through
the Programmers list. As the employee ID changes in the current row,
the GET- EMP- PROJ procedure is fired and returns all of the projects
assigned to that specific ID number.
IO. Add a Button component and caption it Add Project. The user will
click on this button to assign new projects to a programmer. For this
we are going to use another stored procedure from the IBLocal database, ADD-EMP-PROJ. This procedure does not return a value, so
rather than using a Query control we are going to use the StoredProc
component. Place a StoredProc control on the form and set the
DatabaseName property to IBLocal. Since the procedures are a part
of the metadata of the database, clicking on the StoredProcName
property will display a list of all of the registered procedures. Select
GET-EMP-PROJ. Double-click on the Params property. The parameters for the selected procedure are displayed and their properties
surfaced for editing. By clicking on each parameter you can determine
what you are working with. Dont make any changes. Close the editor
to return to the Object Inspector for StoredProcl.

Chapter S--Data Access with Delphi n

1%

$2
I I. The code for adding a project record is going to be placed in the but- y$
tons OnClick event handler. GET-EMP-PROJ
has two parameters thag 1s
are going to be gathered from the EMPLOYEE and PROJECT tables. >.?I
Enter the following code for the event handler:
r;
procedure
TForml.ButtonlClick(Sender:
TObject);
begin
StoredProcl.Prepare;
StoredProcI.ParamByName('PROJ~ID').AsString
:=
QueryZ.FieldByName('PROJ-ID').AsString;
StoreProcl.ParamByName('EMP-NO').AsInteger
:=
Queryl.FieldByName('EMP-NO').AsInteger;
StoredProcl.ExecProc;
StoredProcl.Unprepare;
end;

12.

Compile the program and save it. Execute the application and select a 2,
programmer and a project. Click the Add Project button and the ,:;
ADD-EMP-PROJ
procedure will take care of inserting the new row. :
When you select the employee record again, the new project will have.
$2
been added to the Assignments list.
fj..

The Database Component (TDatabase)


Every Delphi database connection is encapsulated by a Database corn- 1
ponent, whether or not you actually place one in your project. The
:
Database component is responsible for providing all of the connection -;
i;
information from the database to your application. An automatically
generated database component is instantiated for each BDE application-;
5
to encapsulate the connection.
.;A,.
The most important task of the Database component is to provide your 3
application with information about the type and location of the data
1
files of your database. Recall that in setting up the alias in the BDE, yogf
were required to specify the data location, the driver type, and some -:,g$
basic connection parameters. When a temporary database component &$
instantiated, these values are read from the BDE and passed to your 13
application. This method of obtaining information about the database .;connection detracts from the scalability and installation flexibility of 7
the application. For example, to simply specify a new location for the
storage of data files, your users would be required to edit the BDE con--
figuration files and modify the parameters of the alias used at the time !
of installation. This is certainly not the type of task that many users or i7

138 n

Part II-The Delphi Database Tools


,. _s. .a.

administrators would be comfortable performing, especially in a corn.


plex client/server environment.
A persistent Database component, instantiated when a Database cornponent is explicitly present in an application, brings the task of
specifying the connection information into your application. Database
connection parameters can then be stored with your application or in
your applications keys in the Registry, allowing the user enormous flex.
ibility in scaling or simply moving an applications data stores. In
addition, much finer control over transactions and logins is possible
when an explicit Database component is added to the application.
Key Prroper rties
Since we have left the comfortable confines of the dataset components, . *db*a
there are a number of new properties that require discussion. The corn- $i
;gj
plete set is detailed in Figure 5.13.
:- y
This determines the BDE alias

AliasName

Connected

that djT

False

DatabaseName
The name of the BDE driver for the

DriverName

Excluwe

False

Setting this to True enables exclusive


database access, preventing others

from

modifying the data.

.z,

Determines whether or not a database


handle is shared.
Creates a persistent connection
the dataset

even if

are closed.

This property controls the display of the


standard login prompt when

connecting

to a database.

Determines if the database

.;I

connection is

. sz4-r
j.._ :;:

- !,
. ..
, ~ _(_I
-.:.lt:

.L; p
:y3
<?j
,-A

SessionName

Default

The name
the database

Tag

This property is for the developers use. -

Translsolation

tiReadCommitted

Specifies the transaction isolation level. :

Figure 5.13 The TDatabase

connection.

component properties
i

AliasName

The AliasName property is used when you want to utilize an already


existing BDE alias in the application. This property is filled from a
drop-down list of all of the known aliases. When an alias is specified
through this property, it overrides other settings in the Database cornponent such as the driver type and items stored in the Params properr$?,
This occurs because the information has already been defined through .i
/aithe BDE configuration.
s- *-4
DatabaseName

The name of the database is the identifier by which the application


gains access to the settings required for a connection; this name is con- :
tained in the DatabaseName property. If you enter a name that matches:
an existing alias, the driver and location information for that alias is
captured and need not be set. When a new application-specific name is
entered, you must also follow the requirements of the alias and provide,2iz
- : $4i
location and database type information through the Params and
DriverName properties respectively
KeepConnection

Unlike the temporary connections created by the default instantiations :$


of the Database component, the explicit placement of a Database corn- ,4
ponent allows you to manage the connection status with the database. ,j
The KeepConnection property determines whether or not a database . ,;*g_
connection stays open even after all dataset objects have been closed. -. $
By creating a persistent connection, the application is not required to : ,;;i.-.
:log in each time the dataset needs to be accessed again.
13,;$__fA-1:
Loginfrompt

.t.,

The LoginPrompt property, a Boolean value, determines whether or not $


the user of the application is required to provide username and password information when a connection is created to a database. Though .f&
little used on a local desktop application, most remote database servers .,

I40 n
/

_,,,i

_.

Part /l-The Delphi Dutabase Tools


..

will require a login at the time of connection. The login can be


: .::;,
bypassed by setting this property value to False and providing the net- ?$
t:-:<&a
-~*P;*;
essary information in the Params property.
Tip

The data in the Params property is not secure. If security considerations are high for the application and the database, it is not
recommended that the login prompt be bypassed in this manner.
An alternative to this method is to utilize the OnLogin handler to
provide the login information from a more secure source.

Params
This property is similar to the Params property in other components we !*Q
-.;.$,: ~ i
have examined. It is a collection of string values that specify certain
characteristics of the database connection. The Params property can
used to override the values contained in the BDE alias definition. The
parameters that can be set are determined by the alias and driver type:
specified.
Trans/so/ution
Transaction isolation is a specification of the interaction characteristics
between simultaneous transactions on the same tables. When transactions occur against the same table at the same time, the value in the
TransIsolation property determines how much of the other transactio
changes are visible to your applications transaction. There are three
settings for this property:
tiDirtyRead-Permits

the reading of uncommitted changes made to


the database by other transactions. Uncommitted transactions are subject to rollback at any time.
tiReadCommitted-Permits

the reading of committed, or permanent, changes to the database. At this point the other transaction has
physically changed the data. This is the default value for this propew

!
.: /_/

--tj
3
:&
tiRepeatableRead-This
level of isolation ensures that once your
,:-?
applications transaction has read the row, its view of that row will not I.
change. Subsequent changes to the row by other transactions are not
$
, _:4i
visible.
.
Isolation levels are supported to different degrees by the variety of
database servers. If the target server does not support the requested
isolation level, the BDE will use the next highest level of isolation.

ea Access with Delphi I

14 1

Somewhat of a letdown after examining all of those exciting properties,


there is only one event for the Database component. The OnLogin
event is fired when a login is required by a database. The handler that
you write provides an alternative method for passing login information
to the server using a more secure source for the username and password information.

Putting ,the Database Comlt>onent to Work


A demonstration of the usefulness of the Database component requires
that you follow this project with an idea towards improvements that
can be made in your applications. We will start by creating a local alias
that is controlled from within the application rather than through the
BDE configuration files.
I. Start a new application and save it. Name the unit file dbaseu.pas
and the project file dbasep.dpr.
2. Place a Database component on the form. You may enter all of the
property values through the Object Inspector but Delphi, as it often
does, offers a better solution. Double-click on the Database component
[or right-click and select Database Editor) to invoke the Database Editor. This tool, shown in Figure 5.14, conveniently gathers all of the
necessary property editors for building the connection.

Figure 5. I#
The Datobose
Editor

3. In this dialog, everything necessary for a BDE alias is going to be set.


Enter ProjectData in the Name field. This is the alias by which this
connection will be known. Because the settings for the database

142 n

Pa

I-The

Delphi

Database

Tools

connection are going to be established through the component, skip


over the Alias Name field to Driver Name. Select STANDARD from
the list
The login parameters for each type of database can be set through the
Parameter Overrides field. To see the available parameters and their
default values, click on the Defaults button. For simplicity purposes,
we are going to point this connection to the Demos directory by placing the full path in the Path parameter. Set this value (for Delphi 4) to:
C:\Program

5.

6.

Files\Common Files\Borland Shared\Data

$
i:
/_ f?f
:-*2
I1

and click OK to establish the properties.


This local alias is now self contained within the application which now
has complete control over the settings. To test the connection, place a
_1
DBGrid, DataSource, and Table component on the form. Set the
DataSource property of the grid to DataSource and the DataSet
property of the DataSource to Tablel. In the Table1 DatabaseName
Z. :
property, drop-down the list of aliases. You will find the ProjectData
alias is now a recognized alias through the BDE. Select this item.
Select the CLIENTS.DBF table for the TableName property.
Activate the Table component and the contents of the CLIENTS table ,:.:
will be displayed in the grid.
As mentioned earlier, all of the alias control function is encapsulated
within the Database component and controlled by the application.
Because all of the parameters and seie&ions were hard coded, we are
not much further ahead in our quest for scalability by using the Database component. The next steps will enhance the project to
demonstrate how the application can modify the values at run time.
This opens up the possibility of modifying the database connection
parameters in light of changing circumstances or modification of the
data store location by the user.

Double-click on the Params property of the Database component to


open up the String list editor. Delete the PATH line so that only two
parameters remain. Click on OK to save the modifications.
2. The database connection must be established as one of the initial
startup steps of the application, so we will use the forms OnCreate
event to trigger our modifications. Create an OnCreate handler for
Form1 and add the following code:
I.

procedure
TForml.FormCreate(Sender:
begin
Databasel.Params.Add(PATH=C:\Program
Shared\Data');

TOBject);
Files\Common

Files\Borland

:~ ~,~ j~3
,<..<:>A &:z
.;j

_*m----Lem.

Chapter 5--Data Access with Delphi n

n,ll,..

.I

t&&?$

.~,

Tablel.Open;
end;

3.

Execute the application again to see the results of this modification. In


a distributed application, this method would allow your application to
determine the path to the data source at run time, making the application infinitelv, more flexible.

The Session Component (TSession)


The Session component, like the Database, is created by default to
manage the database connections in any Delphi database application. A
session represents all of the database connections, drivers, cursors, queries, etc., that are activated as part of an application. All database and
dataset components are associated with the automatically generated
default session component, originally named Session. All sessions
within an application are in turn automatically managed within a list
called Sessions.
The default Session component effectively manages all of the temporary and persistent database components and it is only in the most
,
demanding circumstances that you will need to create supplementary
session components. Applications that would require supplemental session components include those that create concurrent queries against
the same dataset and multithreaded database applications.
Key \Voperties
The default properties of the automatically generated Session component are rarely, if ever, changed. The properties detailed below and
listed in Figure 5.15 are required when you create a new session at
design time or dynamically at run time.

Active

False

This property specifies whether or not

the

session is active.
AutoSessionName

False

Controls whether or not a unique

session

name is generated when additional sessions are instantiated.


KeepConnection

True

Creates a persistent connection

even if the

dataset is closed.
Name
NetFileDir

Session I

The internal name of this component.


The directory that contains the BDE file
PDOXUSRS.NET

network

control

file.

I:i
i
.,tq
.t~-s
.{
:
.;
:

.&

144 n

Part II-The Delphi Database Tools

The directory in which temporary files

PrivateDir

associated with the session will be


SessionName

stored.

A unique identifier for this session as

it is

known to the application.


SQLHourGlass

This determines whether or not the

True

mouse pointer changes to the SQL


hourglass during operations.
1 Tae

Fieure 5. I5 T h e TSession

This DroDertv

combonent

is for the develoDers use.

DroDerties

Auto

The Boolean property AutoSessionName determines whether or not a


:$
.

.
.$
..*
unique session name is automatically generated. This property is
.ii
designed to ensure that multithreaded applications always have unique 3;
session names regardless of r-he number of sessions created. When this .:!
$
property is True, SessionName cannot be set. Also, a Session compo..j.
nent cannot be added to a form or data module that already contains a
Session component that has the AutoSessionName property set to True::.+!!
NetFileDir

The NetFileDir property specifies the location of the Paradox network


control file PDOXUSRS.NET. Applications that work with Paradox files
use this file to manage file sharing. All of the clients that share the
same database must use the same network control file.
PrivateDir

Similar to the previous property PrivateDir specifies the directory in


which the BDE will create its temporary files.
Tip

aif

The performance of an application can often be measurably


improved if installed on a network by specifying a local subdirectory as the location for the PrivateDir value. This allows the
temporary files to be created on the fast local driver rather than
having to travel the network to the server.

TSession Events
The Session component exposes two events, OnPassword and
OnStartup. The OnPassword event is triggered when an attempt is
made by the application to open a Paradox table and the BDE reports

.1,,

Chapter S-Data Access with Delphi

insufficient access rights. The event handler for this event must
to this anomaly. OnStartup is triggered when a session is first a
Any actions that need to occur during this process can be executed
through the handler for this event.

BatchMove Component (TBatchMove)


Moving quantities of data records from one table to another is a cornmon operation and Delphi offers two ways of performing this task. The -:,
first is to use the Table components BatchMove method. This method h-3
an import process in which the calling table seeks to add records from ,:i:
..;:
&@, A
another source. The method takes two parameters: the name of the
source table from which the rows will be extracted and the mode. The --:z
use of the BatchMove method is limited, however, in comparison to the $
functionality offered by the component that encapsulates all of Delphis .!?
data movement capabilities, the BatchMove component.
The BatchMove component expands on that methods capabilities.
Shared operations include:
H Copying rows from one table to another
w Updating rows in a target that match those in a source table.
w Appending rows to a target table from the source table.
n Deleting rows in the target that match those in the source.
In addition, the BatchMove component offers the ability to dynamically
create a target table for the rows to be moved to. Data types can be
explicitly mapped from the source to target, and exception handling is
improved with the ability to define key violation and problem tables
and the return of operational counts.

,$
f?hj
,i9
4

+operties
There are a number of new properties introduced with this component.
The comolete list of oublished properties is shown in Figure 5.16.
..;

AbortOnKeyViol

True

When True, the batch move

operation is

cancelled immediately when a key


violation or integrity error is
encountered. If False, the exception
records are written to an alternate

table.

:
,: 2
. ;:

.?

I46 H Part II-The De/phi Database Tools

AbortOnProblem

True

When True, the batch move

operation is

cancelled immediately when data


requires truncation to fit into

the target

table. If False, the data is truncated and

written

the original problem rows are


to an alternate

table.

This specifies the name of the table to

ChangedTableName

which copies of all changed rows are


written during the batch move
ooeration.
The count of rows that are moved

CommitCount

before a

commit

ooeration occurs.

Destination

T h e name of the destination table.

KeyViolTableName

T h e name of the table to which key


violation rows will be written.
This property

Mappings

contains

the

column-to-column mapping

instructions

between the source and target tables.


Mode

batAppend

Determines the operation that occurs


when a batch operation is executed.

Name

Batchplove I

The internal name of this component.


The name of the table to which

ProblemTableName

problem

rows will be written.

Determines

RecordCount

the

maximum

number

of

records that are applied to the


destination table when the batch
operation

The name of the source table for the

Source

batch

operation.

This property

Tag
Transliterate

is executed.

True

IS

for the developers use.

Determines if character translation is

to

occur during the batch operation.

Figure 5. I6 The TBatchMove component properties

AbortOnKeyViol

This Boolean property determines what actions are taken when a key
violation or integrity exception occurs. A key violation occurs when a
row is inserted or appended or so modified that an integrity error is
generated against the primary key. Recall that the highest order rule of
the primary key is that it cannot be duplicated. An attempt to break this
rule generates a key violation.

_j .~.

___

.i_

Chapter

;-ricrii-ir-.-i-,(-.

S-Dota
.--

Access

with Delphi n 147


I . ft./amP

Referential integrity exceptions are similar in nature. If an attempt to


delete or modify a key value that is integral to a relationship between
two tables such that it destroys the relationship, an integrity exception
will be raised. If the AbortOnKeyViol property value is set to True,
immediate cancellation of the batch operation will result.

.,l

.8:;(
_,.,
.,.,<t-;s
+*
$
i-g,;

The property can be set to False, giving the application an opportunity


to gracefully handle such problems. The rows that were the cause of
the exception are written to an alternate table, defined in the
KeyViolTableName property. This is a table that will be dynamically created to contain all of the exception records. The application can expose F$
.:
these records to the user for correction or deletion. The property
KeyViolCount contains the count of records in this table.
AbortOnProblem

The Boolean value in this property determines whether or not the batch
operation is cancelled immediately when a data type problem is
encountered. A type exception is a condition in which data from the
source table must be truncated in order to successfully fit in the target
column it is mapped to. When the AbortOnProblem property is True,
the batch operation is cancelled immediately. Set the property to False
to have the problem fields truncated to fit into the target columns.
The ProblemTableName property contains the name of a table to which
copies of the original, non-truncated records are copied when a problem is encountered.
ChangedTableName

The ChangedTableName property contains the name of a local Paradox


table used to hold copies of modified records. This dynamically created
table saves the original data rows from a table that has been modified.
The table will hold records that were either updated or deleted, giving
rudimentary rollback ability to local tables that do not support the
function.
Moppings

Implicit through its name, the Mappings property contains the column
mapping instructions from the source table to the target table. The
BatchMove default action is to match columns based upon their position, column zero going to column zero, etc. The Mappings property
overrides this default.

;j
.:
;1
2-%I

j
t

:<
.;
;$
* -i
d
.: f,
~-;
__fb$

3
.?f

148

H P a r t II-The Delphi Database Tools

The string list used to define the mapping cannot be partially done or
unexpected results may occur. To define a mapping from a column to a
same-named column in the target table, use the column name alone. To
map from two dissimilar named columns, use a complete equation in
the form Target-Column-Name = Source-Column-Name. Columns that are not defined in the mapping will be Null filled,
necessitating the complete mapping definition in most cases.

.I
,.u

.::$
,i.i
:.J
,;.;
::li

Mod e
The BatchMove operations consist of five defined modes. The Mode
property contains the selected mode of operation. The modes are as
follows:
batAppend-This is the default mode. Rows from the source table
will be appended to an existing target table.

:;J

.I ..+*
:3z$-2
z*(. .rg

-.i

-~, :;
-,:,
_:
;I

batupdate-Rows in the existing target table will be replaced by


matching records from the source table. An index must exist that aliow~c;~xq
the matching to occur.
batAppendUpdate-Rows
in the existing target table will be
replaced by matching records in the source table. If no match is found _
in the target table for a source record, the source record is appended w _
the target table.

batcopy-When this mode is selected, a new target is created based


on the structure of the source table. If a similarly named target exists, it
will be deleted and replaced by the new table.

batDelete-This mode deletes rows in the target table that match


those in the source table.
The mode selected also affects the actions determined by the
AbortOnKeyViol and AbortOnProblem properties and may be a consideration in your choice of modes.

Putting

the BatchMove

Component

to

.- :g_

Work

The BatchMove method and component are enormously useful addit-ions to your programming repertoire. For all of the additional
functionality that the component offers, it is relatively simple to imple- : $
ment. The Copy Machine project that is built in the following steps will :?
experiment with the functions in two segments. The first will simply
:
demonstrate the component and its copy mode. After this has been
.
<$j$3
done, we will work with some of the error handling abilities of
. 3.63
BatchMove.
;iii

.j

,_

Chupter S-Date Access with Delphi H 149

I. Start a new project and place two DataSource, Table, and Grid components on the form similar to the form shown in Figure 5.17. Add a
BatchMove component, a BitBtn, and a Button to round out the form.

g4, .
T, k :

2. Set the BitBtn components Kind property to bkclose.


3. Point DBGridl to the DataSource control and point the DataSource
DataSet property to Tablel, completing the troika. For the Table component, set the DatabaseName property to DBDEMOS and the
TableName property to COUNTRY.DB,
4. Point DBGrid2 to DataSource and set the DataSources DataSet property to Table2. The Table2 components DatabaseName property will
also be set to DBDEMOS. Since we are going to creare a new table by
the batch operation, the name that we will provide for the TableName
property will not be selected from the list. Add the name COPIES.DB
to the property. Remember that this table will be a persistent structure, remaining on disk after the project is closed.
5. Selecr the BatchMove component. Set the Source property to
Tablel. This will be the table from which the rows are going to be
copied. The destination for records involved in the barch operation
will be defined in the Destination property. In this example, well set
this value to Table2. Set the Mode property to batCopy
6. The BatchMove component is not activated. Instead, it is executed
using the Execute method of the component. Since the user of the
application usually initiates this process, we will duplicate this
through the buttons OnClick event handler. Enter the following code
for the handler to perform the two-part process. First, it executes the

150 n Pm-t

IL-The Delphi Database Tools

BatchMove
grid.

process and then opens the new table to display it in the

procedure
TForml.ButtonClick(Sender:
begin
BatchMovel.Execute;
Table2.Active
:= True;
end;

7.

-.:
,.$I
..

TObject);

Compile and execute the program. Click once on the Copy button to
copy all of the rows from COUNTRY.DB to COPIES.DB.

-2
3
,:

The BatchMove component has error handling capabilities built in that


the BatchMove method does not, making it much more attractive for
;\-z;
use. In the next set of steps we are going to modify the copier project t-6:$
L;.-G
raise an exception and let the application deal with it. Using the
COPIES.DB table just built, we are going to force key violations to
I
occur by appending the same set of records to the table. Since the
.
Name column is the primary key for the COUNTRY table, each time a
similar value is tried, a key violation error will occur. A table is de
to hold them, KEYVIOLS.DB, and this table is displayed for the use
review. In a production application, you could allow the user to mo&
these records and you could repeat the batch process to cleanly hand
,>- .
the exception situation.
.
I. Add a Label component to the right of the Copy button.
2.

3.

Select the BatchMove component. Change the value of the


AbortOnKeyViol property to False. Enter the name KEYVIOLS.DB to
the KeyViolTableName
property. Finally, change the Mode property to
batAppend.
Modify the Copy buttons OnClick event handler as follows:
procedure
TForml.ButtonlClick(Sender:
TObject);
begin
TableZ.Close;
Table2.AddIndex('NameIdx',
'Name', [ixprimary]);
BatchMovel.Execute;
Table2.Active
:= True;
if BatchMovel.KeyViolCount <> 0 then '
begin
MessageDlg('There
were Key violat ions in the move.',
mtError, LmbOkl , 0) ;
Labell.Caption := Concat(
InstToStr(BatchMovel.KeyViolCount
),

?9
.k

Chapter S--Data Access with Delphi n

1 sf

'Key violations');
Table2.Close;
:= BatchMovel.KeyViolTableName;
TableZ.TableName
TableZ.Open;
DBGrid2.Color := clAqua;
end;
end;
4.

Compile and execute the application. When the key violations occur,
you will be notified by a message dialog. The key violations table will
be displayed in the second grid, changing its background color to
announce the change.

.
I .,."
-. 'c
"i

+,
$J
$$y
3$3

IdaLteSQL Component (TUpdateSQL)


We discussed cached updates earlier in the context of the
CachedUpdates property published by the dataset components. When
cached updates are enabled, data is retrieved from a local or remote
table and stored in local memory All operations that occur against the
data work against this local dataset. When the modifications are written to the remote dataset, they are written as a large block rather than
individually. This strategy serves to reduce transaction times and minimize network traffic through the proximity of the local data.

-a
&.:;
:
1
:a
Jj

There are also caveats involved in the use of cached data that the
developer needs to be aware of when making the design decision to
implement this strategy First, it must be remembered that since the
li ,;$,G
user is working on a dataset that is local and viewed only by them,
changes that they are making to their dataset are not seen by other
.I
concurrent users until they are committed. This issue also appears in
.
reverse; the local dataset is not refreshed when other users update the
$
remote source dataset, thereby rendering the local dataset obsolete.
Additional consideration must be given to the committing of modifica- ::i
11.a*
tions in parent-child relationships due to referential integrity
q?$$
constraints.
$j: :Cached updates are enabled through the aforementioned property
,,
being set to True. When cached updates are enabled, a read-only query ,_,.
gathers enough data as needed to display and places these records into
local memory. The local dataset is updated by periodic fetches from
-:~
the remote dataset as needed by the user scrolling through the data. All ,$
updates are posted to the cached data until complete and then the
-:
entire cache of modifications is written back to the remote dataset in a
.:
$*
single transaction.
i

in a read-only dataset that can be modified on a local level but cannot


be easily written back to the original relations. To perform this operation you must use an update object, the UpdateSQL component. As you
can see from reviewing the property list in Figure 5.18, this is a simple
component that belies its power.

Figure 5. I8 The TUpdateSQL component properties

UpdateSQL uses SQL statements to update the remote datasets. Its


usage requires one instance of the control for each table involved in the
original
transaction.
The UpdateSQL component encapsulates three Query controls, one
each for the purposes of insert, update, and delete operations. Each
associated property contains the SQL statement used to process the
appropriate query against the remote dataset. The SQL statements
allowed through the UpdateSQL control support extended parameter
binding and syntax. These properties are string lists and can be modified at design time or run time. To determine which of the three
properties to use in the update process, the control relies on the
UpdateKind parameter that is automatically generated for each records
update.
UpdateSQL and the process of updating cached data are very particular.
Only the type of SQL statement intimated by the property name should
be entered into that property, i.e., only a DELETE statement should be
entered for the DeleteSQL property. Also, all of the statements in the
properties for an instance of UpdateSQL must reference the same table,
as the control is associated on a one-to-one basis with a DataSet object.
Building the statements for each of the properties is simplified through
the tools that Delphi offers. As an example of this process, start a new
application in Delphi and add a Query and an UpdateSQL component
to the form. Select the Query component and set the DatabaseName to

w S-Data Access with Delphi II ! 53


_

IBLocal

(be sure that the LIBS is running) and the CachedUpdates


value to True. Set the SQL property to:
SELECT * FROM EMPLOYEE

ullll 1_11+ .,Yu-.-vbject property to UpdateSQLl. This last property


links the dataset object to the specific update object.
Right-click on the UpdateSQL control to invoke the UpdateSQL Editor.
This tool builds the SQL statements for the properties encapsulated by
the control. The editor, as shown in Figure 5.19, will have filled in the
default values as determined by the associations made earlier.

Figure 5. I
The

UpdateSQI
Editor

The options default on the side of including all fields and indexes tvhen
invoked but you can pare them down to the desired selections. Click on
the Generate SQL button to automate the creation of the SQL that will
be used for the update process. This statement may not be complete
but provides a good framework to start from. Clicking on OK saves the
SQL statement to the property rhat was selected, the default sratement
going to the ModifySQL property This process is repeated for each of
the types of operations that will be handled. The ensuing code is executed during the OnRecordUpdate event.

The NestedTable Component (TNestedTable)


The records in a nested detail set are represented in Delphi by nested
datasets accessed by the TNestedTable component. This type of data is
found in Oracle 8 relations only and cannot be created through Delphi.
The fields NestedDataSet property contains a reference to the nested
dataset. BDE functionality is extended to the access and manipulation

154 W Port l/-The Delphi Database Tools


.;*.
11/
. il _,

)I

Ila..m*~*\

of the nested table data. The nested table then has most of the functionality of a single table component recognizing the nested nature of
the data.

;$fJ
a3
;,-

Figure 5.20 details the properties of this control.

etermines

Figure 5.20 The TNestedToble

component properties

Putting this control to work requires an in-depth knowledge of the Oracle data structure that you are addressing. The DataSetField property of
the component is the determinant of the field that contains the nested
data. A DataSource component is directed at the NestedTable control to
pass the result set to the display controls of the application.

Summary
This chapter has covered all of the components that are available to the
developer on the Data Access tab of the VCL. These controls serve the
purpose of connecting to the datasets in various ways, from straight
table display with the Table component to complex multiple join result
sets from the Query control. A minimum of one of these controls, and
more likely two or more, is necessary in every database application
developed using Delphi and the Borland Database Engine.

Looking Forward
The next chapter is going to provide the same coverage to the data controls. These components provide the visual interface components that
the user sees and interacts with in using the database application.

, ( -**3

.$3

2
Y
-?$
.q
25

Included in This Chapter:


n
n

Common Properties n The DBGrid Component


The DBNavigator Component n The DBImage Component n The DBMemo Component n The DBText
Component n The DBEdit Component n The
DBCheckBox
Component n The DBRichEdit Component
n The DBListBox Component n The DBComboBox Component n The DBLookupListBox and DBLookupComboBox Components n The DBRadioGroup Component
n The DBCtrlGrid Component

elphis data-aware components are the other half of the RDAD picture introduced in Chapter 5 where we put the data access components
to work. The data-aware controls are TControl descendants that have
been modified to display and manipulate the contents of associated
-
dataset fields. Delphi includes a wide variety of tools to display every- !
thing from a single row of data that represents an individual record to
DBGrid control.
Whatever your need, Delphi includes a control on the Data Controls tab
to support every kind of data accessible through the Borland Database
Engine. Different from the components on the Data Access tab which
perform their tasks in the background never seen by the applications
users, the data-aware components are the interface building blocks for
the program. Each of the controls is a visual component, and the range
of properties, events, and methods is vastly expanded because of this.
.I

I56

.F_ ..

W Part II-The Delphi Database Tools

A.__

You should find working with the data-aware components to build a


functional database program a relatively familiar exercise. The controls
essentially mirror their non-database counterparts in functionality, differing only in the source of their values. As we saw in the previous
chapter, each control will be associated with a dataset object; this association defines its source of data through the interface of a DataSource
control.

Common Properties
Before diving into an examination of the individual components, the
common elements of all of the data-aware controls will receive our
attention. The table in Figure 6.1 summarizes the common published
properties that carry the same contextual meaning between all of the
components. The majority of these are familiar to experienced Delphi
developers because they have the same usage and purpose when used
with the non-data interface controls. Additionall!; there is a small subset of properties that is unique to the data-aware versions of these
controls.

This TControl setting determines how a


control lines up to Its container or parent
control.
Anchors

__t

Determnes
parent

IS

anchored to Its
Top, Bottom.

Left. Rreht

-4
BiDrMode

how a control

control Four- options

bdLeftToRrgh:

This property speclfles

the BiDlrectional

mode for the contr-ol, whrch


r-eadrng

determines the

order for text and the placement of

the scroll bar


Ctl3D

-t

True

If True, the assoclared

controt WIII have a

beveled, three-drmenslonal

appearance

When False, the controt will have a fiat

Cursor

crDefault

appearance See ParentCtl3D


__--Determines wtllch cursor Image

IS

drsplayed

when It passes over this control.


DataFIeld

This property assocrates


single
the

DataSource

the control with a

field fron the dataset connected to


DataSource

component.

This connects the data-aware controls to a


specified dataset object.

:hap lter 6-l

bhis Data-Aware Components I

m
DragCursor

crDrag

Determines which cursor image is displa) .~~~


when the control is being dragged.

DragKind

DkDrag

This specifies the reason for the control


docking or normal draggim.2.:

DragMode

DmManual

Determines the drag and drop behavior OF

Enabled

True
This set of properties controls the attribut&.j
of text displayed by the control.

Font

1The disolav heipht of the comoonent.

Height
HelpContext

This value is utilized to determine what


page in online help is displayed when the
applications user presses the F I key.

Hint

The text of the fly-over hint message that 6


displayed for this control.
c$

ImeMode

ImDontCare

This property determines the behavior of ::


the input Method Editor, a conversion tool

ImeName
language

characters.

The default internal name for this


component.

Name
ParentBiDiMode

True

Determines whether or not this control


uses its parents BiDiMode setting.

ParentColor

False

Determines whether or not the component-


will use the same color properties as its
parent.

ParentCtl3D

True

This property determines whether or not


the control will use its parents Ctl3D
property setting.

ParentFont

True

Determines where a control will look for its -.:z


;
font information.
I Jf

ParentShowHint

True

When True, the control will use the


ShowHint property setting of its parent. If
False, the controls behavior is determined
by its own setting.

PopupMenu

Specifies a context menu to associate with


this control.

. .!

ReadOnly

False

ShowHint

False

TabOrder
Tag
Visible
Width

This property determines whether or no-*%


I:,~~
the user can modify the data contents of the
5:p
control.
_ -,,a;
Determines whether or not flv-over helo is
%
A numeric value specifying the controls tab
position in its parents TabOrder list.
This property is for the developers use.

True

This property determines


visibilitv at run time.

the

components
1

.:;
,.j

This specifies the components width for


display purposes.

Figure 6. I Common Properties among the data-aware components

Key Properties
The key properties shared among the data-aware controls are those
that define the database interface and manipulative abilities of the
component. Without these properties, the components would have abil- :.J$
ities on the same level as the controls from which thev are subclassed.

The value in the DataField property specifies the field in the dataset
from which the control derives and to which it sends its data. The
TField objects associated with the dataset determined through the
DataSource property determine the possible values accepted by the
component. The property may refer to a field from the table referenced
or to a persistent lookup or calculated field defined through the Fields
Editor.
DataSource

The DataSource property binds the data-aware control to the specified


dataset object. The DataSource component handles all of the navigation and data access requirements of the database, leaving the interface
components free to concentrate on displaying and manipulating the

This property enables or blocks the users ability to modify the underlying dataset. When ReadOnly is set to True, the data-aware controls will

Chapter 6-Delphis

i,.

Data-Aware Components
^I e-*__ls_18/

I!$@
.q

2-*

display the data from the dataset but will not allow it to enter the Edit z
state.

The DBGrid Component

4
The DBGrid component is a workhorse among the data-aware controls. ;
Using a DBGrid, your application can display or manipulate a dataset in
a familiar table-like form. The data is displayed and can be edited in an 1:
easily discernable rows-and-columns format. The columns represent the
field definitions from the dataset with each row representing an indi:
vidual record.

Without any modification to its properties, the appearance of data in


the DBGrid component is largely defined by the properties of the fields
in the underlying dataset. By default, the headings for the columns and
the order of their presentation matches those of the table. Changes to
the properties in the dataset are immediately propagated to the grid.
The grid does not display Field objects in the dataset whose Visible
property is set to False.

.
I,

-:
4
:4i
The columns in the grid are dynamic and can be added, deleted, and
1
rearranged at design or run time. The fact that must always be remem- j
bered is that the controlling properties for these columns do not belong 1-i
to the grid itself but rather to the field objects contained in the dataset
*
iQ
that the grid is composed of. Dynamic grid columns, for example, exist --::?J
only so long as the field upon which they are based exists. When the
I.$/:
dataset is closed, the column is destroyed.
i
Because the field objects determine the makeup of the DBGrid, the con- . .,$
struction of the grid enjoys a great measure of flexibility. The same
display grid in an application can display a local database table at one
:;
moment, then be completely redrawn seconds later to display the
g
results of a query. The new dataset and its properties will immediately
,.jreplace all of the properties of the former table. This change is as simple as pointing the DBGrid to a different DataSource control.
.:*,-.:.($.
Key Properties
There are a number of properties that control the display elements of
the DBGrid component, some of which will make their only appearance
in this context.

:i
r
-:>

The Columns property is an indexed collection of TColumn objects,


.1X9
This property is used to view or set the display attributes or field bind- ,j
ings that make up the column collection. The columns can be modifid.
5i
at design time by using the Columns editor or through the application .:3
at run time. This object is of type TDBGridColumns, a container to h& :$
the collection of TColumns. DBGridColumns can return the number of .j$
columns, or index numbers, for the collection. Each column has its ow$,;i$ 1
set of properties, listed in the table in Figure 6.2.
<a$&

m
Alignment

taLeftJustify

ButtonStyle

cbsAuto

e value of this property det


and how a value can be selected

Color

if

clWlndow

the number of ,
if the
column is connected to a lookup table or
has values in its PickList
property. Action

This property determines

DropDownRows

rows displayed in a drop-down list

property.
Expanded

False

FieldName

Font

ImeMode

imDontCare

This property specifies the Input

Specifies the name of the input

ImeName

Method

Method

Editor.
A string list that determines the

PickList

values that

can be selected for the column.


This property specifies the

PopupMenu

popup me

associated with this column.


ReadOnly

False

Determines whether or not the data

in the

column can be edited.


Title

Specifies a TColumnTitle object


contains the attributes of the title
column.

that
for the

.._. I. _jI

,,. _ _

-i

Visible

Chapter 6--Delphis
_
.*,,

Data-Aware Components W 16
(weeL_(_ -

Determines whether or not the

True

column is!-;

visible in the grid.


1W i d t h

column.

Specifies the display width of the

Figure 6.2 TColumn properties accessible through the Columns editor


Options

The Options property is a set of values that specify a wide range of


behavioral and display attributes for the DBGrid control. The set can bc
made up of different combinations of the elements shown in Figure 6.2
Each of the options is a Boolean value that turns on or off the various
grid options.

dgEditing

The grid can be used for editing.

dgAlwaysShowEditor

The grid is always prepared for editing. Must


in coniunction

dgTitles

be used

with the deEditine settine.

If this option is included, titles are displayed

at the top

of the column.
dglndicator

This option causes a pointer to appear in the first


column to indicate the current record.

dgColumnResize

This allows columns to be resized or moved.

dgColLins

This option causes lines to be drawn between the


columns of the grid.

dgRowLines

This option causes lines to be drawn between the


rows of the grid.

dgTabs

This enables the Tab and Shift+Tab keys for


navigation in the nrid

dgRowSelect

This allows the entire row to be selected in addition


to individual cells. If this option is included, dgEditing
and deAlwavsShowEditor

dgAlwaysShowSelection

are @ored.

A focus rectangle is displayed on the current cell even


w h e n the focus has transferred to another

dgConfirmDelete

control.

This option causes confirmation of a deletion in the


et-id.

dgCancelOnExit

This causes inserted records to which no


modifications were made to be discarded when the
user exits the et-id.

dgMultiSelect

This option allows multiple rows to be selected at


one time.

Figure 6.3 Options values for the DBGrid component

I62 n

Part II-The

Delphi Database Tools

DBGrid Events

The events exposed by the DBGrid focus on the movement of the cursof -:
within the grid and the presentation of the grid by the application.
-$
: .s$,;
~>
OnCeKlick
This event fires when the mouse click is released in a cell of the grid.

-$
.-i
..
T>

OnColEnter

The OnColEnter event fires when the focus shifts into a new cell in the .:.I
grid including navigation by keyboard or mouse click. OnColExit events .*:;
fire when the focus is in the process of leaving the cell.
OnColumnMoved

This event fires when dragging with the mouse moves a column.
OnColumnMoved occurs just after the column has been moved.

OnDrawColumnCell

This event is provided to give the application an opportunity to repaint


a cell.
OnEdit6uttonC/ick

The OnEditButtonClick
event fires when the ellipses button on a
drop-down or pick list column is pressed.
OnTitleClick

This event is fired when the user clicks on the title of a column.
We will put the DBGrid component to work in a moment after we discuss another control that is often found in close proximity, the
DBNavigator. We have seen simple usage examples in the previous
chapters, so we will attempt something a little more ambitious with
DBGrid in this chapter.

The DBNavigator Component


The DBNavigator control gives the users of your application a simple
and friendly tool for navigating through the datasets used by your
application. In addition to its navigational abilities, the DBNavigator
provides a wealth of tools for manipulating the data by performing
insertions, deletions, confirmation of the changes, or cancellation of the

:h
,,i
- 2

.+

Chapter

6--Delphis

Data-Aware

Components m
--- .~*s.nm,I*~

transaction. The toolbar takes the familiar shape of a VCR button set,
giving the user visual clues as to the actions of the navigation buttons.
Using the button set the user is able to skip to the first or last record iti
a dataset, move to the next or previous record, insert new rows, de&&j
existing rows, post changes, or cancel changes. The buttons that are *:$$
visible to the user can be controlled through the manipulation of the 4
property settings. The DBNavigator component is especially useful
j
when used in conjunction with a DBGrid control to make navigation I
and editing fast and simple.
,3:*<
+operties
The majority of the properties for the DBNavigator control are listed &$
among the common properties. There are two specifically that are of :{
interest to the developer.
ConfirmDelete
The ConfirmDelete property is provided to the application developer to-2
help prevent the careless deletion of rows of data. When ConfirmDeleM
is True, a message box is displayed to the user asking for confirmation
of the pending deletion. If the user chooses the OK button, the deletion
action commences; otherwise it is cancelled. If ConfirmDelete is set to .J.-pj
False, no message box is displayed and the deletion is immediately
.i,: s1

a
2
allowed.
, .?

Tip

Leave the ConfirmDelete property set to True for all of your


applications to prevent data loss through careless actions. The
only time that users will generally resent the message box is
when they are deleting large bodies of data; for this task you can
easily provide an alternate solution.

;i
.:

VisibleButtons

This property controls which buttons are visible to the user on the
-t
DBNavigator and can be set at design time or run time. VisibleButtons
-;$
is a set of TNavigateBtn objects and removing one from the set has the ..:,.:I
effect of hiding the button. The buttons available for the VisibleButtons /,:y
set and their associated actions are:
First
Prior
Next

Go to the first record.


Go to the previous record.
Go to the next record.

I64 n Pm-t II-The Delphi Database Tools


Last
Insert
Delete
Edit
Post
Cancel
Refresh

Go to the last record.


Insert a blank record.
Delete the current record.
Place the current record in Edit mode.
Post the updates to the current data.
Cancel the transaction.
Refresh the data displayed.

:;$g
-,I,;:$<: .x
::;
1;G.
.e
!&
.qj
::*$
IQ
;:+q
pj
DBNavigator Events
. ~,1:xJ
Two events are native to the DBNavigator class, both allowing you to ;J
trap the push of one of its buttons and initiate any special processing
$)
-y.2
that you want to implement.
24
BeforeAction

The BeforeAction fires when a button on the navigator is clicked but


immediately before the default action for that particular button is
executed.
4
OnClick is fired when any type of click event occurs. This includes a .-j
mouse click, the spacebar being pressed when the navigator has focus, ij
or the BtnClick method being called. The default action for the associ- : ;~I.;
ated button will be executed unless overridden by the event handler. ii
One important item to remember when designing your application
.;.
around the DBNavigator is that the dataset object. not the DBNavigator,i$
encapsulates the datasets navigation capabilities. The navigation but- :z
,%
tons only initiate the execution of the datasets methods. If the
application needs to interrupt or manipulate these abilities, it should .-j
_&_\.b
point directly to the dataset.
Putting DBGrid and DBNavigator to Work
The project that we are going to build will be expanded throughout th
chapter, adding capabilities as appropriate to the components being dli;-.
cussed. The program is a tool for the Portland Office of Theaters and j;
Arenas which is going to be managing the Blue Sky Sporting Festival. .:.
We will utilize two tables provided by Borland in the DBDEMOS data- I :r,
:x
base in the development of the application. We are going to begin
simply, placing the tables into grids and associating the navigation
buttons.

his Duta-Awure

Components I 165
,,

The two tables used throughout the program are going to be linl red L1.in -=
manner that was not in Borlands original design. We want to set up a
master-detail relationship with the VENUESDB table being the master.
In order to accomplish this, a new index needs to be added to the
EVENTS.DB table.
I. Start the Database Desktop application and select Tools 1 Utilities (
Restructure from the menu. At the bottom of the Select File dialog,
set the Alias to DBDEMOS. Select the EVENTS table from the files
listed by double-clicking on the filename.
2. The Restructure Paradox dialog will display the schema for the
EVENTS table. In the Table Properties drop-down box, select Secondary Indexes, You will find that one index, Date-Time, has already
been created for the table. Click on the Define button.
3. The Define Secondary Index dialog will be displayed as shown in Figure 6.4. Select the VenueNo field and click on the Add arrow button.

The field name will be displayed in the Indexed Fields list and none of
the options at the bottom of the dialog will be modified. It is very
important that the Maintained check box remain checked. Maintained
indexes are kept up to date automatically by the BDE and should be
your standard choice unless the overhead requirements are too severe.
Click on OK to define the index. You will be asked for a name for the
new index; name it VenueIdx.
4. Click Save on the Restructure dialog to save your changes to the file.
With the new index defined, the tables have been prepared for use in
the sample application. Thinking about the design of the program and

Pa

II-The Delphi Database Tools

other typical Delphi database applications leads to the need to discuss Se?4
an important tool that falls outside of either of the VCL component
I:$
tabs, the Data Module. The Data Module is a non-visual container fom :.g
that serves one very specific purpose: It provides a common access
i:
point to the dataset objects from all of the forms in a project. The alter- si
native to using the Data Module approach would be to place multiple ,~;,lIh,r.Sti
sets of dataset objects on each of the forms of an application. All of the A:, *
objects would required individual configuration settings, and the appli- .c$
cation would be required to provide the code necessary to maintain the ~~5
relationships, record pointers, etc., in the transference between forms. .-:,;
>:,t -:
The Data Module has uses beyond simply sharing database tables. Any Gi
g,C.. ;
Delphi object, with the exception of those that descend from TControl, ::l
can be placed on a data module and become available to all of the
$3
forms in the program. This carte blanche should be used wisely with
:$
consideration for the actions of the components themselves prior to .) :
placing such things as your main menu in the module.
/ -ji
Adding a data module to an application is done through menu selections. Select File 1New and pick the Data Module from the New Q&
the repository A small container form is added to the application with :
three properties:
n Name
H OldCreateOrder
,.
n Tag
>
OldCreateOrder is the only unfamiliar property surfaced. Its purpose is ,-,
to control the firing of the modules OnCreate and OnDestroy events for .$:i
backward compatibility.
i
3%
For all of its benefits, there are also times in which it is not appropriate, .j
to use the data module. The first consideration is a situation that does ,z$
not require the sharing of components between forms. The overhead ot,:$
adding the module is wasted in this situation; place the dataset objects Y.7
directly on the form that needs them. Also place the datasets directly
1:
when a form has a completely unique view of the data not used in any
other place in the application such as report generation tables and special views.
tab

The Venue Manager will use a data module so that the datasets that
are going to work with can be set up once and shared among all oft
forms used in the demonstration. Clear naming rules for the dataset .
objects become much more important in this type of situation because-:
of the new distance between the datasets and their interface controls, 2::
,.s

I.

Chapter

6-Delphis

Data-Aware

Components

__

n I&%

..v.-&
:.

-iStart a new Delphi application and add a Data Module to the project. i :$$
Place a Table and DataSource component on the data module form. A!_. . 1
Name the Table control VENUE-TBL and set the DatabaseName prop- :i$
.-p$
erty to DBDEMOS. The TableName property should be set to
yqd
i 4
VENUES.DB. Activate the table by setting the Active property to
True. Save the project; the form unit will be named VenMgrU.pas,
.~f
the data module unit will be dModU.pas, and the project will be
.t
VenMgr.dpr.

2. Select the DataSource component and set the DataSet property to


1.
VENUE-TBL. Name the DataSource VENUE-SRC. To make the con-

I_ $4gs
tents of the data module visible to the other forms in an application
you must create an association between the objects. Select Form1
-I!#
and then File 1Use Unit from the menu. Choose the dModU unit and g$
click OK. Alternately, you could simply type the Uses declaration in the
Implementation section of your forms unit.
3. Add a DBNavigator component to the form. If you want to position
this control so that the resizing of the form will not affect it, use the
.
Align property to lock the position. Select alTop as the value for the
property; the navigator will move to the top of the form and size itself
to match the client area. To associate the control with a dataset object,
set the DataSource property to DataModulel.VENUE-SRC. To
familiarize your users with each buttons function, set the ShowHint
property to True. This will provide fly-over hints to the user as they
_
move their mouse across the navigator bar.
4. Now add a DBGrid control to the form. This component also offers an ,>..g2
-,,j
alignment property; we will set the Align property for the grid to
.$j
:
alTop as well. It will fit itself in just below the navigator and will
receive the same benefits of using Align as discussed for the navigator.:
Point the DataSource property to DataModulel.VENUE-SRC
to
make the table connection.
Execute the application and use the navigator to move around in the
table. Note that the movements controlled by the navigator are row
and table centered. Movement between the fields and columns is
driven by the cursor control keys.
Before adding the detail end of the relationship there are a couple of
items that need attention on the Venues grid. The last two columns in
the VENUES.DB table are of type BLOB and MEMO, data types that do
not lend themselves to display in a DBGrid. For the time being, we will
hide these columns to give the table a neater appearance. Additionally,
we will unlink the table from the top of the form and adjust its width to
match the displayed columns.

;:i
i.
;
;:
-1
,4
.wa

II-The

De/phi Database Tools

Select DBGridl and set the Align property to alNone. This will let
you move the table down a bit from the DBNavigator bar.
Right-click on the DBGrid and invoke the Columns Editor from the
.:j
context menu. The Columns Editor is similar to the Fields editor that
:
we have worked with previously. The first action that needs to occur is .i.lj
to add the columns that we want to manipulate to the list. Click on the -i
Add All Fields button to perform this transfer. You will notice that the
underlying properties of the columns are displayed in the Object
Inspector. Select the Remarks column and ser its Visible property to
False. The column will disappear from the DBGrid but remain unaffected within the dataset. Do the same thing for the Venue-Map
column.
3. Before making the modifications suggested in the next step, the cre;
ation order of the forms should be reviewed. Select Project 1Optiom
from the menu. As it stands, Form1 will be created before
DataModulel. Since we are going to make use of the forms OnCreate: :
handler and it requires knowledge of the internals of the datasets, w&j
need to have the data module created first. To move DataModule up i L
in the order, select it and drag the list entry above Form1 and release:?
ygj
it. Select OK to save the changes.
4 . After hiding the columns, the table is left in a rather odd-looking grid
:+.cs;j*:_
with excessive white space on the right-hand side. If we add a little
code to the project, the DBGrid component can size itself to accommW.i.I,. : 1..
date the columns that are displayed, regardless of changes to that
:$I
.$
number. Add the following code to the forms OnCreate handler:
.:j
procedure TForml.FormCreate(Sender:
TObject);
var
tablewidth : integer;
: integer;
begin
tablewidth := 0;
with OBGridl do
begin
for x := 0 to (Columns.Count - 1) do
begin
tablewidth := tablewidth + Columns[x].Width;
end;
(Add column widths + scroll bar width}
ClientWidth := tablewidth + 18;
end;
end;

Chapter 6--Delphis

Data-Aware Components I 169


. . _/

One note of caution before adding this code to a production application; this code would have to be highly modified if there were a risk
that the ClientWidth of the DBGrid component would be greater than
the ClientWidth property of the form.
The next step in the project is to add the detail table to the form. Setting the relationship between the two tables will be done in the same
fashion that has been used in previous projects. When the new table
has been added, some additional programming will enable the
DBNavigator to be shared between them.
Add a second Table and DataSource component to the data module.
Name the table EVENT-TBL and set the DatabaseName to
DBDEMOS and the TableName to EVENTS.DB. Select the
DataSource control and point the Dataset property to EVENT-TBL.
Name the DataSource EVENT-SRC.
2. Add another DBGrid to the form and set the DataSource property to
DataModulel.EVENT-SRC. Similar to the Venues table, this grid
contains two rows that will not display well. Using the Columns Editor, change the Visible property for the Event-Description and
Event-Photo columns to False. You may also want to apply the grid
sizing procedure to this table as well if appearances are important.
3. Establish the relationship between the two tables with VENUE-TBL
being the master table in the connection. We are seeking to display al .l
of the events assigned to each venue. The new index that was established a few pages back is the one that will be used to establish the
relationship.
4. The last modification we will make to this project will share the navigator bar between the two tables, rather than adding another
DBNavigator to the form. Rather than coding separate event handler
procedures to implement this, we are going to write a single procedure and then call it when the OnEnter event of the DBGrids is fired.
Add the following procedure to the VenMgrU unit:
I.

procedure
TForml.ChangeDataSource(Sender:
TObject);
in
beg
i f Sender = DBGridl then
DBNavigatorl.DataSource
:=
DBGridl.DataSource
e lse
DBNavigatorl.DataSource
:= DBGridZ.DataSource;
end

Dont neglect to add the procedure declaration to TFormls type


declarations.

70

II Part II-7-k? Delphi Database 7-00/s


,., _.
,,.
I ._

5, Select either of the Grid components and click on the OnEnter event in
the Object Inspector. Select the ChangeDataSource procedure for this
event. Be sure to add this to the other grid as well.

I
i
1

The running application should appear similar to that shown in Figure


6.5.

*: __
__\,\,

Y a:? ,iud:lcrum
1400

:\
,:,

E Gutlltrma Fald

Figure
i

6.5

The Venue

Manager

For each venue selected, the Events grid should only display the events
scheduled for that venue. Selecting either of the grids will transfer the
focus of the navigator to that table. You can use rhose control buttons
to move around freely and manipulate the data regardless of the table
chosen.
Tip
L
/

The Data Module by default will be an auto-created form. If the


dataset objects are also automatically opened when the data
module is created, you should do a performance assessment to
judge whether or not this is the best approach. Opening all of the
datasets in the data module could severely slow the startup process of your application, leaving your users with negative
impressions.

The DBImage Component


i
t

Its a graphical world out there, and Delphis darabase supporr for a
graphics object is second to none. Nearly all database products supported by the BDE support the inclusion of bitmapped graphics in BLOB
{Binary Large OBject) data fields within the table. (BLOBS are not

Chapter B-Delphis Data-Aware Components I 4.9

limited to being containers for graphics but this is one of their most
common uses.) If a field type is supported by the BDE you can count _, _,,
Delphi offering a component to work with this type of data. For graph-.:?;a
its, that control is the DBImage component, a data-aware version of t&&3$
TImage class.
&z
When the DBImage control is enabled, the BLOB graphics images are :t+%
;!+j
:$
captured from the dataset and then stored internally in the Windows
DIB format. Device-independent bitmaps are an internal data format -.
that is sent to the computers display adapter driver for translation into. :
a screen display. Because of the size of images and the time needed to -g
process them to and from memory, BLOB fields are cached locally wheti.
rows are retrieved from a dataset. This improves the applications per--.-,
formance when a user scrolls through the rows of a table and the
.%!
*;$$
images are retrieved from memory rather than a server.
*.
Key Pr
The properties that will receive our attention here are those that are
unique requirements of the image orientation of the control.
AutoDisplay

The value of this property determines whether or not the contents of


the graphic BLOB will be automatically displayed. When set to True,
each time the contents of the BLOB field change, such as when the user
browses from row to row in a dataset, it is automatically redrawn.
Depending on the size of the image, this continual redisplay of the
graphics could become a serious impediment to the performance of the
system.
If the performance of the application suffers when displaying the
images, the value of AutoDisplay can be set to False. With this setting in
place, the image in the DBImage is not automatically redrawn; the
name of the field is displayed in its place instead. To view the image,
the user can simply double-click on the DBImage space and the image
will be loaded.

:
a
.-i
.?
!
I.

:
=
Ti
:

Center

The amount of the image that is displayed by the DBImage control is


constrained in its default state by the physical measurements of the
.i$
control itself, and the portion of the image that is displayed may not
satisfy the users needs. In addition, an image that is much smaller than 1;
,,$
the measurements of the container may be displayed in an awkward
IZ

^
Part I/-The

Delphi Database Tools

(_ .-..- q
D
::#j

<j
;&&j&
._ s;j
Setting this property to True will center the image within the DBImage ,.z
control. When this property is False, the upper-left corner of the image /.i?
;-2i
is displayed in the upper-left corner of the DBImage container.
-i::
..hif.$-Zai
QuickDraw
f -1j-4;
When QuickDraw is False, a custom color palette is used when drawing ..$
the image into the control. This gives a very high quality picture at the : ;$
expense of processing speed. If the property is set to True, no palette is .;%
used and the image is displayed much faster. The tradeoff for process- -2%
ing speed is lower image quality
manner. The Center property controls whether or not the image is
automatically centered within the boundaries of the control.

Stretch

i
&3,.

.- :
The name of this property is misleading. If the Stretch property is True,,:<2
the BLOB image stretches or shrinks to fit within the boundaries of the .,gi
DBImage component. The DBImage component does not change size
fit to the image. This propertys actions can cause distortion of the
image because the width and height of the image are independent of 32
;:, +j
one another.
$

Our sample application will take advantage of the DBImage compo.


nents capabilities later on this chapter but now would be a good
opportunity to discuss the way that the Windows clipboard can be used,.,~$$
with the component. Images can be assigned to BLOB fields directly in !<I:;
the same manner other values are assigned to other field types, but a
nice feature of the DBImage component is that it can also receive input .I
8
from the clipboard.
,)
Figure 6.6 shows the Image Copier demo program that can be located
on the CD-ROM as Imgcop.dpr.

*g :
; s
:~

This application is designed to demonstrate how easily the clipboard


keyboard shortcuts work with the control. Ctrl-X, Ctrl-C, and Ctrl-V cut,
copy, and paste images to and from the clipboard. You may select the
image displayed and copy it to the clipboard. Use the Ctrl-V shortcut to
paste this data into the other tables Picture field. The Clipboard class
has methods that emulate these keystrokes so that you can write custom event handlers if your application needs to pursue this capability.

The DBMemo Component


The DBMemo component is the brother to the DBImage data-aware
control; it displays BLOB data from the associated database field. The
BLOB data supported by this control is large blocks of text in Memo
fields. Memo data is free-form text in which the user has the benefit of
a variable length field. With no constraint placed on the maximum size
of the field, the user is able to maintain lengthy collections of text,
updating it as necessary.
The text contained in the field can be subject to a number of formatting
abilities. Alignment, font, and word wrapping are just some of the control that the application can exercise over the appearance of the text in
this control. All of the capabilities of the Windows clipboard are also
supported for cutting and pasting the contents of the component.

Pan //--The Delphi Database Tools


_p_--mG*^-

-i-*.3*.

----~

Key Pr operties
There are a number of properties of interest published by the DBMemo
component. Mostly centered on the display characteristics of the text
exhibited by the component, properties are also available to help with
the applications performance.

1
.-j 4
:
::
:.$I,

Alignment

The Alignment property determines how the displayed text is displayed


within the controls frame. The text can be Left-justified, Right-justified,
or Centered.

1.
-.i

AutoDisplay

AutoDisplay, similar to the function it performs for the DBImage con<


trol, determines whether or not the contents of the graphic BLOB are
automatically displayed. If the property value is True, when a dataset & :
browsed from row to row, the contents of the BLOB field are automatitally displayed. Depending on the amount of text contained in the
datasets field, the process of displaying it could be slow and lengthy.
- f~$,:+$z
If the performance of the application suffers when displaying the bloch 1<~*;?$j$
-Tg
of text, the value of AutoDisplay can be set to False. Rather than displaying the text block, the field name is displayed in the control. The
.-?
k, . .
user may view the underlying text by double-clicking on the DBMemo 1
control.
The developer must be cautious in utilizing this property. The value displayed by the DBMemo control is the value of the components Text
property. When the text block is not displayed, the value of the Text
.I
property is a string representing the field name. If the value is queried
,:
for a particular string that may exist in the BLOB object, a false nega: .L
tive response may be returned.
2..-;5
-$
;.;g

MaxLength

\?:j

You will use the MaxLength property to limit the number of characters ,$22
that can be entered through the DBMemo control. When the value of
I
the property is set to 0, the default value, there are no limits in place. if
a maximum length is entered, the underlying memo field is safe; no
:
text is truncated or lost. The limitation is meaningful only within the
scope of the control itself.

_ .,,= __il ; c_. - .,_

ScrollSars
The text displayed in the DBMemo control is not constrained by the
size of the component. Because of this it can easily be hidden from
view off to the side or past the bottom of the editing region of the control. The ScrollBars property determines what, or if, any scroll bars are
displayed along with the text. The developer has the option of adding
single scroll bars to either the bottom or right side of the control or
adding both simultaneously. The scroll bars are not automatic and are
displayed at all times, even if they are not needed to display the text.
Wantlabs

The WantTabs property is an interesting by-product of the Windows


world. The Tab key is commonly used for navigation purposes in an
application and especially for moving among the fields of a dataset. A
user attempting to place a tab character into the text of a DBMemo
would find that he had suddenly exited the field with the keystroke.
The WantTabs property can be set to True to allow the application to
accept the tab characters literally in the DBMemo text. In this state, the
user will be able to tab into the control but not out of it. If this property
is used, it will be necessary to explain this behavior and the
workaround to your users so that they do not become confused and
frustrated.
Word Wrap

The formatting property WordWrap allows the control to wrap text at


its right margin when displayed. Setting this property to True places
soft returns into the displayed text so that it does not run off the edge
of the control. The returns are for display purposes only and do not
affect the stored text. When this property is True, no horizontal scroll
bar is needed.
mo Events
An important event for the DBMemo control as well as the other edit
controls is the OnChange event. This event fires whenever the Text
value of an edit control changes. The event is triggered after the Text
property of the control has been updated and gives the application its
first opportunity to respond to changes in the text. Before responding
through the event handler, it is suggested that you query the Modified
property to determine if a change truly occurred.

.?

?
<

:
-

I 76

II Part I/-TIE De/phi ~otubose TOO/S

:ting DBMetno and DBlmage to Work


The Venue Manager application is going to undergo some serious re novation after having served us well for several pages. We are going t( )
modify the Venue Manager to put these two controls through their
paces. Each of the tables that are used by the application has Memo
and Image fields within them that lend a lot to the program. Figure 6.7
shows the modified Venue Manager as it will appear after the follovving
modifications have been applied.

I. Adjust the width of DBGrid 1 to approximately the size of the three displayed columns and the scroll bar and move it to the left as shown in
the picture. Add a DBImage component to the form, placing it to the
right of DBGridl.
2. Adjust the size of the conrrol so that its height measurement is the
same as that of the Venue grid. Set the width so that it fills out the
space comfortably Depending on the size of your form, the image
should be roughly rectangular. Set the DataSource property to
DataModulel,VJZNUE-SRC and the DataField property to
Venue-Map. Use the Stretch property to have the different-sized
images display in the DBImage control by setting the property to
True. We are not going to worry about the distortion of the image in
this application, though it should not be discounted for others.

*.n=1

,*. *

*.

Chapter 6-Delphis

Data-Aware Components n

. --a--

3 . Add a DBMemo

control to Form1 just below the DBImage compel


Set the DataSource property to DataModulel.VENUE-SRC
and d .]
DataField property to Remarks. Set the width to the same size as the
image control and the height to display about six lines of text. We are ,!.!+
going to leave the WordWrap property set to True so the only scroll ., ,*d*i
4
bar we will need is a vertical one. Set the ScrollBars property to
*-:q
ssvertical to add this attribute.
Execute the modified application and scroll through several rows of &+
database. The images are small so they display quickly in this applica-Y :
tion but you should always take into consideration that this is not
always the case. If the images are large and of high quality, con
setting the AutoDisplay property to False to improve the users br
ing speed. Speaking of the image, we see that the map is not of
use to us due to its limited size. Giving the user the ability to displ
small graphic image at a larger size is an excellent addition to an a
cation such as this. Well add this capability in the next steps.

I.

Add a new form to the project. Name this form EnlargedF and add?%
DBImage control. Set the Align property to alclient so that it fills a$-:
:
of the available area inside of the form. Save the new unit as
EnlargU.pas.

2. The application is going to take advantage of the sharing ability of the<$

data module so we need to add that unit to the new form. Select the ,:$j
EnlargedF form and then select File 1Use Unit and the dModU unit $8
to include the code into the forms unit.
3. Select the DBImage control and set the DataSource property to
.
DataModulel.VENUE-SRC.
This value is now available since the
.-j
CL,
data modules unit was added to the Uses clause. Set the DataField
property to Venue-Map and the Stretch property to True. The
,:$
a
graphics that we are using are not of the highest resolution but they
.-;
give us a good opportunity to experiment.
4 . One last thing must be done on the new form. When the image form is A<
closed, the resources should be released to the system. Add the follow- ;.
:>
ing code to the forms OnClose handler:
_ .:P>*
procedure TEnlargedF.FormClose(Sender:
TCloseAction);
begin
EnlargedF.Release;
end;

TObject; var Action:

5 . This form should not be displayed as a part of the application until it

is called. For this reason, it should not be auto-created when the

.',' .(
$
_^"X41
>a*;
% .?
".9.<
=c,;
.8
5f;1
L

178 n Part

l/-The Delphi Database Tools


..

application starts. Select Project) Options from the menu and move
the EnlargedF form from the Auto-create list to the Available list.
6. Select the VenMgrU unit and modify the Uses line under Implementa,
tion to read:
uses

dmodu,

enlargeu;

,$
72
f
I-.

7. The application is going to allow the user to click on the small image L.:
and receive an enlarged view of the same picture that would let them ;:
view the seating sections seating better. Press F12 to switch from the ,z
unit editor to the form. Double-click on the DBImage control so that
C2
2
you can modify the OnClick event handler and enter the following
:r
.a Xi
,d
code:
;:
procedure TForml.DBImagelClick(Sender:
TObject);
begin
EnlargedF := TEnlargedF.,Create(Application);
EnlargedF.Show;

.-+!
8. Compile and execute your application. Click on the image displayed .w+:d
from the database and you will be treated to the sight of a much
~4
larger version, Though the image quality causes these to pixelate
,,a
;:zjj .<
when displayed, you are able to see the idea in action.
;;

The use of the data module is what enables us to create this multiple :!
form application so easily. Both forms are accessing a single dataset .%$
object so the record pointer is always synchronized without us having Ji.<
to write the complex code needed to manage independent table
accesses.

The DIBText Component


The DBText component is a data-aware version of the standard Label
:$B# 3
control and is used to display the contents of a dataset field when they ^i
: .i
will not be edited. By its nature, the representation of the data is
.$ -4,
,-;
read-only, making this control an excellent choice for displaying the
data with a high degree of security, A common use for DBText is to dis- $$
play a key value, such as a customer account number or ID, items that 1%
,-.,;$
cannot be modified without disrupting the relational integrity of the
.i-,42
database.
9 2.
)
The control is simple to implement; set the DataSource and DataField +j
properties and it is activated. The text of the control is derived from the:!?
specified field but unlike the Label or StaticText components, it cannot ,$p
I
be set at design or run time.
::

Chapter 6--Delphis Data-Aware Components n


:,_.Psi/-,/

.iA _. I_;

/ _ . , .

. . --

179

$ .;
.;.,
?
+
,>YJ%c
7%:;

operties
DBText is a simple control to implement and use with just a few properties that need attention when placed on your form.

Alignment

The Alignment property specifies the text alignment within the boundaries of the control, the choices being Left-justified, Right-justified, and
Centered.
Autosize

The amount of text contained in a database field can vary in size, and
the AutoSize property determines how the DBText component supports
this attribute. When the property is set to False, the size of the control
is fixed in both height and width. If the text to be displayed exceeds
these measurements either through length or typeface, the display is
truncated at the boundaries. Setting AutoSize to True allows the control to stretch horizontally to accommodate the data.

;
.;

. ,. .
.i:;

.I
,$$;
:.:Yt,i,$
-

When used in conjunction with the WordWrap property, the control can*.
also expand vertically to fit the text. Some thought at design time is

necessary to ensure that this wildly expanding and contracting component does not interfere with other controls in the vicinity.
Transparent

The Transparent property of the DBText component determines


whether or not the form or control behind the label shows through.
When False, the text appears on a background of the color specified in
the Color property. Setting the property to True allows whatever control or form that is behind the label to show through.
Word Wrap

WordWrap serves the same purpose for this control as it did with the
DBMemo component. Setting this property to True will insert soft
returns into the displayed text so that there is no truncation of lines
longer than the width of the control. To be effective, this property
needs to be used in conjunction with the AutoSize property.
There are no events of note for the DBText control. We will put the
.i
component through its paces in a few pages, after we have a look at the
DBEdit control.

180 n

Part l/-The Delphi Database Tools

The DBEdit Component


DBEdit is the basic building block of any type of data entry application,
This component is limited to a single line of text and both displays and
opens the dataset field to modification. DBEdit offers some degree of
control over the formatting of the data being input and displayed using
a number of property settings. Similar to the previous component,
DBEdit is a data-aware version of the TEdit class. Review the data type
that needs to be supported to determine if this is the appropriate control to be used or if the data can be better represented using a DBMemo
control.

Key Properties
The properties to be discussed in the context of the DBEdit control are
focused mainly on the formatting of the data for input or display
purposes.
AutoSelect

To aid the user in quickly replacing the text displayed in a DBEdit control, the AutoSelect property can be set to automatically select all text
when the control receives focus. If the property is set to the default
value of True, all of the text is automatically selected when the user
clicks on the field or tabs into the field. The contents of the field are
then replaced as soon as the first editing keystrokes are received by the
control. When the property is False, the user must double-click on the
text in order to select it.
AutoSize

The AutoSize property for the DBEdit component differs slightly from
similarly named properties seen previously. When the property is True,
the height of the control can resize automatically 10 accommodate
changes in the text size. The changes can be driven by items such as th
font or border style of the control. When the value of the property is
False, the height is fixed and changes in conditions are ignored.
C h a rrCase

$3
7 4$7
-3 -54

The case of the text in the DBEdit component can be controlled


A-G2
5: :;
through the CharCase property. The default value of ecNorma1 accepts
and displays the text input as the user provides it. When the property is - I3
set to ecUpperCase or ecLowerCase, text that is input is automatically
converted to the appropriate case and permanently modified. The text

_.-

-.

Chapter

6-Delphis

Data-Aware

Components

.f

is stored in the dataset field as it is formatted by CharCase and cann@.$52


.: g
be reversed by selecting the opposite or normal case.
MaxLength

.t

MaxLength determines the maximum number of characters that can b!$i


,. +; .g
entered into this control when no EditMask is in use.
::
P asswordchar

_.

The PasswordChar property is unique to the DBEdit component. It


?
replaces the actual characters typed with the character specified in th$$
property, an asterisk for example. When the user types into the field, -i
the string is hidden behind the string of asterisks, making it ideal for :.[$
21
use in gathering password information or other data that must be
--.+;
guarded.

DBCheckBox Component
The DBCheckBox component is a data-aware check box control used ;.i$j
for input and representation of Boolean or other logical fields in a
-4
dataset. Unique to this control is that it does not strictly require that

the values in the database be True or False. Using the ValueChecked


$
and ValueUnchecked properties, your application can select any pair of.$
7
characters or strings to represent the two states of the control. Using
the attribute based on the values that best represent the data you are
modeling will build a great deal of flexibility into your Delphi solutions. : .*
The check box is a highly intuitive interface element that allows the
user to quickly represent one of two states. Since the value written to
the dataset does not have to be strictly True or False, the application
can use this component anyplace that a coded response is necessary.
This saves the user from having to remember codes for the dataset or
translate visual cues on the form into the necessary string.
Voperties
The properties listed here are unique to the DBCheckBox component
and directly influence the action and outcome of the control.
AllowGrayed

.i
,
,:
:i
-&;:
3
:;p
;.,y:;
E*:
:a.$
j -$
/+l7
:Tj

The property AllowGrayed determines whether or not the DBCheckBox I


:,g
can be in a grayed state. If this property is True, three states for the
@.-a:

182
component are allowed: Checked, Unchecked, and Grayed. The gra
state is a non-valued state, applying no data to the database.
Caption

The DBCheckBox control identifies itself through the Caption p


The string in this property appears as a label bound to the chec
Using the Alignment property, the label can appear to the right or tQ . . -!
the left of the check box.
ValueChecked

ValueChecked works in coordination with ValueUnchecked to specify z!


the field values generated when the DBCheckBox control is modified,, r
The control works two ways based on the value in this property. If th&,
value in the field of the dataset matches the value in this property, &
DBCheckBox appears checked. On the other hand, if the DBChe&&#
checked by the user, the fields value is set to the string contained i~:~
ValueChecked.
The ValueChecked property can represent several values in a semic&
lon-limited list of items. If the field value matches any of the items 7R
the list, the check box is checked. As an example, the vaiue of
7
ValueChecked can be set to Yes;Si;Oui. If the value of the assign&$&!
dataset field matches any of these three items in a case-insensitive,&; 4
parison, the DBCheckBox will be checked. If the user clicks on an
:<
unchecked check box, the first element of the list will be assigned to -.g
23-.1
the dataset field.
The same set of conditions exists for the ValueUnchecked property. If, ;:
on comparison, the value in the assigned dataset field does not match
either of the two propertys values, the check box is grayed out. This
. *;q
;a
action is dependent on the value of AllowGrayed of course.

The DBRichEdit Component


The DBRichEdit component is a data-aware control that can display
t
formatted text stored in BLOB fields. Rich Text describes text fields tk@
support the formatting of individual characters, words, and paragraph:
Text searching and printing functions are supported by Rich Text con- :#
trols and by default they support the following types of formatting: z
$2
w Font attributes such as typeface, size, color, etc.
H Text alignment, tabs, indentation, and list numbering
.

**.. =, _

Chapter 6-Delphis Data-Aware Components W

Drag and drop capabilities


H The conversion of Rich Text to plain text
DBRichEdit supports these formatting capabilities but does not provide
any interface elements to implement them. Your application will have
to support these functions separate from the control.
One other aspect of this control is important to realize when implementing it in an application. DBRichEdit will automatically place the
dataset object into the Edit state when the text is changed within the
control. If only the formatting of the text displayed by the control is
modified, the application must programmatically put the dataset into
Edit for the changes to be saved; this is an important distinction
between the text and the formatting that should be remembered.
Key

Properties
DBRichEdit surfaces a number of properties, some that are very familiar and others focused on the unique tasks of this component.
Alignment

The Alignment property affects the justification of the text contained


with the control. The standard choices are offered: Left-iustified.
Right-justified, and Centered.
AutoDisplay

AutoDisplay serves the same purpose in this control as it does in the


other BLOB-related components. By default, the contents of the BLOB
field will be automatically displayed as the user scrolls through the
database. If the size of the BLOB fields becomes an impediment to good
performance, turn off the automatic display through this property
HideScrollBars

The HideScrollBars property is used to control the automatic display of


scroll bars in the DBRichEdit component. If the property is set to True
and the entire contents of the text fit within the edit window, the scroll
bars are unnecessary and disappear from the control. They will return
again, if modifications to the text make it exceed the size of the edit
window.
When a user is editing a large block of text and the contents are prone
to condense or enlarge below or beyond the size of the edit window,

.>,
, :4

:
..i9
:$i
1:

I 84

Ll-I1ma

W fart //--The Delphi Database Tools

they may become annoyed at the flashing of the scroll bars. If this is
the case, you might want to consider setting this property to False.
HideSelection

The DBRichEdit component allows selection of blocks of text either


with the mouse or through standard Windows keyboard methods and :I
marks the text in the standard fashion with a colored block highlight
it. Depending on the setting in the HideSelection property this select
marking may remain or disappear when the focus shifts to another co&?;
trol. Setting this property to True will cause the selection marking to
visible only when the focus remains on this component. When the
,;.
visual cue of the selected text is needed in the application, set this
property to False to retain the marking even when the focus moves to
another control.
*
i
..:gL.
Plain Text
:a.*; sq
The PlainText property setting determines how the DBRichEdit in&&:$!
prets the text when reading or writing from a file. If the setting is Tn
the data is treated as plain text and all formatting is removed. Whe&
is False, the default value, all formatting information is retained and,j
interpreted when displaying the text.
WantReturns

If it is necessary for the users to have the ability of entering Returns in


the DBRichEdit control, set the value of WantReturns to True. When
property is set to False, the user pressing the Enter key will trigger the
default button or some other Windows-specific action and the text will
not be modified.
WantTabs

As discussed earlier, the Tab key is a standard navigation tool in Windows. If your application requires that the DBRichEdit control accept
tabs for formatting purposes, set the WantTabs property to True.

.:!!$!
- : I<,$
:,,::j
,3
:k
.~C
.,:;
_,..?r
I
?
: ;:.$

D5Ric :hEdit Events


The majority of events for this control are inherited and are standard EO:-~,~
the Delphi interface components but there are a number that are par&$ 1
ular to Rich Text controls.

Chapter 6-Delphis Data-Aware Components

l 1
-. c-

OnProtectChange

The OnProtectChange event fires when a user attempts to modify ternt; :


that is protected. Text is protected through the setting of the Protect&:
property found in SelAttributes.
. .-+.I
.5 a;<$++$
_ -i-.. .7ss$;
I

OnResizeRequest

_. ;A$:*
:,.$

This event fires when the edit control attempts to resize to accommo- et>
date expanding or contracting text. The application then has the
::r
opportunity to adjust or retain the edit window size and manipulate-a
necessary user interface elements to handle the text.
OnSaveClipboard

The OnSaveClipboard event is fired when Windows is in the middle c&<.


transferring data to the clipboard and the user is attempting to destrov:Ig,
the edit window that is the source of the data. The event handler can ::&
be written to delay the window closing until the transfer is complete (,%
i:.
to cancel the clipboard transfer.
OnSelectionChange

When the user changes the text that is selected, the OnSelectionChange
event is fired. The application is able to effect the modification or inter-i!
rupt it altogether if necessary.
_-.c;
A
OnChange

We have seen OnChange before but it is also an important facet of


p!
managing the Rich Edit component. When the event is fired, the text
property of the control may have changed and the event handler is in a
position to manage the change. The application should verify that
f.:
$$
actual change occurred by querying the Modified property
Puttiti

tg DBText, DBEdit, and DBRichEdit to Work


Being able to simply view items in a database will not satisfy many clients. They keep wondering if there isnt some way that the application
could allow them to modify the records or even add new data. Dig out
the Venue Manager application-this next exercise is going to add the&~>
capabilities to the program. Whenever designing major changes into an 5
application, the business rules that control the transactions must be
; _ l%&
reviewed to ensure that all will be met by the architecture.
*:*

I86 H P a r t II--The D e l p h i D a t a b a s e T o o l s
,,,
__.\

,.

When a new event is scheduled, it must be for a venue that exists on


the list. The referential integrity of the application depends on this
requirement, as this is the common field creating the relationship
between the two tables. To maintain the integrity of the EVENTS table,
the event number must also be unique; the tables selected for use in
this program will handle that requirement on their own. The data type
of the EventNo field is set to AutoIncrement, making it automatically
read-only and guaranteed unique. Finally, any event added to the database cannot conflict with another scheduled event for the selected
venue.
What rules apply to the modification of events that already exist in the
database? First, with the exception of fully deleting the record, the
event number should not be subject to modification. Allotting the nun]ber to be modified would subject the application to the risk of integrit!
problems. The venue number should also remain read-onI:< ro avoid
referential integrity problems but it does reveal an opport\lnit>- for a
new feature, the possibility of triggering an addition to the Venues list
LIThen an unknown number is added. The last concern for editing existing records mirrors that of the addition process; changes to a record
cannot allow it to conflict with an existing event. Managing these constraints is easy when the triggering operations are on the surface, such
as when we add new forms and controls, and not so eas! lifter the iorr-il
has been integrated into the program.

Figure 6.8
The completed
Event Scheduling form of
the Venue
Manager
project

. , ., .

phis Dota-Aware Components II 187

Associate rhe data module with the new form so that it has access to
the shared data tables. Under the Implementation section of the
EditEventU unit, enter the following line:
Uses

dmodu:

Switch your attention to the main form for the application, Forml.
Add the EditEventU unit to the Uses statement for this application,
either by typing the code in by hand or selecting File 1Use Unit from
the menu.
.Add a Button control to the form somewhere in the vicinity of the
Events grid. Caption the button Edit Events and then double-click on
it to access the OnClick event handler. The application uses this button
to pop up the event editing form, so add the following code to the
procedure:
procedure TForml.ButtonlClick(Sender:
TObject);
begin
EditEventF :=
TEditEventF.Create(Application);
EditEventF.Show;
end;

Test the button to ensure that your form will pop up when needed. If
everything is working as planned, switch your attention back to the
edit form and add the following line to the forms OnClose event
handler:
procedure TEditEventF.FormClose(Sender:
TCloseActlon);
begin
EditEventF.Release;
End;

TObject;

var

Action:

The first components we will add are shown in Fig.


ure 6.9.
: : Memorial Siadiilml I
.,..
.._.........
A DBText component is going to be used to display ; ; ~y3t-l~ lyyt$er. ; ; jI i :
the currently selected venue because there is no
,,
#* _.....r.,\,
. . .., 4
need to edit the field, nor do we want the user to
have that kind of access. Position the component in
the upper- left corner as shown and set the
Figure 6.9 Adding
DataSource to DataModule 1 .VENUE-SRC and
a cornponen t
the DataField to Venue. Set the AutoSize property
to True so that it will accommodate the variously
sized labels that will be encountered and the Font
Size property to 12 to make the label more prominent.
a...
l
. . . . . .._.
.:.
\
.~__~.,..~,,.~~_.~~~
_,,,..._,,.,.,....I~
.
._
.

1
/

I88 q Purt //--The Delphi Datobose Tools

7, Add a Label control just below the venue name and set the Caption
nronertv to Event Number. Increase its font size to 10. Immediateh,,
beliw this label the Event Number field is going to be added. To meTi
the business rules for this field, we want the field to remain under the
control of the program and not the user. Again we can use a DBText to
display the field in a read-only format that will prevent tampering. Add
the component and set the DaraSource property to
DataModulel.EVENT-SRC and DataField to EventNo. For the sake
of appearance, change the Alignment property to taGenter.
8. Add four more labels distributed as shown in Figure 6.8 for the
remaining edit fields in the table. Caption them as Event Name,
Event Date, Event Time, and Ticket Price. Set the font size to 10
KO match the other label.
9. Add four DBEdit components to the form, distributing them below the
labels that were just added. Set the DataSource on all of them to
DataModulel.EVENT-SRC.
The properties of the controls from left
to right are:
II DBEditI
q DataField = Event-Name
0 Width = 150
II DBEdit2
n DataField = Event-Date
II DBEdit3
q DataField = Event-Time
II DBEdit4
II DataField = Ticket-price
IO. Add another label, setting the font attributes to match the others and
the caption to Brochure Description. Below this label add a
DBRichEdit control. Set the DataSource property to DataModule l.EVENT-SRC and the DaraField property 10 Event-Description. Size the component as appropriate for the data. The
applications values are set to a Height of 135 and a Width of 240.
I I. To the right of this pair of controls, place another label with a font size
of 10 and set the caption to read Publicity Photo. Below this we
want to display a picture so add a DBImage control, setting the
DataSource to DataModulel.EVENT-SRC and the DataField property to Event-Photo. A good size for the images contained in the
database is to set the Height at 135 and the Width at 200.
12. The last component to add to this form for now is a DBNavigator. Set
the Align property to alBottom so that the control will hug the bottom

Chapter 6-Delphis Dota-Aware

Components W I89
.w-

of the form. Compile and execute the application to ensure that everything is working as planned. Notice that because we have the
relationship between the Venues and Events set up in the data module,
when you switch to the edit form you are only able to access those
events for the currently selected venue.
The event handlers and methods of the components are used to implement the integrity rules in this application. It would be common for the
developer to set many business rule items at the table level, close to the
data. The integrity constraints such as date or time conflicts discussed
earlier would be best handled through code additions to the event handlers of the specific field. The date, for example, could be tested against
the other elements in the same column to determine if a match occurs.
If so, an error message would be displayed explaining the conflict and
the focus returned to the date field. A good place to put this code
would be in the OnExit handler. After a date is entered and the user
tabs over to the next field, the event would be triggered.
To explore some of the capabilities of the DBRichEdit control, well set
up a way in which the contents of the field can be modified. This
means adding a couple of additional components to the Event Scheduling form.
I. Add a FontDialog and a PopupMenu component to the form. Both of
the controls are non-visual so their placement is not important. Select
the DBRichEdit control and set the PopupMenu property to
PopupMenul.

2. Double-click on the popup control to start the Menu Designer and add
a single entry that is captioned Font. Double-click on the Font entry
so that its OnClick handler can be modified. Add the following code to
the procedure:
procedure
TEditEventF.FontlClick(Sender:
begin
if FontDialogl.Execute
then
begin
DBRichEditl.SelAttributes.Color
:=
FontDialogl.Font.Color;
DBR ichEditl.SelAt tributes.Size :=
FontD ialogl.Font.Size;
DBR ichEditl.SelAt tributes.Style :=
FontD ialogl.Font.Style;
end;
End ;

TObject);

190 n
^- ..cx.s;_,

Part II-The Delphi Database Tools


1_ . _j

*- % ir*maCI-i

__

The SelAttributes property refers to the text that is selected by the user, ,,
If you want to affect all of the text in the control you should address
-:.x.$_ 1<Y
the Font properties of the DBRichEditl control directly.
-$ I
3. Compile and execute the modified program. Select some of the memo ,I[!
text by using the keyboard or the mouse and then right-click on the
iz
DBRichEdit control. This will pop up the menu, which can certainly be 2
:sj
a good deal more detailed, from which you can start the Font dialog.
.;j
Set the properties the way that you want them to appear and click
,9
OK. If you press the Cancel button, the code will bypass implementing %
i --i
the property changes.
._ 2,;

The DBListBox Component


A list box control presents the applications user with a list of elements
through which they can scroll and select one or more items. The
DBListBox component is a data-aware version of the list box. The item
that is selected from the list is entered into the database field that is
associated with the control. A list box is used in interface situations in
which you want to provide the user with a fixed number of selections
from which to choose.

:
i,
;I
t

The list can be built at design time using the Strings Editor or the collection of strings can be dynamically constructed at run time, giving the l.:,
application the opportunity for the list to always contain an up-to-date
.,!-$
d
set of choices. The DBListBox will highlight an element of the list if
<52$a
there is a match with the contents of the associated data field. If the
value contained in the field does not match any element of the list,
nothing is highlighted.
Key Properties
The properties of the DBListBox are mainly composed of the commonly
inherited set of values. There is a common pair of properties, however,
that often is misunderstood in the context of this control. The
DataSource and DataField properties do not refer to a field that supplies the items for the item list, but instead the DataField property
determines the receiver for a choice from the list of strings. The string
list is defined at design time or modified from some other external
source at run time.
lntegrdfeight

The IntegralHeight property determines whether or not a partial view


of any of the items in the list will occur. Since the height of the control

Chapter 6- Delphis Data-Aware Components n

can be set at design time, there is a risk that it will cut off a string at
half or more of its height, rendering it unreadable. When True, the
property will cause the height of the component to be a multiple of the
ItemHeight property value.

-4
:.

The ItemHeight property contains an integer value representing the


height, measured in pixels, of each item in the list. Both of these properties will only have an effect on the control when the Style property is
set to IsOwnerDrawFixed.

i
%

The elements contained in the list box are defined through the Items
property Items is a collection of strings that can be defined at design
time through the Strings Editor. This tool allows the developer to add,
rearrange, or remove strings from the list without having to manage
the string list through Delphi code. At run time, if you choose to take
that route, the application can make use of the Add, Delete, Insert,
Exchange, and Move methods to perform the same operations. Managing the list takes a bit more work within the application, requiring
that the program track index values. A closely related property,
Itemindex, returns the number of the currently selected string.

-;
I:
:
!

Items

Style
The Style property can be used to specify whether the DBListBox contains strings or graphical images. Owner-draw list boxes are used to
display items other than the standard string collection. Owner-draw
boxes require that the application supply the code to draw the list.

DBListBox Events
Two events differ from the user-action oriented events of the other
interface controls and both are connected to owner-draw style
DBListBox controls. OnDrawItem is fired when a new item needs to be
drawn in the list. OnMeasureItem supports this capability for variablesized owner-draw lists by measuring and returning the dimensions of
the object to be drawn.

The DBComboBox Component


The DBComboBox is very similar to the DBListBox control with one
exception: If the value that the user wants to enter into the field is not
listed in the list box, it can be added through the integrated edit box.
The DBComboBox also allows much greater flexibility in configuring

-1.

.>!

192 n

Put-t II-The Delphi Database Tools

the control. Also different from the simpler DBListBox is the action @.-.
the control when used to display the current contents of a data fie&@#
the field value is displayed regardless of its membership in the strin&d
list.
Key PIl

operties

DropDownCount

DropDownCount is an integer value that determines the maximum


number of items displayed in the drop-down portion of the control. (
default size of the container will hold eight items. The Items string 14
is not truncated nor is the control filled with wasted white space if
number is too small or too large. If the value in DropDownCount is
larger than the number of strings in the Items property, the drop
list will be sized to the item count. If the value is too small to dis
the number of elements, a scroll bar is added to the drop-down
that all items are accessible. The effect of DropDownCount is co
gent upon the Style property being set to any value but cssimple. ?s:
Y
ReadOnly
The ReadOnly property determines whether or not the user can edit
value in the dataset field. When the value of the field is True, the
combo box is used to display the value only; it cannot be modified
regardless of the underlying permissions of the field. If the value is
.;.
False, t-he field can be edited through selection from the list or entry in:;
the edit box.
Sorted

Setting the Sorted property to True can alphabetize the item list in thedrop-down portion of the control. Any new kerns that are added to 9
list will be inserted in the correct sequence as long as the value of&@(g
pJS
property remains True. Once the list has been sorted, it cannot be
returned to its original configuration through the change of the vale __
to False.

Style
Similar to the Style property used with DBListBox, this property deter*:
mines how the DBComboBox will display the items. The control
supports the same owner-draw options as DBListBox that we have d#&~~
cussed. The default style for the component is csDropDown, which

Chapter

B-Delphis

Data-Aware

Components

n I93:gj

,
I..

creates a drop-down list of string items and allows the user to modify
the value shown in the edit box.
::g
The cssimple value creates an edit box with a fixed list below. This
4q%
style still allows the user to modify the contents of the field.
csDropDownList is much closer to the actions of DBListBox. An edit box
:@4
is displayed on the control but the only way to modify the contents of a .
field is to select one of the items in the list. The edit box will only display text if the value of the field matches one of the items in the string
list.

~8ComboBox Events
OnChange

The OnChange event fires immediately after a user modifies the text in
the edit box of the control or selects an item from the list. You might
use this to query the user and determine if they want to add a new item.
to the list, allowing the application to modify itself to new usage.
OnDropDown

The OnDropDown event is triggered by the user clicking on the arrow


to the right of the edit region to drop down the Iist of items. This event
allows you to initiate any special processing required by the list being
dropped down.

The DBLookupListBox and DBLookupComboBox


Components
This pair of components is so similar in nature that it makes sense to
discuss them in tandem. DBLookupListBox and DBLookupComboBox
are data-aware controls that build their display items lists from either
of two sources: The data can come from a lookup field defined in a
dataset or from a secondary data source altogether. The controls
present the user with a limited list of choices with which to fill the
associated dataset field.
Physically, these controls are the same as their non-lookup counterparts
discussed in the previous paragraphs. The lookup controls differ in the
source for the items contained in the list. They come from external
sources, and because of that they offer unlimited flexibility as to the
items presented in the list. Only data supplied by the sources can be
selected as the value for the underlying field.

Part II-The Delphi Database Tools

operties
The lookup controls take a little study to integrate properly into your
:
application, and the key to understanding them is to be knowledgeable ,*!
about the settings for the properties.

KeyField

;. . x

The KeyField property identifies the field from the source listed in the
ListSource property that must match the value of the field specified in
DataField. This links the ListSource dataset to the DataSource. The
fields do not need to share a name but must have the same values.

-~:
--:z
.?V

The field specified in KeyField is not the field listed in the list box
region of the control. That data is derived from the ListField property.

-;
.A$
;7;
.,;,;
-. -a

ListField

, .&;,cSg
t:

The string entered in the ListField property determines the field or


-1
+-:j
fields that are displayed in the list region of a lookup control. This
i:*:
property works in tandem with the ListSource property to fully define $4
the source for the data.
,.d> .j: 3
: MuItiple items can be specified to build the lookup list. The fields are
listed in the ListField property in a semicolon-delimited string.
y -a ,:;
ListFieldlndex

The ListFieldIndex property is used when the ListField property specifies multiple fields to specify the field to use for incremental searches.
Specifically for the DBLookupComboBox,
the value in ListFieldIndex
determines the field that appears in the edit region of the control.
This property builds flexibility into the design of the elements for your
lookup controls. When multiple fields are used to build the item list, it
is not necessary to place the most important element in the first po&
t-ion. You can sequence your elements in the order that makes the most
sense for the appearance of the application and specify the proper field
through this property

3
4::

.&a.2

.:
,
._I,
:?
.,:g.

ListSource

The ListSource property identifies the dataset that contains the


KeyField and the ListField fields. When the DataField property specifies.-:
a lookup field, do not enter a value for this property. The data cont_rqls .$
will automatically use the lookup fields LookupDataSet property to CT
*c-J.<
ate a data source.
il.

Chapter 6-Delphis
_

Data-Aware Components n
S.~

@$

Lookup
Lookup fields must be mentioned in any thorough discussion of this
ji
brace of components. These field types are read-only fields that display $
values at run time based on search criteria specified by the application. ?!$f
;&
The simplest example is a lookup field that is passed the name of an
.,G
existing field to search on, a field value to search for, and a completely j.
different field in a lookup dataset whose value will be displayed. A brief&$!
sample application will help to demonstrate how these fields work.
i!J
Two tables are needed for this exercise, ORDSHIP and DESTDIST.
,..!*
(Both can be found on the CD-ROM under the Chapter6 directory.) The, :-$i
ORDSHIP relation represents customer orders on which we need to
+,
compute the postage. The total of the shipping cost is a formula that
.351
involves the distance to the customers city and the value of the merchandise. Lookup fields will be added to the table definition to pull the. g,
customer city and the distance to that city. A computed field will then
i8z
use this information to return the shipping cost. When you copy these .,:$
i
tables from the CD-ROM, place them in the same directory as the
Borland sample files so that they will fall under the DBDEMOS Alias. Gij
Start a new application and place two DataSource and two Table corn- .;$
,:
ponents on the form. Set the TableName property of Table1 to
ORDSHII?DB and on Table2 to DESTDISKDB. The DatabaseName
,i
properties for both controls will be set to DBDEMOS. Point
DataSource to Table1 and DataSource to Table2. Add a DBGrid
and DBNavigator to the form, pointing both to DataSourcel. Save
the program, naming the unit lookeru.pas and the project
looker.dpr.

Select Table1 and invoke the Fields Editor (right-click and select from\ _ _
the context menu or double-click on the Table component). Add a new
i
field to the table. The first field to be added to the ORDSHIP table will
be called Destination and will look up the destination city from the
-;T3
DESTDIST table based on a match with the zip code entered for the
order. The Component field will fill itself in with the field name. Set
the data type to Float and be sure that the Lookup radio button is
1
selected. Selecting Lookup will enable the Dataset and Key Fields edit
boxes. Set the Dataset property to Table2. Key Fields and Lookup
Keys are the columns in each of the two datasets that will be compared when seeking a match. Set both of these to the Zip field. The
Result Field is the data that will be returned from a match. For this
-.
field, select City. The completed form should appear as shown in the
example in Figure 6.10.

Figure 6. IO
Defining 0 new
field
3.

Add anorher new field for a second lookup field. This one will be
called MilesTo and will represent the number of shipping miles
between the factory and the recipient. The link between the two tables
will be the Zip code again and for this column the Result Field will be
Distance.
The last new column will be a calculated field of type Float. Name this
field ShippingCost. After the field has been created, edit the
DisplayFormat property and set it to a mask of ##O.OO.
With the calculated field in the table, we need to establish the formula
for calculating the shipping costs for the furniture. Close the Fields
Editor and select Tablel. Double-click the OnCalcFields event and add
the following code to the handler:
procedure TForml.TablelCalcF~elds(DataSet:
TDataSet);
begin
TablelShippingCost.AsFloat
:=
(TablelMilesTo.AsFloat
* 0.15) +
(TablelCost.AsFloat
* 0.02);
end;

6.

Figure 6, I I
The completed
form for the
Looker project

Compile and execute the application. The running program is shown


in Figure 6.11.

his Data-Aware Components II 197

As you add new entries, a match is made on the zip code that you
enter. If there is a matching entry in the DESTDIST table, the lookup
fields will be filled with the appropriate information. The entry being
added shows the result of a non-matching entry in the Zip field.
As you spend more time working with them, numerous uses for lookup
fields will be discovered. As with all persistent fields they will remain
defined and prepared for the applications use. Remember that the key
fields, those columns on which the match is made, do not have to be
similarly named; the only requirement is that they share the same values. Also, the columns do not need to be displayed as we did in the
sample application, They can be hidden within the program and used
in other calculations.
putting

DBLookupListf3ox and DBLookupComboBox

to Work

After completing the sample project above you have a good working
knowledge of how lookup fields work and the concept behind them;
based on some matching column between two dataset objects, other
columns from that row can be displayed. The two data-aware lookup
controls work on the same principle with one great advantage: They
are able to modify the contents of the underlying dataset, This lets you
look up the entries for a field from another dataset and build consistent
data into the column.
The Look2 project is a modification of the Looker application just completed. In this admittedly contrived example the user will select more
of their order information from lookup objects, including a
DBLookupListBox and a DBLookupComboBox. To begin this project,
open the Looker project, select File 1Save As, and save your work as
Lbok2.

I. The new layout for Form1 is shown in Figure 6.12. Use this as a guide
for adding and moving the components that are discussed.

Figure 6. I2
The completed
form for the

look2

project

9387

80439

Rechm

.
Ill:

n
n
n
n

POw-t I/---The Delphi Database Tools

2.

3.

4.

5.

6.

The DBGrid has been left on the project so that the effects of the modi:;;;;
fications can be monitored on the table. Take it out of the tab order so
.,g
that it is not available for input. Also, all of the columns in the grid are
hidden with the exception of CustNo, Zip, Item, and Cost.
Add a third DataSource and Table pair to the form. Point the
DataSource to Table3 and select the new table. Set the
DatabaseName to DBDEMOS and the TableName to INVENTRY.DR.
->, .,
Select Table1 and invoke the Fields Editor so that we can add a new
2
lookup field. Name this field ItemCost and set the data type to
.
Float. The lookup dataset is going to be Table3, the Key Fields are
zT
both Item,and the Result Field will be Price. This lookup field will
*.
return the price when an item is selected from another of the controls. *!i
Standard DBEdit boxes are used to represent the Customer, Destina-I
_r.
tion, Cost, and Shipping fields. All of the assigned fields are from
DataSource and they are, in the order listed above, CustNo, Destina- 123
tion, ItemCost, and ShippingCost.
Add a DBLookupListBox in the Zip Code position. The lookup box will
return all of the values from another dataset column and allow the
user to choose only one of those for the value of the underlying field:-:+
Different from the lookup fields, the lookup controls will automatical&
assign the value selected to a dataset field. The DataSource and
DataField properties of this control determine the dataset and field
.I,
that the selected value will be written to. Set these values to
DataSource and Zip, respectively. The values that are displayed in the ,:,*~
list are drawn from the DESTDIST table. Set the ListSource property to
.g
DataSource and the List-Field and KeyField to Zip. The word contrived was used earlier as this example assumes that the only zip codes
the company will ship to are contained in the DESTDIST table. Perhaps a more realistic example would have been to ship only to those
customers who have an assigned CustNo. Oh well, perhaps in the set\
ond edition.
The next component to add will be the DBLookupComboBox.
This
control provides the same services as the DBLookupListBox but in a
more convenient package. The combo box rolls up when not needed,
giving the application a cleaner interface, and the selection tools work :i
incrementally. When selecting an entry from the list, as the user types .;
letters the selection changes to the element that most closely matches i
that letter. Assign DataSource to the DataSource property and
5
Item to the DataField. The ListSource is going to be set to Table3

and the KeyField and ListField both set to Item. This example of
usage is a bit closer to reality. The order clerk can only select those
items that are in our inventory table. The entries in the order table

Chapter 6--Delphis Data-Aware Components n


I

will now have consistent spelling and capitalization, making SQL and
.?&j
other queries much more effective.
7. As long as the Item is going to be pulled from the lookup set, we
might as well get the price that matches it. This is easy enough to do
by using the ItemCost field created in step 3. The data that appears in , , t:
the edit box will have been looked up in response to the item that is
,,W
. >+4%F
selected in the DBLookupComboBox. We would like to retain this
,<?&
$$
number as a part of the ORDSHIP database so we need a method to
write the value shown here into the new row. To do this, use the
OnExit event handler of the combo box so that when the user tabs out i.=
of there, the data is written. Add the following code to the handler:
d-a
-:.,.&.,,
procedure TForml.DBLookupComboBoxlExit(Sender:
begin
TablelCost.AsFloat
:= TablelItemCost.AsFloat;
end;

TObject);

+3

.x
.

a. One last modification needs to be made to accommodate our changes. ,:


The formula for the ShippingCost Calculated field needs to be updated~,:.;
to draw the cost of the furniture from the ItemCost field. Modify the
3
OnCalcFields handler as follows:
procedure TForml.TablelCalcFields(DataSet:
TDataSet);
begin
TablelShippingCost.AsFloat
:=
(TablelMilesTo.AsFloat * 0.15) +
(TablelItemCost.AsFloat
* 0.02);
end;

9. Compile and execute the program. As you utilize the new components,
experiment with them to note the differences between the two so that
you can make the best design decision for your own applications.

The DBRadioGroup Component


The DBRadioGroup component is a data-aware version of the
RadioGroup control. Similar to the data-aware list components, it
presents the user with a limited choice of selections for a data field.
The user selects the value that they want to enter for the field by select
ing one of a group of radio buttons. A radio button interface should
only be used in situations where the number of choices is very limited.
The mutually exclusive nature and default selection options of these
controls require familiarization and do not work well for more than a
handful of choices. When the collection of possible values exceeds the
number of fingers on your hand, use a list box.

I::

200 n

Port l/-The Delphi Database Tools

3
_ .- eIxIe~_ $*:
.:? a

The display actions of the DBRadioGroup control differ significantly


?
from the list components. If the value of the underlying database field .-s
matches one of the elements in the controls Items property, the corre- ,,$
~q ,d
sponding button will be selected when the data is displayed. In
addition to the Items string list, there is a second property called
=!$
Values. The string collection entered in the Values property are the values that will be passed to the data field in lieu of the strings in the
;
Items property. The value that is sent is selected on the basis of the
,w<.$!
index value of the button that is pushed.
i .,:1
$
This second string list can cause some confusion at first glance. If the 2
value in the data field does not match an element from the Items list or !;j
the Values list if it exists, no button will be selected. However, if the
ig
Values list exists and the data field value does not match an element in.j.j
the Items list but does match an element in the Values list, a button v&
be selected.

Key Pr -0perties
Columns
The radio controls in Delphi do not need to be designed vertically on1
they have the capability of spreading over multiple columns horizontally as well. The Columns property specifies the number of columns
for the DBRadioGroup control. The maximum number of columns is 16, i
Items

The Items property contains the string list that describes each of the i
buttons in the radio group. For each item in the list, a button will be
created. In the absence of a list in the Values property, the elements of
the Items string list are the data values that will be written to a field
specified in the DataField property

i
;
-

.,:
i
Value
.i
The Value property contains the content of the current data field. If a
i
different radio button is selected, the value of this property will change.,<::
This property can be queried to retain a value before a change is made. $
&
2;^;
Values
,.i&
1
In some cases your application will require that the value that is writ- ~~~2
ten to the data field is different from the caption of the button. This
-i
might be for simplification or clarity purposes. When this is the case,

the string list that defines the actual data values to be written is

..\

..

his Data-Aware Components II 20 i

defined in the Values property. The elements contained in this property


must correspond one-to-one with the elements in the Items property in
order to function correctly. The value that is selected from the list will
match the ItemIndex value.

:-.
a..&:..<f,;5
n

,. %I. .
$5
n

To cement your understanding of the data-aware radio group control,


you might want to assemble the following quick demonstration project.
It uses the RHPS.DB table, located in the Chapter6 directory of the
CD-ROM.
I. Start a new application and add a DataSource, Table, Navigator,
DBGrid, and two DBRadioGroup components on the form as shown in
Figure 6.13.

i1:i

The Table setting for the DatabaseName will be DBDEMOS and the
TableName is RHPSDB. Point the DataSource to Table1 and point all
of the remaining controls to DataSourcel. Activate the table.
2. Select the first radio button control and set the Caption property to
WHO. This component is going to control what values get entered
into the WHO field of the database. Set the DataField property to
WHO and then double-click on the Items property, starting the String
Editor. Add the following strings to the list, each on its own line: Riff
Raff, Columbia, and Magenta.
3. Select the second DBRadioCroup component on the form and set the
caption on it to read WHAT. Set the DataField property ro WHAT and
duplicate the string list from the previous control in the Items property. In this group the value written to the underlying dataset field is
going to come from the Values property rather than the Items list.
Double-click on the Values property to get to the string editor and add
the following elements to the list: Handyman, Entertainer, and
Housekeeper.

n,z.
,.
,::
n

n
n
n
m

202 n
*

Part II-The Delphi Database Tools

4. Compile and execute the application. The first selection is very


straightforward; the user clicks on a button and the value shown as
the caption of the radio button is transferred to the database. T
ond radio group is a little more furtive. Rather than sending the value
shown to the database, the element corresponding to the buttons
ItemIndex in the Values property is written to the data field.

The DBCtrIGrid Component


Saving the DBCtrlGrid for last makes the most sense, as the component :?
itself is a container for a select group of the other data-aware controls. :;
DBCtrlGrid begins with the fundamental concept of the grid, that is, to Lt
display a number of columns of numerous rows simultaneously. This
-:$
data is configured in a very familiar table layout that conveys the inforc $j
mation to the user simply and clearly once they have divined the
;.
relationship between the rows and columns. The control grid begins ;:
%_
with this premise and then kicks it up several notches.
The DBCtrlGrid control is composed of panels on which a selection of .:.z
the other data-aware (or non-data-aware) controls can be placed. At $
design time, the controls on the panel represent a single row in the
dataset. At run time, however, the layout that you create is duplicated :
for every row in the underlying dataset, filling the component with a I;,?
view of multiple fields from multiple records. The navigation controls -,aIr*
that are used will cause the display to skip from panel to panel.
The DBCtrlGrid component provides a unique interface opportunity for
your applications. If single field controls are placed directly upon a
form, the user can only view a single record in that format at a time.
cf
There are many times when this is appropriate. In some cases, however, 2
,;
the information is better presented as discrete fields but in a way that
;j
more than one row of the database can be displayed at one time. For
,-J
these times, Borland has included the DBCtrlGrid.
Key Properties
The DBCtrlGrid component introduces some new concepts and proper- *<
ties to the data-aware VCL controls. Be aware that the control grid is a .zi
container object like a panel and that some of the properties set for the ,s
container will override the properties of the individual controls.

Chapter 6--Delphis Data-Aware Components n 3

Allow
The setting in the AllowDelete property determines whether or not the :!g
current record can be deleted from the dataset by pressing Ctrl-Delete. ;d
When False, no deletion can occur through the DBCtrlGrid, either
,>
through the aforementioned key combination or through the DoKey
-$
method passed with the applicable parameters. If the property is True, ..$?
the default value, the row can be deleted by the key combination or usi.<$
$2
of the DoKey method.
The DoKey method matches a keystroke combination to a method to
perform specific actions. The action is specified by the Key parameters
and supports the activities shown in Figure 6.14. A number of these
-::
actions are specific to navigating within the DBCtrlGrid component,
which can become complicated when the interface has been separated ;
j
into columns and rows.

gkNull

Do

nothing

GkEditMode

Toggle the EditMode

gkPriorTab

Move to the previous panel

gkNextTab

Move to the next panel

gkLeft

Move one panel to the left

gkRight

Move one panel to the right

gkUp

Move one panel up

gkDown

Move one panel down

gkScrollUp

Move one panel up

gkScrollDown

Move one panel down

gkPageUp

Move ColCount * RowCount


dataset.

property

number of records up

in the

(Page measured by Columns * Rows)

I
gkPageDown

Move ColCount + RowCount


dataset.

number of records down in the

(Page measured by Columns * Rows)

gkHome

Move to the first row in the dataset

gkEnd

Move to the last row in the dataset

gklnsert

Insert a new row in the dataset above the current row. Set
EditMode

to True.

.?2
24

gmPPend

Add a new row at the end of the dataset. Set EditMode

to

True.
gkDele !te

t
gkcancel

Delete the currently selected row from the dataset.


EditMode to False.
L
Set the EditMode to False and cancel any

Set

the

modifications that

have not been written to the dataset.

Figure 6.14 The DoKey actions

Allowlnsert

The twin property of the previous property, AllowInsert controls the


ability to insert new rows by using the Ctrl-Insert key combination or
the GoKey method. When the property is set to False, no insertions are
allowed through the grid. Setting the property to True and placing the,
dataset into Edit mode will enable the user to insert new rows.

<.f> $1

co/count

The DBCtrlGrid is capable of subdividing the panels into columns, creating smaller panels. Each of the smaller panels continues to represent .i:
a single row in the dataset. The new panels will fit themselves within
:-f
the original panel, constrained by the Width property of the
component.
Orientation

The Orientation property determines the order in which the rows of the
dataset are displayed by DBCtrlGrid. If the property is set to goVertical,
the panels will be ordered in rows and the grid will display a vertical
scroll bar. When the property is set to goHorizontal, the panels are
arranged in columns with a horizontal scroll bar displayed.
Pane/Height

The PanelHeight property determines the height of the panels. This


:c
number is different from the Height property for the entire DBCtrlGrid ;i
qg
control.
Pane/Width

Similar to the PanelHeight property, the PanelWidth property carries


the value that determines the width, in pixels, of the panel. Again, it
a different value from the grids Width property

.j
-4

;_

Chapter 6 -Delphis Data-Aware Components m


.>

,..i

SelectedColor
Changing the color of the panel can highlight the panel that contains
the currently selected row. The SelectedColor property contains the current color value and should be modified to change the default shade.
ShowFocus

The ShowFocus property determines whether or not a focus rectangle is


drawn in the current panel to indicate the currently selected record. Set
the value to True to display the focus rectangle. When the value is
False, the application should indicate the selected panel through some
other visual cue.
DBCtrlGrid Events and Related Methods
Just as a number of the properties are unique to this control, there are
events not seen previously that only make sense in the context of the
DBCtrlGrid.
OnPaintPanel

The OnPaintPanel event fires whenever a panel needs to be redrawn in


the grid. The handler allows the developer to customize the paint process. This handler does not affect the controls contained by the panel,
as they are drawn on their own.
GetTabOrderList

Executing GetTabOrderList overrides the default to remove all of the


controls contained by the grid from the tab order. This allows the application to gain more control over the navigation of the database.
KeyDown

This method performs special processing within the application when


specific keys are pressed. The keys handled and their associated actions
are displayed in Figure 6.15. The KeyDown method translates the key
presses into a logical key code and calls the DoKey method to perform
the associated action.

206 n

Part II-The Delphi Database Tools

UpArrow
DownArrow

gkDown

PageUp

gkPageUp

PageDown

gkPageDown

Home

gkHome

End

gkEnd

Return

gkEditMode

F2

gkEditMode

Insert

gwPPend

Ctrl + Insert

gklnsert

Ctrl + Delete

gkDelete

Escape

gkCancel

All other keys

gkNull

figure 6. IS Key and Shift values handled by the KeyDown

method

Putting DBCtrlGrid to Work


The DBCtrlGrid is an esoteric component rife with possibilities, so any
minor demonstration is going to seem insignificant. While this is certainly true, the ticket sales demo will, at the very least. give you an idea
of how the component works so that you might consider it as a solution
to your next design. The application that we are going to build is the
last piece we will construct for the Venue Manager application. The
program written here is a stand-alone application meant to run on a
ticket kiosk with a touch screen where the purchasers can scroll
through the events scheduled and then press the bar that represents the
event to order tickets.
I.

Start a new application and add


the form. Set the Align property
the same property of the control
nicely filled with both of these

a DBNavigator and a DBCtrlGrid to


for the navigator to alBottom and
grid to alclient. The form should be
components now. Save your project.

2.

Add two Table components and a DataSource component to the form.


Table1 should have the DatabaseName
property set to DBDEMOS
and the TableName property set to EVENTS.DB. Table2 will have the
same database name and the TableName property set to VENUES.DB.
Point the DataSource component to Tablel.

,..

his Data-Aware Components B 207


,I , . , .j___,~x,l~,__.._~ _,.,,, I_ __~.=-~-~_llll,lti-_- .illi ..,.. ,,.**
\i.,, _

Set the DataSource property of the navigator to DataSourcel. The


DBCtrlGrid is a panel-based container control. When the DataSource
property is set to DataSourcel, this becomes the DataSource property for all of the child components and cannot be modified.
4 . We need a little information from the VENUES table so a lookup field
will be created. Select Table1 and invoke the Fields Editor. Add all of
the existing fields and then click on New Field from the context
menu. Name the new field Venue, set the Type property to String,
and make the length 25. Be sure to check the Lookup radio button to
access the rest of the properties. The DataSet property will be Table2,
the Key and Lookup fields will be VenueNo, and the Result field will
be Venue. This will return the name of the venue so that it can be displayed on each panel.
5. Have a look at Figure 6.16 to see how the remaining components are
going to be laid out.

Figure 6. I6
The Tickets

main form
The top panel of the grid is the one on which all of your layout should
occur so that the desired behavior of the control grid is retained. The
striped panels represent the placement of the other rows of the database and are shown for your design reference.
6. As shown, add the appropriate components to the top panel of the
grid. There are some limitations to the DBCtrlGrid; foremost, the following components cannot be used on the grid:

208 n
/s#_pn-

Part /i-The Delphi Database Tools


n
n
n
n
n
n

i,

_ .,., ___ .>v .

._ __ . ,- ---=--*-

DBGrid
DBNavigator
DBListBox
DBRadioGroup
DBLookupList
DBRichEdit

All of the fields, with the exception of the DBImage, are DBText controls so that they cannot be modified.
7. Set the SelectedColor property to clWhite so that the current record
will really stand out on the screen.
8. Finally, this application will use the OnKeyPress event handler to emulate the touch screen. We will trap for the B (bar) key in the handler
and simply pop up a message. Add the following code to the event
handler procedure:
procedure TForml.DBCtrlGridlKeyPress(Sender: TObject;
var
Char);
begin
if UpCase(Key) = 'B' then
MessageDlg('Ticket
Master down for maintenance.',
mtError,[mbOk],O);
end;

Key:

9. Compile and execute the application. Your running application should


closely match that shown in Figure 6.17.

Chuoter

&--Delphis Data-Aware Components


.

q 209

x__---_--I-.
------

-----

Womens Basketball Finals


6/21/l 336

R 00 100 PM

Memorial Stadiu

figure 6.1

7
The
final
components of
the Venue
Manager
application

Use all of the different navigation options including the Up arrow, the
Down arrow, and the PgUp and PgDown keys so that you can become
familiar with their actions.

Summary
This chapter has covered all of the data-aware controls and, hopefully,
provided the usage information needed to put them to effective use in
your applications. The projects that were built in the chapter have demonstrated the interplay between the data access controls and the
interface components. The key to successfully designing effective
Delphi database applications is to understand the interplay between
these separate but interlocked groups of components. The interface
components, those data-aware controls we have just reviewed, provide
the windows through which the user sees their data. The management
of that data is centered in the data access controls we explored in the
previous chapter. Recognizing this difference in task assignments gives
the developer direction when deciding where a specific job is to be performed in a program.

2 IO H Part //-The Delphi Database Tools


_*;i,en.l;n(*l_,
~._*jlCr_~j~_._._. *...__ -- *. -- * .( -

Looking Forward

..;/ z
I.

*.

,.*

.&

: I%

The next chapter rounds out the application development topics by cl&:$
cussing reporting. This item is often left to last in the application des
and development process but this sometimes has a detrimental effect .j
on the overall quality of the program.

Included in This Chapter:


n

Report Analysis n Report Formats n Producing the


Reports n Using the TPrinter Object n Columnar
Reports n Component-Based Reports n Form
Reports n Label Reports n Cross-Tab Reports
n External Report Generators

N
early every application generates hard copy output of some sort,
and database applications are often designed with the primary goal of
producing analytical reports from the collected volumes of data.
Designing and producing printed reports that satisfy the users information requirements ask that the developer come to the task with a
wide-ranging knowledge base. From the users you will receive a list of
output needs to be satisfied by the application, and your job will be to
map the output desired to the data structures that have been designed
to collect the raw facts that are to be analyzed.
Before discussing the tools that Delphi offers to satisfy your printing
and reporting needs, we will examine report structures themselves. We
will apply working names to different types of reports and examine
their structure and any special requirements surrounding their production. Once a decision is made about the format and layout of the
report, the methodology of producing it becomes the focus. You have a
choice to make in your report programming: to manually code the
instructions needed to produce the report, to utilize the QReport components to assemble the output, or to utilize a third-party tool that
gives your users the flexibility to create ad hoc reporting as their needs
grow. We will explore each of these options to give you a feel for the

212 n Part IL-The Delphi Database Tools


,.

.;.

effort involved so that it can be measured against the payoff when


planning your application.
One last thought before diving in: Report definition and design is ofi __,_
left to be the last task in a development project. Because of this, it ge&#
the least attention and many times is rushed through to meet the pw::
grams completion date. This is the wrong approach to take. Would yp
leave the interface design to the day before shipping to design and
?.<,#
build? Of course not. The interface of the program is the manner in -:.3
which the users interact with the application and it is far too impow :;Tg
to treat so hastily. The hard copy output is arguably the same thing.
3
Many users, distant from the actual application, will be affected by th&$
information produced and the success of the application will be judm-~
by the efficacy of the reports they receive. The report design process L:
should occur early on in the analysis process when the data structun
are being designed. Two reasons support this schedule. First, you a
a position at the beginning of a project to ensure that all raw data !
needed to produce the desired output is included in the tables. It w@$?
be much easier to make any adjustments to the data sets at this poi+i
rather than when they have been integrated into the application. Stct
ondly, you may find that hard copy is not the best answer in each OF
requested cases; providing an outlet for interactive query abilities I
satisfy some of the user requests more efficiently. Now-not later-&
the time to design these interfaces into the application.

Rep ortA ,nalysis


In the process of designing reporting to meet the needs of your users, -.A:.:
one question is a major determinant in the direction that you will follow: Are you replacing an existing system or creating a new one? If
latter is the case, your task is much simpler to accomplish. The syste
users and the information that they desire from the application will
define the output. They will not come to the design process with a PII+<
determined bias towards a favorite report or a fixed way of doing
things and they will be more open to exploring online information in .i:
place of more printed matter.
When your application is replacing an existing system, you still want @:a
arrive at the same destination. Unfortunately your path may be a litt
less clear and a little more difficult as users lobby for their favorite li~+!,,~
ings. Your biggest design task is going to be to examine the current i .+i
report output with an eye towards redundancy and need. Users may
hard pressed to explain why a specific report is necessary to accornp&#&~~~~~
their job; they only know that this is the XYZ report and that they haw,$ .;

Chapter 7-Reporting and Printing n 2 13


. . . l-6------.

always used it. Your task is to comprehend what the reports are presenting and explain, if possible, how the report can be deleted and
replaced with another information source. Tell the users that multiple
reports are redundant, determine if two or more reports can be rolled
into a single output, inform them that the output of a certain report is
meaningless in the current or future environment. Since you are doing
this in the initial analysis phase of your project, you are also in a position to explain that some simple summary reports can easily be
replaced by an online query that provides dynamic information at their
fingertips, when needed. With their agreement, an interactive reporting
module can be built into the program rather than added later, and the
user can pull data from the application rather than having it pushed
at them.
During the report analysis phase, you are seeking to answer three
questions:
I. In analyzing the data that is to be collected, can all of the users information requests be satisfied?
2. If the application is replacing an existing system, what is the utility of
the current reports? Can they be combined or deleted for greater
efficiency?
3. Is hard copy output really the best way to answer a question?
The answer to the first query may send you back to the design table
with your data structures, adding or deleting some facts that had previously seemed important. The second and third questions open up more
fertile design possibilities. Not only will the reports come under scrutiny for efficiencies and usefulness, but the application that wraps them
will also be considered. Your analysis may point to pieces of the program that would benefit from being reworked.
Once a decision has been reached as to elimination, combination, simplification, etc., in the reporting scheme, analyzing the reports
themselves takes a different eye. Your initial query will be to frequency.
How often does this information get produced? Is it produced infrequently, in a manner that suggests an ad hoc query would be an
improvement, or does the report get printed so often that the usefulness of previous editions still exists while the daily copies go into the
recycling pile? Examine the distribution of the report to determine who
receives it and why Again, examine the recycling bin in the users area
for clues. Efficacy is the last consideration of the report. In reviewing
each report or information request, consider the following points:

far -t II-The Delphi Database Tools


._
ll(,./l*
,i,
_> e _( _.Iq . _

,.

,.

.,

What is the amount and level of detail?


What decision-making capabilities does this affect?
What is the degree of accuracy required from the report and can the
data collected support it?
IS the information presented clearly or does the user draw up a sum1.
mary just to interpret the original report?
IS the appropriate format being used? More detail, less detail? Cross .j
tabulation?
;
IS the key information that justifies the report buried on multiple
!
pages or summarized on the top of the report where it can be easily
found?
Does the report or information request satisfy the need for which it .;
.>
was designed or requisitioned?

Playing this game of twenty questions can be time consuming and


easy to find yourself in the middle of a holy war over the need for a
specific piece of reporting. Forge on; the payoff in the productivity a
value upside exceeds the downside of ignoring the analysis or prod
ing standard reports by a wide margin.
So what then are the measures of a good report? The overriding
response to that question is that the report has a significant bearing on 1s
the operational goals of the user or department receiving it. Addition& $
_
success factors include:
The report compares actual results to a goal so that the user can
quickly determine their operational efficiency.
Only essential information appears on the report so that the user
can quickly assimilate it and come to a decision.
The information on the report is in the users language. They should
never have to translate or summarize the figures or other information in order to use it.
Trends on the report should be crystal clear. Again, the user should
not be producing trending reports from your reports in order to utilize the information.
Get to the point! Nice to have and other extraneous information
should be saved for other reports or, better yet, discarded altogether.;
Use a critical eye and an analytical frame of mind during the report
I
analysis phase of your project and you will quickly be able to weed out
the offenders in your efficiency quest. Think differently from the users
on your design committee. A manager will often frame things in terms _
of accomplishments, for example, cost reductions, Why not produce a

a- I__*~___*~~svi~~

-a-.. _, ,**..- _ i,. I~___p_rlx____(~;__)_

Chapter 7-Reporting and Printing n

I=-~pl~~L-.I-._.-sr*.

L- .._L ^.O

2 15
.l -/_ . ^$,% wmr_*P

report that shows cost increases.7 It would be much easier to understand a rapid increase in costs than to assimilate a reduction in the rate
of cost decreases.

Repot%

Formats
The formatting of reports can be generalized into four recognizable categories: columnar reports, forms, production labels, and cross-tab
reports. You may discover more exotic forms in your work but well use
these as a baseline for discussion.
Columnar reports are easily the most familiar form to you and your
users. These are traditional multiple-row listings of data usually reporting all or an important subset of the users data. The rows are tabulated
and grouped to produce information that is important to the user. A
regional listing of all of the firms clients would be a good example of
this type of report.
Forms are ubiquitous in the business world but many users and developers fail to consider them as reports. A form usually reports one
record at a time and formats it in a highly readable layout that conforms to a preprinted form. The form is also commonly used to report
master-detail information. An invoice, for example, shows the client
address and credit information as well as all of the services performed
for the reporting period.
Production labels are not limited to mailing information but also
include package and warehousing information. Labels can often serve
multiple reporting purposes. A label may initially serve as a pick ticket
for a customer order that later translates, when stuck on a shipping
form, as the basis for an invoice.
Cross-tabulation reports will be most familiar to spreadsheet users. The
ability to arrange data so that columns and rows meet and produce
information at the intersection is one of the strengths of the electronic
spreadsheet. Users will often discover new trends and uses for data
when it is presented in this form.
Being familiar with the different forms that information can take when
output to paper can make your advice of much greater value to your
users. Listening to the needs of your applications users will enable you
to steer them towards the most beneficial format for their reports.
Showing a user how a report that they have used for years can be
transformed into an important competitive tool by selecting the appropriate format can often make or break their positive impression of your
application.

2 16 1 Part l/--The Delphi Database Tools


**-hI*, ., - - -

4 .; 9
-3<.

Producing the Reports


Once the format has been defined, the developer is left to select the
best method of programming and producing the report. Choosing
between traditional hand coding and component-based methods is one
of the primary decisions. This often comes down to a measure of the
quantity and style of output required compared against the effort and
overhead necessary to implement the programmatic solution. Simple
text and formatted database reports are sometimes more expeditiously
handled by the manual manipulation of the Windows printer functions
rather than the multiplicity of components and their associated
overhead.

Using the TPrinter Object

~c
.iG
,$
3
!
;;
i .:

_ 133, j;;;* I
..t*

Before approaching the Delphi object directly, a brief discussion of


:<j;
printing in Windows will lay a sound foundation for understanding
,*,d
what Delphi wraps. Printing in Windows is handled by the graphical $j
device interface (GDI). The procedure for outputting text and graphi&&
on a printer device is very much the same as that used for displaying $
text and graphics to a video output device. The program will retrieve 4
handle to a device context and then direct the desired output to that 3
device. The device context is Windows method for ensuring device
3
i_
independence.
,>c
Your application does not need to know any of the specific commands
required by the printer to which it is directing output. Instead, it calls
high-level functions from the GDI that then convert these commands
into low-level device instructions specific to the output device. When
the application sends output to a printer device context, Windows activates the print spooler to manage the print request. The print manager
provides six functions for use in controlling the print job as shown in
Figure 7.1.

AbortDocTrrninates a print iob.


EndDoc
EndPane

Ends a print job.


1Ends a paEe.

SetAbortProc

Sets the abort function for a print job.

StartDoc

Starts a print job.

StartPage

Prepares the printer driver to receive data.

Figure 7. I The printer spooler functions

:$

:
.+$1 .
f
j
4;G

TPrinter is the Delphi class that encapsulates this printer interface.


Within the Printers unit, the variable Printer is declared as an instance
of TPrinter, ready to be called. 32-bit Delphi instantiates a global object
of class TPrinter that is local to the printer unit relating to the addition
of the function SetPrinter, which can change the global object. The
properties of the TPrinter class are shown in Figure 7.2.

Aborted

When True, the print job was aborted by the


user.

Canvas

This represents the surface of the currently


printing page.

Capabilities

This property contains the capabilities for the


current printer. It contains information on the
orientation, number of copies, and if the
report is to be collated.
1 Specifies the number of copies to be printed.

Copies

Fonts

-d.-::
,,.I
%sGt

1Contains a list of the TrueType fonts supported


by the printer.

Handle

The Windows handle for the printer object.

Orientation

Determines if the print job is printed in


Landscape or Portrait mode.

PageHeight

This is the height of the currently printing


page, measured in pixels.

PageNumber

Indicates the page that is currently printing.

PageWidth

This is the width of the currently printing


page, measured in pixels.

Printer-Index

The index value of the currently selected


printer from the Printers property.

Printers

A list of all of the installed Windows printers.

Printing

A Boolean variable indicating the current print


status (printing or not).

Title

The document title that appears in the print


manager or on network header pages.

Figure 7.2 The TPrinter class properties

The key property of the TPrinter class is Canvas. The canvas of the
printer is utilized in the same fashion as the canvas of a form. Text and
graphical output is directed to the printer canvas from which it is converted to printer commands and sent to the device. To utilize Canvas and
begin a print job, the developer calls the printers BeginDoc method,
opening the canvas surface to receive output. All of the Canvas related
methods are then available to the program to format the output as

j
;&Z
,.:z

P a r t II-The Delphi Database Tools


a..., _ .
_.i

,;.

(.

._

__

__

desired. When a page of output has been laid out on the canvas, the
printers EndDoc method is used to send the canvas output image to the
printer. The Abort method can be called to discard a print job once trans.
ferred to the print manager. The NewPage method alternately will send
output to the printer and start working on a new canvas page. The met&
ods used to format the output on the canvas differ in their abilities. To
present the methods in context, their presentation will be in order of
declining precision with regard to the placement of text on the canvas.
Before continuing the discussion of the specifics of text placement,
important differences in usage of the Canvas of a print device and that of
a video device should be noted. Output location coordinates for printed
,::
output lie along a plane that is much more dynamic than that of a video
_
output device. Changing the resolution for the applications carefully
:,?
designed printed output can happen as quickly as the user can switch
from the network laser printer to her local line printer or fax software. X .:+:
and Y on the laser printer plane will not fall in the same spot on lower
.;:z
resolution print devices such as the line printer. Hard-coded output coor- ;3J$
dinates will produce poor output and no scroll bars will appear on the
document to allow the user to access the missing data as they would on e:;.
screen form. Windows eases the task of managing the dynamic environ- C
ment by returning the information needed about the capabilities of the
device through its device context. Specific information such as pixel mea- ,gj
::;i
surements can be retrieved through a call to the API function
GetDeviceCaps. A specific example of calling the function with a device
context and an enumerated constant will be demonstrated in an upcom- -::
ing example.
3
Z
The best way to begin our exploration of manual print control is
through some simple, non-database examples that build up to the pro
duction of a database report printed strictly through the Delphi code.
The first example will print text lines to the printer canvas using the
canvas TextOut method. TextOut requires three parameters when
called: the X and Y pixel coordinates for placing the text string and the
text string itself. The code below demonstrates a simple procedure to
send text to the printer.
implementation
uses Printers;
{$R *.DFM}
procedure
TForml.ButtonlClick(Sender:
begin
Printer.BeginDoc;

TObject);

Printer.Canvas.TextOut( 10, 10, 'This is a line of t


Printer.EndDoc;
end;

The power of the TextOut method lies in its ability to precisely place
the output at a desired location on the document through the X and Y
coordinates. Consider the placement of a centered report title. To provide the proper starting location to the TextOut method, you must do
some simple calculations with the call or prior to it. Centering text on
the printed page is demonstrated in this snippet:

:<
.;gi++
-is
$
$2

implementation
uses Printers;
{$R

*.DFM}

procedure
TForml.ButtonlClick(Sender:
var
Outline
: string;
centerpoint
: integer;

TOb jet

begin
Printer.BeginDoc;
outline := 'Text 1 ine to be centered.';
.':I
centerpoint := (Pr inter.PageWidth div 2) ..$
* (Length(outline) -$
(Pr inter.Canvas.TextWidth('A')
div 2));
Printer.Canvas.Tex tOut(centerpoint.
10. outline);
Printer.EndDoc;
end;

To determine the center of the canvas surface as defined by the printer


interface, the PageWidth property is queried. This property contains the
width of the currently selected printer page measured in pixels. This
value is divided in half to determine the approximate centerline of the
page. A pair of sub-calculations provides the center of the text string.
The TextWidth method of Canvas provides the width, in pixels, of the
string data passed to it. In the example, the function is passed a single
character and returns a base width value per character to use as a basis
for computing the length of a full line of text. The base value is then
multiplied by half the number of characters in the text line to be
printed. This equation returns a factor in pixels which, when subtracted
from the center point of Canvas, gives a starting point to the X parameter of the TextOut method.

!-Ty
, %
c
:
..
tc
.k
.r

/l-The

Delphi

Database Tools

Utilizing the TextOut method with dynamic parameters to specify the


text placement on multiple planes takes still more work on the part of
the developer. To print multiple lines along the Y axis of the page, you
must compute the line height of characters shaped in the applicable
font, add some spacing between the printed lines, and maintain a sum
of printed lines so that the end of the page is not overwritten and output lost. The first step in this process is to retrieve a measure of the
resolution per inch in pixels, using the following API call:
GetDeviceCaps(

Printer.Handle,

LOGPIXELSY

);

LOGPIXELSY will return an integer value that represents the number of ,~$
pixels along an inch of the display height. This value is then utilized in .,@
an algorithmic computation of the line placement. The snippet below
.>i
demonstrates the sequence of calls needed to determine the height of . ;
the text lines.
. *.
{ - Get vertical pixels per inch - }
VPixelsPerInch
:=
GetDeviceCaps(Printer.Handle,

LOGSP IXELSY);

g
i

{ - Set the line spacing l/10 of Vertical pixels per inch - }


:= VPixelsPerInch div 10;
VLineSpacing
{ - Set the line height - )
LineHeight := Printer.Canvas.TextHeight('A')
. . .

+ VLineSpacing;

e.s j
',
.-.._.
i

The total line height is measured as the sum of the number of pixels in
VLineSpacing plus the number of pixels returned from the TextHeight .
call. Note that the divisor value, 10, used in the computation of
VLineSpacing is a completely arbitrary value and is to be adjusted as
necessary for the specific application. As with the TextWidth call used
earlier, the Canvas method TextHeight takes into account the metrics of
the font currently in use, an important factor in printing to various
printers. Querying the PageHeight property of the canvas provides the
total pixel height of the page. The number returned is the total against
which the accumulated printed lines are measured to determine when
a page break is required. The TPrinter object provides a method,
NewPage, that forces the canvas to send its current output to the
printer and to begin printing on a new canvas page. This method will
handle the canvas itself, resetting its Pen position to 0,O and incrementing the page number. You must reset all of the print variables, such as
LinesPrinted, within your application.

.s:
:;
I$. (_i<
1-*,b$
)_
f,i
$
.?

.;;
..

Producing a simply formatted data table listing and other columnar


reports is an operation that can be quickly handled through an application without the need for the use of Delphi components. Aside from the
controlling loop that steps through the items in the table, the only additional computations necessary for placing the output on the canvas are
those which determine the starting point for each column on an output
line. Placement of the columns is by pixel across the X plane much as
the line is printed on the Y-axis of the canvas. The columns in the
example are placed by adding each fields DisplayWidth property to the
accumulated pixels utilized on the line. The X parameter is constrained
by the PageWidth property of the Printer object.

Columnar

Reports
The columnar report, a simple listing of rows, is a good place to start
building reports. We will use that format to demonstrate the report creation process through simple coding in a procedure. The key to this
approach to reporting is speed, not the number of features on the
report. You can make this report as simple or as complicated as you
desire. The format that we will use in laying out the report is shown
below.
l/l/99
PAGE:

I
DIVERS

SUPPLY

CO.

CUSTOMER

LJST

CustNo

Company

Country

1221

Kauai Dive Shoppe

US

I 23 I

Unisco

Bahamas

1351

Sight

I354

Cayman

Diver
Divers World Unlimited

Cyprus
British

West

Indies

Start the process by opening a new project.


I. On the form, place a Table and a Button component. Caption the button Print. Select DBDEMOS as the Tables DatabaseName property
and choose CUSTOMER.DB as the TableName value. The button
OnClick event will be used to create and run the report.
2. Add the following values to the forms Private declarations.

222 n
,I*smIL-L

Part Ii-The Delphi Database Tools


^

._

II

_. _

I/).

.I

private
: integer;
VPixelsPerInch
VLineSpacing
: integer;
: integer;
LineHeight
LinesPrinted
: integer;
: integer;
CharWidth
: integer;
LinesPerPage
procedure PrintHeading;
procedure
PrintTimeStamp;

Making the variables global to the unit eases the need for parameter
passing in this example. Production needs may differ.
3. Enter the code shown in Listing 7.1 to the buttons OnClick handler.
procedure
var
NextCol

TForml.ButtonlClick(Sender:

TObject);

: integer;

begin
***************k~***********************~~~~~~~~~~~~
1
I
{ DETERMINE THE NUMBER OF LINES PER PAGE
I
*******X**********X******k*************X~~~~~~~~~~~~
1
I
VPixelsPerInch
:=
GetDeviceCaps(Printer.Handle,
LOGPIXELSY);
VLineSpacing
:= VPixelsPerInch div 10;
LineHeight := Printer.Canvas.TextHeight('A')
+ VLineSpacing;
LinesPerPage := Printer.PageHeight div LineHeight;
LinesPrinted := 0;
Printer.BeginDoc;

********************X***t***************~~~~~~~~~~~~
I
I
( Center Report Title - Add 2 lines to linecount for
1
{ 2 more lines for spacing
I ****X************X**t**X***Xf***********~~~~~~~~~~~~
1
Printer.Canvas.TextOut((Printer.PageWidth
div
2)
(Printer.Canvas.TextWidth('A')
*
(Length('DIVERS SUPPLY CO. CUSTOMER LIST'))),

Chapter 7-Repor -ting and Prir Ming


i.__._r___ __

LinesPrinted
LinesPrinted

L ineHeight * 2, DI VERS SUPPLY


1IST');
:= L.inesPri nted + (L ineHeight * 2);
:= L.inesPri nted + (L ineHeight * 2);

begin
if LinesPrinted

<

Printer.PageHeight

co. c :us1

then

begin
NextCol := 10;
Printer.Canvas.TextOut(
NextCol,
LinesPrinted,
Tablel.FieldByName('CUSTNO').AsString);
NextCol

:= NextCol +
(Tablel.FieldByName('CUSTNO').DisplayWidth
Printer.Canvas.TextWidth('A'));

Printer.Canvas.TextOut(
NextCol,
LinesPrinted,
Tablel.FieldByName('COMPANY').AsString);
NextCol

:= NextCol +
(Tablel.FieldByName('COMPANY').OisplayWidth
Printer.Canvas.TextWidth('A'));

Printer.Canvas.TextOut(
NextCol,
LinesPrinted,
Tablel.FieldByName('COUNTRY').AsString);
Tablel.Next;
:= LinesPrinted
LinesPrinted
end
else
begin
Printer.NewPage;
LinesPrinted := 0;
PrintTimeStamp;
PrintHeading;
end;
end;

+ LineHeight;

224

H Part IL-The DelphiDatabase Tools


Printer.EndDoc;
end;
end.
Listing 7. i Modifying the OnCiick event handier

The OnClick handler encapsulates the creation of the Customer List


,:i
report. The first four lines of code handle the important task of computing the height of each line and the number of those lines on each page. *
The number of lines per page is to be monitored throughout the printing process so that the NewPage method can be called when
,>.-28
appropriate. Three sections follow that should be a part of every report: -
a date and time stamp, a report title, and the column headings. Each of .I$
these tasks would be best handled through a procedure declaration so j.
I;
that their utility could be utilized by multiple report processes using
parameters to determine, for example, the report title or column head., 3
:. $S~.T
ing set.
;
In the PrintHeading procedure we make use of a couple of the avail&&$
.+&j
style properties to set off the column headings from the rest of the
data. Listing 7.2 contains the code for the procedure. Add this to the 2
:q
ColPrintu unit.
5.**
procedure TForml.ButtonlClick(Sender:
var
NextCol
: integer;

,'

TObject);

begin
i

*************************t*******t******~~~~~~~~~~~~

{ DETERMINE THE NUMBER OF LINES PER PAGE


i **XX*****k**X***X***********************~~~~~~~~~~~~

i
I
LOGPIXELSY);

:=
GetDeviceCaps(Printer.Handle,
VPixelsPerInch
VLineSpacing
:= VPixelsPerInch div 10;
LineHeight := Printer.Canvas.TextHeight('A')
+ VLineSpacing;
LinesPerPage := Printer.PageHeight div LineHeight;
I ***X*******k****t***********************~~~~~~~~~~~~
{ PRINT A COLUMNAR REPORT WITH HEADING

Chapter 7-Reporting
. . .-

and Printing m
li^r_sB

( *****************************k****************~*~**~~***

( Add the Time/Date stamp to the report


I t****************************************~********~*
PrintTimeStamp;

jL. .a

1
1

{ ****XX*********************************~************
I
{ Center Report Title - Add 2 lines to linecount for
)
{ 2 more lines for spacing
**********************x*********************~**~****~**
I
I
Printer.Canvas.TextOut((Printer.PageWidth
div
2)
(Printer.Canvas.TextWidth('A')
*
(Length('DIVERS SUPPLY CO. CUSTOMER LIST'))),
LineHeiaht * 2. 'DIVERS SUPPLY CO. CUSTOMER
LIST');
LinesPrinted := LinesPrinted + (LineHeight * 2);
LinesPrinted
:= LinesPrinted + (LineHeight * 2);
I **********X****k*****************************~***~~*****
( Print Header for Columns
I ********f*XX****************************~~~~~~*~~~~~
PrintHeading;
I *X***X****XX*********kX*X******************~**~*****
{ Spin through all rows in the table and extract the
( specific columns to be printed
I tXX***t*****X*******~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tablel.First;
While not Tablel.EOF do
begin
if LinesPrinted
< Printer.PageHeight then
begin
NextCol := 10;
Printer.Canvas.TextOut(
NextCol,
LinesPrinted,
Tablel.FieldByName('CUSTNO').AsString);
NextCol

:= NextCol +
(Tablel.FieldByName('CUSTNO').DisplayWidth
Printer.Canvas.TextWidth('A'));

Printer.Canvas.TextOut(
NextCol,
LinesPrinted,
Tablel.FieldByName('COMPANY').AsString);
NextCol

:= NextCol f
(Tablel.FieldByName('COMPANY').DisplayWidth

p
*I

226 n
1.~., . .

Part Ii-The Delphi Database Tools


Printer.Canvas.TextWidth('A'));
Printer.Canvas.TextOut(
NextCol, LinesPrinted,
Tablel.FieldByName('COUNTRY').AsString);
Tablel.Next;
:= LinesPrinted
LinesPrinted
end
el se
begin
Printer.NewPage;
LinesPrinted := 0;
PrintTimeStamp;
PrintHeading;
end;
end;

+ LineHeight;

Printer.EndDoc;
end;
Procedure TForml.PrintHeading;
X*******C*****X**X**i*X****X****t****tX*~~~~~~~
1
i
{ Prints the column headings for the report when }
/ a new page is started
'r
i
i ********r***************************~********x*
var
Spdclng
:
integer;

Printer.Canvas.Textout(
Spacing, LinesPrinted, Tablel.FieldByName('CUSTNO').DisplayLabel);
Spacing :- Spacing + (

Chapter 7-Reporting and


Tablel.FieldByName('CUSTNO').DisplayWidth
Printer.Canvas.TextWidth('A'));
Printer.Canvas.Textout(
Spacing, LinesPrinted,

Tablel.FieldByName('COMPANY

Spacing := Spacing + (
Tablel.FieldByName('COMPANY').DisplayWidth
Printer.Canvas.TextWidth('A'));
Printer.Canvas.Textout(
Spacing, LinesPrinted,

Tablel.FieldByName('COUNTRY

end;
end.
Listing 7.2 The PrintHeader procedure

The column headings for our report will be underlined and set in bold.
To accomplish this we use the line:
Printer.Canvas.Font.Style := [fsBold, fsunderline];

to set the style. The style values are then released in the last line of the
procedure. The spacing of each column is determined by utilizing the
DisplayWidth property of each of the fields of the table. This property
contains the number of spaces needed to properly display the contents
of the field so its a good number to utilize when laying out your
columns.
Another procedure that must be added handles the need for time and
date stamping on each page of the report. The code below lists the
PrintTimeStamp procedure.

228 n

Part IL-The Delphi Database Tools


begin
With Printer.Canvas do
begin
Textout(l0, LinesPrinted,
DateToStr(DATE))
LinesPrinted := LinesPrinted + LineHeight;
Textout(l0, LinesPrinted, 'PAGE:
'+IntToStr(Printer.PageNumber));
LinesPrinted := LinesPrinted + LineHeight;
end
end;

Although recommended, we did not create a procedure to print the


report titles and simply integrated this process into the OnClick handler. (As I say, not as I do?)
The bulk of the code spins through the rows of the table from top to
bottom and extracts the data that we want to report. The code starts by
setting the table pointer to the first record and setting up a loop to
move record by record. Remember that the BDE offers this navigational
capability on types of tables, regardless of the underlying database
structure. A counter, LinesPrinted, is monitored with each line of data
printed to determine if the page has been filled. When the appropriate
number of lines is reached, a small subset of code forces a new page,
resets the line counter, and adds the time stamp and column headings
before returning to the database to continue printing the rows.
This should give you a good idea of the work involved in manually producing a report. It takes a bit of work but the results, as measured by
the speed of producin g the report output, are well worrh it. You should
consider this approach specifically in situations where 170~ know that
the amount of output is so low that the overhead of the components
would be unnecessary, Exception and error reports are a good example
of situations in \yhic.h this skill can be utilized.
While the majority of the methods that the developer is familiar with
from the Form object Canvas property will work on a Printer object,
there are important differences that must be heeded. The most important facet of the pt-inter \.ersus video display comparison is the fact that
the printer is a mechanical entity L\Tith a unique set of exceptions that
must be handled by the application. The items most likely to trip up the
programmer are mechanical errors generated by the printer: out of
paper, jamming, etc. The program calling the Printer object directly will
be responsible for handling the Out of Paper or Printer Not Online
errors gracefully. Calls to the Printer methods should be bracketed by a
resource protection block, try.. .finally? so that all resources are released
and exceptions handled.

.I_L_- ____* _i__ ., _^ ^.-

_./ j -= . __

Chapter 7-Reporting and Print


.._O )__, ~_ ___I*;
bw.e*

Exception handling aside, the output capabilities of the two devices


must be considered by the developer. Since the user will be running
Windows, the developer is guaranteed that graphics can be displayed
on the screen. There is no guarantee however, that the printer selected
is capable of reproducing them. The user should be given the option af .i
printing graphics or leaving them out based on the capabilities of their !
chosen printer. Text and graphics sent to the screen can also be erased;
output to the printer canvas is sent to the printer and cannot be
recalled. Finally, drawing to the screen canvas is instantaneous; drawing to the printer canvas is not. Sending output to the printer is slow,
and the developer should always provide the user with the ability to
abort a print process. The TPrinter object provides the Abort method
for handling this requirement.

Component-Based Reports
Delphi supports component-based report building by including the
QReport components on the same-named tab in the VCL. These components are provided by a third-party developer, QuSoft, and are well
integrated because the entire set of components is written in Delphi.
The component set gives the developer complete control over the page
layout, all printer settings, and output. The next set of reports that we
will examine will be built using these components.
Well begin by building a form report. A form is usually based around
reporting the data of a single record and is formatted in a highly structured way so that the person receiving the report can quickly assimilate
the information. The report that the next project will create is going to
take the form one step further and create a master-detail report in the
form of an invoice.
QuickReport IS a banded report generator. This means that the report
form is built up using bands, sections that contain the elements of your
report and act according to their type. For example, a Detail band will
automatically recognize the necessity to repeat itself when the data it is
reporting contains multiple items such as with a database table. Before
jumping into the fray with the Invoice report, lets re-create the simple
customer listing that was coded in the last example to get a feel for the
component-based process.
I. Start a new Delphi project. Add a Table component and set the
DatabaseName property to DBDEMOS and the TableName property
to CUSTOMER.DB. Activate the dataset. Add a QuickRep component
to the form and set the DataSet property to Tablel. Expand the form
as large as your monitor allows while not covering the Object

-
:
:

230
, I -, =.

H Part I/-The

2.

3.

4.

5.

6.

7.

8.

9.

Delphi Database Tools

Inspector. In turn, move the QuickRep control so that you can have
the maximum viewing area available. Save the project. Name the unit
CustlistU.pas and the project CustList.dpr.
The first band that we will add is a Detail band. This is the workhorse
of the report, containing our listings. Double-click on the QRBand button to place the control on the form.
Next, we need to add the data fields to the band. Double-click on the
:I
QRDBText button three times to place the field components on the
,_
band. Select all three and set the DataSet property to Tablel. Dese
lect the group and, as appropriate to the layout of the report, set the
DataField property of the fields to CustNo, Company, and Country,
Right-click on an open area of the QuickRep control to invoke the con-
text menu and select Preview. The preview function lets you see the
WYSIWYG layout of your report. At this point you should have a complete customer list. If not, examine the settings of all of the controls on -the report to ensure that all of the properties are set as needed.
The spacing on the report seems a little wide for our purposes. The
1::
size of each line is controlled by the height of the Detail band. Move ,,-z
the field components to the top of the band and then select the band
itself. Grab the handle at the bottom center of the frame and pull it
upward to tighten up the spacing. Use the preview function to examine your changes and modify as necessary to suit your tastes.
Select the QuickRep form again. Click on the Bands property to
expand it and set the HasTitle property to True. Notice that when you
do, a new band is added to the report with its type set to Title. This is
an alternate method to add new bands to the report. A band of type
..:
Title will print once on the first page of the report. This is different
:.
from a page header that will print on all pages.
Double-click on the QRLabel button to add this control to the Title
band. Set the caption for this component to DIVERS SUPPLY CO.
CUSTOMER LIST Set the Alignment property to tacenter and the
AlignToBand property to True. These settings will have the effect of
centering the reports title in the band.
The last thing that we need to add is the time and date and the page
number. Because we want to have this information appear on every
:
page of the report, we are going to use a PageHeader band. Add a new ,.
band to the report, using either method, and set the BandType property to rbPageHeader. Did you notice that as bands are added to the I
QuickRep form they position themselves correctly?
Add a QRSysData component to the form. These controls obtain system information to add to your report. Align the control to the upper

(_.___

Chapter T-Reporting

and Printing n 23 I
.._, ...e*__ilf_L, -.s(x*-

left-hand corner of the PageHeader band. Set the Data property to


qrsDate so the current date will be automatically added to the report
each time it is run. Now add a QRLabel component to the band and
set its caption to PAGE:. Add another QRSysData control just to the
right of the Page label and set its Data property to qrsPageNumber.
Preview the report again. While you are previewing the layout, move
to the second page to confirm that the title does not appear.
IO. Only one thing is missing-the column headings, and QuickReport has
us covered there as well. Add a new band to the report and set its type
to rbColumnHeader. To this band, add three QRLabel controls to
the new band and caption each in order, CustNo, Company, and
Country, To set these labels off from the detail, they should appear in
a bold typeface. Select all three controls and expand the Font property
and then the Style property. Set the fsBold property to True.
I I. Rather than underline each as we did on the coded report, we are
going to add a single line to separate the labels from the data. Each of
the bands is contained in a frame and we can use this frame to draw
lines or boxes with. Select the ColumnHeader band and expand its
Frame property. Set the value of the DrawBottom property to True.
Preview the report and print it if you like. Your project should look
something similar to Figure 7.3 with the final report appearing in Figure 7.4.

Figure 7.3
The final form
design of the
project

232 n

Part I/--The Delphi Database Tools

1131
1351
1354
!3513
I380
1384
1 5 3
153
1551
1560
563
524
545

Figure 7.4
The final
report

12. All of this work has been done in the design mode of our project. Td

activate the report in the project is a simple matter using the met
of the QuickRep component. Since the size of the QuickRep contr&$
has no bearing on the way that it prints, size it down a little so th&$
some of the underlying form is visible and add two button controls
the form. Caption one Preview and the other Print.
:-a
1 3 . Double-click on the Preview button and add the following line to the:,+
event handler:
a:5;
3
QuickRepl.Preview;

.2

Do the same to the Print button and add this line:


QuickRepl.Print;

:,L...
d
.:

.$
,_ $
One additional note is in order about the bands. These components a&$
container objects, meaning that they own the other components place&,!
within them. Once you place a field or other object, you will not be
:
able to simply drag it to a different band. Also, remember that when ki
container object is deleted, it takes all controls and settings with it. : Y$i
This completes the customer list project. Save your work.

Form Reports
Form reports use more of a free-form layout when presenting data to tk
user. Rather than list the data rows, the data is presented in different
areas of the page for clarity and readability purposes. A good example4
this type of report is an invoice. The top of an invoice will usually CO&

Chapter 7-Reporting and Printing n 233


*-*~*a-

the customers name and address, contact, and telephone information.

Below that, the invoiced items are listed and summarized, usually ending :
with an amount owed. The next project creates a form report using the
same fundamentals learned in the last segment.
Start a new application, adding two Table and two DataSource components to the form. On the first Table, set the DatabaseName property i.
to DBDEMOS, the TableName to CUSTOMER.DB, and the Active
;I>
property to True. The second Tables properties should be set as
DatabaseName to DBDEMOS, TableName to ORDERS.DB, and
Active to True. Point DataSource to Table1 and DataSource to
.,. ,.:,;
Table2. Save the unit as InvoiceU.pas and the project as
Invoice.dpr.
. ~z.1r
2. The form that we are creating requires data from both tables so we
-:-.- . .
must establish the master-detail relationship between the tables.
Select Table2 and set the MasterSource property to DataSourcel.
Click on the MasterFields property to invoke the Field Link Designer.

Using this tool sets the relationship between the two tables on the
?i,*
CustNo field.
.:
3. Drop a QuickRep component on the form and stretch it as necessary SO .$; 1
that you can easily work with the whole page width.
4 . Add a Detail band to the report. Set the DataSet property to Tablel.
:
Drop a QRDBText control on the band and set the DataSet property to 1.Bz
Table1 and the DataField value to Company. If you preview the
report at this point you will find that the report consists of a list of
:&.-4*s
company names.
Expand the size of the Detail band. Drop two more QRDBText components on the band. Move one to the left of the Company field and set
the DataSet to Table1 and the DataField to CustNo. The other will
go directly below the Company field; set the DataSet to Table1 and
the DataField to Addrl.
The next line takes a bit of fancy footwork to handle. The final line of
the address block is traditionally filled with three items: City, State, and _:_
Zip Code. A comma usually separates the City and State entries and a
_
space separates the State and Zip Code. This leaves us with a difficuh
situation in placing the punctuation because it needs to float with the
varying size of the City field. If the City field is placed, followed by a
5
Label with the comma as its caption, followed by the State field, we
would wind up with varying amounts of space between these elements,
depending upon the size of the City string. Hardly a professional
appearance. A further problem presents itself with the particular
dataset that we are working with because the CUSTOMERS table contains international clients and many do not have an entry in the State

_,

^.._ . ,

or Zip Code fields, If we solve the first issue alone, the comma will still
be floating out there without a reason for being. A Zen dilemma that is
easily solved using a little ingenuity and the QRExpr component. This
control accepts expressions that determine its output, and by carefully
constructing a formula to handle this line we can solve both of our
issues.
6. Drop a QRExpr component on the band and position it below the
AddrI field. Click on the Expression field; this will invoke the Expression Wizard shown in Figure 7.5. The Expression Wizard will help you
to build a number of different types of computed expressions for use
in your reports.

Figure 7.5
The
Expression
Wizard

In this instance we are going to utilize it to build a string expression


that uses a logical operation to determine what gets printed. The logic
that we will apply will be based on the contents of the State field; if the
field has content, a string will be assembled for printing, and if not, a
blank string will be appended to the City field for printing. To do this
we will use the Immediate If function. This function returns one of two
strings depending upon the logical results of the expression that controls it. The IF function reads IF{ <EXP> , (X), (Y) ). If the FXP
statement evaluates to TRUE, the X value is returned; if FALSE, the Y
value is returned, We can use this to our advantage when constructing
the target statement. Enter the following statement for use as the
expression:
Tablel.City+IF(Tablel.State=",

'I, ', '+Tablel.State+'

'+Tablel.Zip)

If the State field is empty, an empty string is appended to the City field.
If there is a value, a comma is prepended to the string composed of the

-Reporting and Printing q

235

State and Zip fields, Because the comma is not in a fixed position, it is
free to float with the varying size of the City field.
7. Add the remaining fields and band as shown in Figure 7.6 to round
out the top of the invoice.

figure 7.6,
Adding the IF
function to the
form

The invoice date will come from a QRSysData control and the heading
should be in a PageHeader band so that it prints on each invoice. To
center the title, set the Alignment property to taCenter and the
AlignToBand property to True. Finally, change the Frame values for the
Detail bands Draw properties all to True to box in the information.
8. With the master portion of the invoice completed, its time to add the
detail items that make up the bulk of the document. To add these
items we will use a QRSubDetail component. When you drop this item
on the report it becomes a new band on the document that links in
additional datasets. This band will point to the second dataset, Table2,
by setting the Dataset property.
9. Add three QRDBText components to the Sub Detail band. The Dataset
for all of these components is going to be Tablea. The DataFields to
be selected, in this order, are OrderNo, ItemsTotal, and
AmountPaid. Preview the report again to see that the list of orders
follows each header.
IO. One additional field will round out each detail line. Add a computed
field using a QRExpr control. We are going to subtract the AmountPaid
field from the ItemsTotal field to determine the amount owed on each
line item. In the Expression property, enter the following statement:
Table2.ItemsTotal

- TableZ.AmountPaid

Set the Mask property of the component to $##,##O.OO to format the


number correctly for display.
I I. Add another QRBand to the report and set its BandType property to
rbGrouDHeader. This band will contain the headings for the

236 n

Par II-The
I_~
_;

Delphi Database Tools


_
_
.-.
.--.--_

._ -------w

sub-detail columns. To link this band to the Sub Detail band, set the
HeaderBand property of QRSubDetaill to the Sub Detail band. Add
four QRLabel components to the new band, captioning them Order
Number, Items Total, Amount Paid, and Amount Owed. Position each over the appropriate column in the Sub Detail band. Now
might be a good time to save the project and preview the report. Its
looking more impressive, isnt it?
I 2. To summarize the Sub Detail records we are going to add a Group
Footer band. Follow the same steps to add a band to the report, this
time setting the BandType property to rbGroupFooter and setting
the Sub Detail bands Footer Band property to the name of the newly
added band.
13. Were going to summarize the columns using three QRExpr components to be placed in the Group Footer band. When using these
controls in a footer band you must set the master property to the
name of the Sub Detail band, in this case QRSubDetaill. For the
first column, the expression statement is SUM(TableZ.ItemsTotal)
and the second is SUM(Table2.AmountPaid).
These components
will size themselves to fit the expression statement and will appear
quite large but they can be overlapped safely on the band for alignment purposes. The final column expression will display the difference
between the two total columns using the expression
SUM(Table2.ItemsTotal) - SUM(Table2.AmountPaid).
After
aligning these controls under the appropriate columns, set the Mask
property for all of them to $##,##O.OO to format the output in currency form. To ensure that each invoice total starts from zero, set the
ResetAfterPrint property to True.
14. Finally, we want each invoice to print on a separate page. To do this,
well utilize the AfterPrint handler of the Group Footer band. Add the
line QuickRepl.NewPage; to the procedure and youre finished
with the Invoice report. To see your handiwork, add a Preview button
that calls the reports Preview method and run the application.
Though we added all of the labels and lines necessary to produce the
actual customer document to this report, this will not always be
needed. You may find that your application is required to produce output on preprinted forms. The quadrille pattern on the QuickRep
component and the variety of page sizes should make this easy to
complete.

Chapter 7-Reportir
__..+-.-

and P I*inting n

_r =

Label Reports
Mailing or product labels are another common task for report develop,3
ers. Labels are unique in their printing requirements because there are :
generally multiple instances of the label form on a single page. As
other designs, the QReport components handle the label task with
. rg..d?
- 4.:
aplomb. After the form report project, this will give you a little
breather. The labels that will be designed are product labels such as
those used in a warehouse to pick products from the shelves. The
..
report we are creating as an example is designed to print on Avery@
i
8164 shipping labels. The dimensions for each label is 3.33 (3 l/3) by
4 inches, two across by three deep.
I. Start a new application and add a Table and QuickRep component to
.y
the form. Select the Table and set the DatabaseName property to
>:,<T
DBDEMOS and the TableName to PARTS.DB. Save the project with
the unit named Labelwpas and the project called label.
.;
2. The form will need to be slightly modified for this report. Set the
.~53
~~
QuickReps DataSet property to Table1 and Bands 1HasDetail to True,::
Expand the Page property to access the sub-properties. To accommo- d
date the sheet labels that we are going to print, we need to adjust the G
dimensions of the document. First, set the number of the Columns
:
property to 2. To make the gutter between the label pairs, set the

i;;*
ColumnSpace property to .1875. Next, the margins require some
minor adjustment. The top and bottom margins will remain at ..500
13
inches. Set the LeftMargin and RightMargin properties to .125.
3. The Detail band is now the correct width and only needs the height
w
adjusted. Expand the Size property and set the Height value to 3.333 -5;. >,g
inches. When you view the report, the detail band should now be
.A>I
correctly dimensioned to match the label type. To make the individual
labels easier to see during the development process, set the frames
,2Draw options to True so that a solid box will be drawn around each
detail band.
4. Add the QRLabel and QRDBText components as shown in Figure 7.7.

PartNo
bescription

Figure 7.7
Example of
label

a
Because the labels are intended to perform the pick ticket function, d
part number and description fields have been enlarged to enable th
viewing from a distance.
5 . Save and preview the report. Mult iple labels will be prepared for ~CH#
printer, one per row in the PARTS. DB table. Sheets of labels will prints
as shown in Figure 7.8.
-

900

1313

Dive kayak

1I

Regulator System

I
- -

Figure 7.8
Example of a
sheet oflabels

912

1314

Underwater Diver Vehicle

Second Stage Regulator

COST

$504 00

msi

$124 10

I.li.

*.

_l., _1 ajl .I i

Chapter 7-Reporting and Printing n


.(^. ~_,._ .^ i__l .,.
x _1 .^-ezc*.e___a

239

Now that you have suffered through manually creating these labels, its
only fair to tell you that a Quick Report form to print labels exists in the
Object Repository. This is set up for three across labels but it is easily
modified to fit any label configuration that you choose. (While we are
in the confessional, there are also forms for QuickReport List and master-detail reports as well.)

Cross-Tab Reports
The last type of report that we are going to create is a cross-tab report.
This style of report is an analytical tool that summarizes on different
planes the numbers that intersect. Placing this process in context
requires that we set the scene with a typical report of sales information.
The first application that we are going to produce uses a Query component instead of the Table controls that we have used as the source, The
information will be in the form of a grouped report in which the header
band repeats when a controlling value changes.
I. Start a new application and place a Query component, a Button, and a
QuickRep con t ro1 on the form. Caption the Button Preview and place
in the OnClick handle a call to the QuickReps Preview method. Select
the Query component and set the DatabaseName value to DBDEMOS.
In the SQL property, add the following statement:
SELECT OrderNo, PartNo, Description, Qty, Company
FROM Orders 0, Items I, Parts P, Customer C
WHERE (O.OrderNo = I.OrderNo) and (I.PartNo = P.PartNo)
(O.CustNo = C.CustNo)
ORDER BY OrderNo

and

Set the Active property to True to enable the query


2. Set the QuickReps DataSet property to Queryl. Add a QRGroup band
from the VCL. This is going to serve to group the data based on a controlling value. The value is set in the Expression property of this new
band. The Order Number orders the dataset that we extracted with the
query; we will utilize that value to group the detail lines. Set the
Expression property to Queryl.OrderNo. Add two QRDBText components to the Group Header band, setting one to the OrderNo field
and the other to the Company field.
3. Finally, add a Sub Detail band to the report. Add three QRDBText components to the Sub Detail band. These controls will have their DataSet
property set to Queryl. One components DataField will be PartNo,
another Qty, and the third goes to Description. Your project should
be similar to that shown in Figure 7.9.

Figure 7.9
Designing the
project

The output produced by the report is shown in Figure 7.10.

Figure 7. IO
The final
report

__ , l_i__ _L;~

_n

Chapter I-Reporting and Printing W 24r I:iF


,a-*

This report groups each orders specific items, changing the data each
time the Order Number changes. This type of report is difficult and
time consuming to analyze when trying to spot trends in the data. Suppose that we want to determine what our most popular items are. We
.J
could analyze the sales report, making a tick mark under each item that <=G
we carry to determine what gets ordered the most but this might be a
2
little too much work to make it worthwhile. What would be ideal is to _yteq
have the data summarized on another report that displays a number of +I:
items across the report and directly below that displays the number of
times that they were ordered. This is the purpose of a cross-tab style
1
report. The running total of each item can be accumulated simply
i~$
, $2
through a query Count function and then rather than listing them as
_.%
normal, we can turn the list on its side to create a more readable set of. 4
data. To give you an idea of what we are trying to accomplish, the sum- 3
mary report to be created by the next application is going to be similar %F;, x. .A
to that shown in Figure 7.11.
-.?%d
I-- ---

figure 7. I I
output ofsummary report, a

cross-tab style
report

Creating this application requires all of the skills you have developed so :;+
far and some you have not. New to this report is the programmatic cre- -<!4
SF
ation of report elements. Like any Delphi component, the QReport
lj.: j
classes can be instantiated in code within your application without the
,,-~
visual control being placed on a form.
I.

Start a new application, adding Query, Button, and QuickRep components to the form. Select the Query component first and set the
DatabaseName property to DBDEMOS. Add the following code to the
SQL property and then set the Active property to True:

:>.**
-:@&
,i-*

SELECT Count(*) PartNo, Description


FROM Items I, Parts P
WHERE I.PartNo = P.PartNo
GROUP BY PartNo, Description
ORDER BY PartNo DESC

Add a Detail band to the QuickRepl component and add a QRLabel.


3 . One of the first processes that is going to occur in the creation of this
report is that some of the rows from Query1 are going to be loaded
into an array to make it easier to work with the data. This is not
2.

;:

242 n

Part l/-The Delphi Database Tools

necessary but it makes the process faster, especially on larger data


sets. Add the following code to the Button1 OnClick event handler:
procedure
TForml.ButtonlClick(Sender:
TObject);
var
i
: integer;
begin
{Load the Top five most popular items}
Queryl.First;
for I := 1 to 5 do
begin
TempMtrx[I, I] := QuerylDescription.Value;
IntToStr(QuerylPartNo.Value);
TempMtrx[I, 21 :=
Queryl.Next;
end;
QuickRepl.Preview;
end;

Before compiling this code, there are a couple of steps needed to set up
some items such as the arrays and the field descriptions. The arrays for
the unit are declared in the Public declaration area as follows:
TempMtrx
ItemsArray
OrdersArray
end;

: array[1..5,1..2]
of string;
: array[0..5] of TQRLabel;
: array[0..5] of TQRLabel;

In addition, if you want to reference the Query fields as shown, they


need to be defined using the Fields Editor. Right-click on the Query1
component and select Fields Editor. Add all fields.
4.

The previous item occurs al run rime when the Preview button is
clicked so that the array is dynamically filled each time the report is
run. There is a block of code that must occur before the report is processed and that is to define and declare the elements within the
ITEMS and ORDERS arrays. Both of these arrays are of type QRLabel.
We are going to use these as the display elements on the report band,
so after declaring them it is also necessary to set a group of property
values prior to the data being assigned to the Caption property of
each. For this exercise, the most convenient place to have these
actions occur is when the form is created, so we want to add the following code to Formls OnCreate handler:
procedure
var
ColWidth

TForml.FormCreate(Sender:
: integer;

TObject);

j :
1 .j

-
!:.?

:!

Chapter 7-Reporting
_^n.
,.

I
: integer;
begin
{Divide the space up evenly for the number of
ColWidth := Trunc( QRBandl.Width / 6 );
{initialize the array elements}
for I := 0 to 5 do
begin
ItemsArray[I]
:=
TQRLabel.Create(Forml);
OrdersArray[I]
:= TQRLabel.Create(Forml);
with ItemsArray[I] do
begin
Parent
:= QRBandl;
AlignToBand := False;
AutoSize
:= False;
AutoStretch
:= False;
Color
:= clWhite;
:= 64;
Top
if I = 0 then
begin
Left := 0;
Width := ColWidth + 6;
end
else
begin
Left := ColWidth * I + 6;
Width := ColWidth - 6;
end;
OrdersArray[I].Parent
:=
ItemsArray[I].Parent;
OrdersArray[I].AlignToBand
:=
ItemsArray[I].AlignToBand;
OrdersArray[I].AutoSize
:=
ItemsArray[I].AutoSize;
OrdersArray[I].Color
:=
ItemsArray[I].Color;
OrdersArray[I].Top
:=
ItemsArray[I].Top + ItemsArray
OrdersArray[I].Left
:=
ItemsArray[I].Left;
OrdersArray[I].Width
:=
ItemsArray[I].Width;

244 n
*I*B,/as

- .i

Part II-The Delphi Database Tools


end
end;
end;

5.

To be good programmers we should also clean up after ourselves. Add


the following code to the forms OnDestroy handler:
procedure
TForml.FormDestroy(Sender:
var
I
:
integer;
begin
for I := 0 to 5 do
begin
ItemsArray[I].Free;
OrdersArray[I].Free;
end;
end;

6.

TObject);

This report is very dynamic in nature and the data changes require
that we wait until the last minute before laying out the actual values.
The Detail bands BeforePrint handler is used to load the arrays of
QRLabel elements:
procedure
TForml.QRBandlBeforePrint(Sender:
PrintBand: Boolean);
var
I
: integer;
begin
{Use the 0 subscript for labels}
ItemsArray[O].Caption := 'ITEM DESC';
OrdersArray[O].Caption
:=
'ORDERS';
{Load the Arrays
for I := 1 to 5
begin
ItemsArray[I].
OrdersArray[I]
end;
end;

7.

TQRCustomBand;

with values}
do
Caption := TempMtrx[I, I];
.Caption := TempMtrx[I,Z];

Your form should be similar to Figure 7.12 at this point.

var

Chapter 7--Reporting

and Printing n 2 4 5
I /a% .,* e-v

Figure 7. I2
Designing the
summary

report
If all of the elements have been added, compile the project and execute
the application. Nothing much will happen until the Preview button is
pushed, which generates the cross-tab.
The cross-tab report is a special-purpose tool to pull out when summarization needs go beyond simply tallying the columns. It is a lot of work
in the QuickReport environment but the results are worth it.

External Report Generators


External report generators are stand-alone software packages that produce reports from a variety of data sources. In addition to handling a
wide range of database and flat file data sources, many of the report
generator packages will provide a component that interfaces the report
engine directly into your application. By doing so you leverage the
power of the selected report engine without having to code all of the
related processes.
External report engines will often have a wider variety of tools for the
developer to work with in creating a report. The cross-tab report, for
example, can be created with a drop-in component in some reporter
environments, saving you the trouble of coding all of the supporting
processes that are necessary to implement this with a QuickReport.
Many paper styles and non-paper destinations are automatically available to your report, allowing your application to generate such output
as HTML documents and fax files.
The external report tools are also very useful in extending the capabilities of your application to the end user. By carefully documenting your
data files and relationships, you enable the user to create custom
reports on their own based on the data collected through your application. Rather than them waiting for you to update the application with
every report request received, the users are able to satisfy their own
report needs quickly and efficiently Plan for the necessary training to

246 n Part

/l-The Delphi Database Tools

make the most of this opportunity though; it may take the users may
take some time to understand the intricacies of your data and the
report tool.
There is a cost associated with integrating most report engines into
your application. When your users press the Print button, they expect
instantaneous response to their request. The overhead of loading the
engine will generally take quite a bit longer than an internal solution,
so you are going to need to take extra care to manage this expectation
with your users. The installation of the application is also going to
expand a bit as well when the engines DLLs are included with your distribution disks.

Summary
I

We have covered reporting and output from the simplest to relatively


complex. Though the report design aspects of an application are often
left until last, your application can benefit from moving this task up on
the time line so that much more of the applications core can be
designed to implement the output needs. Be sure to explore the full
range of reporting options with your users; there may be a lot of information formats that they have not considered.
This chapter brings to a close the development tools segment of the
book. We have explored the components, tools, and programming strategies utilized when de\;eloping database applications in Delphi. The
chapters that follow will focus on specific topics of interest to the database developer.

lnclucled
n
n
n

in This Part:

Chapter g-Tips and Techniques


Chapter 9-The Borland Database Engine API
Appendix A-BDE API Quick Reference

Included in This Chapter:


n
n

The Access Database n Performing a Search in Delphi


Indexing Tables within the Application n Building a
Table within an Application n Using SQL at Run
Time n The DBChart Component n Alternatives to the
-_
Borland Database Engine
,

$4
aving read this far on the topic of database programming with the
a
Delphi tools, you have correctly surmised that there is a lot more to the
process than meets the eye. Good database programs are made in the
planning stages as much as they are in the final execution. We have
:I
extensively covered the thinking that goes into a successful database
,:
design, judged as one that is efficient, mindful of the integrity of the
users data, and does not have to resort to pained contortions of logic
and code to meet the users information needs. The coverage of the
Delphi tools in the previous chapters has been referential in nature,
focusing almost exclusively on the component of the moment. This chapter will take a different approach and discuss the tools in terms of solving
specific tasks and the methods that Delphi offers for doing so.
There are two goals in writing this chapter. One is to share with you a
number of examples of usage that you can integrate into an application
to improve it on some level. Secondly, in exposing some techniques and
demonstrating how to perform them using Delphi, it is hoped that you
will see a glimmer of an idea for further development on your projects.
For example, to kick things off, well discover that A is for Access. The
PC data world at one time was dominated by .DBF formatted data files
and as a programmer you learned the ins and outs of the file structure.

1 Part //l--The Well-Rounded Application

Well, things are much different these days as Microsofts Access enjoys -: .J
a good deal of success and the file format shows up more and more at
.$
your clients locations. Are the Delphi development tools up to the ta& ~. f
of working with these structures? Lets find out.
.-2

TheAccess Database
Whether by default or choice, Access 97 is one of the most popular
)!,(.I
database products on the market today Improvements in recent years -:3
have made the user interface much more friendly to the casual and
:.;;i
non-technical business user, making it an inviting end-user and devel- .a&!
3
opment platform. Because of this, most developers will eventually run j
::;f:
-IAT$
into a situation in which their data source is going to be in an Access
database format. Rather than convert the data unnecessarily, it makes $$
more sense to understand the unique aspects of the format and learn t+
integrate the data source with the Delphi tools, deriving the best from
i -..
both worlds.
::
3

Oper 9 Database Connectivity-ODBC

,-

The Open Database Connectivity standard was developed over a number of years to provide a method of communication between different ~.$?
SQL databases. The goal of the standardization was to create a utopia
where any product that communicates through SQL would be able to
J,
speak to any other product that uses SQL. This WIould be beneficial to
p;
developers, allowing them to develop an application in any selected
&g
tool and then be able to manipulate data from any SQL source. As SQL
products were developed, the companies that created the tools added
their own extensions and syntactical differences to the standard SQL
language, making it difficult at best to communicate between one product and another.
A consortium of companies decided to find a way of maintaining the
unique aspects of their SQL implementations and continue communieating with each other. The group, known as the SQL Access Group,
defined a common base SQL implementation that would be shared
among all of the participating products. These companies developed
the Common Language Interface (CLI) standard and committed their
products to supporting it.
Microsoft improved upon this idea and developed its own standard
called ODBC, an acronym for Open Database Connectivity. Access provides the basic driver and administrator program to translate ODBC
SQL to Microsoft SQL Server SQL. When you connect to a remote

-1.;
I
.;
I.i.:I
-,:i

q
-...a
.,$

Access database through an ODBC driver, your commands are translated through the driver from a local products implementation of SQL
statements to those needed to manipulate the Access database. Figure
8.1 shows the Microsoft ODBC architecture.

8,
??

Delphi 32 Application
(or any SQL application)

ODBC Driver Manager

1 ODBC

Driver

I
/ ODBC Driver

/
ODBC

Driver

Figure 8. I

The Microsof?
ODBC
architecture

Combining the strengths of Access and Delphi is not difficult, thanks to


the design of the BDE and its driver-based architecture. In the next few
pages, we are going to look at using the Access 97 ODBC driver with
the BDE and then manipulate some Access 97 data through a Delphi
interface.

;
.$-;
:$

Setting Up the Alias


Unlike the native database tools in Delphi, any ODBC database alias
must be set up in advance of trying to use it within the BDE administration tools. You cannot establish this on the fly as you would with a
<
Paradox or dBASE driver. The first step to setting up the alias is to
,r
access the ODBC Administrator to create a Data Source Name. This
utility is redistributable by vendors supporting ODBC so you may discover that you can access it in any number of places on your system.
The first place to look will be on the Control Panel. Documentation for
this utility is online through the Help option in the product, though
.
development environments that redistribute these components may
provide additional documentation. Borlands documentation on this
-5
9
topic can be charitably described as skimpy
.54

252 n

Part I/l-The Well-Rounded Ap@ication

When you start the ODBC Data Source Administrator you will find
yourself looking at the User DSN {Data Source Name) tab as shown in
Figure 8.2.

rrgurc: 0.L

The User DSN i


tab used to
addo connec- i
rion to the

database

You must add a connection to the database that you want to access in
your project. Click on Add and you are prompted to select a driver for
the data source you are building. Select Microsoft Access Driver
(*.MDB). The resulting Create New Data Source dialog is shown in
Figure 8.3.

Press Finish to continue on to the next step in the process.

_- ,_b j ,._*a #*.w..- ,I~cx&y~,*~ i-. *~~,.<*,_;_x,~~b

.d.._>*~/l:.~~

Chapter 8-Tips and Techniques q


.._ >*, __i.. ./S./
(. (, _.,<c*I,,,# _ -..>,

253
.<% _

For this demonstration project, name the Data Source MEDINFO. In


the Database panel, click the Select button and navigate to the directory in which you stored the MEDINFO database. Select the directory
that contains the MEDINFO.MDB (from the CD-ROM) database and
click OK, The ODBC Microsoft Access 97 Setup dialog should appear as
shown in Figure 8.4, with the exception of the database directory.

Figure 8.4
ODBC
M icrosofi
Access 97
Setup dialog

The advanced options available behind the Advanced button will allow
you to define login information or modify the configuration settings.
We will leave these as is for now.
i

Adding the Data Source Name to the BDE configuration file is the next
step in the integration. Start the Database Explorer and you will be able
to locate the DSN that was just defined. To tie this to a specific database file, select the object from the Databases tab and click on the
Database Name definition region. You can type the fully qualified filename into this region or select the ellipsis button and browse to find
the file. Save your updates by clicking on the Apply button and exit the
Explorer.
Separating Access from the dBASE and Paradox databases that we commonly encounter as Delphi developers is the way that Access stores its
tables. All objects that make up an Access database are encapsulated
within the .MDB file. When you work with this file, you must address
this database first before you can directly address the tables. Fortunately, Delphi makes the unique aspects of the MDB transparent to your
application through its design.
The Alias will take care of addressing the database file, exposing the
tables and query files to a Delphi application. A quick example can
demonstrate the transparency offered by the BDE. This simple,
one-form application will start by accessing a Table and a Query
I

Start a new application and add the components shown in Figure 8.5.

occiooco, 1ci3 1/1, J! 337


OC8834222256

,,,LlXW

10/31/139?

Figure 8.5
The AccessX
main form
2.

Select the first DataSource and point it to Table1 and then point the
DBGridl DataSource property to DataSourcel. Select the
DatabaseName property and select MEDINFO, exposing the tables
contained within so you can address them individually as though they
were stand-alone tables. Set the TableName property ro VISITSEG, an
Access table. Set this table to an Active state, and the data will flow
into the grid, You will norice that both the Fields and Columns editor
function the same as they do with the Paradox or dBASE tables.
3. How about the Access query? The query is a SQL-based tool that
returns a dataset using the SELECT clause. With a dataset being
returned, it can be treated as though it is a simple table. Follow the
instructions in the previous step for the second set of components but
select the VISITSEG Query as the TableName. When the table is activated, you will notice that the mouse pointer changes to the SQL
hourglass as the SQL statement is executed and the dataset returned.
The disadvantage in accessing the query files in this manner is that
you are unable to manipulate the SQL statement itself.
4. Does the Delphi Query component offer the same function when working with an Access database? Replace the Table2 control with a Query
component. Set the DatabaseName property to MEDINFO and add
the following statements to the SQL property:
SELECT vs-account-num,
vs visit num,
Vsradmitrdate,

#4>*-I.xx=

_ ;

. _~ II_S

Chapter &-Tips and Techniques n


_ _

255

vs disch date
FROM visi;seg
-

Set the Query to an Active state. The BDE and the Access driver will
take care of translating the Local SQL statements to the SQL used by
Access to return the same dataset that we were just working with.
The Access database tables and queries are fully shared and both types
of applications can access them simultaneously, If you can, open the
MEDINFO database in both Access 97 and your Delphi application on a
shared basis. Access reports and forms are not compatible with Delphi
but it is not likely that you would attempt to use them anyway,
The driver-based design of the BDE makes the option of using Access
data in a Delphi application a reality, As you have seen, there are no
major impediments to using these data sources that should dissuade
you from selecting Delphi as the development tool of choice. The
advantage of the Delphi application is that it will be a stand-alone executable, not requiring the Access application behind it for support.

Performing a Search in Delphi


A very common task for a database application is to seek out a specific
record based on some criteria and move the record pointer there, displaying the record. Earlier, we discussed filtering a table to limit the
displayed data to a subset of the larger dataset. Searching a dataset is a
more active and specific task than creating a subset; the application is
going to examine each field individually to determine if it meets the
criteria. This action is also different from the SQL SELECT starement
that always ends up providing a new dataset. With a search, we want
to remain in the selected dataset and merely find the specified record.
Any reasonably experienced programmer could write a procedure that
starts at the top of a table, examines the field to be compared in each
row, and stops the process on the selected record or at the bottom row,
whichever comes first, Delphi programmers take a different approach,
using the methods of the controls. The Table component offers a number of methods specifically designed to perform this task. One
assumption that is made when working with Paradox and dBASE tables
is that they are indexed.
There are five methods used for accomplishing this task:
FindKey-The FindKey method searches for a row that contains the
specified key values. When it locates the item, FindKey positions the
record pointer on that record and returns TRUE. If it fails to locate a

Par t Ill-The Well-Rounded Application


*_ ,.. ,
., - ,
,
.-tl,hs..il..IY

Ir(_li.w-

, x I~~_..e.+^*I_~~-.

.w_p.~s.~l->~

matching row, a FALSE is returned and the record pointer is not


from the current record.

A<
._ /
The parameters passed to this procedure are called keys. The array SU$
scripts match the columns used in the index currently in use for the
:i
table. For example, assume that the table is indexed on a single field.,:;
The FindKey method would take an array with a single element as i@:-parameter. If the index was a composite index, each key is comma
arated and any value not explicitly declared is assumed to be NIL.
.;
FindNearest-This brother of the FindKey method searches for the-l;
row that most closely matches the array of key values. This could be--,$$
the record that matches exactly or the next highest row if an exact :q$
, .-g4
match cannot be found.
GotoKey-The GotoKey method works in conjunction with the Se
or EditKey functions to move the record pointer to a matching row
the table. Unlike the FindKey method, the key that specifies the vti
to be matched is specified separately from this method. If a match ,~ ~
achieved, the record pointer is moved to the matching record and &i
TRUE value is returned. If no match is located, a FALSE is returned82..!
:
the record pointer stays on the current record.
GotoNearest-The behavior of this method matches that of
FindNearest. If no match is found to the key values specified by Se!
this method moves the record pointer to the next highest row.
SetKey-The SetKey method is used to set the key value for a datasc
prior to performing a search with the GotoKey method. This method&$
the first step in a two-step search process that matches the functional&#
of the single-step FindKey method. When SetKey is called, it places @,I
table into the dsSetKey state, setting up a search key buffer. Values a~$$
added to this buffer prior to calling the method used to move the cur- -$
sor. EditKey is closely related to this function, maintaining the curre@f
contents of the search buffer so that they can be modified rather thart,Yz
.: *;?
replaced.
%..a4$
all Me the Seeker
Lets examine the coding needed to implement these methods in an
application. Figure 8.6 shows the Sear&X program that can be 1-a .*r
on the companion CD-ROM in the Chapter8 directory.
-9<d

It uses the DBDEMOS database and the COUNTRYDB table from that
database. The table is indexed on the Name field, representing the
name of the country. All of the search methods default to using the
tables primary index for searching. If you want to use an alternate
index for the search, you must set the tables IndexName property to
the name of the index.
FindKey is the simplest method to utilize since it is self-contained. The
code behind the OnClick handler of the first button is simple:
procedure TForml.ButtonlClick(Sender:
TObject);
begin
if not Tablel.FindKey([Editl.Text])
then
MessageDlg(Editl.Text+'
not found', mtInformation,
[mbOkl ,O) ;
end;

The text to search for is provided from the Edit1 edit box and passed as
the only element of the search values array FindKey takes care of
everything else. If no match is found in the table, an appropriate message is returned to the user but the record pointer remains on the first
record.
FindNearest acts in a similar fashion when the button is pushed, with
one exception-it moves the record pointer to a value just beyond the
search value if a match cannot be found. No value is returned so the
code for this procedure is even simpler:
procedure TForml.Button2Click(Sender:
begin
Tablel.FindNearest([Editl.Text]);
end;

TObject);

Figure 8.7 shows the results of a search for the country Korea. Because
that country does not exist in the table, the pointer is positioned
between Jamaica and Mexico.

Figure 8.7
Searching

COUNTRY

the

table

As you can see from the code involved, the GotoKey and GotoNearest
methods are the more complicated of the search methods. They have
rhe edge in the flexibility department, allowing you to use EditKey to
modify the key values, but that edge is slight. The code needed to
implement the same simple search is:
procedure TForml.Button3Click(Sender:
TObject);
begin
with Table1 do
begin
SetKey;
Fields[O].AsString
:= Editl.Text;
if not GotoKey then
MessageDlg(Editl,Text+'
not found', mtInformatlon,
lImbOk], 0) ;
end;
end;
I

The search methods provided through the Table components are simple
and effective. You have enormous flexibility both in the way that you
want the search handled and in the source for the search value as well.

Indexing Tables within the Application


There are going to be times when you need to introduce an index
option that was not a part of the original design. Perhaps the table was
already heavily laden with indexes to be maintained and ir would have
been a performance impediment to include another or the need for the
index is limited to certain specific situations. Whatever the reason, it is
a simple matter to add a new index to a table during rhe execution of
the program.
The AddIndex method of the Table component creates a new index for
the specified table. When the index is created its name will be added to
the IndexFieldNames array property and made available to the program. AddIndex takes three parameters. The first is simply the name of

?!
the index and the second is a semicolon-delimited list of fields to
include in the index. The last parameter is Options, a collection of 3.,
attributes and restrictions applicable to the index. These options
include the following:
w ixPrimary--Represents the primary index for a dataset.
n ixunique-The index contains no duplicate values.
,;
n ixDescending-The index sorts the records in descending order.
n ixCaseInsensitive-The
index records are sorted without regard ted
p,*the case of the characters.
w ixExpression-The rows are sorted using dBASE key expressions,-:J.$r<.
Be sure to note which options are appropriate for the table type &a$:3
you are indexing. The use of incorrect options will result in an excep-j
i
tion being raised, (i.e., ixExpression with a Paradox table).
To add an index using the AddIndex method, the lines will read as
follows:
Tablel.Active := false;
Tablel.Exclusive
:= True;
Tablel.Active := true;
Tablel.AddIndex('ITEMIDX',
'Item',

i 6
_. -.:
I
I -i
.;
[ixoescending]);

This code block modifies the ORDSHIEDB table, adding a new index i
based on the Item column. Note that the table must be in Exclusive ;,,.$
mode before making this kind of modification. Also, note that the
needs to be opened exclusively for this operation to be carried out.
sure to return to Shared mode operations if your application is
multi-user.
t

But Its Still in the Same Order

Noting much happened after the last series of instructions. The table is
still in the same order as it was before the new index was built. The *;
reason for this is that the index being used to display the records has T-3
not been changed. To select the new index, we can use the Tables
IndexName property. This property specifies a secondary index for a .j
table. If the value is empty, the primary index for the table is used to --j
display the records. To change the table to using the new index, add ,:I>
-7:.
the following line:
Tablel.IndexName

:=

'ITEMIDX';

If the table is displayed, in a grid perhaps, the new sort order will be .i.:-;
.d,
immediately apparent.

260 n Par
xI ._ ., _,-),. l

An alternative method for specifying the index for a table is to use the
IndexFieldNames property. The propertys value is a semicolon-delimited list of column names. If the table is a Paradox or dBASE relation,
the columns must already have been indexed for this process to work
correctly. SQL tables are more dynamic and do not require that the
index already exist. When you use this property, you immediately override the IndexName property and vice versa; the two are mutually
exclusive.

3;:
j~:;iiy

Since any table can have a plethora of index possibilities, how can you
application determine what you have to work with? The IndexDefs
property is an array of index items that can be queried to determine if
the column that you want to work with has been indexed. The array
elements are composed of the semicolon-separated column name
entries used to define the indexes. The number of elements in the
.
IndexDefs array is maintained through its Count property. With this
number in hand, an application can walk through each element and
compare the column names to find the one that it wants to use. For
example, to find the ITEMIDX index, the application will search for & :-$g
,.:&I_ $ ;;b
index based on the Item column:

for x := 0 to (Tablel.IndexDefs.Count
- 1) do
begin
if Tablel.IndexDefs[x].Fields
= 'Item' then
begin
Tablel.IndexName
:= Tablel.IndexDefs[x].Name;
end;
end;

To ensure that you are working with the most complete array, issue a
call to the IndexDefs Update method prior to using the array. This will
refresh the collection after any changes may have occurred.

.q
-2
c"i .":
:
"A5

.i

:i
.T.jj$

Building a Table within an Application


Although it is certainly the most common method, creating tables
through the Database Desktop is not the only way to approach this
task. A table can be created at design time through the Delphi interface
or at run time using code in your application. The choice you make
depends on the goal you are after. If the table were going to be needed
only temporarily and then destroyed, creating it within the applications code when it is required would be the best choice.

;
,i
j.
ni
!,
:
,-:.

oeas.m--

Creating the Table at Design Time


One word of warning before approaching this task: You must be well
i.
versed in the TTable class before putting this information to work in a i,.rl
production situation. You are going to be defining every aspect of a
?
Table without the benefit of the Desktop, which masks a lot of this frotriE%y4
you and automates many operations. The properties, methods, and
events of the Dataset object should be reviewed to ensure that all of th&.!,
values that you are going to define are appropriate for the object that - ;
A
you want to create.
_,;i.?a
To explore the design-time creation of a table, well approach this in a d&;
step-by-step method, creating a sample project as we go along.

:.$
I. Start a new application and drop a Table component on the form.
,
2. The first order of business is to assign a Database alias to the
DatabaseName property. This is the database in which the new table
i
will be a member. Use DBDEMOS for this example by selecting it for
.Zc&f
the DatabaseName propertys value.
3. The table must have a name, which will be assigned through the
:!
TableName property Call this table CDCOLL. If the table is going to .:y
be of a database type other than Paradox or dBASE, you must includi Z
the appropriate file extension in the string you pass as the table name.%
4 . All tables must have a table type that defines the database structure of 2
the object. If the database is going to be something other than ASCII, - 2
Paradox, or dBASE, set the TableType property to ttDefault. Otherwise-,,~l
select the appropriate type from the drop-down list. For the sample
Zi
that we are building, set this property to ttDBase.
5 . The fields for the table need to be defined. Double-click on the
FieldDefs property to call up the collection editor. The process being
_,
described here will need to be repeated for each field in the table. The
schema that we will use to define the CDCOLL table is shown in Fig1,,
z-3
ure 8.8. Click the Add New button and add the data shown for each
field.

Figure 8.8 The schema of the CDCOLL table


6.

When you have completed the definition of the table you will use the
Create Table menu option. Right-click on the Table component to

-7

-,iz

invoke the context menu and select Create Table. A physical representation of the table will be created in the background if everything
has been defined correctly.
7. The table definition is complete at this point but one more item should
exist with nearly all of your tables, an index. We will define a primary
key for this table to keep it sorted and to meet the relational standard.
Double-click on the IndexDefs property to start the collection editor
again. Click the Add New button to define a new index. Name the
index TITLE and the Fields property Title. Click on the Options property to see the properties listed below it. Set the ixPrimary and
ixunique properties to True. Close the collection editor and right-click
on the Table again. This time, use the Update Table Definition command to modify the table definition.
8. Test your table by adding a DataSource and a DBGrid to the form. Set
the Active property of the Table to True and execute the application.
Try entering a few CDs, making sure that the index is working by
inserting them randomly and making sure that they insert into the correct spot.

Creating the Table at Run Time


Now that you understand that the process of dynamically creating a
table is not much more involved than appropriately setting properties
and calling methods, you can approach the prospect of creating a live
table within the code of an application. To demonstrate this process, we
will re-create the CDCOLL table as a Paradox table. Lay out a new project as described at the end of the last project with just a DataSource
and a DBGrid. Add a button to the form; the application will use the
OnClick handler to initiate the process.
The following code block will perform all of the necessary steps to
define the table and its index, create the table, and then activate the
table in the grid for input.
procedure TForml.ButtonlClick(Sender:
var
Tablel:
TTable;
begin
TTable.Create(Owner);
Table1 :=
with Table1 do
begin
DatabaseName := 'DBDEMOS';
:= 'CDCOLL.DB';
TableName

:i:4
;a
,\: s4
4
:z
1
.

.,.:-i

TObject);

,+ij
-1
"7:->

Chapter 8- Tips and Techniques W


FieldDefs.Clear;
FieldDefs.Add('Title',
FieldDefs.Add('Artist',
FieldDefs.Add('Rating',
IndexDefs.Clear;
IndexDefs.Add('TITLE',
end;
Tablel.CreateTable;
DataSourcel.Dataset :=
Tablel.Active := True;
end;

263

ftstring, 25, false);


ftStr ng,
20, false);
ftSma lint, 0, false);

'Title I [ixprimary,

ixunique]);

Tablel;

The first step in the process is to instantiate a TTable class to work


with. With that instance, the application begins to define the properties, adding the name of the database and the table. Each of the fields
in the table must be defined and added separately, followed by the
indexes if used. Finally, the CreateTable method is used to create the
physical representation of the table.
An important consideration to remember when creating tables within
your application, whether through code or the design interface, is that
a newly defined table will overwrite an existing table of the same
name. No warning will be given before the existing table is destroyed,
so your application must take this issue into account. Check to see if a
same-named table already exists before creating the new table and be
sure to provide your users with the opportunity to back out of the
process.

Using SQL at Run Time


The heart of the Query component is the SQL statement that resides in
its SQL property. This statement is wholly responsible for the dataset
that is returned by the component. In an earlier chapter we worked
with the SQL property by entering the statements directly into the component using the String Editor. While this gives you an excellent tool
for extracting subsets of your data for use in the application, the true
flexibility comes from using the Query control dynamically
Loading the SQL Property from Another String List Object
The SQL property is a string list that can be loaded at design time or at
run time. The design-time aspects of this task have been covered

P art I II-The

Well-Rounded Application
,.- 1(
_...

before, so this section is going to focus on loading and modifying the


SQL property at run time. There are some common elements to the p
cess regardless of the method chosen to load the string list. The first is
that the Query must be closed before modifying the SQL strings. You
might be tempted to utilize the known behavior of the Query component that automatically deactivates the query but you should not, as
this is bad programming practice. The SQL strings should be cleared
next using the SQL propertys Clear method. Finally, after the string list:
has been loaded, you must execute the Query using either ExecSql or
Open. The Open method is used for SELECT queries that return a
dataset. For queries that modify the dataset in some way using the
INSERT, UPDATE, DELETE, or CREATE TABLE statements, use the
ExecSQL method.

.
i

(
,!
:(~#,;,
,I

There are three different methods for loading the SQL string list at run 3
time: adding strings to the SQL property directly, deriving the strings .$
from a file, and copying the strings from another string list object. We. ..ii
will examine this last option first. A string list is an object derived froftvj
the TStrings base class and it represents a list of character strings. The ,:
SQL property of the Query component is of type TStrings. String lists 1
can be copied from one TString object to another just as any similar j II
data type can be copied. From the TStrings base class, the SQL props -
inherits a method called Assign whose purpose is to copy the value of .5Yi.
iJ
one TStrings object to another.
Figure 8.9 shows a demonstration program from the CD-ROM called
SQLBBX.DPR. The SQL Breadboard is a good learning tool that could
-:c
6
not be simpler to build. The user can enter SQL SELECT sta tements
.$
into the Memos edit region and with a click of a button they are executed and the results shown in the grid below. The Run SQL buttons
.?
OnClick handler encapsulates all of the code for the program. The lines .$
!,.%J
read as follows:
procedure
TForml.buttonlClick(Sender:
begin
Queryl.Close;
Queryl.SQL.Assign(Memol.Lines);
Queryl.Open;
end;

TObject);

;$
,,
,I -6
.'
, -4
.j
..:$
-;
.?
* 3
i
I?

With a bit of additional coding to keep the users out of trouble, this
approach can provide a simple free-form query interface for an
application.

;:*.9
:<:-aj.pi
-*
. ,. :,<,t:;

Filling the SQL Property from un External Source


What if the queries that are needed for the application are relatively
fixed in structure? Your initial approach to developing queries not
requiring user intervention would be to code them directly into the SQL
property of the Query component. This is probably not the best solution
because, inevitably, user requirements will change and the query will
require modification. If the SQL is coded deep in the heart of the program, it will require that you recompile and redistribute the application
to bring it up to date. (Of course, by the time you do, the requirements
have changed yet again!) Secondly, coding directly into the SQL property requires that you provide a commensurate number of Query
components to match the number of information requests.
An alternative to leaving the SQL code inside of the program is to move
the SQL statements to a source that lies outside of the program such as
a text file. The SQL property has a method called LoadFromFile that
facilitates this action. The parameter passed to LoadFromFile is the
name of a Text file that is read and loaded into the SQL property. The
file should contain a properly formatted SQL statement. When the file
is read and loaded, the Query component will clear the SQL property
and prepare the query for execution. Calling one of the two execution
methods completes the sequence.
The project SQLLFFX from the Chapter8 directory on the CD-ROM
demonstrates this process, When the Load button is clicked, the file
SQLSCRIPTTXT is read from the disk and the contents are loaded into
the SQL property. The lines that perform this task are:

266 n Purt Ill--The Well-Rounded Application


Queryl.Close;
Queryl.SQL.LoadFromFile(
'D:\BProjects\Chapter8\SQLSCRIPT.TXT');
Query1 .Open;

Two important actions occur automatically using this technique. First,


the string list in the SQL property is cleared by LoadFromFile so you
are always ensured of a clean slate for your statement. Secondly, the
Query component prepares the SQL statement for execution, alleviating
the need for an explicit call to the Prepare method. Review your
requirements carefully as there are many circumstances which will
require an explicit call to Prepare.
Parameterized queries extend the infinite flexibility of a SQL statement
by substituting user values into the sentence at run time. Parameters
used in a Query component have two requirements for use: They must
be predefined and the statement using them must be prepared, binding
the values to the parameters. The first requirement takes a little
thought if we are to use a single Query component in the project as the
target for our external query statements. Parameters must be defined in
the Params property before they are recognized in the SQL statement.
The following two lines take care of this requirement:
Params.CreateParam(ftString, 'WhatState', ptInput);
Params.ParamByName('WhatState').AsString
:=
ListBoxl.Items[ListBoxl.ItemIndex];

The first line adds a parameter definition to the Params list and the second assigns the value from the list box control to it. With the parameter
issue addressed, the rest of the code block is straightforward:
with Query1 do
begin
Close;
Params.CreateParam(ftString, 'WhatState', ptInput);
Params.ParamByName('WhatState'.AsString
:=
ListBoxl.Items[ListBoxl.ItemIndex];
SQL.LoadFromFile(
'D:\BPROJECTS\CHAPTER8\SQLPARAMS.TXTi);
Prepare;
Open;
end;

The completed SQLLFFX project is shown in Figure 8.10.

Addressing the SQL String List Directly


After seeing the two alternative methods for adding the SQL sratements
to the Query components SQL property, this section might seem a bit
anticlimactic. There is no great mystery to loading the property using
the Add method. The code block below shows that the process is
straightforward:
with Query1 do
begin
Close;
SQL.Clear;
SQL.Add('SELECT
Open;
end;

* FROM country');

This simplified method lends itself well to taking a building block


approach to the construction of the SQL sentence. To show what this
means, consider the same statement shown above built from blocks:
with Query1 do
begin
Close
SQL.Clear;
SQL.Add('SELECT
');
SQL.Add('* ');
SQL.Add('FROM
');
SQL.Add('country');
Open;

a;.
#&
.": Ev
n

end;

This statement will work the same way as the original statement, ignoring all of the white space when the sentence is parsed. By building the
statement piece by piece, it is a simple operation to substitute one pan
for another. Lets assume that we want to allow the user to select all of
the fields from the COUNTRY table, as demonstrated in the two examples, but we want to give them the option of using a WHERE clause in
the statement to apply some selection criteria.
i,
Adding an Edit box control to the application allows the user to type a
clause for the SQL statement without having to worry about structurinz
the rest of the sentence. Modifying the SQL statement is as simple as
adding another block, in the case of the WHERE clause:
with Query1 do
begin
Close
SQL.Clear;
SQL.Add('SELECT
');
SQL.Add('* ');
SQL.Add('FROM
');
SQL.Add('country');
SQL.Add(Editl.Text);
Open;
end;

Taking a look at the rest of the statement, the design side of your brain
is probably buzzing with ideas for the ultimate SQL Builder that is
based around this method. How about CheckListBox controls that list
all of the fields available or push buttons for the equals, greater than,
or less than symbols?

DBChart Component
The DBChart control is another of Delphis hidden gems that is available to lend additional features to your application. The class is derived
from the TChart class and is extended to utilize a dataset for the source
of its data series. Using the columns of a dataset for its source, the
chart becomes a dynamic display that is updated as the dataset is modified. You have a wide variety of chart type options to choose from and
all of the parameters of the display can be controlled within an application. Through interface controls that you provide, the user can spend

:G

3
39
.!{
?I

~,I*L-*pM~--- _..__ r_ ,~~~..ij __i _i_,

Chapter 8-Tips and Techniques n


. . _ .,_, _ __*X__=..-jl/_=lfl__^=l/ii_,( ,=*- -1 x/
ii** -a-, i

21

hours changing the look and feel of their charts rather than playing
solitaire.

:;*>4
.:
All of the above-mentioned things are true, if you can solve the
-$
design-time interface! Like QuickReport, this set of controls (TChart, ,y;$
TDBChart, and TQRChart) is provided to Delphi from an outside ven- .$$j
dor and it takes a little getting used to in order to become proficient ;K.I+$$
its use. Charting also requires the knowledge of a new set of terminol- f:
ogy that applies to the properties and methods. Figure 8.11 is a table of?:;-:
selected key properties sufficient to get you started with the charting -g
tools.

sible
BufferedDisplay

True

Hides the four Chart axis at once.

True

Determines whether or not the chart is

These settings determine the rectangle


that contains the chart.
This indicates the total height in pixels
1between the top and bottom Chart ax&
positions.

ChartBounds
ChartHeight

This property determines the


1 rectangular space defined by the four
t .Chart axis.
This indicates the total width in pixels
between the top and bottom Chart axis
positions.
This property determines the text and

drawing attributes of Charts textual


12
7
representation of Series and Series
1:

ChartRect

ChartWidth

Legend

MaxPointsPerPage

i.;

The property controls the AutoPaging.

Monochrome

Allows charts to be drawn in


monochrome.

Series

The series are the collections of data


values.

SeriesList

This array contains a list of all of series

View3d

This determines whether or not the


chart is drawn in three dimensions.

Figure 8. I I Selected properties of the TDBChart component

..

Figure 8. Ii?
The completed

DBChart
project

Well go step by step to build this first chart before applying some of the
different options,
I. Start a new application and add a DataSource, a Query, and a DBGrld
to the form. Create the chain pointing the DBGrid to the DataSource
and the DataSource to the Query.
2 . Set the Querys DatabaseName property to DBDEMOS. The SQL
statement used to build the dataset is:
SELECT Count(*) Customers,
WHERE State <> "
GROUP BY State
ORDER BY State

State

FROM

Customer

Add this to the SQL property and activate the query to ensure that it is
functioning correctly.
3. Add a DBChart component to the form and set the Align property to
alclient. There are TWO avenues to use when approaching the properties of the chart, though they both lead to the same place, the Chart
Editor. The easiest route to get to the form shown in Figure 8.13 is to
right-click on the DBChart.

and Techniques II

27

4, Click Add in the Series tab. A series is the set of data elements to be
charted. Multiple series are used on a chart when comparing one set
of figures against another. The first thing that you are asked to select
is the representation, or type of chart to build. Select the Horizontal
Bar chart from the TeeChart gallery and click OK.

!
f
!
I
/

The selection of a graph type is determined by the message that you are
trying to convey and the data that is involved. A bar chart is excellent
for conveying a sense of comparative amounts but would not tell the
viewer how much of a whole each bar represents. We selected a horizontal bar chart simply to make the labels describing the states more
legible. On the other hand, a pie chart quickly tells the viewer how
much of the whole picture is made up by each segment. Line charts
show a connected series of points, representing changes in a single data
element over a period. The bar chart is selected for this data because
we want to quickly ascertain where the bulk of our orders come from.
5. Double-click on the new series or select the Series tab to edit the
source for Seriesl. Select the Data Source tab. Since were going to
use a dataset object as the source for the series, select Dataset from
the drop-down menu.
6. The tab is now filled with choices that are appropriate to the data
source that youve selected. Start by setting the newly displayed
Dataset property to Queryl. The next three choices determine the
way that the information will appear on the graph. The Labels property is used to draw labels for each of the bars in the graph, Select the
State column for those values. The Bar property determines what
value each bar is representing; for this well use the Customer column
that contains the counts of customers by state.

7. Click Close and your chart will be generated. If any changes are made
in the dataset, the chart will be automatically redrawn as long as the
AutoRefresh property is True.
8. Lets view the data in a different format to see if the information is as
clearly disseminated as it was with the bar chart. Start the Chart Editor and select the Chart tab. Series1 should be selected already so click
Change. Youll see the TeeChart gallery again. Select the pie chart
from the available choices. Notice that the types of charts have been
limited by the original dataset decisions. Click OK to make the choice
and your chart will be redrawn as a pie chart. When the data is viewed
in this format, it is not as clear what you are viewing and it doesnt
have the quick impact of the bar chart. Change the chart type back to
Horizontal Bar chart.
9. The title of the chart is somewhat misleading when the default value
is used. Click on the Titles tab, and in the edit region name the chart
Customer Distribution by State. Click Close to complete the
chart and view the newly painted control.
This completes the bar chart for this project. An alternative to building
the report by hand is to use the TeeChart Wizard. This is accessed by
selecting File ) New from the Delphi menu and then selecting the Business tab of the repository. The wizard will start by asking you what type
of chart you want to create, giving you a choice between the Database
Chart and the Non Database Chart. Selecting the Database chart then
asks you to select a table to use as the data source. Select the fields you
want included on the chart and then the type of chart to display them
on. A new form that contains the dataset and the DBChart is generated
for you, completing the process.

Alternatives to the Borland Database Engine


Among the advantages of the separation of the BDE from the internals
of Borlands development products is the freedom of choice. The open
architecture of the Delphi development environment allows you to
replace the database engine with other, sometimes better, products,
Why would you want to consider this avenue? The reason that is most
often listed supporting the replacement of the BDE is size and overhead. When you create a Delphi application and prepare it for
distribution, you must also include the redistributable portions of the
BDE. This can add enormously to the size of your distribution media
and to the complexity of the installation.

Chapter 8-Tips and Techniques n


_-- .
I

273
r*lr.~~

Alternative database engines are available that bind directly to your


application. This means that the engine becomes a part of the executable file, releasing you from the overhead of the external engine. When
you distribute your application, only a single executable is required,
which simplifies installation. In addition, there are no configuration
issues involved as there can be with the BDE, setting up aliases and
such. This type of replacement is generally referred to as a thin BDE
replacement.
An alternate strategy to this approach, and one that is required for
multi-user access, is to scale your application up to a client/server format. Using this strategy, all database transaction work occurs at the
database server. The client code that is compiled into your executable
only makes requests for data from the server. This approach is less
about compressing the installation and more about performance and
security Lets contrast the approach of the BDE and a typical client/server system.
When a database transaction occurs within the scope of an application
using the BDE, the database file is opened and closed in response to the
data request. This is because the BDE is a file manager system. All of
the users of your application are opening and closing the same files
through the auspices of the BDE. As you will discover when you build
networked applications, there are high management requirements for
locking and unlocking the rows and tables to maintain the integrity of
the data.
A client server system takes a much different approach to handling the
database transactions. The data tables are secured behind the server
portion of the application. The client applications that sit on your users
desktops merely request that the server perform some transaction,
whether it is a read or a write to or from the database. When the server
receives the request, the concurrency controls of the server manage the
integrity constraints, returning or accepting the transaction from the
client without exposing the data tables to them. This approach offers a
superior level of data integrity.
The following information gives you a brief idea of what is available to
the Delphi programmer. It is not to be considered exhaustive, and complete coverage of each of the listed tools is beyond the scope of this
book. Contact information is provided for each of the products so that
you can explore them more fully.

274 n Part //l-The Well-Rounded Application


I _ I~i__~~~~,ezw.C.~

;-x

*/l~~~~~~,.~~~_^_/_~,.~~~~~~~~~~~~r)~.~li-*~~~.~y~~idl~^ii

.li__ccuIeL-mIIIII*-----~

Local InterSuse Server (LIl3S)


The Local InterBase Server was seen earlier in the book when we discussed stored procedures. Though we used the BDE for access, this is
not necessarily the required method. The LIBS can be accessed directly
in a client/server mode, bypassing the BDE. Delphi provides a component, TClientDataSet, that can address the LIBS directly, giving you the
advantages outline above.
InterBase connectivity is included with Delphi, making it an excellent
choice for exploring the non-BDE options. Being a component-based
solution and being able to initially address the local server make this an
excellent tool for learning to build client/server applications. Another
advantage of this approach is that scalability is excellent; the move
from local to remote database access is simplified because the change
from the local server to a remote server is nearly transparent.
InterBase 5.0
InterBase Software Corporation
1800 Green Hills Road, Suite 150
Scotts Valley, CA 95066
(408) 430-1500
www.interbase.com

The Advantage Database Server


Similar to InterBase, the Advantage Database Server is a
high-performance, client/server database solution. There is a suite of
Advantage products that gives the Delphi developer a choice in how to
address the Advantage Server. There is a BDE Alternative, a DLL that
intercepts BDE calls and translates to Advantage Client Engine API
functions. Using this tool eliminates the need to use or distribute the
BDE with your application. Delphi applications can use the TTable class
or Advantages TAdsTable component in an application, lessening the
learning curve.
Advantage Database Server
Extended Systems
5777 N. Meeker Ave.
Boise, ID 83713
(800) 235-7576 x5030
www.advantagedatabase.com

v..

.-

.$

.x
-:
*,&Atj

FlashFiler is another BDE replacement that points up a possible sticking


point for developers that hasnt been mentioned yet. FlashFiler (as well
as Advantage) works with its own proprietary data file format. This fact
must be carefully considered over the lifespan of your application to
ensure that this will not interfere with any future modification or
expansion plans. Though the data file format is proprietary, all of the
Delphi data controls work with it so your interface development is not
hindered.
An advantage of the FlashFiler software is that the developer has the
option of creating a single database file that contains all of the data
and index files, similar to Access. On the disadvantage side of the ledger is that SQL is not supported, so Query components cannot be used
in your application.
FlashFiler 1.5
TurboPower Software Company
EO. Box 49009
Colorado Springs, CO 80949-9009
(800) 333-4160
www.turbopower.com

-1:9
-3

-T:$

.$
.*.!. *
:zFy3-,
:I)r
_., ,
;.&&.&+
.S\
Q$4

it

Included in2X.s Chapter:


n

API Architecture

Using the API

API Functions

T he Borland Database Engine exposes a rich collection of API func-

tions to the Delphi programmer. Though its generally better practice to


perform the vast majority of your database development through the
use of the visual components, there are many non-trivial tasks that are
better performed through direct calls to the API functions. In fact, there
is some functionality, packing dBASE tables for instance, that can only
be performed through an API call. The BDE API functions are available
to any programming language that can access Windows DLL files, and
Delphi is no exception.
Using the API in a Delphi application is not an overly complicated matter but the programmer must be willing to learn the sequence and
architectural requirements of the engine before forging ahead. There
are potentially destructive functions surfaced through the engine that
can quickly wipe out or damage your applications data, or expose wide
security gaps if all of the engines requirements are not met. This chapter will begin with a discussion of the architecture of the API followed
by a boilerplate API application. The template can be used as a guide to
implementing the functions in your own programs.

277

API Architecture
The BDE API architecture unites the two types of database systems that
have developed through the years, supporting both through an interface unique to the BDE. PC-based database systems have developed
along the model of ISAM (Indexed Sequential Access Method) database
systems. The ISAM model is a navigable file system in which directions
such as Previous and Next have meaning to the file structure.
SQL-based systems found on servers do not, in theory support such
navigation. As we discussed in earlier chapters, SQL databases are set
oriented with no specific order to the records.
The BDE takes the best of both of these worlds and connects to either
type through its common interface. The cursor-based engine extends
the most powerful features of both types of databases into the other.
The navigational features supported, for example, are drawn from the
ISAM design. SQL tables not having a directional property can now be
navigated through a common set of functions while providing the rich
capabilities of the SQL command set to the mix. ISAM databases, in
return, can then benefit from the set-oriented query capabilities inherent with SQL databases.
The common functionality supported by the API, using a native BDE
driver or an ODBC driver, encompasses the following:
Opening and closing of databases
Getting and setting the properties of BDE objects
Accessing and manipulating the data stored in each type of database
Database definition, such as creating tables and indexes
Operations that are performed across database systems, such as
copying and joining tables

Using the API


In planning your approach to a programming task, take note that it will
forever be easier to utilize the BDE functions through the visual components provided in Delphi. Application efficiency or functional need, on
the other hand, may drive you to explore the possibility of accessing
the engine directly through the API. As with all things programming
oriented, there is a specific sequence of steps required to be successful
at implementing this access. Each of the required steps is described in
the following paragraphs.

_ilx.iml279*>

Chapter 9-The Borland Database Engine API n

__,.-*f*s-m------~ ***se-= I~IPClbBI*6~~*-1-~11iii--~~-e~*h-V-(.LX

>VL _._l _ii^_G_

_S,~W

.*o__i._

._

Step One: Initialize the BDE


Initialize the BDE by using the DbiInit function:
Dbi Ini t(NIL)
Rather than the default NIL, you can optionally pass a pointer to a
structure of type DBIEnv. This data structure allows you to pass parameters that change the working directory and the location of the
configuration file, set up a different language driver, and pass a client
name to the BDE. If a null is passed as the parameter, the starting directory will be the working directory and the client name will be empty.

Step Two: Open a database object


This step will connect to a database. The BDE accesses all tables
through the context of a database. Single-tier applications will generally create an alias to a file system directory and utilize that as the
database context. The function DbiOpenDatabase is used to open a
database:
DbiOpenDatabase('DBDEMOS','STANDARO',
nil, 0, nil, nil, hDB)

dbiREADWRITE,

dbiOPENSHARED,

This sample function call opens the DBDEMOS database, assuming that
it has been properly configured within the BDE. The second parameter,
STANDARD in this case, determines the type of database that is being
accessed. A database can be opened in either Read/Write or Read only
modes and in Shared or Exclusive mode. These choices are set through
the settings found in the third and fourth parameters of this statement.
The remaining parameters are optional with the exception of the crucial database handle found in the last slot.
DbiInit must be called prior to calling this function and
DbiOpenDatabase must return a satisfactory result value prior to
attempting any table access. The success of any BDE function is determined by querying the return value of the function. This result is
returned as a type DBIResult. This data object will tell your application
if an error was encountered; a result of DBIERR-NONE,
no errors,
means that the function call was a success.
When an error occurs, the BDE will place much more detailed information about the error onto the BDE error stack. These error contexts can
be examined further through a set of error handling functions built into
the BDE. These are:

280 n

Part Ill-The Well-Rounded Application


n

DbiGetErrorEntry

DbiGetErrorString

n DbiGetErrorContext
n

DbiGetErrorInfo

Further information about these functions is contained in Appendix A.

Step Three: Set the database object to point to the directory


containing the table.
With the database open, you must set the table directory. The working
directory is the directory in which the BDE expects to find the data
tables when no specific path is provided. Use the DbiSetDirectory function to set the working directory:
DbiSetDirectory( hOb, 'D:\CMT32\data')

The database handle is passed in several API functions; in


DbiSetDirectory, it is the first parameter. This variable is instantiated as
a data type of hDBIDB, a type declared in the file BDE.INT. This file is
located in the ..\DelphiLF\Doc directory and contains declarations for all
constants, types, and data structures used by the BDE.

Step Four: Set the directory for temporary objects


Although it is not required for all projects, some BDE sessions will
result in the creation of large temporary objects. This step sets up the
required temporary directory using DbiSetPrivateDir:
DbiSetPrivateDir('D:\CMT324\Temp')

This directory should be one that is not used by any other programs
and must be write accessible, a consideration if this application is
installed over a network.

Step Five: Open a table, creating u cursor object


Finally, we get to open a table! Opening a table results in a cursor
object, an abstraction that lets you access queries and tables in the
same method. The DbiOpenTable function has an extensive parameter
list:
DbiOpenTable(hDb,'DOCTORS.DB',nil, nil, nil,
dbiOPENEXCL, xltFIELD, False, nil, hTb1)

0,

dbiREADWRITE,

1 refer you to Appendix A again for the data type and descriptions of
each of the parameters in this extensive list. The sample function

<I-I-xlm(iiielixl--.---

. ,r

I.,l^ . .

Chapter 9-The Borland Database Engine API n 286tT


__._9
(
.*rr,*x*w -j

demonstrates one of the great design advantages of the Borland Database Engine. Being a driver-based tool, the same function is used to
open all types of supported files including text, dBASE, Paradox, and
SQL files, regardless of their underlying architecture.

__
11
j
-.%:

Step Six: Get the properties ofthe table


The cursor properties retrieved in this step include the table name, size, 2;
.5
type, number of fields, and record buffer size. The properties are
3
returned from the function DbiGetCursorProps:
DbiGetCursorProps(hCur,

CursorProps)

CursorProps is another structure defined in BDE.INT. It is a record with


numerous fields used to gather all salient information about the cursor
for your applications use. Note that the use of the word return is
!j
incorrect in the proper programming sense of a function declaration.
;:i
API functions return a data type of DBIResult. Structures and variables $
that receive values through the execution of a function are passed as
-ey$$3
either a pointer to a structure or variable or as a variable parameter.
;$
Understanding both of these methods of parameter passing is crucial to .i::
4
successful usage of the API functions.
2
Step Seven: Allocate memory for the record buffer
Using the cursor properties obtained in the previous step, you must
allocate memory for the record buffer:
GetMem(pRecordBuf,

CursorProps.iRecBfSize

Sizeof(BYTE))

Be sure to verify this step by checking the size of the allocated buffer
space. If the buffer has not been established, further steps in this process will be unsuccessful.
Step Eight: Position the record point on the appropriate record
This step introduces some new vernacular to the developers vocabulary
in the use of the word crack. Cracks are the imaginary lines that separate each record in a database. Using this terminology allows you to
f
envision the record pointer being positioned before the first record in a
table or after the last. You can also place it between two records, committed to neither. Use the DbiSetToBegin function to set the pointer to
the crack before the first record:
DbiSetToBegin(hCur)

Crack semantics also lets you use a single method to access all records
in the table by using DbiGetNextRecord.
Step Nine: Retrieve the desired record from the cursor
Finally, after hours of toil we are coming closer to achieving our goal of
retrieving data from the database. Because were sitting on the crack,
we can use the DbiGetNextRecord function to retrieve the structure:
j
DbiGetNextRecord(hTb1,

dbiREADLOCK,

RecBuffer,

nil)

If a record buffer is passed in the parameter list, the data for the record
is placed in the buffer. When NIL is passed, instead of a pointer to a
record buffer, no data is retrieved and the positioning on the record is
the only result.
Step Ten: Get the desired fields from the record
The object of our desire is in sight now. This step will retrieve the data
from the record and place it into local variables for use by the application. The DbiGetField function retrieves the contents of the specified
field from the record buffer:

3
* .g$4:f

::$
:,
.>?

DbiGetField( hTb1, 2, RecBuffer, szFld, bBlank)

The final parameter, bBlank, is a Boolean variable that tells you


whether or not the field you are querying is blank.

i
.i

This function relies on an ordinal to describe the field to be retrieved.


DbiGetFieldDescs is a function that will retrieve the ordinals and field
names in a data structure for your use in circumventing this
requirement.
Step Eleven: Clean up afier yourself
Any time memory is manually allocated in a program, good programming practice states that you must deallocate it. All of the structures
and buffers that you have manually allocated in the previous steps
must be removed from memory
Free(pRecordBuf)
DbiCloseCursor(hCur)
DbiCloseDatabase( hDb )
DbiExit()

{
{
{
(

free record buffer }


close the cursor }
close the database }
close the BDE }

Upon closer review of the steps involved, you might reconsider the
amount of effort involved against the eventual payoff. Listing 9.1 is a
template program, provided by Borland, that has been converted from

.;
,;+f
I. ;$

:.,s
.
c'"?
?

*
(:
Tii

C to Object Pascal. It demonstrates each of the steps described above.


Because it was not meant to be a finished application, you will notice
that the error handling is limited to describing the error message and
returning to the code. You should plan on improving the VerifyDBI procedure before putting this to use in a critical application.
unit BDETmplt;
i
I
i
(

1
BDE API Template
Copyright 1998 Borland Corporation

1
1

Converted to Delphi - Warren Rachele

I
1

interface
uses
Windows, Messages,
Dialogs,
StdCtrls, BDE;

SysUtils,

Classes,

type
TForml = class(TForm)
Buttonl: TButton;
Labell: TLabel;
procedure ButtonlClick(Sender:
private
{ Private declarations }
public
{ Public declarations )
end;

Graphics,

TObject);

var
Forml: TForml;
implementation

($R *.DFM)
procedure VerifyDBI( inResult.Code : DBIResult);
var
ErrorMsg
: DBIMsg;

Controls,

Forms,

284 n

Part Ill-The Well-Rounded Appkation


begin
if not (inResultCode = DBIERR- NONE) then
begin
DbiGetErrorString( inResultCode,
ErrorMsg );
ShowMessage('Error
' + IntToStr( inResultCode
StrPas( ErrorMsg));
end;
end;
procedure
TForml.ButtonlClick(Sender:
var
hDb
: hDBIDb;
hCur
: hDBICur;
CursorProps : curProps;
pRecordBuf : pBYTE;
buffer
pFieldVa1
: string;
isBlank
: bool;
CustNum
: real;
begin

) + ': ' +

TObject);
,// handle to the database
// handle to the cursor
// cursor properties
// pointer to the record
i/ variable for field data
/,/ is the field blank

{ - init the database / cursor handles - }


hDb := nil;
hCur := nil;
{ - Step one - initialize the engine - )
SnowMessage('Step
1: Initialize Engine');
VerifyDBI(DbiInit(ni1));
( - Step two - Open a database object - 1
ShowMessage('Step
2: Opening database');
{ database name }
VerifyDBI(DbiOpenDatabase('DBDEMOS',
'STANDARD',
{ database type }
DbiREADWRITE,
( mode for opening the
database }
dbiOPENSHARED, { open as shared or
exclusive )
{ password if needed )
nil,
{ number of optional
0,
parameters }
nil,
( field desc for optional
parameters )

_,..,.%_.

Chapter 9-The Borland Database Engine


, _
,_
. l%**ea
nil,
hDb)) ;

{ values for opt parameters']


{ database handle }
^ :

{ - Step three - set the table directory - }


ShowMessage('Step 3: Setting Table dir.');
{DbiSetDirectory( database handle, table directory)}
VerifyDBI( DbiSetDirectory( hDb,'C:\Program Files\Common
Fi les\Borland Shared\Data'));
( - Step four - set the temp directory - }
ShowMessage('Step 4: Setting temp dir');
VerifyDBI( DbiSetPrivateDir('C:\Windows\Temp'));
{ - Step five - Open table/establish
ShowMessage('Step 5: Open table');
VerifyDBI(
DbiOpenTable(hDb,
'customer.db',
nil,
nil,
nil,
0,
dbiREADWRITE,
dbiOPENSHARED,
xltFIELD,
false,
nil,
hCur));

cursor - }
( handle to database }
( table name (ext optional
{ driver type - not needed
if ext used )
{ index name
{ index tag
{ index ident fier }
{ open mode }
{ shared or e X elusive use 1
{ translation mode - almost
always xltFIELD }
{ determines if cursor is
unidirectional
}
{ optional parameter }
{ handle to the cursor }

{ - Step six - get the cursor properties - }


ShowMessage('Step
6: Get Cursor Properties');
VerifyDBI( DbiGetCursorProps( hCur, CursorProps));
{ - Step Seven - allocate memory for record buffer - )
ShowMessage('Step
7: Allot memory for Record Buffer');
GetMem( pRecordBuf,
CursorProps.iRecBufSize * SizeOf(BYTE));
if pRecordBuf = nil then
{ - Not enough memory - }
ShowMessage('Record
buffer
else
begin

allocation

failure')

{ - Step Eight - if buffer good, set record pointer - }


ShowMessage('Step
8: Set Crack');
VerifyDBI( DbiSetToBegin(hCur));
( - Step Nine - go to the desired record - )
ShowMessage('Step
9: Goto Record');
( cursor to get record
VerifyDBI(
DbiGetNextRecord(
hCur,
from 1
dbiNOLOCK, { lock type 1
pRecordBuf,( record buffer }
nil));
{ record properties optional}
( - Step Ten - get the desired fields from the record - )
ShowMessage('Step
10: Get field data');
VerifyDBI( DbiGetField( hCur,
{ cursor handle }
{ ordinal value of field }
1,
pRecordBuf, { record buffer )
{ variable to receive
@CustNum,
data }
{ is the field empty?
IsBlank));
boolean }
labell.Caption

:= FloatToStr(CustNum);

end;

{ - Step Eleven - shut it down - ]


ShowMessage('Step
11: shut it all down');
if pRecordBuf -z> nil then
freemem(pRecordBuf);
if hCur c> nil then
VerifyDBI( ObiCloseCursor(
if hDb <> nil then
VerifyDBI( DbiCloseDatabase(
VerifyDBI(DbiExit);
end;
end

Listing 9.1 Tem@te program

hCur ));

hDb ));

Chapter 9-The Borland Database Engine API

__-__ >.a_li__i(_aj_/. _see*l_i(

. I-li,.ea.ea

Rather than using the API directly to build your database applications, :$
the more likely scenario is one in which you will combine the ease of T
development that comes with the visual components and the depth
d
available through the API. Using the API to perform selective tasks
allows you to accomplish some items that cannot be performed throu;
the visual components. The first of these is packing a dBASE table.
:

P a c k i n g dB.A S E Tables
The deletion of records in dBASE tables is a deceiving process. When ,-i
you request a deletion from a table, the record is not deleted; the row .$
:$
is merely marked for deletion. The advantage of this methodology is
that you have the ability to instantly recover deleted records. The dis- --.
advantage is that over time these marked records continue to take up
space in your table, slowing down your table processing. The solution r
to rhis problem is a process called packing. The Packing procedure
.s
removes the deleted rows and reclaims the space. In addition, with the ,li
correct parameter settings, all associated indexes are rebuilt in the
.\?
,.
+g
same operation.
,+:*zJ
The code shown in Listing 9.2 demonstrates a call to the API function 1-i
DbiPackTable. The table at which this procedure is directed must be

jq
opened in Exclusive mode, which might require that you close and
; .$
jj
reopen the table. Also be sure to follow good programming practice
and return all objects to their original state.
procedure dbasePack(InTable
: TTable);
var
: boolean
bHoldExc1
bHoldStat
: boolean
ResCode
: DBIResu 1t;
: DBIMSG;
EMessage
begin
{ - Hold current state - )
with InTable do
begin
bHo ldExc1 := Exclusive;
bHo 1dStat := Active;
I - table must be open/exclusive to pack - }
Close;
Exclusive := true;
Open;

-^
c

288 n

Part Ill-The Well-Rounded Application

.r.rrressrerrr~s

end;
ResCode

:=

DbiPackTable(InTable.DBHandle,
InTable.Handle,
nil,
nil,
True);

if ResCode <> DBIERR - NONE then


begin
DbiGetErrorString(
ResCode, EMessage
ShowMessage( EMessage );
end;

);

( - Restore state of table - 1


with InTable do
begin
Close;
Exclusive := bHoldExc1;
Active := bHoldStat;
end;
end;

Listing 9.2 Calling the API function DbiPackTuble

Packing Paradox Tables


Paradox tables do not need to be packed as often as dBASE tables.
When a record is deleted from a Paradox table, that space is made
immediately available to new entries. The situation may arise where a
large number of records are deleted all at once, leaving a lot of unused
space in the table that will not be immediately filled. To pack Paradox
tables you are required to use a different function, DbiDoRestructure.
Working with Paradox tables requires that you use a structure called
CRTblDesc, a table descriptor. This structure, declared with all of the
other BDE primitives in BDE.INT, passes information to the engine
about the structure of the table and the modifications that you want to
make. This structure, in addition to the purposes we are exploring here,
is used to add fields, modify their size or type, etc.
Listing 9.3 shows the steps needed to implement the function. A bit
more complicated than the simple DbiPackTable function, the function
call requires some setup prior to calling it. The tables name, for

Chapter 9-The Borland Database Engine API


wes.-e.aa_ e..? .___, ._il_,_~*_ _i.._ ~~_~~s../jl__ii_l_..~l~_i~~-=~-~~~~ *_ a- _I ; Ir

example, must be copied into the descriptor table as a nullstrinp. To arcnmnlish this. we must first oerform the conversion
throigh the use of the StrPCopy procedure.
procedure ParadoxPack(InTable : TTable);
var
bHoldExc1
bHoldStat
ResCode
EMessage
PdoxTableDesc

:
:
:
:
:

boolean;
boolean;
DBIResult;
DBIMSG;
CRTblDesc;

begin
ResCode

:=

DBIERR-NONE;

( - Hold current state - }


with InTable do
begin
bHoldExc1 := Exclusive;
bHoldStat := Active;
{ - table must be open/exclusive to pack
Close;
Exclusive := true;
Open;
end;

- 1

{ - Fill the descriptor table - }


FillChar( PdoxTableDesc, SizeOf(CRTblDesc),
0);
StrPCopy(
PdoxTableDesc.szTblName,
InTable.TableName);
{ - tell the desc table that you want to pack - }
PdoxTableDesc.bPack := true;
ResCode

:=

DbiDoRestructure(InTable.DBHandle,
1,
BPdoxTableDesc,
nil,
nil,
nil,
false);

if ResCode <> DBIERR NONE then

!
/,

begin
DbiGetErrorString(
ResCode,
ShowMessage( EMessage );
end;
( - Restore
with InTable
begin
Close;
Exclusive
Active
end;

EMessage

);

state of table - 1
do

:= bHoldExc1;
:= bHoldStat;

end;

Listing 9.3 Implementing the DbiDoRestructure function

Examining a Paradox Table


Internal validity checks are an important feature of Paradox tables. As
the tables are established, constraints can be established for each column that go into immediate effect when the table is opened. To
examine these validity checks, as they are known in the Paradox vernacular, the standard process had been to view them through the
window of the Database desktop. The function DbiOpenVchkList provides a different way of attacking this problem.
Listing 9.4 shows an OnClick event handler that handles this task. The
DbiOpenVchkList creates an in-memory cursor that contains specifics
about the validity checks set for each field in the table. The sample uses
the table NEIGHBOR.DB included on the CD-ROM. The parameter
ValidityBuf is a record buffer of type VCHKDesc. From this record
buffer the discrete fields are extracted that contain the data shown in
the memo component.
procedure TForml.ButtonlClick(Sender:
var
szTableName : DBIName;
ValChecks
: hDBICur;
ValidityBuf : VCHKDesc;
Result
: DBIResult;

TObject);

begin
StrPCopy( szTableName,

Tablel.TableName

);

( - Create a table with the validity checks - }


DbiOpenVChkList(
Tablel.DBHandle, szTableName, NIL, ValChecks
repeat
{- Spin through all records in the table - }
Result :=
DbiGetNextRecord(ValChecks,
dbiNOLOCK,
nil);
if (Result <> DBIERR - EOF) then
begin
Memol.Lines.Add('Field
Number: ' f
IntToStr(ValidityBuf.ifldNum));
If
ValidityBuf.bRequired = True then
is required: TRUE')
Memo 1 .L i nes.Add('Field
else
is required: FALSE');
Memo 1 .L i nes.Add('Field
If Val i di t yBuf.bHasMinVa 1 = True then
Memo 1 .L i nes.Add('Has M inimum Value: TRUE')
else
Memo .L i nes .Add('Has Minimum Value: FALSE');
If Val di t YBU f.bHasMaxVal = True then
Memo .L i nes .Add('Has Maximum Value: TRUE')
else
Maximum Value: FALSE');
Memo .L ines.Add('Has
If Val di tyBuf.bHasDefVal = True then
Default Value: TRUE')
Memo .L ines.Add('Has
else
Default Value : FALSE');
Memo .Lines.Add('Has
end;
until Resu

<> DBIERR - NONE;

{ - Release Validity Checks Cursor - 1


DbiCloseCursor(ValChecks);
end;
Listing 9.4 Performing a validity check

);

-' 9
,%*I
2::.
1

@ValidityBuf,

292 H Part Ill-The We/l-Rounded Application

API Functions
To conclude this chapter, the following lists categorize the API functions
by functional groups. Scan this list when making the decision on how
best to accomplish a task in your application.

Capability or Scheme functions


These functions return information regarding a tables set of capabilities or the structure of the table itself.
DbiOpenCfgInfoList
DbiOpenDatabaseList
DbiOpenDriverList
DbiOpenFamilyList
DbiOpenFieldList
DbiOpenFieldTypesList
DbiOpenFunctionArgList
DbiOpenFunctionList
DbiOpenIndexList
DbiOpenIndexTypesList
DbiOpenLockList
DbiOpenRintList
DbiOpenSecurityList
DbiOpenTableList
DbiOpenTableTypesList
DbiOpenVchkList

cursor Functions
These functions either perform an action on a cursor, such as positioning, linking, or creating and closing a cursor, or return information
about a cursor.
DbiActivateEilter
DbiAddFilter
DbiApplyDelayedUpdates
DbiBeginDelayedUpdates
DbiBeginLinkMode
DbiCloneCursor

293
DbiCloseCursor
DbiCompareBookMarks
DbiDeactivateFiLter
DbiDropFilter
DbiEndDelayedUpdates
DbiEndLinkMode
DbiExtractKey
DbiForceRecordReread
DbiForceReread
DbiFormFullName
DbiGetBookMark
DbiGetCursorForTable
DbiGetCursorProps
DbiGetExactRecordCount
DbiGetFieldDescs
DbiGetLinkStatus
DbiGetNextRecord
DbiGetPriorRecord
DbiGetProp
DbiGetRecord
DbiGetRecordCount
DbiGetRecordForKey
DbiGetRelativeRecord
DbiGetSeqNo
DbiLinkDetail
DbiLinkDetailToExp
DbiMakePermanent
DbiOpenTable
DbiResetRange
DbiSaveChanges
DbiSetFieldMap
DbiSetProp
DbiSetRange
DbiSetToBegin
DbiSetToBookMark

294 n
,-vIcbBe-

Part Ill-The Well-Rounded Application


..,l_- , ~(_i_ x,^i ,_,.. 1_ ,e,.;. s, __- .A . _I
-~B_~-~ij..ll__r
_.

i_, .~w--wz..-m-.e-

DbiSetToCursor
DbiSetToEnd
DbiSetToKey
DbiSetToRecordNo
DbiSetToSeqNo
DbiUnlinkDetail
Data Access functions
These functions perform data access operations on tables.
DbiAppendRecord
DbiDeleteRecord
DbiFreeBlob
DbiGetBlob
DbiGetBlobHeading
DbiGetBlobSize
DbiGetField
DbiGetFieldDescs
DbiGetFieIdTypeDesc
DbiInitRecord
DbiInsertRecord
DbiModifyRecord
DbiOpenBlob
DbiPutBlob
DbiPutField
DbiReadBlock
DbiSaveChanges
DbiSetFieldMap
DbiTruncateBlob
DbiUndeleteRecord
DbiVerifyField
DbiWriteBlock
Database Functions
The BDE functions listed here perform database-level tasks or return
information about a database as a whole.

DbiCloseDatabase
DbiGetDatabaseDesc
DbiGetDirectory
DbiOpenDatabase
DbiOpenDatabaseList
DbiOpenFileList
DbiOpenIndexList
DbiOpenTableList
DbiSetDirectory

Date/Time/Number

Functions

These functions encode and decode date and time objects and set or
retrieve the current numeric or date and time formats for a session.
DbiBcdFromFloat
DbiBcdToFloat
DbiDateDecode
DbiDateEncode
DbiGetDateFormat
DbiGetNumberFormat
DbiGetTimeFormat
DbiSetDateFormat
DbiSetNumberFormat
DbiSetTimeFormat
DbiTimeDecode
DbiTimeEncode
DbiTimeStampDecode
DbiTimeStampEncode

Environmental Functions
The BDE environment functions return information pertaining to the
application environment. This includes the supported table, field, and
index types or available types. In addition, tasks such as loading a specific driver or initializing the engine fall into this category.
DbiAddAlias
DbiAddDriver
DbiAnsiToNative

DbiDeleteAlias
DbiDeleteDriver
DbiDllExit
DbiExit
DbiGetClientInfo
DbiGetDriverDesc
DbiGetLdName
DbiGetLdObj
DbiGetNetUserName
DbiGetProp
DbiGetSysConfig
DbiGetSysInfo
DbiGetSysVersion
DbiInit
DbiLoadDriver
DbiNativeToAnsi
DbiOpenCfgInfoList
DbiOpenDriverList
DbiOpenFieldTypesList
DbiOpenFunctionArgList
DbiOpenFunctionList
DbiOpenIndexTypesList
DbiOpenLdList
DbiOpenTableList
DbiOpenTableTypesList
DbiOpenUserList
DbiSetProp
Error functions
Error handling for the BDE is performed using these functions.
DbiGetErrorContext
DbiGetErrorEntry
DbiGetErrorInfo
DbiGetErrorString

Chapter 9-The Borland Database


i
./_ i_ _ . .

Functions

These functions c,reate or drop incjexes or return infc


specified index.
DbiAddIndex
DbiCloseIndex
DbiCompareKeys
DbiDeleteIndex
DbiExtractKey
DbiGetIndexDesc
DbiGetIndexDescs
DbiGetIndexForField
DbiGetIndexSeqNo
DbiGetIndexTypeDesc
DbiOpenIndex
DbiRegenIndex
DbiRegenIndexes
DbiSwitchToIndex
Locking Functions
The BDE functions listed acquire or release record or table locks or
return information about a locks status.
DbiAcqPersistTableLock
DbiAcqTableLock
DbiGetRecord
DbiIsRecordLocked
DbiIsTableLocked
DbiIsTableShared
DbiOpenLockList
DbiOpenUserList
DbiRelPersistTableLock
DbiRelRecordLock
DbiRelTableLock
DbiSetLockRetry

298 n
___..

Part Ill-The Well-Rounded Application


i

Query Functions
The listed functions perform query-related tasks.
DbiGetProp
DbiQAlloc
DbiQExec
DbiQExecDirect
DbiQExecProcDirect
DbiQFree
DbiQGetBaseDescs
DbiQInstantiateAnswer
DbiQPrepare
DbiQPrepareProc
DbiQSetParams
DbiQSetProcParams
DbiSetProp
DbiValidateProp

Session Functions
These functions return information about a session or perform session-related tasks.
DbiAddPassword
DbiCheckRefresh
DbiCloseSession
DbiDropPassword
DbiGetCallBack
DbiGetCurrSession
DbiGetDateFormat
DbiGetNumberFormat
DbiGetSesInfo
DbiGetTimeFormat
DbiRegisterCallBack
DbiSetCurrSession
DbiSetDateFormat
DbiSetNumberFormat
DbiSetPrivateDir

x_*.*%*.

-...

-.,

--_

l(

Chapter 9-The Borland Database Engine API a


. _, lll. r* /(
wm _ Y a,. .i .,eIUIeeI-e-L .<, _* (.,. _ ,) . e ~~sa,.ee.w-

DbiSetTimeFormat
DbiStartSession
Functions
k.j
;&fj
This list of functions performs tasks related to the tables. Some will
return information about a table such as lock status, a list of referenti*!!
integrity links, or the indexes open on the table. Functions that perfor&%
>-2%
table-wide tasks such as copying and deleting are also included.
:>g
/ $j
DbiBatchMove
,j : :
41il
DbiCopyTable
3ji
DbiCreateInMemTable
) :.,.-,g.;i
DbiCreateTable
DbiCreateTempTable
,^T
DbiDeleteTable
.;
g
DbiDoRestructure
.+2b
_
DbiEmptyTable
.:;:,~:$
DbiGetTableOpenCount
5:
DbiGetTableTrpeDesc
DbiIsTableLocked
DbiIsTableShared
DbiMakePermanent
DbiOpenFamilyList
DbiOpenFieldList
DbiOpenIndexList
DbiOpenLockList
DbiOpenRintList
DbiOpenSecurityList
DbiOpenTable
DbiPackTable
DbiQInstantiateAnswer
DbiRegenIndexes
DbiRenameTable
DbiSaveChanges
DbiSortTable

300 g Part I&--The Well-Rounded Application


..,,
. . ... x._ ___ ,.

Transdction

Functions
These functions begin and end or return information about a
transaction.
DbiBeginTran
DbiEndTran
DbiGetTranInfo

Summary
The API of the Borland Database Engine exposes a raft of useful functions for the programmer willing to learn the complexities of working
with the interface. Careful review of the architectural requirements
described in this chapter and the function listings in Appendix A will
provide the programmer with a number of opportunities to expand thl
functionality of their products. In addition to directly improving the
programmers repertoire, a secondary benefit is accrued through a
much deeper understanding of the underlying structure of the Delphi
development tool itself through interaction with its native interface.

Function Reference
Every Borland Database Engine API function, whether it is surfaced in
the visual components or not, is documented in the following pages.
Note that the majority of these functions will use structures and data
types defined in the BDE.INT file. The function reference should be
cross-referenced with that file for further information about the data
structures. The structure of these calls also drives home the point that
this engine was originally designed by and for C programmers. Dont
let this sway your determination to utilize the API to its fullest. Explore
the functionality and see if there is anything here that will improve
your programs.
Documentation Format
Each entry in this reference will appear in the following format. The
following entry shows an annotated example:
The function declaration is given in shorthand. This shows the calling
sequence of the parameters.
DbiActivateFilter

( Kursor,

Milter )

Each of the parameters is documented in a table that follows the function declaration.
Parameter

D a t a Tvbe

Descrivtion

hCursor

hDBlCur

The cursor handle.

hFilter

hDBlFilter

The handle to the filter to be applied.

@ m Part Ill-The Well-Rounded Application

The functions all return a code indicating whether they were successful
or not. If there was an error in the transaction, a code will be returned
here.
RETURNS : DBIResult

A text description of the function is provided at the end of each


definition.
DbiActivateFilter activates a preset filter associated with the cursor.
Numerous filters can be associated with the same cursor. To activate all
filters for a cursor, set the hFilter parameter to NIL.

E; A(phabetic Function Listing


i

DbiAcqPertistTableLock

( hDb, szTableName,

szDriverType

hDb

hDBlDb

Database

handle.

szTableName

PChar

Reference to the table name.

szDriverType

PChar

Reference to the tables driver type.

RETURNS: DBIResult

DbiAcqPersistTableLock
acquires an exclusive, persistent table lock that
prevents other users from accessing the table or creating one of the
same name.
DbiAcqTableLock

( hcursor, eLockType )

hCursor

hDBlCur

The cursor handle.

eLockType

DBILockType

Specifies the type of table lock required.

RETURNS: DBIResult

DbiAcqTableLock allows the user to acquire a table-level lock that can


prevent other users from updating a table.
DbiActivateFilter ( hcursor, hFilter )
hCursor

hDBlCur

The cursor handle.

hFilter

hDBlFilter

The handle to the filter to be applied.

RETURNS: DBIResult

DbiActivateFilter activates a preset filter associated with the cursor. To


activate all filters for a cursor, set the hFilter parameter to NIL.

Appendix A-BDE API Quick Reference H 303


DbiAddAlias ( hCfg,
hCfg

szAliasName, SzDriverType,
hDBlCfg

szParams,

&Persist )

The configuration file to be used. Must be


set to NIL to indicate the current
configuration file is to be updated.

szAliasName

PChar

The name of the new alias to be added.

szDriverType

PChar

The table driver type of the new alias.

szParams

PChar

An optional list of parameters.

bPersist

Boolean

The scope for the new alias; if True, the


alias is stored for the next session.

RETURNS: DBIResult
This function adds a new alias to the BDE configuration file. You must
call DbiInit prior to a call to DbiAddAlias.
DbiAddDriver ( hCfg, szDriverName,
hCfg

hDBlCfg

szParams, &Persist )
The configuration file to be used. This
parameter must be set to NIL.

szDriverName

PChar

The name of the new driver to be added.

szParams

PChar

A list of optional parameters.

bPersist

Boolean

This sets the scope of the new driver; if


set to True, the driver will be available for
future sessions.

RETURNS: DBIResult
DbiAddDriver adds a driver to the BDE configuration file. with all
parameters set to the default values unless overridden by the szParams
list. DbiInit must be called prior to calling this function.
DbiAddFilter ( hCursor, iC/ientData,
Filter, hFi/ter )
hCursor

hDBlCur

ipriority, bCanAbort, pCanExpr,

The cursor handle of the table to which


the filter will be applied.

iClientData

UINT32

Not used-must be 0.

iPriority

UINTI6

N o t u s e d - m u s t b e I.

bCanAbort

Boolean

Not

pCANExpr

A pointer to a CANExpr

pCanExpr

used-must

be

False.
structure. This

structure describes the filter condiuon


a Boolean expression.
Filter

pfGENFilter

Not used-must be NIL.

hFilter

phDBlFilter

A pointer to the filter handle.

as

-%-*i*

ij

Part Ill-The Well-Rounded Application

RETURNS: DBIResult
DbiAddFilter adds a filter to a table. The CANExpr structure must be
defined and filled prior to calling this function.
DbiAddlndex ( hDb, hCursor,
szKeyViolName )

szTub/eName,

szDriverType,

IdxDesc,

hDb

hDBlDb

The database handle.

hCursor

hDBlCur

The cursor for the table. This parameter is


optional if szTableName

and szDriverType

serve to identify the table.


PChar

The table name.

szDriverType

PChar

The driver type.

IdxDesc

plDXDesc

A pointer to the index descriptor structure

szTableName

(I DXDesc).
szKeyViolName

PChar

The name of a Key Violation table (if


used).

RETURNS: DBIResult
DbiAddIndex
DbiAddPassword
szPassword

creates an index for a table in the database.


( szPassword )
PChar

The password to be added.

RETURNS: DBIResult
DbiAddPassword provides users with access to operations on a previ
ously encrypted table. DbiCreateTable and DbiDoRestructure can be
used to add or remove encryption.
DbiAnsiToNative
LdObj

( LdObj, NativeStr,
Pointer

AnsiStr, iLen, bDataLoss

Pointer to the language driver object


returned from calling DbiGetLdObj.

NativeStr

PChar

The

translations

string.

AnsiStr

PChar

The client buffer containing ANSI data.

iLen

UINTl6

Specifies the length of the buffer to


convert. If 0, a null-terminated string is
assumed.

bDataLoss

Boolean

If the result is True. the ANSI string cannot


map to a character in the native character
set.

Appendix A--BDE API Quick Reference n


RETURNS:

305

DBIResult

DbiAnsiToNative functions as a translator of strings from ANSI to the


language drivers native character set.
DbiAppendRecord

( hcursor, RecorciSuf)

hCursor

hDBlCur

The cursor handle.

RecordBuf

Byte

Pointer to the record buffer

RETURNS:

DBIResult

The function DbiAppendRecord appends the contents of the record


buffer to the end of the table associated with the cursor.
DbiAppiyDelayedUpdates

( hcursor,

UpdCmd )

hCursor

hDBlCur

The cursor handle.

UpdCmd

DBlDelayedUpdCmd

The operation to be performed on the


cached updates cursor.

RETURNS:

DBIResult

DbiApplyDelayedUpdates
commits or rolls back any changes made to
cached data when cached updates mode is active.
DbiBatchMove ( SrcTbiDesc, hSrcCur, DstTbiDesc, hDstCur,
ebatMode, ifidcount, SrcFidMap, szindexName, szindexTagName,
iindexid, szKeyvio/Name, szProbiemsName, szchangedhlame,
ProbRecs, IKeyvRecs, IChangeciRecs, bAbortOnFirstProb,
bAbortOnFirstKeyvioi, IRecsToMove, brransiiterate )
SrcTblDesc

pBATTblDesc

A pornter to the source table


descriptor

hSrcCur

hDBlCur

structure

The cursor handle for the source


table.

DstTblDesc

pBATTblDesc

A pointer to the destination


descriptor

hDstCur

hDBlCur

table

structure.

The cursor handle for the destrnation


table.

dBATMode

Mode of operation

iFldCount

UINTl6

The number of fields, normally 0.

SrcFldMap

PWORD

Array of source field numbers.

szlndexName

PChar

The Index name.

ebatMode

6 H Part Ill-The Well-Rounded Afaplication


m
szlndexTagName

PChar

The index tag name.

ilndexld

UINTI6

The

szKeyViolName

PChar

The Key Violation table name.

index

szProblemsName

PChar

The Problems table name.

szChangedName

PChar

The Changed table name.

IProbRecs

UINT32

Number of records added to the


Problems

IKeyvRecs

Violations

table.

table.

Number of records added to the

UINT32

Changed
bAbortOnFirstProb

number.

Number of records added to the Key

UINT32

IChangedRecs

identification

table.

If True, operation is cancelled on first

Boolean

entry to Problems table.


bAbortOnFirstKeyvio1

If True. operation is cancelled on first

Boolean

entry to Key Violations Table.


IRecsToMove

The number of records to be read

pUINT32

from the source table.


bTransliterate

Specifies whether to transliterate data

Boolean

from one character set to another.

DbiBatchMove is used to append, delete, copy, or update rows or fields


from a source table to a destination table.
DbikdFromFloat

( iVa1, iprecision, iPlaces, Bed )

iVal

pDFLOAT

The float data to convert.

iprecision

UINTI6

The precision of the BCD number.

iPlaces

UINTI6

Specifies the number of decimal


positions in the BCD number.

pFMTBcd

Bed

Pointer to the structure that receives


the converted data.

RETURNS : DBIResult

The function DbiBcdFromFloat converts BDE float data types to


binary-coded decimal data.
DbiBcdToFloat
Bed

( Ekd, iVal )
pFMTBcd

A pointer to a FMTBcd

structure containing the

binary-coded decimal data to convert.


iVal

pDFLOAT

A pointer to the client variable that receives the


converted

data.

Appendix A-BDE API Quick Reference n

307

RETURNS : DBIResult
DbiBcdToFloat

converts BCD format to BDE float format.

DbiBeginDelayedUpdates

( hCursor

phDBlCur

hCursor

On input, this specifies the original cursor. On


output, returns the new cursor.

RETURNS: DBIResult
The function DbiBeginDelayedUpdates is used to activate the cached
update mode.
DbiBeginLinkMode

( phcursor
phDBlCur

phCursor

)
For input. this specifies the orIgInal cursor. The
new cursor is returned on output.

RETURNS : DBIResult
Used after a call to DbiLinkDetail,
between two tables.
DbiBeginTran

this function enables the linking

( hDb, eXIL, phXact )

hDb

hDBlDb

The database handle.

eXlL

eXI LType

The transaction Isolation

phXact

phDBlXact

The

transaction

level.

handle

RETURNS: DBIResult
DbiBeginIran starts a transaction on a given database. The actions that
occur upon issuance of this command vary from setl-er to server.
DbiCheckRefresh

No

parameters

RETURNS : DBIResult
The function DbiCheckRefresh is used to scan for remote updates to
tables for all cursors in the context of the current session. If changes
are noted, the cursors are updated.
DbiCIoneCursor
hCurSrc

( hCurSrc,
hDBlCur

bReadOnly, bUniDirectional,
The source cursor handle.

phCurNew

308

-~*-d~~*~.-*-

Part Ill-The Well-Rounded Application


bReadOnly

Boolean

When True, the clone will be read-only. When


False, the access mode will be read-write. (The
access mode of the cloned cursor can be selected
only if the access mode of the source cursor is
dbiREADWRITE.)

bUniDirectional

Boolean

(SQL only) If True, the clone will be unidirectional.


When False, the cursor will be bidirectional.

phCurNew

phDBlCur

This is a pointer to the cursor handle for the


cloned

cursor.

RETURNS: DBIResult
The function DbiCloneCursor serves to create a new cursor that is a
copy of the source cursor. The cloned cursor inherits certain properties
of the source:
n

Current index

w Range
H Translate mode
n

Share mode

Position

w Field maps
n

Filters

At the same time, the new cursor remains completely independent of


the source with relation to position and ordering.
DbiCloseCursor

( hCursor )

hCursor

hDBlCur

The cursor handle.

RETURNS: DBIResult
DbiCloseCursor closes the specified cursor.
DbiCloseDatabase
hDb

( hDb )
hDBlDb

The database handle.

RETURNS: DBIResult
DbiCLoseDatabase
cursors.

closes the specified database and any associated

Appendix A-BDE API Quick Reference n


DbiCloseFieldXlt

( hXlt )

hXlt

hDBlXlt

The field translation handle

RETURNS: DBIResult
The function DbiCloseFieldXlt closes a field translation object.
DbiCloselndex

( hcursor, szlndexfrlame, ifndexld )

hCursor

hDBlCur

The cursor handle.

szlndexName

PChar

A pointer to the index name.

ilndexld

UINTl6

Currently not used.

RETURNS: DBIResult
The function DbiCloseIndex
DbiCloseSession

closes the specified index for the cursor.

( hSes )
hDBlSes

hSes

The session handle

RETURNS: DBIResult
DbiCloseSession closes the session and frees all resources associated
with it including database handles, cursor, table-level locks, and
record-level locks.
DbiCompareSookMarks
pCmp5kmkResult )
hCur

( hCur, p5ookMark

I, pBookMark2,

hDBlCur

The cursor handle.

PByte

Pointer to the first bookmark.

pBookMark2

PByte

Pointer

pCmpBkmkResult

pCMPBkMkRslt

pBookMark

to the second bookmark.

Pointer to the variable to receive the


comparison

result.

RESULTS: DBIResult
The function DbiCompareBookMarks
is used to compare the relative
positions of two bookmarks set on the same cursor.

3 IO n Part Ill-The Well-Rounded Application


in ,mee+l-*m~
I
j
.
.
DbiCompareKeys

( hCursor, pKey I, pKey2, iFields, Len, piResult )

hCursor

hDBlCur

The cursor handle.

PKeyl

PByte

Pointer to the first key value.

WY2

PByte

Optional pointer to the second key

iFields

UINTI 6

value.
The number of fields to be included
when a composite key is used. iFields
and iLen

work together to determine

how much of key is used in matching.


iLen

UINTl6

The length of the key field to be used in


matching.

piResult

pINTI

Pointer to the variable to receive the


comparison

result.

RETURNS: DBIResult

The function DbiCompareKeys compares the values of two keys based


on the current cursor index.
DbiCopyTuble ( hD6, bOver Write, szSrcTub/eName,
pszDestTab/eName )

szSrcDriverType,

hDb

hDBlDb

The database handle.

bOverWrite

Boolean

When True, an existing destination table


is overwritten. If False, an error is
returned.

szSrcTableName

PChar

The source table name to be copied.

szSrcDriverType

PChar

The driver type name.

PszDestTableName

PChar

The name of the destination table.

RETURNS: DBIResult

The function DbiCopyTable copies tables of the same driver type from a
source table to a destination table.
DbiCreatelnMemTable

( hDb, szName, ifields, pfldDesc, Kursor )

hDb

hDBlDb

The database handle.

szName

PChar

The table name.

iFields

UINT

pfldDesc

pFLDDesc

Pointer to an array of field descriptor

hCursor

phDBlCur

The cursor handle.

I6

The number of fields tn the table.

(FLDDesc)

structures.

Appendix A-BDE API Quick Reference n

3 1 1

RETURNS: DBIResult
DbiCreateInMemTable
creates a temporary table in memory. The data
types and capabilities supported for in-memory tables are very limited.
DbiCreateTable ( hDb, bOverWrite, crT6lDsc )
hDb

hDBlDb

The database handle.

bOverWrite

Boolean

When True and there is an existing


table, it will be overwritten. If False and
the table already exists, an error will be
returned.

crTblDsc

pCRTbIDesc

A pointer to a table descriptor


structure.

RESULTS: DBIResult
The function DbiCreateTable
by the handle.

creates a table in the database pointed to

DbiCreateTempTable ( hDb, pcrTb/Dsc, phcursor )


hDb

The database handle. (If this parameter

hDBlDb

is NIL, all temp tables will be created in

pcrTblDsc

pCRTblDesc

the

working

The

table

directory.)

descriptor

structure

(CRTblDesc).
phCursor

phDBlCur

The cursor handle.

RETURNS : DBIResult
DbiCreateTempTable creates a temporary table that is deleted when the
cursor is closed.
DbiDateDecode ( dateD, piMon, piDay, piYear )
dateD
piMon

DBIDATE

The

pUlNT

The variable that will receive the

I6

encoded

decoded
piDay

pUlNT

I6

pINTI

component.

day

component.

The variable that will receive the


decoded

RETURNS: DBIResult

month

The variable that will receive the


decoded

piYear

date.

year

component.

iIm
,~ .*
it.
?,

3 12 n
.*erz_*

Part Ill-The Well-Rounded Application

DbiDateDecode decodes the data type DBIDATE in separate month, day,


and year components.
DbiDateEncode ( iMon, iDay, Near, pdateD )
iMon

lJINT16

iDay

UINTl6

iYear

INTl6

The month value. Valid values range from I


through 12.
The day value. Valid values range from I
through 3 I.
The year value. Valid values range from
-9999 to 9999.

pdateD

pDBlDATE

The variable that will receive the encoded


date.

RETURNS: DBIResult
DbiDateEncode creates a data type DBIDATE from individual date
components.
DbiDeactivateFilter ( Kursor: hDB/Cur; hfilter: hDBlfi/ter )
hCursor

hDBlCur

The cursor handle.

hFilter

hDBlFilter

The filter handle. NIL deactivates all filters.

RETURNS: DBIResult
DbiDeactivateFilter turns off a specified filter so that it will not affect
the records displayed from a cursor.
DbiDeleteAlias
hCfg

( hCfg, szAliasName
hDBlCfg

)
The configuration file to be used. This
parameter must be NIL.

szAliasName

PChar

The alias name to be removed.

RETURNS: DBIResult
DbiDeleteAlias deletes an alias from the BDE configuration file.
DbiDeleteDriver ( hCfg, szDriverName, &Save )
hCfg

hDBlCfg

The configuration file to be used. This


parameter must be NIL.

szDriverName

PChar

The name of the driver to be removed.

bSave

Boolean

When True, the changes to the configuration


file are saved.

Appendix A-BDE API Quick Reference n

3 13

RETURNS: DBIResult

The function DbiDeleteDriver deletes a database driver from the BDE


configuration file.
DbiDeletelndex ( hi%, hcursor, szTab/eName, szDriverType,
rzlndexhlame, sz/ndexTagName, ilndexld )
hDb

hDBlDb

The database handle.

hCursor

hDBlCur

The cursor handle.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

szlndexName

PChar

The name of the Index to be dropped.

szlndexTagName

PChar

The Index tag name. (Used only to identify


dBASE .MDX or FoxPro

ilndexld

UINTl6

.CDX

indexes.)

The Index identifier, used for Paradox only.

RETURNS : DBIResult

DbiDeleteIndex is used to drop an index.


DbiDeleteRecord ( hCursor, pRecBuf)
hCursor

hDBlCur

pRecBuf

Byte

The cursor handle.


Pointer to the buffer that can receive the
deleted

record.

RETURNS: DBIResult

The function DbiDeleteRecord deletes the currently selected record of


the provided cursor.
DbiDeleteToble ( hDb, szTab/eName, szDriverType

hDb

hDBlDb

The database handle.

szTableName

PChar

The name of the table to delete.

szDriverType

PChar

The driver type of the table being deleted.

RETURNS : DBIResult

DbiDeleteTable deletes a table from the database provided. The client


application must have appropriate permissions to be able to lock the
table for exclusive use.

No

parameters

RETURNS: DBIResult
DbiDllExit is used to prepare the BDE to be disconnected from within a
DLL only. The function is called immediately prior to calling DbiExit
within the DLL.
DbiDoRestructure ( hDb, iTb/DescCount, pTb/Desc, szSaveAs,
szKeyviolName,
szProb/emsName, bAnalyzeOnly )
hDb

hDBiDb

The database handle

iTblDescCount

UINTl6

The number of table descriptors. Must


be I.

pTblDesc

pCRTblDesc

The CRTblDesc

structure that contains

the changes to be applied.


szSaveAs

If not NIL, a restructured table with this

PChar

name is created. The original remains


unchanged.
szKeyviolName

PChar

The Key Violation table name.

szProblemsName

PChar

The Problems table name.

bAnalyzeOnly

Boolean

Not

currently

used.

RETURNS: DBIResult
The function DbiDoRestructure has a wide range of uses. It performs
modifications to tables such as modifying field types or sizes, adding or
deleting fields, changing indexes and passwords, or packing Paradox
tables.
DbiDropFilter ( hCursor, hfilter )
hCursor

hDBlCur

The cursor handle

hFilter

hDBlFilter

The filter handle.

RETURNS: DBIResult
The function DbiDropFilter
associated resources.
DbiDropPassword ( szPassword
szPassword

PChar

drops the filter specified and releases all

)
The password to be dropped. If NIL, all
passwords for the session are dropped.

Appendix A-BDE API Quick Reference H 3 15


._

RETURNS: DBIResult
DbiDropPassword
DbiEmptyTable

removes a password from the current session.

( hDb, hCursor, szTub/eName, szDriverType

hDb

hDBlDb

The database handle.

hCursor

hDBlCur

The cursor for the table.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type name.

RETURNS: DBIResult
DbiEmptyTable

deletes all records from the specified table.

DbiEndDelayedUpdates
phCursor

( phCursor )
phDBlCur

The cached updates cursor handle.

RETURNS: DBIResult
The function DbiEndDelayedUpdates removes the cursor from cached
updates mode and returns a new cursor handle.
DbiEndLinkMode

( phcursor )

phCursor

phDBlCur

The linked cursor handle.

RETURNS: DBIResult
DbiEndLinkMode takes the cursor out of Link mode and returns a new
cursor handle.
Dbifndnon ( hDb, hXact, eEnd )
hDb

hDBlDb

The database handle.

hXact

hDBIXact

The

eEnd

eXEnd

The transaction end type.

transaction

handle.

RETURNS: DBIResult
The function DbiEndTran
SQL server table.
DbiExit

No

parameters

ends a transaction on either a local table or a

;[ 3 16 H Part Ill-The We/l-Rounded Application


RETURNS:

DBIResult

DbiExit disconnects the application from BDE. It releases all resources


allocated by the client application and should be the last DBI/BDE call
made by the client application.
DbiExtractKey

( hCursor, pRecSuf, pKey5uf)

hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to the record buffer that contains


the key to be extracted.

pKeyBuf

RETURNS:

The variable to receive the key value.

Byte

DBIResult

The function DbiExtractKey gets the key value for the current record
from either the cursor or the record buffer.
DbiForceRecordReread

( Kursor, pRec5uf)

hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to the record buffer.

RETURNS:

DBIResult

DbiForceRecordReread reads a single record from the server and


refreshes a single row only. This does not re-execute the query.
DbiForceReread
hCursor

RETURNS:

( Kursor )
hDBlCur

The cursor handle.

DBIResult

The function DbiForceReread refreshes all data for the cursor. The
refreshed data will include any remotely updated rows.
DbiFormFullName

( hDb, szTab/eName,

szDriverType,
database

szFul/Name

hDb

hDBlDb

The

handle.

szTableName

PChar

The table name.

szDriverType

PChar

The name of the driver type.

szFullName

PChar

The variable that will receive the fully


qualified table name.

RETURNS:

DBIResult

Appendix A-BDE API Quick Reference N 3 17

DbiFormFullName
supplies a fully qualified filename based on the settings for the working directory known to the BDE.
DbifreeBlob

( Kursor, pRecSuf, iField )

hCursor

hDBlCur

The cursor handle.

pRecBuf

we

Pointer to the record buffer that contains

iField

UINTl6

the BLOB handle.


The subscript that indicates the position of
the BLOB field.

RETURNS: DBIResult
DbiFreeBlob releases a BLOB handle that was opened through a call to
DbiOpenBlob.
DbiGetSlob

( Kursor, pRecBufi ifield, iOffSet, Len, pDest, iRead )

hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to the record buffer containing the


BLOB

iField

UINT16

handle.

The subscript that indicates the position of


the BLOB field.

iOffSet

UINT32

iLen

UINT32

The starting location for retrieval within the


BLOB

field.

The number of bytes of the BLOB field to


retrieve.

pDest

Byte

The variable that WIII receive the BLOB data.

iRead

pUINT32

The variable that receives the count of the


number of bytes read.

RETURNS: DBIResult
The function DbiGetBlob retrieves the BLOB data from the field specified. This function is used with Paradox tables only and is needed to
support the feature of storing a minimal amount of BLOB data in the
tabIe itself.
DbiGetBlobHeading

( Kursor, ifield, pRecBuf,

pDest )

hCursor

hDBlCur

The cursor handle.

iField

UINTI6

The subscript that Indicates the position of


the BLOB field.

pRecBuf

Byte

Pointer to the record buffer.

--.i 3r--aTvl*I*
a 18 ..i n

Purt l/l-The

Well-Rounded Application

pDest

The variable that will receive the BLOB

Byte

heading.

RETURNS: DBIResult
DbiGetBlobHeading provides information about the specified BLOB
field in the record buffer.
DbiGetSlobSize

( Kursor,

pRecSuf, ifield, piSize )

hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to the record buffer.

iField

UINTI6

The subscript that indicates the position of


the BLOB field.

piSize

pUINT32

The variable that WIII receive the BLOB size

RETURNS: DBIResult
DbiGetBlobSize retrieves the size, in bytes, of the specified BLOB data
from the record buffer.
DbiGetSookMark

( hCur, pBookMark

hCur

hDBlCur

The cursor handle.

pBookMark

Byte

Pointer to the bookmark buffer.

RETURNS : DBIResult
The funcrion DbiGetBookMark saves the current row position in the
provided cursor to the pBookMark buffer defined by the application.
DbiGetCallBack

( hCursor, ecbType, piClientData,

piCbSufLen,

PPCbM PPWJ 1
hCursor

hDBlCur

The cursor handle.

ecbType

CBType

The type of callback.

piClientData

pUINT32

Pointer to the passthrough client data.

piCbBufLen

pUlNT

The callback buffer length.

ppCbBuf

ppVOlD

The callback buffer pointer.

wfcb

ppfDBlCallBack

The variable that receives the pointer to the

I6

callback

RETURNS : DBIResult

function.

Appendix A-ME API Quick Reference H 3 19

The function DbiGetCallBack is usually used to determine if a specified


callback function was previously registered for the cursor. It retrieves a
pointer to the callback function if it was registered, NIL if it was not.
DbiGetCIientlnfo

( pClient/nfo

pclientlnfo

pCLlENTlnfo

Pointer to a structure of type


CLIENTlnfo.

RETURNS : DBIResult
DbiGetClientInfo
retrieves system-level information about the application that can determine if other sessions are present when exclusive
access is required for a table.
DbiGetCurrSession

( phSes

phSes

phDBlSes

The handle for the current session.

RETURNS: DBIResult
DbiGetCurrSession retrieves the handle for the current session.
DbiGetCursorForTable

( hDb, szTableName,

szDriverType,

phCursor )

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name

szDriverType

PChar

The name of the driver type.

phcursor

phDBlCur

Pointer to the cursor- handle.

RETURNS : DBIResult
DbiGetCursorForTable retrieves the cursor for the specified table in the
current session.
DbiGetCursorProps

( hCursor, pcurProps )

hCursor

hDBlCur

The cursor handle.

pcurProps

pCURProps

Pointer to a structure of type CURProps.

RETURNS: DBIResult
The function DbiGetCursorProps retrieves the properties of the specified cursor and places them in the CURProps structure.

Part Ill-The Well-Rounded Application


DbiGetDatabaseDesc

( szfrlame, pdbDesc )

szName

PChar

The database name.

pdbDesc

pDBDesc

Pointer to a structure of type DBDesc.

RETURNS: DBIResult
DbiGetDatabaseDesc retrieves description information about the
base from the configuration file and fills the DBDesc structure.
DbiGetDateFormat
pfmtDate

data-

( pfmtDate )
pFMTDate

Pointer to a structure of type FMTDate.

RETURNS: DBIResult
DbiGetDateFormat returns the date format for the current session.
DbiGetDirectory ( hDb, bDefau/t,

szDir )

hDb

hDBlDb

The database handle.

bDefauIt

Boolean

This

parameter

determines

whether

the

function will retrieve the default


directory or the working directory.
szDir

PChar

Pointer to the variable that will receive


the

directory

information.

RETURNS: DBIResult
The function DbiGetDirectoty
retrieves either the default directory or
the current directory, depending on the setting in the bDefault
parameter.
DbiGetDriverDesc

( szDriverType,

pcfrvType )

szDriverType

PChar

The driver name.

N-vType

PDRWpe

Pointer to a structure of type DRVType

RETURNS: DBIResult
The function DbiGetDriverDesc retrieves information about a database
driver and fills the DRVType structure.
DbiGetErrorContext (econtext,
econtext

INTl6

szcontext )
The

context

type.

Appendix A-BDE API Quick Reference H


szcontext

PChar

32

Pointer to the variable that will receive


the context string.

RETURNS: DBIResult

DbiGetErrorContext gives the application the ability to get more information regarding a BDE error. The DBIResult is translated to its string
equivalent and passed as a parameter to this function. The function
returns specific extended information with regard to the error string
passed.
DbiGetErrorEntry ( uEntty, pulNativeError,
uEntry

UlNTlb

pulNativeError

pUINT32

szError )

The error stack entry.


Pointer to the variable that will receive
the native error code.

szError

PChar

Pointer to the variable that will

receive

the error string.

RETURNS: DBIResult

The function DbiGetErrorEntry retrieves the error description of the


specified entry on the error stack.
DbiGetErrorlnfo (bFull, pErrlnfo )
bFull
pErrlnfo

Boolean

Not

pDBlErrlnfo

Pointer to a structure of type DBlErrlnfo

currently

used.

RETURNS: DBIResuit

DbiGetErrorInfo retrieves descriptive information about the last error


that occurred. It also provides error contexts for the first four error
messages on the error entry stack.
DbiGetErrorString ( rslt, szError )
rslt

DBlResult

BDE error code.

szError

PChar

The variable that will receive the error


message string.

RETURNS: DBIResult

DbiGetErrorString retrieves the descriptive error message related to the


DBIResult code provided.

g$kT
;,v+a..$*..-sF$ i
5:

Part Ill-The
.

Well-Rounded Application

DbiGetExactRecordCount

( hcursor, piRecCount

hCursor

hDBlCur

piRecCount

pUINT32

The cursor handle.


Pointer to the variable that will receive
the record count.

RETURNS:

DBIResult

The function DbiGetExactRecordCount retrieves the number of records


related to the cursor regardless of the filters or ranges in effect.
DbiGetfield ( Kursor, iField, pRecf3uff

pDest, 66lank )

hCut-sor

hDBlCur

The cursor handle.

iField

UINTl6

The subscript that indicates the position


of the field.

pRecBuf

Byte

pDest

Byte

Pointer to the record buffer.


Pointer to the variable that will receive
the data from the requested field.

bBlank

Pointer to a Boolean variable. True if the

pBOOL

specified field is blank.

RETURNS:

DBIResult

DbiGetField retrieves the data contained in the field specified.


DbiGetfieldDescs ( Kursor, pfldDesc )
hCursor

hDBlCur

The cursor handle.

pfldDesc

pFLDDesc

Pointer to a structure of type FLDDesc.

RETURNS:

DBIResult

DbiGetFieldDescs retrieves a list of field descriptors for each field in the


cursors table.
DbiGetFieldTypeDesc

( szDriverType,

szTab/eType, szFie/dType, pfldType )

szDriverType

PChar

The driver type name.

szTableType

PChar

The table type.

szFieldType

PChar

The field type.

PfldTyw

pFLDType

Pointer to a structure of type FLDType.

RETURNS:

DBIResult

Appendix A--BDE API Quick Reference n

The function DbiGetFieldTypeDesc


ified field type.
DbiGetFilterlnfo

323

retrieves the description of the spec-

( Kursor, hFi/ter, ifilterld,

iFi/terSeqNo,
cursor

pFilterinfo

hCursor

hDBlCur

The

hFilter

hDBlFilter

The filter handle.

iFilterld

UINTI6

The

filter

identification

iFilterSeqNo

UINTI6

The

filter

sequence

pFilterinfo

pFlLTERlnfo

Pointer to a structure of type

handle.

number.

number.

FILTERlnfo.

RETURNS: DBIResult
DbiGetFilterInfo
DbiGetlndexDesc

retrieves information about a specified filter.

( Kursor, i/ndexSegNo,

pidxDesc )

hCursor

hDBlCur

The

cursor

handle.

ilndexSeqNo

UINTl6

The

subscript

that

indicates

the

position

of the index in the list of indexes.


pidxDesc

pl DXDesc

Pointer to a structure of type IDXDesc.

RETURNS: DBIResult
The function DbiGetIndexDesc
index.
DbiGetlndexDescs

retrieves the properties of the specified

( Kursor, pidxDesc )

hCursor

hDBlCur

The

cursor

handle.

pidxDesc

plDXDesc

Pointer to a structure of type IDXDesc.

RETURNS : DBIResult
The function DbiGetIndexDescs retrieves the properties for all indexes
associated with the cursor. The user must supply a structure sufficiently
large to contain all of the index information.
DbiGetlndexForField

( Kursor, iF/d, bProdTagOn/y,


cursor

pidxflesc )

hCursor

hDBlCur

The

iFld

UINTl6

The field number.

bProdTagOnly

Boolean

(dBASE only) If True, only dBASE

pidxDesc

plDXDesc

production

handle.

tags

are

searched.

Pointer to a structure of type IDXDesc.

Port /l/--The

Well-Rounded Application

RETURNS: DBIResult

DbiGetIndexForField retrieves the index description on the specified


field.
DbiGetlndexSeqNo
pilndexSeqN0 )

( Kursor,

szlndexkme, szTagName,

hCursor

hDBlCur

The cursor handle.

szlndexName

PChar

The index name.

SzTagName

Pchar

(DBASE/FoxPro

only)

ilndexld,

The

index

tag

name.
ilndexld

UINTI 6

The index ID.

pilndexSeqNo

pUINTl6

The variable that will receive the index


sequence

number.

RETURNS: DBIResult

The function DbiGetIndexSeqNo retrieves the ordinal position of the


specified index within the index list.
DbiGetlndexTypeDesc

( szflriverlype, sz/ndexType, pidxType )

szDriverType

PChar

The driver type.

szlndexType

PChar

The index type.

pidxType

pIDXType

Pointer to a structure of type IDXType.

RETURNS: DBIResult

The function DbiGetIndexTypeDesc retrieves a description of the index


type.
DbiGetLdName

( szhiver, pObjName, pLdName )

szDriver

PChar

The driver name.

pObjName

PChar

The table name.

pLdName

PChar

The variable that will receive the


language driver name.

RETURNS: DBIResult

DbiGetLdName retrieves the specified tables language driver.


DbiGetLdObj
hCursor

( hCursor, pLdObj )
hDBlCur

The cursor handle.

Appendix A-BDE API Quick Reference


pLdObi

pVOlD

325

Pointer to the variable that receives the


pointer to the language driver.

RETURNS: DBIResult
DbiGetLdObj
cursor.
DbiGetLinkStatus

returns a pointer to the language driver for the specified

( Kursor,

phCursorMstr,

hCursor

hDBlCur

phCursorMstr

phDBlCur

phCursorDet,

phCursorSib )

The cursor handle.


Pointer to the master cursor, if
applicable.

phCursorDet

phDBlCur

phCursorSib

phDBlCur

Pointer to the primary detail cursor, if


applicable.
Pointer

to a secondary detail cursor, if

applicable.

RETURNS: DBIResult
The function DbiGetLinkStatus returns the master and detail cursors of
the specified linked cursor.
DbiGetNetUserName ( szNetUserName )
szNetUserName

PChar

Pointer to the variable that will receive


the network user login name.

RETURNS : DBIResult
DbiGetNerUserName retur-ns the users network login name. User
names are available for all networks supported by Microsoft Windows.
DbiGetNextRecord

( Kursor,

eLock, pRechff, precProps )

hCursor

hDBlCur

The cursor handle.

eLock

DBILockType

The lock request type.

pRecBuf

Byte

Pointer to the variable that will receive


the record data.

precProps

pRECProps

Pointer to a structure of type RECProps.

RETURNS: DBIResult
The function DbiGetNextRecord retrieves the next sequential row from
the specified cursor.

326
_ ._

Part Ill-The Well-Rounded Application


DbiGetNumberFormat

( pfmtNumber

pfmtNumber

pFMTNumber

Pointer to a structure of type


FMTNumber.

RETURNS: DBIResult

DbiGetNumberFormat provides the numeric format for the current


session.
DbiGetObjFromName

( eObjType, szObjName,

phObj )

eObiType

DBIOBJType

The type of the object.

szObjName

PChar

The name of the object.

phObj

phDBlObj

The object handle.

RETURNS: DBIResult

The function DbiGetObjFromName returns an object handle of either


the specified type or the specified name.
DbiGetObjFromObj

( hObj, eObjType, phO6j )

hObj

hDBlObj

Specifies the object.

eObjType

DBlOBJType

The type of the object.

phObj

phDBlObj

Pointer to the object handle.

RETURNS: DBIResult

DbiGetObjFromObj

returns an object of the type specified. associated

wit-h or derived from the given object.


DbiGetPriorRecord

( hcursor, eLock, pRecBuff precProps )

hCursor

hDBlCur

The cursor handle.

eLock

DBlLockType

The lock request type.

pRecBuf

Byte

Pointer to the record buffer.

precProps

pRECProps

Pointer to a structure of type


RECProps.

RETURNS : DBIResult

DbiGetPriorRecord retrieves the previous sequential record from the


specified cursor.

Appendix A-ME API Quick Reference


DbiGetProp

( hObj, iProp, pPropValue, iMaxLen,

hObj

hDBlObj

iProp

UINT32

pPropValue

pVOlD

327

piLen )

The system, session, client, driver,


database, cursor, or statement object.
The

property

to

retrieve.

Pointer to the variable that receives the


value of the property.

iMaxLen
piLen

UINTl6

The length of the pPropValue

pUlNT

The variable that will receive the buffer

I6

buffer.

length.

RETURNS: DBIResult
The function DbiGetProp retrieves the properties of an object.
DbiGetRecord

( hcursor, clock, pRecBuf, precPropr

hCursor

hDBlCur

The cursor handle.

eLock

DBILockType

The lock request type.

pRecBuf

Byte

The variable that will receive the record

precProps

pRECProps

data.
Pointer to a structure of type RECProps.

RETURNS: DBIResult
DbiGetRecord retrieves the current record from the specified cursor.
DbiGetRecordCount

( hCursor, piRecCount

hCursor

hDBlCur

piRecCount

pUINT32

The cursor handle


Pointer to the vat-table

that WIII receive

the number of records In the cursor.

RETURNS : DBIResul t
The function DbiGetRecordCount returns the approximate number of
records in the specified cursor. The number r-eturned is affected by the
filters and ranges that are in effect at the time of the function call.
DbiGetRecordForKey
pRec6uf)

( hcursor, bDirectKey,

ifields, iLen, pKey,

hCursor

hDBlCur

The cursor handle.

bDirectKey

Boolean

If True, the value in pKey is used to


specify the key. When False, pKey is the
record

buffer.

Part //l-The Well-Rounded Application


iFields

UINTI6

The number of fields to be used for


composite

iLen

UINTl6

PKey

Byte

keys.

The number of positions in the last field


to be used for composite keys.
Pointer to the value indicated by
bDirectKey.

pRecBuf

Byte

Pointer to the variable where the new


current

record

is

returned.

RETURNS: DBIResult
DbiGetRecordForKey retrieves a record from the specified cursor that
matches the key value specified.
DbiGetRelativeRecord

( hcursor, iPosOfFet,

clock, pRecRuf, precProps )

hCursor

hDBlCur

The cursor handle.

iPosOffset

INT32

The (signed) offset from the current


record.

eLock

DBlLockType

The lock request type.

pRecBuf

Byte

Pointer to the buffer that will receive the


record

precProps

pRECProps

data.

Pointer to a structure of type RECProps.

RETURNS: DBIResult
DbiGetRelativeRecord
positions the record pointer to the record relative
to the current position of the cursor.
DbiGetRintDesc

( Kursor, iRintSeqNo,

printDesc )

hCursor

hDBlCur

The cursor handle.

iRintSeqNo

UINTl6

The

referential

integrity

sequence

number.
printDesc

pRJNTDesc

Pointer to a structure of type RINTDesc.

RETURNS: DBIResult
The function DbiGetRintDesc retrieves the referential integrity
descriptor that matches the referential integrity sequence number and
the cursor.

Appendix A--BIDE

..._._
DbiGetSeqNo

API Quick Reference n

329

( hcursor, piSeqNo )

hCursor

hDBlCur

The cursor handle.

piSeqNo

pUINT32

Pointer to the variable that will receive


the logical sequence number of the
current

record.

RETURNS: DBIResult
The function DbiGetSeqNo retrieves the sequence number of the current record in the table.
DbiGetSesln

fo ( psesln fo )
pSESlnfo

pseslnfo

Pointer to a structure of type SESlnfo.

RETURNS: DBIResult
DbiGetSesInfo
DbiGetSysConfig

returns the environment settings of the current session.

( psysconfig )
pSYSConfig

psysconfig

Pointer to a structure of type SYSConfig.

RETURNS: DBIResult
The function DbiGetSysConfig retrieves system configuration information for the BDE.
DbiGetSyslnfo

( psysln fo )

psyslnfo

pSYSlnf0

Pointer to a structure of type SYSlnfo.

RETURNS: DBIResult
DbiGetSysInfo
DbiGetSysVersion
psysversion

retrieves the system status and information.


( psysversion )
pSYSVersion

Pointer to a structure of type


SYSVersion.

RETURNS: DBIResult
DbiGetSysVersion returns the system version information, including the
BDE version number, date, and time.

DbiGetTableOpenCount
piOpenCount )

( hDb, szTableName,

SzDriverType,

hDb

hDBlDb

The database handle.

szTableName

PChar

The name of the table.

szDriverType

PChar

The name of the driver type.

piOpenCount

pUlNT

Pointer to the variable that will receive the

I6

number of cursors opened on the table.

RETURNS: DBIResult

The function DbiGetTableOpenCount returns a count of the total number of cursors that have been opened on the specified table.
DbiGetTableTypeDesc

( szDriverType,

szTableType, ptb/Type )

szDriverType

PChar

The name of the driver type.

szTableType

PChar

The table type.

ptblType

pTBLType

Pointer to a structure of type TBLType.

RETURNS: DBIResult

The function DbiGetTableTypeDesc retrieves the capabilities of the


table and driver types specified.
DbiGetTimeFormat
pfmtTime

(pfmtTime
pFMTTime

)
Pointer to a structure of type FMTTime.

RETURNS: DBIResult

DbiGetTimeFormat retrieves the time format for the current session.


DbiGetTranlnfo

( hDb, hXact, pxlnfo )

hDb

hDBlDb

The database handle.

hXact

hDBlXact

The

pxlnfo

pXlnf0

Pointer to a structure of type Xlnfo.

transaction

handle.

RETURNS: DBIResult

The function DbiGetTranInfo retrieves transaction information.


DbiGetVchkDesc

( Kursor, iValSeqNo, pvalDesc )

hCursor

hDBlCur

The cursor handle.

iValSeqNo

UINTl6

The validity check sequence number.

Appendix A-BDE API Quick Reference n


pvalDesc

pVCHKDesc

33 1

Pointer to a structure of type VCHKDesc.

RETURNS: DBIResult
DbiGetVchkDesc retrieves the validity check specified by the sequence
number and cursor.
Dbifnit ( pEnv )
pEnv

pDBlEnv

(Optional) Pointer to a structure of type


DBIEnv.

RETURNS: DBIResult
DbiInit

initializes the BDE environment.

DbilnitRecord

( Kursor, pRec6uf)

hCursor

hDBlCur

pRecBuf

Byte

The cursor handle.


Pointer to buffer that WIII receive the
initialized record buffer.

RETURNS: DBIResult
DbiInitRecord initializes a record buffer, an operation required before a
record can be inserted.
DbifnsertRecord

( Kursor, eLock, pRecBuf)

hCursor

hDBlCur

The cursor handle

eLock

DBlLockType

The lock request type.

pRecBuf

Byte

Pointer- to the record buffer.

RETURNS: DBIResult
The function DbiInsertRecord
the cursor.
DbilsRecordLocked

inserts data from the record buffer into

( Kursor, pbLocked )

hCursor

hDBlCur

The cursor handle.

pbLocked

pBOOL

Pointer to the variable that will receive the


result of the operation.

If True, the record

is locked.

RETURNS: DBIResult
DbiIsRecordLocked

retrieves the lock status of the current record.

Well-Rounded Application
DbilsTtbleLocked

( hCursor, epdxLock, @Locks )

hCursor

hDBlCur

The cursor handle.

epdxLock

DBILockType

The lock type to verify.

piLocks

pUlNTl6

Pointer to the variable that will receive


the number of locks of the specified
type.

RETURNS: DBIResult

DbiIsTableLocked retrieves the number of locks placed on the table of


the type specified.
DbilsTubleShared

( hcursor, bShared )

hCursor

hDBlCur

The cursor handle.

bShared

pBoolean

Pointer to a variable that will receive the


results of the operation. If True, the
table is physically shared.

RETURNS: DBIResult

The function DbiIsTableShared retrieves the physical sharing table


status.
DbiLinkDetail
piDetlfie/ds )

( hMstrCursor,

hDet/Cursor,

iLnkFields, piMstrfields,

hMstrCursor

hDBlCur

The cursor handle for the master table.

hDetlCursor

hDBlCur

The cursor handle for the detail table.

iLnkFields

UINTl6

The number of link fields.

piMstrFields

pUlNTl6

Pointer to the array containing the field


number of link fields in the master table.

piDetlFields

pUINTl6

Pointer to the array containing the field


number of link fields in the detail table.

RETURNS: DBIResult

DbiLinkDetail sets the link between two cursors. The detail cursor will
only display records that have matching values in the master cursor.
DbiLinkDetailToExp
hCursorMstr

( KursorMstr,

KursorDetl,

iKeyLen, szMstrExp )

hDBlCur

The cursor handle for the master

hDBiCur

The cursor handle for the detail table.

table.
hCursorDetl

Appendix A--BE API Quick Reference n


iKeyLen

UINTI6

szMstrExp

PChar

333

The length of the key to match.


Pointer to the dBASE expression
string.

RETURNS : DBIResult
DbiLinkDetailToExp
expression.
DbiLoadDriver

links dBASE cursors through the use of a dBASE

( szfhiver Type )

szDriverType

PChar

The driver name

RETURNS: DBIResult
DbiLoadDriver loads the specified driver.
DbiMokePermanent

Kursor, szName, bOverWrite

hCursor

hDBlCur

The cursor handle

szName

PChar

The name of the permanent table.

bOverWrite

Boolean

If True, an existing file will be


overwritten.

RETURNS: DBIResult
DbiMakePermanent

makes a temporary table into a permanent table.

DbiModifiRecord ( hCursor, pRecBufi bFreeLock )


hCursor

hDBlCur

pRecBuf

Byte

The cursor handle


Pointer to the record buffer that
contains the modified

bFreeLock

Boolean

record.

If True. record lock IS released on


completion.

RETURNS: DBIResult
The function DbiModifyRecord updates the current record with the
data contained in the record buffer.
DbiNativeToAnsi
pLdObj

( pldObj,

pAnsiStr, pNativeStr, Xen, pbDataLoss )


pVOlD

Pointer to the language driver object.


(Returned by DbiGetLdObj)

pAnsiStr

PChar

Pointer to the variable that returns the


ANSI

data.

t$?--~

&
kr,?
us!.
:*::
I ;.

334 m Part Ill--The Well-Rounded Application


#,s>%.ni_
pNativeStr

PChar

Pointer to the variable that contains


the data to be translated.

iLen

UINTl6

The length of the buffer to convert.

pbDataLoss

pBoolean

Pointer to the variable that will receive


an error result if a character cannot be
mapped to an ANSI character.

RETURNS:

DBIResult

DbiNativeToAnsi translates strings from the language drivers native


character set to ANSI.
DbiOpenSlob

( hcursor, pRecBuf, ifield, eOpenMode )

hCursor

hDBlCur

The cursor handle.

pRecBuf

pBYTE

Pointer to the record buffer.

iField

UINTI6

The ordinal position of the BLOB field

eOpenMode

DBlOpenMode

within the record.

RETURNS:

The open mode for the BLOB.

DBIResult

The function DbiOpenBlob prepares the record buffer for a cursor to


access a BLOB field.
DbiOpenCfglnfoList
phCur )
hCfg

( hCfg, eOpenMode,

hDBlCfg

eConfigMode,

szCfgPath,

The configuration file handle. This must


be NIL.

eOpenMode

DBlOpenMode

The open mode.

eConfigMode

CFGMode

The

configuration

cfgpersistent
szCfgPath

PChar

IS

mode.

Only

supported.

The configuration file path name used


to locate data within the configuration
file.

phCur

RETURNS:

phDBlCur

Pointer

to a cursor handle.

DBIResult

DbiOpenCfgInfoList returns a handle to a list that contains all of the


nodes in the BDE configuration file that can be accessed by the path
provided.

Appendix A-t3DE API Quick Reference W 335


DbiOpenDatabase ( szDbName, szDbType, eOpenMode, eShareMode,
szfassword, iOptFlds, pOptFldDesc, pOptParams, phDb )
szDbName

PChar

The alias name string defined in the


configuration

file.

szDbType

PChar

The database type.

eOpenMode

DBlOpenMode

The open mode.

eShareMode

DBlShareMode

The share mode.

szPassword

Pchar

The password string (SQL only).

iOptFlds

UINTI6

The

pOptFldDesc

pFLDDesc

Pointer to an array of field descriptors

number

of

optional

parameters.

for the optional parameters.


pOptParams

Byte

Pointer to the optional parameters


required by the database.

phDb

phDBlDb

Pointer to the database handle.

RETURNS : DBIResult
DbiOpenDatabase opens a database in the current session.
DbiOpenDatabaseList
phCur

( phCur )
phDBlCur

Pointer to an in-memory table.

RETURNS: DBIResult
The function DbiOpenDatabaseList
returns a cursor on a list of accessible databases found in the configuration file.
DbiOpenDriverList
phCur

( phCur )
phDBlCur

Pointer to the cursor handle.

RETURNS: DBIResult
DbiOpenDriverList returns a list of drivers available to the application.
DbiOpenFamilyList

( hDb, szTab/eName, szDriverType,

phFm/Cur )

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name.

szDriverType

PChar

The table type.

phFmlCur

phDBlCur

Pointer to the family list table.

RETURNS: DBIResult

36 n

Part Ill-The Well-Rounded Application

The function DbiOpenFamilyList


creates a list containing all of the family members associated with the table.
DbiOpenFieldList
phCur )

( hD6, szTab/eName,

szDriverType,

bPhyTypes,

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

bPhyTypes

Boolean

If True, the native physical types are


returned. If False, the BDE logical types
are

phCur

returned.

Pointer to the field list cursor.

phDBlCur

RETURNS: DBIResult

The function DbiOpenFieldList


for a specified table.
DbiOpenFieIdTypesList

creates a table containing the field list

( szDriverType,

szTb/Type, phCur )

szDriverType

PChar

The driver type.

szTblType

PChar

The table type.

phCur

phDBlCur

Pointer to the cursor handle

RETURNS: DBIResult

DbiOpenFieldTypesList
creates a table containing the legal field types
for a given table or driver type.
DbiOpenFieldXlt ( szSrcDriverType,
szSrcLangDrq pfldSrc,
pszDestTab/eType, SzDstLangDrv, pfldDest, pbDataLoss, phXIt )
PChar

The source driver type.

szSrcLangDrv

PChar

The language driver name of the source.

pfldSrc

pFLDDesc

Pointer to the source field descriptor.

pszDestTableType

PChar

The destination

szDstLangDrv

PChar

The language driver name of the

szSrcDriverType

table type.

destination.
pfldDest

pFLDDesc

Pointer to the destination field


descriptor.

pbDataLoss

pBoolean

Pointer to a variable used to indicate


both the possible and actual data loss for
each field translated.

Appendix A-BDE API Quick Reference n


phXlt

phDBlXlt

337

Pointer to the translation object handle.

RETURNS: DBIResult
The function DbiOpenFieldXlt
builds a field translation object used to
translate a logical or physical field type into any other compatible type.
DbiOpenFileList

( hDb, szWild, phCur )

hDb

hDBlDb

The database handle.

szWild

PChar

The search strtng for retrievtng a

phCur

phDBlCur

selective list of tables.


Pointer to the file list cursor.

RETURNS: DBIResult
DbiOpenFileList creates a table containing a list of files contained
within a database.
DbiOpenFunctionArgList

( hDb, szFuncName,

uoverioad,

phCur )

hDb

hDBlDb

The database handle.

szFuncName

PChar

The data source function name.

uOverload

UINT16

The

phCur

phDBlCur

Pointer to the returned cursor handle.

overload

number.

RETURNS: DBIResult
DbiOpenFunctionArgList returns 21 cursor containing the aI-guments to
the specified function.
DbiOpenFunctionList

( hDb, eoptBits, phCur )

hDb

hDBlDb

The database handle.

eoptBits

DBlFUNCOpts

(InterBase

only) To include user-defined

functions.
phCur

phDBlCur

Returned function cursor handle.

RETURNS: DBIResult
DbiOpenFunctionList retrieves a description of a data source function.
DbiOpenlndex

( hCursor, szlndexName,

ilndexld )

hCursor

hDBlCur

The cursor handle.

szlndexName

PChar

The index name.

Part

/l/-The

Well-Rounded

ifndex\C

Application
-_
UINTI6

The Paradox index number.

RETURNS : DBIResult
The tinction DbiOpenIndex opens the specified index for the table
associared with the cursor.
DbiOpenlndexList

(hDb, szTableName, szDriverType, phCur )

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

pt?CL!r

phDBlCur

Pointer to the cursor handle.

RETURNS:

DBIResult

The tinction DbiOpenIndexList


creates a cursor containing the indexes
and :heir descriptions for the specified table.
DbiOpenlndexTypesList

(szDriverType, phCur )

szDrwerType

PChar

The driver type.

phCur

phDBlCur

Pointer to the cursor handle.

RETURNS:

DBIResult

DbiOpenIndexTypesList
creates a cursor containing all supported index
r)pes for the driver provided.
DbiOpenLockList (hcursor,

bA//Users, bAIILockTypes,

phLocks

hCursor

hDBlCur

The cursor handle.

bAllUsers

Boolean

If True, locks acquired for all sessions are


listed. If False, only locks acquired in the
current session are listed.

bAllLockTypes

Boolean

If True, locks of all types are listed. If


False. only record locks are listed.

phLocks

RETURNS:

phDBlCur

Pointer to the cursor handle.

DBIResult

The function DbiOpenLockList creates a cursor containing the locks


acquired on the specified cursor.
DbiOpenRintList
hDb

( hDb, szTab/eName,
hDBlDb

szDriverType, phChkCur )
The database handle

Appendix A-BDE API Quick Reference n


szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

phChkCur

phDBlCur

Reference to the cursor handle.

339

RETURNS: DBIResult
DbiOpenRintList
creates a cursor containing the referential integrity
links and their descriptions for the specified cursor.
DbiOpenSecurityList

( hDb, szTableName,

szDriverType,

phSecCur )

hDb

hDBlDb

The database handle

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

phSecCur

phDBlCur

Pointer to the cursor handle.

RETURNS: DBIResult
DbiOpenSecurityList creates a cursor containing the record-level security information about the specified table.
DbiOpenSPList

( hDb, b&ended,

bSystem, szQual, phCur )


The database handle for the database

hDb

hDBlDb

bExtended

Boolean

Not

bSystem

Boolean

If True, include system procedures.

szQual

PChar

Must be NIL

phCur

phDBlCur

Pointer to the cursor handle.

where the stored procedure exists.


currently

used.

RETURNS : DBIResult
The function DbiOpenSPList creates a cursor containing information
about a stored procedure associated with the specified database.
DbiOpenSPParamList
phCur )

( hDb, szSP#ame,

bPhyTypes, uOverload,

The database handle for the database

hDb

hDBlDb

szSPName

PChar

The

bPhyTypes

Boolean

If True, physical types are returned. If

where the stored procedure exists.


stored

procedure

name.

False, BDE logical types are returned.


uoverload
phCur

UINTI 6

The

phDBlCur

Pointer to the cursor handle.

overload

number.

340

Part Ill-The Well-Rounded Application

RETURNS: DBIResult
The function DbiOpenSPParamList
creates a cursor that lists the parameters for a specified stored procedure.
DbiOpenTable( hDb, szTab/eName, szDriverType, szlndexhlame,
szlndexTagName,ilndexld, eOpenMode,
eShareMode,exItMode,
bUniDirectional, pOptParams, phcursor ,I
hDb

hDBlDb

The database handle for the database


that contains the table.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

szlndexName

PChar

The name of the index used to order the


result set.

szlndexTagName

PChar

The tag name of the index.

ilndexld

UINTl6

The index identifier number.

eOpenMode

DBlOpenMode

The table open mode.

eShareMode

DBlShareMode

The table share mode.

exItMode

XLTMode

The data translation mode.

bUniDirectional

Boolean

The scan mode of the cursor. (SQL only)

pOptParams

Byte

Not

phCursor

phDBlCur

Pointer to the cursor handle.

currently

used.

RETURNS: DBIResult
DbiOpenTable
that table.

opens the specified table and returns a cursor handle to

DbiOpenTubleList ( hDb, b&tended, bSystem, szwild, phCur )


hDb

hDBlDb

The database handle.

bExtended

Boolean

If True, extended table information is


returned. When False, only standard
table information is returned.

bSystem

Boolean

If True. system tables are included.

szWild

PChar

The search string used to retrieve a


selective list of tables.

phCur

phDBlCur

Pointer to the cursor handle.

RETURNS: DBIResult
DbiOpenTableList
creates a cursor containing information about all of
the tables accessible to the application.

Appendix A-BDE API Quick Reference n


DbiOpenTableTypesList

( szDriverType,

phCur )

szDriverType

PChar

The driver type.

phCur

phDBlCur

Pointer to the cursor handle.

RETURNS:

34 1

DBIResult

DbiOpenTableTypesList creates a cursor containing table type names for


the specified driver type.
DbiOpenUserList
hUsers

( hUsers )
phDBlCur

RETURNS:

Pointer to the cursor handle

DBIResult

DbiOpenUserList creates a cul-sor conwining


same network file.
DbiOpenVchkList

a list of IIWI-S shtrr-ing the

( hDb, szTab/eName, szDriverType, phChkCur

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

phChkCur

phDBlCur

Pointer to the cursor handle

DbiPackTable

( hDb, hCursor, szTableName,

szDriverType, bRegen/dxs )

hDb

hDBlDb

The database handle

hCursor

hDBlCut

The cursor handle for the table to be


packed

szTableName

PChar

The table name.

szDriverType

PChar

The driver type.

bRegenldxs

BOOL

Determines

whether

or

not

out-of-date

Indexes are regenerated.

RETURNS : DBIResult

DbiPackTabte performs a pack operation on the specified table to optimize use of space.

@jY;
y;?
_
*- *
I.

9..
g.,
1:

342 n

Part I/l-The Well-Rounded Application


.(
( hcursor, pRecBuf, ifield, iOffSet, Len, pSrc )

DbiPutSIob
hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to the record buffer.

iField

UINTI6

The ordinal position of the BLOB field


within the record buffer.

UINT32

iOffSet

The starting position, offset from the


beginning of the BLOB, where the data
is to be written.

iLen

UINT32

The number of bytes to be written to


the BLOB field.

Byte

pSrc

Pointer to the data to be written.

RETURNS: DBIResult
DbiPutBlob
DbiPutField

writes data into an open BLOB field.

( hcursor, ifield, pRecBufi pSrc )

hCursor

hDBlCur

The cursor handle.

iField

UINT16

The ordinal position of the field to be


updated.

pRecBuf

We

pSrc

Byte

Pointer to the record buffer.


Pointer to the data to be written in the
field.

RETURNS: DBIResult
DbiPutField writes data to a specified field in the supplied record
buffer.
DbiQAlloc

( hDb, eQryLang,

phStmt )

hDb

hDBlDb

The database handle.

eQ+-ang

DBlQtyLang

The 4uet-y language.

phStmt

phDBlStmt

Pointer

to the statement handle.

RETURNS: DBIResult
DbiQAlloc

allocates a new statement handle for a prepared query

DbiQExec ( hStmt, phCur )


hStmt

hDBlStmt

The

statement

handle.

phCur

phDBlCur

Pointer to the cursor handle.

Appendix A--BDE API Quick Reference n

343

RETURNS: DBIResult
The function DbiQExec executes a previously prepared query associated
with the specified statement handle. If a result set is generated, the cursor handle to that result set is returned.
DbiQExecDirect

( hD6, eQryLang, szQuery, phCur )

hDb

hDBlDb

The database handle.

eQ+ng

DBlQtyLang

The query language.

szQuery

PChar

Pointer to the correctly formatted query


statement.

phCur

phDBlCur

Pointer to the cursor handle.

RETURNS: DBIResult
DbiQExecDirect executes a SQL or QBE query and returns a cursor handle for the result set.
DbiQfIxecProcDirect

( hDb, szProc, uParaml)escs, paParumDescs,

pRecf3ufi phCur )
hDb

hDBlDb

The database handle.

szProc

PChar

The

uParamDescs

UINTI6

Number

paParamDescs

pSPParamDesc

Array

pRecBuf

Byte

Pointer to the record buffer.

phCur

phDBlCur

Pointer to the cursor handle.

stored

of

of

procedure
parameter

parameter

name.
descriptors.

descriptors.

RETURNS: DBIResult
DbiQExecProcDirect executes a stored procedure and returns that cursor handle to the result set.
DbiQFree

( phStmt )

phStmt

phDBlStmt

Pointer to the statement handle.

RETURNS : DBIResult
The function DbiQFree releases the resources associated with a previously prepared query statement.

Part Ill-The Well-Rounded Application


DbiQGetBaseDescs

( hStmt, phCur )

hStmt

hDBlStmt

The

statement

handle.

phCur

phDBlCur

Cursor of type STMTBaseDesc.

RETURNS: DBIResult
The function DbiQGetBaseDescs returns information regarding the original database, table, and field names of the columns that make up a
result set.
DbiQInstantiateAnswer
( hStmt, hCursor, szAnswerName,
szAnswerType, bOverWrite, phDstCursor )
hDBlStmt

The

hCursor

hDBlCur

The cursor handle.

szAnswerName

PChar

The name of the permanent table.

szAnswerType

PChar

The driver type.

bOverWrite

Boolean

If True, overwrite an existing file.

phDstCursor

phDBlCur

Pointer to the cursor handle.

hStmt

statement

handle.

RETURNS: DBIResult
DbiQInstantiateAnswer creates a permanent table from the cursor
specified.
DbiQPrepare

( hStmt, szQuery )

hStmt

hDBlStmt

The

statement

handle.

szQuery

PChar

The

correctly

formatted

query.

RETURNS: DBIResult
DbiQPrepare prepares a SQL or QBE query for execution. It returns a
handle to the prepared statement.
DbiQPrepareProc
( hDb, szf rot, uParamDescs,
pRecBuf, phStmt )

paParamDescs,

hDb

hDBlDb

The database handle.

szProc

PChar

The

stored

uParamDescs

UINTI6

The

number

paParamDescs

pSPParamDesc

procedure
of

name.

parameter

descriptors.

Pointer to an array of parameter


descriptors.

pRecBuf

Byte

Pointer to the record buffer.

Appendix A--BDE API Quick Reference n


phStmt

phDBlStmt

RETURNS:

returned

statement

handle.

DBIResult

DbiQPrepareProc
procedure.
DbiQSetParams

The

345

prepares and optionally binds parameters for a stored

( hStmt, uFldDescs, paFldDescs, pRecBuf)

hStmt

hDBlStmt

The

statement

handle

uFldDescs

UINTI6

The number of field descriptors.

paFldDescs

pFLDDesc

Pointer to an array of parameter field


descriptors

pRecBuf

Byte

RETURNS:

Pointer to the record buffer

DBIResult

DbiQSetParams
pared query.
DbiQSetProcParams

sets up data to be passed to pal-ameter\

( hStmt, uParamDescs,

lvithin a pre-

paParamDescs,

pRecBuf)

hStmt

hDBlStmt

The

statement

handle.

uParamDescs

UINTl6

The

number

paParamDescs

pSPParamDesc

Pointer to an array of parameter

pRecBuf

Byte

of

parameter

descriptors

descriptors.

RETURNS:

to the record buffer.

DBLResdt

The function DbiQSetProcParams


pared statement.
DbiReadBlock

Pointer

( hcursor,

binds the p,trnmcre~-

cl;lra ctith a pre-

iRecords, pBuf)

hCursor

hDBlCur

The cursor handle

iRecords

pUINT32

A variable that contains the number of


records to tread For output, the actual
number of records {read

pBuf

Byte

IS

returned

through

this

parameter.

Pointer

to the buffer that WIII receive

the records.

RETURNS:

DBIResult

The function DbiReadBlock reads a specified number of rows and


places the data in a user-defined buffer.

p
1 346 n
a*-._i &I, *,*/_^^iF
,
_

Part Ill-The Well-Rounded Application


DbiRegenlndex ( hD6, hcursor, szTub/eName,
szlndexName, SzlndexTagName, ilndexld )

szDriverType,

hDb

hDBlDb

The database handle.

hCursor

hDBlCur

The cursor handle.

szTableName

PChar

The table name.

szDriverType

PChar

The table driver type.

szlndexName

PChar

The name of the index.

szlndexTagName

PChar

The tag name of the index.

ilndexld

UINTl6

The

index

number.

RETURNS: DBIResult
DbiRegenIndex
regenerates the specified index, ensuring that all current records are in the index and in the proper order.
DbiRegenlndexes

( hCursor )

hCursor

The cursor handle.

hDBlCur

RETURNS: DBIResult
The function DbiRegenIndexes
indexes for the given cursor.
DbiRegisterCallf3ack

( hcursor,

regenerates all of the out-of-date

ecbType, KlientData, iCbBufLen,

PCbwi pm )
hCursor

hDBlCur

The cursor handle to which the callback


is being reglstered.

ecbType

CBType

The callback type.

iClientData

UINT32

Passthrough data specified by the


application.

iCbBufLen

UINTI6

The callback buffer length.

pCbBuf

pVOl D

Pointer to the buffer where the callback

pfCb

pfDBlCallBack

data is to be returned.
Pointer to the desired callback function.

RETURNS: DBIResult
DbiRegisterCallBack
application.

registers a callback function for the calling

Appendix A-BDE API Quick Reference n


DbiRelPersistTableLock

( hDb, szTableName,

szDriverType

hDb

hDBlDb

The database handle.

szTableName

PChar

The table name.

szDriverType

PChar

The driver type name.

RETURNS:

347

DBIResult

DbiRelPersistTableLock releases a persistent table lock on the specified


table.
DbiReiRecordLock

( hcorsor, bAll )

hCursor

hDBlCur

bAll

Boolean

The cursor handle.


If True, all record locks acquired by the
current session are released. If False,
the cursor must be on a record in order
to release the lock for that record.

RETURNS : DBIResult

DbiRelRecordLock releases either all of the record locks acquired by the


current session or the lock on the current record.
DbiRelTableLock

( Kursor, bA/l,

eLockType )

hCursor

hDBlCur

The cursor handle.

bAll

Boolean

If True, all locks on the table are


released. If False, the locks to be
released are determlned

eLockType

RETURNS:

DBILockType

by eLockType

The table lock type to be released.

DBIResult

DbiRelTableLock releases either the specified lock type or all locks for
the specified table.
DbiRenameTable

( hDb, szOldName,

szDriverType,

szNewName

hDb

hDBlDb

The database handle.

szOldName

PChar

The existing table name.

szDriverType

PChar

The driver type name.

szNewName

PChar

The new name for the table

RETURNS:

DBIResult

The function DbiRenameTable renames the table specified, along with


all of its resources, to the new name specified.
DbiRetetRcmge
hCursor

( Kursor )
hDBlCur

The cursor handle.

RETURNS: DBIResult

DbiResetRange removes the limitations of a range previously set by


DbiSetRange.
DbiSaveChanges
hCursor

( Kursor )
hDBlCur

The cursor handle.

RETURNS: DBIResult

DbiSaveChanges forces all changed records associated with the cursor


to be written to disk.
DbiSetCurrSession
hSes

( hSes )
hDBlSes

The session handle.

RETURNS: DBIResult

The function DbiSetCurrSession sets the current session for the application to the specified session.
DbiSetDateFormat
pfmtDate

( pfmthte )
pFMTDate

Pointer to a date format structure.

RETURNS: DBIResult

DbiSetDateFormat sets the date format for the current session.


DbiSetDirectory

( hDb, szDir )

hDb

hDBlDb

The database handle.

szDir

PChar

The new current directory string.

RETURNS: DBIResult

DbiSetDirectory sets the current directory for a standard database. If


NIL, the current directory is set to the default directory

Appendix A-BDE API Quick Reference n


DbiSetFieldMap

349

( hCur, ifields, pFldDesc )

hCur

hDBlCur

The cursor handle.

iFields

UINTI6

The number of fields to map.

pFldDesc

pFLDDesc

Pointer to an array of FLDDesc


structures.

RETURNS: DBIResult
The function DbiSetFieldMap
DbiSetLockRetry

sets a field map of the specified table.

( iWait )

iWait

INTl6

The lock retry time In seconds

RETURNS: DBIResult
The function DbiSet1.ockRetr-y sets the record and table lock retry for
the current session.
DbiSetNumberFormat
pfmtNumber

( pfmtNum&er
pFMTNumber

Pointer to a structure of type


FMTNumber.

RETURNS: DBIResult
DbiSetNumberFormat
DbiSetPrivateDir
szDir

sets the number format for the current session.

( szDir )
The full path name of the private

PChar

directory.

RETURNS: DBIResult
DbiSetPrivateDir sets the private directory for the current session.
DbiSetProp

(hObj, iProp, iPropValue

hObj

hDBlObl

)
The object handle to a system, client,
session, driver, database. cursor, or
statement

object.

iProp

UINT32

The property to set.

iPropValue

UINT32

The value of the property.

RETURNS : DBIResult

Ill-The Well-Rounded

Application

DbiSetProp sets the specified properties of an object to the given value.


DbiSetRange ( hCursor, 6Keyltselfi iFields I, iLen I, pKey I, bKeyl Inch,
iFields2, iLen2, pKey2, bKey2Incl)
hCursor

hDBlCur

The cursor handle.

bKeyltself

Boolean

The key buffer type. If True, the key


parameters contain the keys directly. If
False, the key parameters contain
pointers to variables containing the
keys.

iFields I

UINTI6

The number of fields to be used for a


composite

iLen I

UlNTl6

key.

The length into the last field to be used


for composite keys.

WY 1

Byte

The beginning key.

bKey I lncl

Boolean

If True, the beginning key value is

iFields

UINTI6

The number of fields to be used for a

included in the range.

composite
iLen

UINTl6

key.

The length into the last field to be used


for composite keys.

WY2

Byte

The ending key.

bKey2lncl

Boolean

If True, the ending key value is included


in the range.

RETURNS: DBIResult

The function DbiSetRange limits the result set to a set bounded by the
beginning and ending key values.
DbiSetTimeFormat ( pfmtTime )
pfmtTime

pFMTTime

Pointer to a structure of type


FMTTime.

RETURNS: DBIResult

DbiSetTimeFormat sets the time format for the current session.


DbiSetToBegin ( hCursor )
hCursor

hDBlCur

RETURNS: DBIResult

The cursor handle.

Appendix A-l3DE API Quick Reference n

DbiSetToBegin sets the cursor to the crack before the first record.
DbiSetToBookMark

( hCor, pBookMark )

hCur

hDBlCur

The cursor handle.

pBookMark

Byte

Pointer to the bookmark.

RETURNS: DBIResult

The function DbiSetToBookMark positions the cursor to the position


indicated by the specified bookmark.
DbiSetToCursor

( hDest, hSrc )

hDest

hDBlCur

The destination cursor handle.

hSrc

hDBlCur

The source cursor handle.

RETURNS: DBIResult

DbiSetToCursor synchronizes the position in the destination cursor to


the position in the source cursor. Both cursors are opened on the same
table.
DbiSetToEnd ( hCursor )
hCursor

hDBlCur

The cursor handle.

RETURNS: DBIResult

DbiSetToEnd positions the cursor at the crack following the last record
in the cursor.
DbiSetToKey ( hcursor, eSearchCond,

bDirectKey,

iFields, Len, p&f)

hCursor

hDBlCur

The cursor handle.

eSearchCond

DBlSearchCond

The search condition.

bDirectKey

Boolean

If True, the pBuf parameter is a pointer


to the key in physical format. When
False, pBuf contains a pointer to the
record

iFields

UlNTl6

buffer.

The number of fields to be used for a


composite

iLen

UINTI6

key.

The length into the last field to be used


for composite keys.

352 H Purt I/l--The Well-Rounded Application


-m*l/i_ ,.
pBuf

Pointer to either the record buffer or

We

the key itself, determined by


bDirectKey.

RETURNS: DBIResult
DbiSetToKey
provided.
DbiSetToRecordNo

positions an index-based cursor on the key value

( Kursor,

iRecNo )

hCursor

hDBlCur

The cursor handle.

iRecNo

UINT32

The physical record number

RETURNS: DBIResult
DbiSetToRecordNo positions the cursor on the specified record number.
DbiSetToSeqNo

( Kursor,

iSeqN0 )

hCursor

hDBlCur

The cursor handle.

iSeclNo

UINT32

The logical record number

RETURNS: DBIResult
The function DbiSetToSeqNo positions the cursor on the specified
sequence number. Paradox only.
DbiSortTable ( hDb, szTab/eName, szDriverType, hSrcCur,
szSortedName, phSortedCur, hDstCur, isortfields, piFie/dNum,
pbCase/nsens, pSortOrder,
plRecsSort )

ppfSortFn,

bRemoveDups,

hDuplicatesCur,

hDb

hDBlDb

The database handle.

szTabieName

PChar

The table name.

szDriverType

Pchar

The driver

hSrcCur

hDBlCur

Cursor handle supplied when a source

type.

cursor is to be sorted to a destination


cursor.
szSortedName

Pchar

The table name for the destination


cursor.

phSortedCur

phDBlCur

Pointer to a cursor handle on the


destination

hDstCur

hDBlCur

cursor.

Used instead of szSortedName

to

specify the sorted destination table.


iSortFields

UINTI6

The number of sort fields to be used.

353

Appendix A---BDE API Quick Reference n

..~. 1
piFieldNum

pUlNTl6

Pointer to an array of the field numbers


on which to sort.

pbcaselnsens

pBOOL

Pointer to an array of values indicating


whether the sort isi to be case-insensitive
for each sort field.

pSortOrder

pSORTOrder

Pointer to an array of the sort order for

ppfSortFn

pfSORTCompFn

each field.
Pointer to an array of pointers to
client-supplied
bRemoveDups

destination
hDBlCur

p1RecsSor-t

pUINT32

functions.

If True, remove duplicates from the

Boolean

hDuplicatesCur

compare

table.

The cursor handle for the duplicates


table.
Input-the

number

of

records

from the current posttlon


cursor.

Output-the

records

actual

to

sort

in the source
number

of

sorted.

RETURNS: DBIResult
The function DbiSortTable sorts an opened or closed table to itself or to
a destination table. Parameters control removal of duplicates, case sensitivity, special sort functions, and the number of records to be sorted.
DbiStartSession

( szhlame, phSes, pNetDir )

szName

PChar

The session

phSes

phDBISes

Pointer to the session

name.

pNetDir

PChar

The network directory for the session.

handle.

RETURNS: DBIResult
DbiSrartSession
DbiSwitchTolndex
bCurrRec )
phcursor

starts a new session for the application.


( phcursor,

szlndexName,

phDBlCur

szTagName,

ilndexfd,

For Input, the original cursor handle. On


output. pointer to the new cursor
handle.

szlndexName

PChar

The name of the index or pseudo-index.

szTagName

PChar

Pointer to the tag name.

ilndexld

UINTI 6

The index ID.

bCurrRec

Boolean

If True, positions the new cursor on the


current record of the original cursor.

i-,, -,.
$2.

$f;

yy
:$;, .I

354

Part Ill-The Well-Rounded Application

RETURNS: DBIResult
DbiSwitchToIndex
cursor.
DbiTimeDecode

changes the active index order on the specified

( timel; piHour, piMin, piMilSec )

timeT

TIME

The

piHour

pUINTl6

The variable that will receive the hour

encoded

time.

component of the decoded ttme.


piMin

pUlNT

The variable that WIII receive the minute

I6

component of the decoded time.


piMilSec

pUlNT

I6

The variable that will receive the


millisecond component of the decoded
time.

RETURNS: DBIResult
DbiTimeDecode decodes a TIME object into its component parts.
DbiTimeEncode

( iHour, iMin, iMilSec,

ptimeT)

iHour

UINTI6

The

hour

iMin

UINTl6

The

minute

iMilSec

UINTI6

The

millisecond

ptimeT

pTlME

The variable that will receive the


encoded

component.
component.
component.

trme.

RETURNS: DBIResult
The function DbiTimeEncode
object of type TIME.
DbiTimeStampDecode

encodes discrete time components into an

( tsTS, pdatel), ptimeT)

tsTS

TIMESTAMP

The

pdateD

pDBlDATE

Pointer to the variable that will receive

ptimeT

pTlME

The variable that will receive the

the

encoded

DATETIME

encoded

encoded

TIME

DBIDATE

timestamp.

component.

component.

RETURNS : DBIResult
The function DbiTimeStampDecode extracts separate
DBIDATE and TIME components from TIMESTAMP

encoded

Appendix A-BDE API Quick Reference n


DbiTimeStampEncode

355

( dateD, time7; ptsTS )

dateD

DBIDATE

The

encoded

date.

timeT

TIME

The

encoded

time.

ptsTS

pTIMESTAMP

Pointer to the variable that will receive the


encoded

TIMESTAMP

RETURNS: DBIResult
The function DbiTimeStampEncode encodes separate DBIDATE and
TIME into a variable of type TIMESTAMI?
DbiTranslateField

( hX/t, pSrc, p/lest )

hXlt

hDBlXlt

The translate handle.

pSrc

Byte

Pointer to the source field

pDest

Byte

Pointer to the destlnatlon

field

RETURNS : DBIResult
DbiTranslateField

translates a field value to any compatible field value.

DbiTranslateRecordStructure
( szSrcDriverType, iF/ds, pfldssrc,
szDstDriverType,
szlanglkiver, pfldskt, Kreatable )
SzSrcDriverType

PChar

The source driver type.

iFIds

UINTl6

The number of fields.

pfldsSrc

pFLDDesc

Pointer to an array of the source field


types.

SzDstDriverType

PChar

The destlnatlon

driver type.

szLangDriver

PChar

The destination language driver

pfldsDst

pFLDDesc

Pointer to an array of the destination

bcreatable

Boolean

type.

fields.
If True. map to treatable

fields only.

RETURNS: DBIResult
DbiTranslateRecordStructure translates the source drivers fields to
equivalent fields of the destination driver.
DbiTruncateBIob

( hcursor, pRecBufi ifield, Len )

hCursor

hDBlCur

The cursor handle.

pRecBuf

Byte

Pointer to a record buffer.

356 n
***e*i _ .i

Part Ill-The Well-Rounded Application

iField

UINTI6

iLen

UINT32

The ordinal position of the BLOB field


within

the

record

structure.

The shortened length of the BLOB.

RETURNS: DBIResult
The function DbiTruncateBlob
truncates the length of the specified
BLOB field. This can also be used to delete the contents of a BLOB
field.
DbiUndeleteRecord

( Kursor )

hCursor

hDBlCur

The dBASE or FoxPro

cursor handle.

RETURNS: DBIResult
DbiUndeleteRecord
DbiUnlinkDetail
hDetlCursor

unmarks a record marked for deletion.

( hDetlCursor )
hDBlCur

The detail cursor handle.

RETURNS: DBIResult
The function DbiUnlinkDetail
detail cursor set.
DbiValidateProp

removes the link between a master and

( hObj, iProp, bSetting )

hObj

hDBlObj

The object handle.

iProp

UINT32

The property to validate.

bsetting

Boolean

If True, DbiValidateProp is setting the


property.
getting

When

the

False,

DbiValidateProp

property.

RETURNS: DBIResult
DbiValidateProp validates a property for a specified object handle
DbiVerifiField

( hcursor, ifield, pSrc, pbf3lank )

hCursor

hDBlCur

The cursor handle.

iField

UINTl6

The ordinal position of the field in the


record.

pSrc

Byte

Pointer to the variable that contains the


data to be verified.

IS

Appendix A--BDE API Quick Reference n

35
., i,
:

pbBlank

pBOOL

Pointer to a variable that receives True if


the field is blank. When False, data exists.

j,!
;

RETURNS: DBIResult

DbiVerifyField verifies that the data specified in the parameter pSrc is a -;:
valid data type for the field specified by the iField parameter. It also
:Y
ensures that all validity checks specified for the field are satisfied.
DbiWriteBlock

( Kursor, piRecords,

pBuf)

hCursor

hDBlCur

The cursor handle.

piRecords

pUINT32

For input, a pointer

Byte

records to write. On output, the number of $


records actually written.
j
The buffer containing the records to be
T

pBuf

to the number of

written.

RETURNS: DBIResult

The function DbiWriteBlock writes a block of records to the table referenced by the cursor.

,, 2
$i.,Z

Você também pode gostar