Previous Page TOC Next Page


31 — Creating, Modifying, and Maintaining Databases

by Frank Font

Most application programs need to store some kind of data. Visual Basic gives the application program developer many options in this area. With the rich VBA language, a programmer can create and read low-level disk files directly; or through API calls, the programmer can read and edit INI files. But for more powerful and maintainable data storage and retrieval, it is Visual Basic's database management capabilities that come into play.

This chapter covers some Visual Basic database fundamentals. It starts with an introduction to the database concept and ends with some useful comments on keeping a database in good shape. Along the way, you will see some examples that create a database and modify it.

Looking at Database Fundamentals

In the simplest conceptual terms, a computer database is just an electronic filing cabinet. Unlike an old-fashioned filing cabinet, however, a well-designed database facilitates automated retrieval and management of the data stored in it.

In practice, a computer database is a collection of tables. Tables hold items that are described by the same list of characteristics. For example, the tblCandidate table in the database of Figure 31.1 contains people's names and other information. Each person whose name is stored in the database has a record in that table that describes their first name, last name, middle initial, and a few other items. The point however, is that each person is described by the same list of characteristics—their name, and so on. It is only the values of those characteristics that change from record to record. For example, the first name and last name of each person is stored, but all persons do not have the same name.


FIGURE 31.1. The tblCandidate table from the BTS (Best Team Selector) database. BTS is a sample program on the CD-ROM that accompanies this book.

In the language of SQL databases, a record also is known as a row, and a field also is known as a column. Figure 31.2 shows the data in the tblCandidate table, which shows why these terms so readily lend themselves. The records appear as rows, and the fields are stacked as columns.


FIGURE 31.2. The tblCandidate table showing columns and rows.

Although a complex definition for a relational database exists, the practical concept is this: In a relational database, tables are related to other tables through their fields. In the BTS sample program, the tblCandidate table is related to the tblCandidateSkill table through the CandidateKey field, which they both share. In other words, when the value of a field in one table record matches the value of a designated field in another table record, those records are related. Table relationships do not happen automatically. It is up to the database designer to designate fields between tables that relate to each other.


Note

Today's common database is a hierarchy of structures. A database is a collection of tables. A table is a collection of records. A record is a collection of fields. A field is the smallest unit of information that a database holds.

Understanding SQL

Sometimes pronounced sequel, SQL stands for Structured Query Language. It has become the de facto interface to relational database management systems and is a database interface available in most of today's development tools. Visual Basic 4.0 supports SQL for its database operations.


NOTE

A commonly used abbreviation for relational database management system is RDBMS. In some cases, the term DBMS is used, which just means database management system. The Jet Engine is the RDBMS for Visual Basic 4.0 when using Access (MDB) databases. When using ODBC, the RDBMS is either the ODBC driver itself or the database server managing the database.

SQL is not a language in the same sense that Basic or C/C++ are languages. Basic and C/C++ are general-purpose languages used to build programs by creating loops and conditions that are executed in a procedural fashion. The XBase dialects (dBASE, Clipper, and FoxPro) are in the same category as Basic and C/C++, although they are targeted at database management. A SQL statement, however, is not a sequentially executing list of instructions. Instead, each SQL statement is a complete query that tells an RDBMS what results you want, not necessarily every detail about how to get those results. The RDBMS translates your query into whatever steps are necessary to fulfill your request.

Webster's New World Dictionary defines a query as "a question." That's pretty much what the word means in SQL. Instead of phrasing SQL questions in English, however, you phrase them using the SQL language. You can go a considerable distance just by knowing how to apply the following keywords:

SELECT

Specifies the table columns (fields) you want to know about.

FROM

Specifies the rows of data to which the query applies. (In the examples used in this chapter, this is where you specify the source table. In more complex examples, like those in Chapter 32, "Unleashing SQL," this is where you embed other SQL queries.)

WHERE

Specifies conditions that limit the number of table rows returned.

Some simple examples showing how to apply these keywords follow; here, the Best Team Selector database is used. (See Chapter 36, "Optimization Selection Database System," for more information on this database. The database is available on the CD-ROM that accompanies this book.)

The following code returns a list of all the candidates' first names:

SELECT    tblCandidate.FirstName

FROM      tblCandidate;

To return the first name of every available candidate, use this code (availability is defined in the tblCandidate table by the Yes/No field "Available"):

SELECT    tblCandidate.FirstName

FROM      tblCandidate

WHERE     ((tblCandidate.Available=True));

To return the first and last name of each available client, just modify the select portion of the SQL statement to get the following:

SELECT    tblCandidate.FirstName, tblCandidate.LastName

FROM      tblCandidate

WHERE     ((tblCandidate.Available=True));

To return ALL fields of the available candidates, use the following code:

SELECT *

FROM tblCandidate

WHERE ((tblCandidate.Available=True));

You can try out these examples by adding a data control to a new form and placing the example SQL query into the RecordSource property. The DatabaseName property should be set to the MAKETEAM.MDB. You can see the result of the query by placing a DBList on the form and setting the RowSource property to the name of your data control and the ListField property to the name of the field you want to display.


NOTE

The MAKETEAM.MDB file is the database used by the Best Team Selector program and can be found on the CD-ROM that accompanies this book. MAKETEAM.MDB contains all the tables described in the preceding examples.

There is also another class of SQL queries usually referred to as action queries. In the strictest sense, these are not really questions, although in most environments they return an answer with regard to the success or failure of the query:

ALTER TABLE

Alters an existing database table.

CREATE INDEX

Creates an index for an existing database table.

CREATE TABLE

Creates a new database table.

DELETE

Deletes records from a table.

INSERT INTO

Inserts records into a table.

UPDATE

Updates records in a table.

The CREATE TABLE, CREATE INDEX, and ALTER TABLE action query keywords are known as Data Definition Language statements, sometimes abbreviated as DDL. These are not supported by Visual Basic 4.0 on non-Jet engine databases. In practice, unless your application needs to create or modify a database (other than at the record level) you will not use these commands. If you do need these actions, Visual Basic supports them through methods of the data control and through SQL Passthrough. In general, you may want to create and edit your databases with other tools or at minimum with Visual Basic's Data Manager.

To create a simple database that contains one table by using the Visual Basic Data Manager, follow these steps:

  1. From the Add-Ins menu, select Data Manager.

  2. From the Data Manager File menu, select New Database.

  3. For the database File name, enter People and click Save.

  4. Press the New button to create a new table.

  5. Fill in the fields of the Add Table dialog to match those of Figure 31.3. Once you've filled in the screen, press the > button to move the new field into the list box. This list box shows all the fields that are part of the table.


  6. Figure 31.3. The Add Table dialog box.

  7. Create another field and call it State. This field should also be of type text, but it should only be of length 2.

  8. Press the > button to move the State field into the list box and then click the OK button.

  9. You can now add records to the database table you've defined by selecting the table, as shown in Figure 31.4, and by clicking the Open button.


Figure 31.4. Adding records to a table.

The remaining action query commands—INSERT INTO, DELETE, and UPDATE are subject only to restrictions of the particular database or table to which they are applied. Some simple examples, which again use the BTS database, follow.

To create a record in the tblCandidate table with Bobby as the FirstName field and Demo as the LastName field, use this code:

INSERT INTO    tblCandidate(FirstName,LastName)

VALUES         ("Bobby","Demo");

This code deletes the Bobby Demo record from the tblCandidate table:

DELETE    *

FROM      tblCandidate

WHERE     (tblCandidate.FirstName="Bobby")

          AND (tblCandidate.LastName="Demo");

To change the last name of all Bobby Demo records in the table, use this code:

UPDATE    tblCandidate

SET       [tblCandidate.LastName] = "Ademo"

WHERE     (tblCandidate.FirstName="Bobby")

          AND (tblCandidate.LastName="Demo");

We've only touched on the SQL language here. For more comprehensive coverage, turn to Chapter 32.

Choosing a Database Back End

Database back end is a common term these days that refers to the type of database used by a program. The back-end part of the phrase stems from the fact that the database is usually not something the users see directly on their desktops. They interact with the database in whichever ways their programs enable them. The particular database in use by the program is of no particular intellectual importance to them if the program they use gives them what they want.

As with most modern Windows development tools, you are not limited to one type of database system when using Visual Basic 4.0—you can use Access, various XBase flavors, Excel and many others for database storage. Visual Basic's ODBC and OLE 2 support enables it to access any database compatible with the ODBC or OLE 2 standard. These databases include Microsoft SQL Server, Oracle, and Sybase. Oracle has both OLE 2 and ODBC drivers. Sybase is accessible through ODBC.

You can choose from two groups of database back ends: ISAM and client/server. ISAM stands for Indexed Sequential Access Method, and just about any popular database that is not configured as client/server falls into this category. ISAM databases include the dBASE (DBF) types, Access (MDB), and many others. The distinguishing characteristic of ISAM databases is that they are directly manipulated in every way by the computer running the query program.


TIP

If you decide to use an ISAM database with your Visual Basic 4.0 program, Access can be a good choice because it is directly controllable by Visual Basic's Jet engine. All other ISAM databases go through additional layers of processing.

Client/server databases, on the other hand (as the name implies), reside on something manipulated at the most basic levels by an RDBMS server. The client interacts with the database by making requests to the RDBMS, which the server then carries out. Oracle, Sybase, and Microsoft SQL Server are some popular client/server databases available today.

If you need a database scheme for use by one computer at a time, ISAM is usually an obvious choice. Among its benefits are more straightforward development and maintenance issues. Depending on the type of system hardware and software function, ISAM also can respond faster than a client/server approach.

However, if you are developing a database application for use by several computers simultaneously and fault-tolerance is a very important factor, client/server schemes should be considered. In the ISAM implementations, generally any computer writing to the database has the opportunity to physically corrupt it. In a multiuser environment, this can mean that everyone is exposed to the instability of just a few user computers. If a corruption can be fixed, all users may have to log out while the database is repaired.

If the RDBMS server in a client/server scheme is well designed and maintained, physical database corruption is less likely. Only the server itself can physically corrupt the database, and if the server is well designed and maintained, this would be unusual. Unlike user PCs, access to the configuration of the server usually is tightly controlled.

In general, the following rules apply when the user or users will be writing to a database in an application:

Database Situation   Comment


Small, single-user database

ISAM is the best choice. Simple to implement and can be the fastest solution.

Small database with

ISAM may not be a bad choice. See the later few simultaneous users section, "Keeping Your Database Healthy."

Small database with

No easy answer here. If the number of transactions many simultaneous users is small, ISAM may be adequate. However, if fault prevention is paramount, RDBMS client/server is the only real choice.

Huge, single-user database

Most RDBMS servers are optimized in ways that significantly enhance performance of the SQL queries.

Huge database with

A client/server RDBMS may be the only way to many simultaneous users implement this system with acceptable performance and reliability.

Sharing Your Database

The RDBMS nature of Visual Basic 4.0 database interaction goes a long way toward simplifying multiuser database access. On the surface, sharing client/server databases is just a matter of appropriate definitions at the server level by the Database Administrator (DBA), and sharing ISAM databases is a matter of placing the database in a network directory and applying the OpenDatabase method without setting exclusive access to True. For quick and simple shared database applications, this can be enough to share a database, because the Jet engine and the client/server RDBMS can handle multiple users behind the scenes.

Looking a little deeper however, there are some subtle issues that can have tremendous impact on the usability and performance of a shared database application. The following items are of particular relevance to both ISAM and client/server implementations:

Every time a table is written to, its indexes must be updated. If there are many indexes or the indexes are very complex or large, the update can significantly impact the total number of operations and time dedicated to the transaction. In an ISAM environment, this means more opportunity for a physical database corruption by the user's machine. In both a client/server RDBMS and an ISAM environment, it can mean slower processing. It is generally a good idea to keep indexes as few and simple as possible, although the inclination is to index everything to cover all the "what if someone asks for..." situations. Ironically, the increased risk of corruption in an ISAM environment from excess indexes also translates into longer down time for repairs. (Compacting an MDB and packing a DBF causes re-creation of every index associated with the affected tables. Compacting and packing of ISAM databases is explained in the "Keeping Your Database Healthy" section of this chapter.) In general, smaller and fewer is better where indexes are concerned.

Although Visual Basic 4.0 does a good job of shielding the mundane issues of shared data access from the application developer, a fundamental fact cannot be ignored when multiple users read and write to the same database: Data cannot be assumed to stay static. This means that it is a good programming practice to issue the Idle method periodically to update program record sets and release unneeded read locks.

Read locks are issued by some databases to ensure that related records are not altered. Sometimes these are set automatically by database management systems, and sometimes they are set at runtime by program code. The Idle method can be used to free those locks sooner than they might otherwise be freed when issued with the dbFreeLocks parameter. (There is a detailed explanation of this method and what it does in the Visual Basic help file under the keyword "idle.")

All users of the shared database benefit when locks are removed, because a lock can interfere with another user's update request. The user issuing an Idle benefits by getting a refresh of the database. This is especially relevant if the user program is displaying a list of table items and some of those items have been modified or removed in the time that the list was displayed originally.

Transactions help performance of a shared database application in two significant ways:

  1. Proper use of transactions improves the responsiveness of the database for all users; in particular, it improves the performance of the machine incorporating transaction processing.

  2. Proper use of the transactions ensures logical data integrity even in the event of some program failures.

Transactions are implemented in Visual Basic 4.0 by the BeginTrans, RollBack, and CommitTrans methods. Start a transaction by applying the BeginTrans method to your database workspace. Until Visual Basic 4.0 executes a Rollback or CommitTrans method on that workspace, it will not actually "write" any updates to the shared database. Instead, all updates are stored in RAM and in temporary files. If your application will update many records in a batch process, the shared database will be impacted only once when the CommitTrans method is issued at the end, instead of during each iteration of the batch.

In the case of a batch update failure, an issue to the Rollback method on the database workspace prevents any of the pending updates from affecting the shared database. This is especially significant when changes to one part of a database are only meaningful if updates in other parts take place simultaneously. A Rollback command can be issued via an On Error construction, as illustrated in Listing 31.1, to correct for unanticipated errors during processing.

Sub BatchDatabaseUpdate(MyDB as Database)

     MyDB.BeginTrans     'Start the transaction block.

     On Error Goto BatchDatabaseUpdate_Undo

     ...

     ... Batch Processing

     ...

     MyDB.CommitTrans  'Commit all the batch processing.

     Exit Sub

BatchDatabaseUpdate_Undo:

     MyDB.Rollback     'Undo the transaction block.

End Sub

An important concept when dealing with multiuser database updates is that of pessimistic versus optimistic locking. A pessimistic locking scheme is one in which a database application assumes that unless it "locks out" other users from a particular record, another user will try to modify it while the program needs to use it exclusively. (Read locking falls somewhat into the pessimistic locking umbrella.) An optimistic locking scheme applies the opposite philosophy: It cheerfully assumes that no one will interfere with the record while it does exclusive processing. These two schemes are of particular significance in the case of a record edit. A pessimistic scheme would issue a lock on a record as soon as the Edit method is issued. This lock would exist for the duration of the edit. In fact, the user could walk away from the machine after starting the edit, and unless the program has a built-in time-out feature, the lock would persist until the user comes back and issues the Update method. The advantage for the user here is that there is no chance something will happen to the record while the user is editing it. Any defined relationships this record might have in the database are not going to change. This ensures that the user update will not conflict with the database constraints when the database update is performed.

However, the downside to pessimistic locking is obvious: No one else can update that record or perform other exclusive operations on the database if they impact that record in any way. Visual Basic 4.0 is pessimistic by default for some databases.

The optimistic locking alternative is more multiuser-database friendly. Optimistic locking can be implemented in Visual Basic 4.0 by setting the LockEdits property of the database to False. A well designed, multiuser database that allows extended-use edits of its data generally should employ optimistic record locking to reduce impact on other system users. Listing 31.2 shows a construction that implements optimistic locking on a modal form called frmEditRecord through a Visual Basic Data control called Data1.


Note

The likelihood of having a problem with pessimistic locking in a multi-user environment is very complex to determine. The essential factors in this case are the number of users that will edit the database simultaneously and the structure of the database itself. A thorough discussion of these issues is beyond the scope of this chapter. Arguably, optimistic locking can be the most user friendly when implemented properly.

———————————————————————————————————

'This subroutine just illustrates the concept of using optimistic

'record locking. In practice, unless a modal edit window

'is desired the DataChanged property check and logic would

'be on the OK or SAVE button of the edit form itself.

———————————————————————————————————

Sub FriendlyEdit()

     Dim Done

     Done = FALSE

     Data1.LockEdits = FALSE

     While Not Done

          Data1.Edit

          frmEditRecord.Show 1    'Modal edit form.

          If Data1.DataChanged Then

               'Not safe to write back - may overwrite someone else.

               Beep

               MsgBox "Someone changed the record data.",48,"Changed"

               Data1.UpdateControls 'Load controls with new data.

          Else

               'Safe to write changes back.

               Data1.Update

               Done = TRUE

          Endif

     Wend

End Sub

A very subtle database-sharing issue lies in the physical type of locking that an RDBMS employs. There are two types: record-level locking and page-level locking. Record-level locking is implemented by the DBF types (dBASE and FoxPro, among others) and some client/server RDBMSs (Oracle is one). A record-level locking scheme locks only those records that need to be locked in order for a particular exclusive operation to take place. Page-level locking schemes, on the other hand, lock entire sections of a database to lock a record. The locked section is the page or pages of the database that contain the record. Pages do not usually correspond to records in size, so several records are included in a single page, or records can span pages. Microsoft SQL Server, Access, and BTrieve are three page-locking databases.

The logic behind a page-locking implementation is that of speed: Less computing is required of the DBMSs to identify and mark the section of the database that contains the target record. For reasons beyond the scope of this chapter, page calculation is computationally simple for the RDBMS. The downside of this scheme is the unnecessary and somewhat unpredictable locking of unrelated records. Page-locking systems, more than any others, need to ensure quick turnaround on locks. It can be quite unacceptable in a large, multiuser page-locking database to employ pessimistic locking because it may lock neighboring records for significantly long periods of time. If you use a page-locking database, you may need to pay particular attention to the dynamics of your application and the relative size of the table records to the table page sizes.

Designing Your Database

The subject of relational database design can be extremely complex. However, there are some fundamentals. A good place to begin is with database normalization.

Normalization is the process of removing redundancies in a database scheme. In the language of relational databases, there are five standard normal forms. The last two are rarely applied, difficult to describe, and difficult to understand. For this reason, I will only discuss the first three in detail. The fourth and fifth forms are only listed here with limited explanation.

Normal Form


Function


1

Eliminates repeating groups. Different items belong in separate tables, and each table should have a primary key.

2

Eliminates redundant data. If a field depends on only part of a multifield key, remove it to a separate table.

3

Eliminates columns not dependent on a key. If fields do not contribute directly to the description of a key, move those fields to a new table.

4

Isolate independent multiple relationships.

5

Isolate semantically related multiple relationships.

Each normal form builds on the one before it; in other words, a database that meets the requirements of third normal form also satisfies normal forms 2 and 1.

An example of a completely non-normalized database is a database consisting of one table containing the names of people with skills they possess (see Figure 31.5). Because this is a single table, a person with more than one skill appears more than once in the table. In each record, the name and other information identifying the person is repeated. To normalize this database, you could remove the fields describing the skills to a new table. The new "tblPeople" table would now use the "PeopleKey" as a foreign key into the new "tblPeopleSkill" table (see Figure 31.6). (Foreign key is just a fancy term for a field or group of fields that are used to "lookup" values in another table.)


FIGURE 31.5. A non-normal form database.


FIGURE 31.6. The first normal form tblPeople, and the tblPeopleSkill database.

The database in the above example is still not in second normal form, because the tblPeopleSkill table lists the name of the skill although the unique key into the table is PeopleKey + SkillKey and SkillName only applies to SkillKey. As a result, the skill name is repeated in the table each time more than one person has a particular skill. To place the database into second normal form, you remove the SkillName field from the table and place it into a new table to which the SkillKey is a foreign key. Figure 31.7 shows the result of changing the tables from Figure 31.6 as described.


FIGURE 31.7. The second normal form tblPeople, tblPeopleSkill, and the tblSkill database.

The database is now in second normal form, but not third because the tblPeople table contains fields that are not really about people. The LocationName field is an attribute independent of the PeopleKey field, so you can move it to its own table and replace LocationName with LocationKey. Figure 31.8 illustrates this change.


FIGURE 31.8. The third normal form of our database. You now have four tables: tblPeople, tblPeopleSkill, tblSkill, and tblLocation.

Normalization can be a good exercise when designing a database because it forces a bit of rigor into the analysis. The elegance of normalization is that it reduces the data stored in the database to its smallest logical components. The cost, however, is sometimes an excess of tables and indexes for a database. In general, fewer tables and fewer indexes can mean less database application overhead. Many databases eventually are denormalized to improve performance. It is not unusual to normalize an original design to the third level and then denormalize it back to the first form to reduce disk storage needs and to improve access time.

One more fundamental relational database design element is that of the relationship definitions themselves. In general, there are three types:

There are variations on the preceding relationship types with respect to whether at least one record is required or not. In other words, some tools allow the specification that at least one record must exist in the many side of a one to many relationship. By the same token, some tools allow a one to many relationship to have zero records on the many side. Figure 31.9 shows the generic symbols sometimes used to illustrate these relationships in database diagrams.


FIGURE 31.9. Generic database diagram relationship symbols.

If you are using a client/server RDBMS, these relationships can be defined in the database and enforced by the RDBMS. When using ISAM databases with Visual Basic 4.0, maintenance of relationships relies entirely on the program code.

Creating Your Database

Unless your program has to create a database at runtime, the best way to create your database for use with Visual Basic 4.0 is to use the Data Manager application or to use a database tool designed for that database. Following are some ISAM database tools:

Database Type


Database Tool


Access

Microsoft Access

dBASE

Borland Visual dBASE

FoxPro

Microsoft Visual FoxPro

Paradox

Borland Paradox for Windows

Most client/server databases come with their own tools for use by the DBA and are of varying quality and usability. If you are working with client/server databases and do not like the tools that come with them, or you just want to take a step up in relational database design, several third-party relational database tools are available that create databases for you after you design them interactively in their environment. Most of the larger mail-order software vendors carry a few tools.

If you choose to create a database from within Visual Basic 4.0 code, there are two ways to do it: You can use the Visual Basic 4.0 methods or if your RDBMS supports it, the SQL language directly. The Visual Basic 4.0 methods can create only Access-type databases (MDB). Listing 31.3 creates an Access database with three tables similar to those of the BTS program found on the CD-ROM. Figure 31.10 shows the result of this code listing.


FIGURE 31.10. Diagram of tables created by Listing 31.3.

'———————————————————————————————————

'Sample code to create a three table Access database.

'———————————————————————————————————

Sub CreateTestDB()

'Define the variables we will use.

Dim MyDB As DATABASE, MyWs As Workspace

Dim tblC As TableDef, tblS As TableDef, tblCS As TableDef

Dim fldC(3) As Field, fldS(4) As Field, fldCS(5) As Field

Dim idxC As Index, idxS As Index, idxCS As Index

Dim fldTemp As Field

Dim DBName As String

'Create the database in the same directory as application.

DBName = App.Path + "\TEST.MDB"

Set MyWs = DBEngine.Workspaces(0)

Set MyDB = MyWs.CreateDatabase(DBName, dbLangGeneral, dbVersion20)

'Create the tables of the database.

Set tblC = MyDB.CreateTableDef("tblCandidates")

Set tblS = MyDB.CreateTableDef("tblSkill")

Set tblCS = MyDB.CreateTableDef("tblCandidateSkill")

'Create the fields of the Candidates table.

Set fldC(0) = tblC.CreateField("CandidateKey", dbLong)

fldC(0).Attributes = dbAutoIncrField

Set fldC(1) = tblC.CreateField("FirstName", dbText)

fldC(1).Size = 20

Set fldC(2) = tblC.CreateField("LastName", dbText)

fldC(2).Size = 25

'Create the fields of the Skills table.

Set fldS(0) = tblS.CreateField("SkillKey", dbLong)

fldS(0).Attributes = dbAutoIncrField

Set fldS(1) = tblS.CreateField("Name", dbText)

fldS(1).Size = 20

'Create the fields of the Relationship table.

Set fldCS(0) = tblS.CreateField("CandidateSkillKey", dbLong)

fldCS(0).Attributes = dbAutoIncrField

Set fldCS(1) = tblS.CreateField("CandidateKey", dbLong)

Set fldCS(2) = tblS.CreateField("SkillKey", dbLong)

'Now append fields to Candidate table and then to DB.

tblC.Fields.Append fldC(0)

tblC.Fields.Append fldC(1)

tblC.Fields.Append fldC(2)

MyDB.TableDefs.Append tblC

'Now append fields to Skills table and then to DB.

tblS.Fields.Append fldS(0)

tblS.Fields.Append fldS(1)

MyDB.TableDefs.Append tblS

'Now append fields to Relationship table and then to DB.

tblCS.Fields.Append fldCS(0)

tblCS.Fields.Append fldCS(1)

tblCS.Fields.Append fldCS(2)

MyDB.TableDefs.Append tblCS

'Now add an index to the Candidates table.

Set idxC = tblC.CREATEINDEX("idxCandidateKey")

idxC.PRIMARY = True

idxC.UNIQUE = True

Set fldTemp = idxC.CreateField("CandidateKey")

idxC.Fields.Append fldTemp

tblC.Indexes.Append idxC

'Now add an index to the Skills table.

Set idxS = tblS.CREATEINDEX("idxSkillKey")

idxS.PRIMARY = True

idxS.UNIQUE = True

Set fldTemp = idxS.CreateField("SkillKey")

idxS.Fields.Append fldTemp

tblS.Indexes.Append idxS

'Now add an index to the relationship table.

Set idxCS = tblCS.CREATEINDEX("idxCandidateSkillKey")

idxCS.PRIMARY = True

idxCS.UNIQUE = True

Set fldTemp = idxCS.CreateField("CandidateSkillKey")

idxCS.Fields.Append fldTemp

tblCS.Indexes.Append idxCS

'Create relationship C to CS

Dim relCtoCS As Relation

Set relCtoCS = MyDB.CreateRelation("CtoCS")

relCtoCS.TABLE = "tblCandidate"

relCtoCS.ForeignTable = "tblCandidateSkill"

Set fldTemp = relCtoCS.CreateField("CandidateKey")

fldTemp.ForeignName = "CandidateKey"

relCtoCS.Fields.Append fldTemp

MyDB.Relations.Append relCtoCS

'Create relationship S to CS

Dim relStoCS As Relation

Set relStoCS = MyDB.CreateRelation("StoCS")

relStoCS.TABLE = "tblCandidate"

relStoCS.ForeignTable = "tblCandidateSkill"

Set fldTemp = relStoCS.CreateField("SkillKey")

fldTemp.ForeignName = "SkillKey"

relStoCS.Fields.Append fldTemp

MyDB.Relations.Append relStoCS

End Sub

NOTE

Although Visual Basic 4.0 now allows complete control of Access database security features, it does not support creation of a secure database. Secure Access databases need a SYSTEM.MDW file, and Visual Basic 4.0 doesn't provide a facility to build one at runtime. To create a secure database, you need to use another tool, such as Microsoft Access.

As Figure 31.10 illustrates, Listing 31.3 creates three tables in the database and each has a defined relationship to one of the others. Because Visual Basic 4.0 does not support the creation of non-Jet engine databases, you have to use other tools to create an initial database. In the case of some ISAM databases, this is just a matter of creating a blank directory and placing the individual database files into it.


NOTE

The Jet engine database does not support SQL statements that define or alter the structure of non-Jet engine databases. If you want to use SQL for those operations, you have to use SQL Passthrough commands that your particular RDBMS accepts.


TIP

If your intent is just to provide the user with a blank database, you do not have to create it at runtime. Instead, you can take the approach implemented in BTS, which is to ship a blank database and then at runtime copy that blank database to a new name. The advantages follow:

Reduced version control complexity. The database design you implement (and change) during development does not impact the new database creation functionality of your program.

Capability to determine contents of "blank" database without recoding the Visual Basic program. In the case of BTS, the TEMPLATE.MDB can be pre-populated with whatever data is appropriate for a new instance of the database. The users creating a new database in BTS then get a copy that already has the settings they wanted to begin with. (For BTS, users may want to have a particular listing of skills in every database, for example.)

Modifying Your Database

There are fundamentally two ways to modify the structure of existing databases within Visual Basic 4.0 if you choose not to use a dedicated database tool. One way is to use Visual Basic 4.0 database methods, and the other is to use SQL action queries. The Visual Basic methods are the same ones employed in the CreateTestDB() function in Listing 31.3.

Visual Basic 4.0 does not support action queries that modify database structure, so any SQL commands you use for that purpose have to be handled entirely by your RDBMS. The SQL action queries would be of the Alter Table variety, but would depend on the RDBMS you use.

Working with Your Database

Visual Basic 4.0 programs can work with database data through native Visual Basic methods, SQL action queries, or OLE 2. Although Oracle has released an OLE 2 interface to its RDBMS, ODBC and native Visual Basic methods are the more common ways to interact with database data; those ways are focused on in this section.

If you have the professional version of Visual Basic 4.0, you can open database objects entirely through code. With both the standard and professional versions, you can open database objects through the data control. Details of the data control are covered in Chapter 27, so I won't cover that here. Instead, let's jump right into working with the BTS database to create and edit tblCandidate records:

  1. Start a new project and get a blank form. You can do this by selecting New Project from the Visual Basic File menu. A blank form then appears automatically.

  2. Place aData control on your form. You can do this by double-clicking on the data control icon displayed in your Visual Basic toolbox (see Figure 31.11).


  3. FIGURE 31.11. The Data control.

  4. Assuming that the MAKETEAM.MDB file from the sample BTS program is already on your drive, edit the Data1 properties as follows:

    DatabaseName Maketeam.mdb
    Name Data1
    RecordSource tblCandidate

  5. Place two TextBox controls on your form. Edit the properties so that all have a data source of Data1. Set the DataField property of one to Title, another to FirstName, and the value for the last control to LastName.

  6. Add four CommandButton controls to your form and edit the properties as in the following code:

    Command1
    Caption &Add
    Name cmdAdd

    Command2
    Caption &Delete
    Name cmdDelete

    Command3
    Caption &Undo
    Name cmdUndo

    Command4
    Caption &Quit
    Name cmdQuit

  7. Place the following code into your form (You can get to the code module by double-clicking on any unused area of the form):

    Private Sub cmdAdd_Click()
    cmdDelete.Enabled = False
    Data1.Recordset.AddNew
    End Sub
    Private Sub cmdDelete_Click()
    Data1.Recordset.Delete
    Data1.Recordset.MoveFirst
    End Sub

    Private Sub cmdUndo_Click()
    Data1.UpdateControls
    End Sub

    Private Sub cmdQuit_Click()
    End
    End Sub

    Private Sub Data1_Reposition()
    cmdDelete.Enabled = True
    End Sub

  8. Add some labels to the TextBox controls by editing their caption properties so that they look like those of Figure 31.12. You can run this program by pressing F5 while in Visual Basic.

    The simple form created in the preceding seven steps demonstrates how to add, delete, edit, and undo database changes. The edit functionality, in this case, is handled by the data control and requires no special coding.


FIGURE 31.12. The final screen.


NOTE

This example does not consider the data integrity of the MAKETEAM.MDB database. If it did, it also would delete skills associated with candidates when the candidates are deleted.

To perform batch updates on your database, SQL is sometimes a good choice. Visual Basic 4.0 SQL is covered in detail in Chapter 32. Only a simple example is shown here to illustrate one way to execute such a query.

Follow these steps using the add/edit/delete/undo form created earlier. These steps will add SQL batch processing to the form:

  1. Add another CommandButton control to your project from the previous Add/Edit/Delete screen.

  2. Edit the properties of the button to be the following:

    Command1
    Caption &Kill All
    Name cmdKillAll

  3. Place the following code into your form:

    Private Sub cmdKillAll_Click()
    Dim r, style
    style = vbYesNo + vbCritical + vbDefaultButton2
    r = MsgBox("Delete all candidates?",style,"WARNING")
    If r = vbYes Then
    Data1.Database.Execute "DELETE * FROM tblCandidate"
    MsgBox "All candidates deleted."
    End If
    End Sub

Now when you run the form, you have a "Kill" button. Click on it to delete all the records in the tblCandidates table.

Keeping Your Database Healthy

Whatever database scheme you implement, periodic backup is cheap insurance that can pay huge dividends should anything ever go wrong. Backup strategies depend on the type of database and the circumstances of the implementation.

If your application uses an ISAM database, backup can be as simple as a periodic PKZIP of the database files. In the case of a shared ISAM database, all users have to log out during this process. If you have the space for it, this is a good idea even on systems that have tape backups, because backup tapes can be somewhat unreliable.

Client/server RDBMSs can be more complex to back up, and the process may be unique to the hardware on which your RDBMS resides. Although a well-designed RDBMS can be very stable, not keeping current backups can be foolish.

There are two types of database corruption: physical and logical. A physical database corruption is when the format of the database files are in some way adulterated. A logical corruption is when a relationship constraint is not preserved or a table restriction is not adhered to. An example of a logical corruption is leaving a field blank when the database requires a non-null value. Examples of physical corruption are a malformed record or an improperly updated index.

As discussed earlier, shared ISAM databases are physically manipulated by the users' machines. In the case of Visual Basic 4.0 database programs, the manipulations are through the Jet Engine or an ODBC driver. If the user computer has a malfunction during a write operation or some user software derails and writes inappropriate data to the shared database files, a database corruption can take place.

Some database corruptions can linger undetected for some time and cause other corruptions and user program crashes. A good practice for any ISAM database, and in particular any shared ISAM databases, is to periodically "repair" them. For non-Access (MDB) databases, you need to use an appropriate utility for that database type or system. In the case of DBF type databases, a common repair function is a "pack." (Pack in the xBase world removes records marked for deletion and rebuilds indexes.) Visual Basic 4.0 does not include a pack utility. It can, however, repair Access databases with the DBEngine.CompactDatabase method. There is no harm in repairing a healthy database, but there is much potential harm in letting a corrupt database continue to serve application users.

Unless a logical database corruption contradicts a stored logical definition in the database itself, a general-purpose repair utility will not detect it. (An example of a logical requirement that is not defined in a database would be in the case of a dBASE database where one table record depends on the existence of a record in another table. Because dBASE databases do not store any relationship information, a pack or third party repair program would have no way of knowing this requirement.) For this reason, it is always a good idea to define all relationships and database constraints within the database instead of within your program code whenever possible. Some ISAM databases, such as the DBF type, do not support constraint storage, but the Access (MDB) type does. All client/server RDBMS systems support constraint definition within the database.

Summary

This chapter introduced some database fundamentals, covered some practical considerations, and touched on the power of SQL. For more details on the SQL language and discussion on how to apply it, turn to Chapter 32.

Previous Page TOC Next Page