Your final week will start with another chapter on SQL keywords. The previous SQL chapter (Day 9, "Selecting Data with SQL") focused on SQL's Data Manipulation Language (DML) keywords. Today's work will focus on SQL's Data Definition Language (DDL) keywords.
In Day 9, you learned how easy it is to select and order data using the SQL SELECT...FROM clause. You also learned that using SQL statements to perform data selection means that your Visual Basic programs will work with almost any back-end database server you might encounter in the future.
In today's lesson, you'll learn that you can use SQL statements to create your databases, too. Using SQL keywords to create your data tables, set relationships, and create indexes gives your programs an added level of portability. The SQL words you learn today work not only on Microsoft Access formatted databases, but also on any database format that is SQL-compliant. The skills you learn today can be applied to almost every database engine on the market.
By the time you are through with today's lesson, you will be able to use SQL keywords to perform the following tasks:
Throughout today's lesson, you use a program called SQL-VB. This is a VB program that processes SQL scripts. All the commands you learn today are in the form of SQL scripts. You can use the SQL-VB program without knowing much about how it was built. However, if you are curious about how SQL-VB works, you can refer to Appendix A. This appendix walks you through a step-by-step construction of SQL-VB. It also contains information on how to use SQL-VB with this book and with other projects you will create in the future.
Before you begin today's lesson in advanced SQL commands, you will take a quick tour of the SQL-VB program. You'll learn how to use SQL-VB to create, edit, and run SQL scripts. The SQL-VB interpreter is a program that reads and executes SQL command scripts. You will use this program throughout the lesson today. You might also find that this program will be useful in the future for creating and managing SQL databases.
To load the SQL-VB Interpreter, locate the TYSDBVB\SQLVB directory that was created from the installation CD (for installation information, refer to the inside back cover of this book). Select either SQLVB.EXE (32-bit version) or SQLVB16.EXE (16bit version) depending on your operating system. After the program loads, you should see a screen that looks similar to the one in Figure 15.1.
The opening screen for SQL-VB Interpreter.
The opening screen is actually a multidocument interface. You can load and run one or more scripts from this interface. To test the system, load and run a simple test script. Using SQL-VB, select File | Run, and at the File | Open dialog, locate and select SQLVB01.SQV (refer to Figure 15.2).
Loading the SQLVB01.SQV SQL script.
When you select the script, SQL-VB begins to read and process the SQL commands in the file. This test script opens the BOOKS.MDB database and then creates six result sets and displays them on the screen. When the script is completed, you see a dialog box announcing the completion of the script along with several result sets displayed on the screen as shown in Figure 15.3.
The completed SQLVB01.SQV script.
SQL-VB creates all result set forms in a cascade starting at the top left of the screen. You can change this to a tiled view by selecting Windows | Tile from the main menu (refer to Figure 15.4).
Tiling the open forms.
You can also use your mouse pointer to resize, minimize, or maximize any form. You can even resize individual columns and rows within a form. Figure 15.5 shows several of the ways you can alter the view of forms.
Altering the form views within SQL-VB.
You can also use SQL-VB to create and edit SQL command scripts. For example, edit the SQLVB01.SQV script you tested earlier. First, load the script for editing by selecting File | Edit from the main menu. Locate and select the SQLVB01.SQV SCRIPT. When you select the script, SQL-VB launches the Notepad editor and loads the selected SQL script.
Let's change the SQL script so that the first result set includes only authors whose Au_ID is greater than 10. To do this, add the text WHERE Au_ID>10; to the first SELECT statement. Be sure to place the semicolon (;) at the end of the line. SQL-VB needs this character to indicate the end of an SQL statement. Also, let's comment out the rest of the view sets for now. You only want to see one result set in this test. To do this, add two slashes (//) to the start of all the other lines that contain SELECT statements. Be sure to place a space after the // comment sign. Your script should now resemble Listing 15.1.
Listing 15.1. Modifying an SQL-VB script.
// // test sql command file for sqlvb interpreter // // open the database dbOpen \tysdbvb\sqlvb\book.mdb; // open some tables to view SELECT * FROM Authors WHERE Au_ID>10; SELECT * FROM Titles; SELECT * FROM Publishers; SELECT * FROM BookSales; SELECT * FROM Buyers; SELECT * FROM [Publisher Comments]; // // eof //
After you have changed the script, save it using the File | Save command of Notepad. Now select File | Run from the SQL-VB main menu to run the updated SQLVB01.SQV command script. Your results should look similar to those in Figure 15.6.
The results of the edited SQLVB01.SQV script.
You can create new SQL-VB scripts by selecting File | New from the menu and entering any valid SQL statement into the editor. After you've created your script, save it with an .SQV file extension. Then use the File | Run menu option to execute your script.
You need to know a few SQL-VB command syntax rules before you can create your own SQL-VB scripts. This is covered in the next section.
The command syntax for SQL-VB is very similar to standard ANSI SQL syntax. In fact, any valid SQL command is a valid SQL-VB command. However, there are a few additional commands in SQL-VB that you should know about.
Three special command words work in SQL-VB, but they are not SQL commands. These special commands are used to create, open, and close Microsoft JET databases. SQL-VB also has a comment command. The comment command indicates to SQL-VB that the information on this line is for comment only and should not be executed. Finally, each command line must end with a semicolon (;). The semicolon tells SQL-VB where the command line ends. The special command words, their meanings, and examples are included in Table 15.1.
Table 15.1. Special SQL-VB commands.
SQL-VB Command | Example | Description |
---|---|---|
// | // this is a comment | Any line that begins with // is treated as a comment line and is not processed by the SQL-VB interpreter. Comments cannot be placed at the end of SQL command lines, but must occupy their own line of text. Don't use the single quote mark for comments as in VB because the single quote is a valid SQL character. Also, you must leave at least one space after the // for SQL-VB to recognize it as a comment marker. |
dbOpen | dbOpen C:\DATA.MDB; | The dbOpen command opens a Microsoft JET database. SQL-VB can only open and process Microsoft JET format databases. A dbOpen command must be executed before any SQL statements are processed. |
dbMake | dbMake C:\NEWDATA.MDB; | The dbMake command creates a new, empty Microsoft JET database on the drive path indicated in the command. When a database is created using the dbMake command, you do not have to issue a dbOpen command. |
dbClose | dbClose; | The dbClose command closes the Microsoft JET database that was opened using the dbOpen or dbMake command word. |
; | SELECT * FROM Table1; | The semicolon is used to indicate the end of a command. Commands can stretch over several lines of text but each command must always end with a semicolon(;). |
You now have enough information about SQL-VB to use it in the rest of the lesson today. As you go through the examples in today's lesson, you will learn more about SQL-VB and how you can create your own SQL scripts. If you want to know more about how SQL-VB works, refer to Appendix A.
Before you jump into the details of SQL keywords, let's talk about the advantages of using SQL statements to create and manage your data tables.
Even though Visual Basic offers several powerful commands for performing the same functions within a Visual Basic program, you might find that using SQL keywords to perform database management gives you an advantage. By using SQL statements to create and maintain your database structures, you can easily create useful documentation on how your databases are structured. Are you trying to debug a problem at a client site and can't remember how the tables are laid out? If you used a set of SQL statements to create the tables, you can refer to that script when you are solving your client's problems.
It is also easy to generate, test, or sample data tables using SQL statements. If you are working on a database design and are still experimenting with table layouts and relationships, you can quickly put together an SQL DDL script, run it through SQL-VB, and review the results. If, after experimenting, you find you need a new field in a table, you can alter your existing script and rerun it. Or, you can write a short script that makes only the changes you need, preserving any data you have loaded into the existing tables.
You can even use SQL statements to load test data into your new tables. After you have created the tables, you can add SQL statements to your script that load test data into the columns. This test data can exercise defined relationships, check for data table integrity, and so on. Using an SQL script to load data is an excellent way to perform repeated tests on changing data tables. As you make changes to your table structures, you can use the same data each time until you know you have the results you are looking for.
Also, you can use the same SQL statements to create your data tables within other database systems, including Microsoft's SQL Sever, Oracle, and others. After you create the test files using Microsoft Access JET databases, you can then regenerate the tables for other database engines using the same SQL statements. This increases the portability of your application and eases the migration of your data from one database platform to another.
The type of SQL keywords you'll learn today are the table management keywords. These keywords enable you to create new data tables, alter the structure of existing data tables, and remove existing data tables from the database.
The CREATE TABLE keyword allows you to create new tables in an existing database. In its most basic form, the CREATE TABLE statement consists of three parts: the CREATE TABLE clause; a TableName; and a list of column names, column types, and column sizes for each column in the new table. The following example shows a simple CREATE TABLE SQL statement.
CREATE TABLE NewTable (Field1 TEXT(30), Field2 INTEGER);
This SQL statement creates a data table called NewTable that has two columns. The column named Field1 is a TEXT column 30 bytes long. The column named Field2 is an INTEGER column. Notice that no size was designated for the INTEGER column. Microsoft Access JET SQL statements only accept size values for TEXT columns. All other columns are set to a predefined length. See Table 2.1 in Day 2, "Creating Databases" for a list of the default field lengths for Microsoft Access JET data fields.
NOTE: If you omit the size definition for the TEXT field, Microsoft Access JET uses the default value of 255 bytes. Because this can result in rather large tables with empty space, it's a good habit to declare a size for all TEXT fields.
Test this SQL statement by creating the SQL script in Listing 15.2 and running it using the SQL-VB application. Start the application and select File | New to create a new script called SQLVB02.SQV. Enter the following script commands into Notepad.
Listing 15.2. Creating the SQLVB02.SQV script.
// // SQLVB02.SQV - Testing SQL Table Management Keywords // // create a new database for our tests dbMake sqlvb02.mdb; // create a simple table CREATE TABLE NewTable (Field1 TEXT(30), Field2 INTEGER); // show the empty table SELECT * FROM NewTable; // eof (end of file)
This script creates a new database, creates a new table in the database, and displays the empty table in a result set. Use SQL-VB to run the script by selecting File | Run and locating and loading the SQLVB02.SQV script file. Your results should appear as shown in Figure 15.7.
Results of the CREATE TABLE statement.
You can also use the PRIMARY KEY command when you CREATE a data table. This can be done by following the name of the primary key field with a CONSTRAINT clause. Use SQL-VB to edit the SQLVB02.SQV script so that it sets the Field1 column as a primary key. Refer to Listing 15.3 for an example.
Listing 15.3. Adding the PRIMARY KEY CONSTRAINT.
// // testing SQL Table Management Keywords // // create a new database for our tests dbMake sqlvb02.mdb; // create a simple table CREATE TABLE NewTable (Field1 TEXT(30) CONSTRAINT PKNewTable PRIMARY KEY, Field2 INTEGER); // show the empty table SELECT * FROM NewTable; // eof
Notice that the CREATE TABLE SQL statement is spread out over more than one line of text. SQL statements can stretch over several lines, as long as each complete SQL statement ends with a semicolon. The continued lines need not be indented, but doing so makes it easier to read the SQL scripts.
You'll look at the CONSTRAINTS clause in depth a bit later. For now, remember that you can create both primary and foreign keys in a CREATE TABLE statement.
There are two forms of the ALTER TABLE statement: the ADD COLUMN form and the DROP COLUMN form. The ADD COLUMN form enables you to add new columns to an existing table without losing any data in the existing columns. Edit the SQLVB02.SQV script using SQL-VB so that it matches the script in Listing 15.4.
Listing 15.4. Using the ADD COLUMN clause.
// // testing SQL Table Management Keywords // // create a new database for our tests dbMake sqlvb02.mdb; // create a simple table CREATE TABLE NewTable (Field1 TEXT(30) CONSTRAINT PKNewTable PRIMARY KEY, Field2 INTEGER); // add a two new columns ALTER TABLE NewTable ADD COLUMN Field3 DATE; ALTER TABLE NewTable ADD COLUMN Field4 CURRENCY; // show the empty table SELECT * FROM NewTable; // eof
Notice that you had to add two ALTER TABLE statements to add two columns to the same table. The ALTER TABLE statement can only deal with one column at a time. Run the SQLVB02.SQV script and inspect the results. Your screen should look similar to the one in Figure 15.8.
NOTE: Note that the ADD COLUMN clause always adds columns starting at the left-most column in the table. You can always control the order of the columns in a display using the SELECT...FROM clause (see Day 9). If you want to control the physical order of the fields, you must add the fields in a CREATE TABLE statement.
Results of using ALTER TABLE...ADD COLUMN keywords.
You can also use the ALTER TABLE statement to remove columns from an existing table without losing data in the unaffected columns. This is accomplished using the DROP COLUMN clause. Edit CH1501.SQV to match the example in Listing 15.5.
Listing 15.5. Using the DROP COLUMN clause.
// // testing SQL Table Management Keywords // // create a new database for our tests dbMake sqlvb02.mdb; // create a simple table CREATE TABLE NewTable (Field1 TEXT(30) CONSTRAINT PKNewTable PRIMARY KEY, Field2 INTEGER); // add a two new columns ALTER TABLE NewTable ADD COLUMN Field3 DATE; ALTER TABLE NewTable ADD COLUMN Field4 CURRENCY; // drop one of the new columns ALTER TABLE newTable DROP COLUMN Field3; // show the empty table SELECT * FROM NewTable; // eof
Run the SQLVB02.SQV script and check your results against the screen shown in Figure 15.9.
Results of the ALTER TABLE...DROP COLUMN keywords.
NOTE: You can also use the ALTER TABLE statement to ADD or DROP CONSTRAINTS. We'll cover CONSTRAINTS in depth later in this chapter.
You can use the DROP TABLE statement to remove a table from the database. This is often used to remove temporary tables, or it can be used as part of a process that copies data from one table to another or from one database to another. Edit and save CH1501.SQV to match the code example in Listing 15.6.
Listing 15.6. Using the DROP TABLE clause.
// // testing SQL Table Management Keywords // // create a new database for our tests dbMake sqlvb02.mdb; // create a simple table CREATE TABLE NewTable (Field1 TEXT(30) CONSTRAINT PKNewTable PRIMARY KEY, Field2 INTEGER); // add a two new columns ALTER TABLE NewTable ADD COLUMN Field3 DATE; ALTER TABLE NewTable ADD COLUMN Field4 CURRENCY; // drop one of the new columns ALTER TABLE NewTable DROP COLUMN Field3; // remove the table from the database DROP TABLE NewTable; // show the empty table SELECT * FROM NewTable; // eof
Save and run the updated SQLVB02.SQV. You should see an SQL error message telling you that it could not find the table NewTable. This happened because the script executed the DROP TABLE statement just before the SELECT...FROM statement. The error message appears in Figure 15.10.
Results of the DROP TABLE statement.
You can create and delete indexes or constraints on a data table using the SQL keywords CREATE INDEX, and DROP INDEX, and the CONSTRAINT clause of CREATE TABLE and ALTER TABLE statements. SQL constraints are just indexes with another name. However, CONSTRAINT clauses are usually used with CREATE TABLE statements to establish relationships between one or more tables in the same database. INDEX statements are usually used to add or delete search indexes to existing tables.
The CREATE INDEX statement is used to create a search index on an existing table. The most basic form of the CREATE INDEX statement is shown in the following line:
CREATE INDEX NewIndex ON NewTable (Field1);
There are several variations on the CREATE INDEX statement that allow you to add data integrity to the data table. Table 15.2 shows a list of the various CREATE INDEX options and how they are used.
Table 15.2. The CREATE INDEX options.
CREATE INDEX Statement | Meaning and Use |
---|---|
CREATE INDEX NewIndex ON NewTable (Field1) WITH PRIMARY | Creates a primary key index. A primary key index ensures that each row of the table has a unique value in the index field. No nulls are allowed in the index field. |
CREATE UNIQUE INDEX NewIndex ON NewTable(Field1) | Creates a unique index on the designated field. In this example, no two columns could have the same value, but null values would be allowed. |
CREATE INDEX NewIndex ONNewTable (Field1)WITH DISALLOW NULL | Creates an index that is not unique, but does not allow null columns. |
CREATE INDEX NewIndex ON NewTable (Field1) WITHIGNORE NULL | Creates a non-unique index that allows null records in the index column. |
Use SQL-VB to create a new SQL script that contains the code from Listing 15.7. After you enter the code, save the script as SQLVB03.SQV.
Listing 15.7. Testing the relationship SQL keywords.
// // sqlvb03.sqv - Test Relationship SQL keywords // // create a database dbMake sqlvb03.mdb; // create a test table to work with CREATE TABLE NewTable1 (EmployeeID TEXT(10), LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20), Department TEXT(10)); // create primary key CREATE INDEX PKEmployeeID ON NewTable1(EmployeeID) WITH PRIMARY; // create unique key column CREATE UNIQUE INDEX UKLoginName ON NewTable1(LoginName) WITH IGNORE NULL; // create non-null column CREATE INDEX IKJobTitle ON NewTable1(JobTitle) WITH DISALLOW NULL; // create multi-column sort key CREATE INDEX SKDeptSort ON NewTable1(Department,LastName,FirstName); // show empty table SELECT * FROM NewTable1; // eof
The preceding SQL script shows several examples of the CREATE INDEX statement. You can use SQL-VB to run this script. Your screen should look similar to the one in Figure 15.11.
Results of SQLVB03.SQV script.
In the code example in Listing 15.7, we introduced a naming convention for indexes. This convention is widely used by SQL programmers. All primary key indexes should start with the letters PK (PKEmployeeID). All keys created for sorting purposes should begin with the letters SK (SKDeptSort). All index keys that require unique values should begin with UK (UKLoginName). All keys that define foreign key relationships should start with FK. (You'll learn more about foreign keys in the next section.) Finally, any other index keys should start with IK (IKJobTitle) to identify them as index keys.
You can control the index order by adding ASC (ascending) or DESC (descending) keywords to the CREATE INDEX SQL statement. For example, to create an index on the LastName column, but listing from Zilckowicz to Anderson, you would use the following CREATE INDEX statement:
CREATE INDEX SKLastName ON NewTable1(LastName DESC);
Notice that the DESC goes inside the parentheses. If you want to control the index order on a multiple column index, you can use the following CREATE INDEX statement:
CREATE INDEX SKDeptSort ON NewTable1(Department ASC, LastName DESC);
If you omit an order word from the CREATE INDEX clause, SQL uses the default ASC order.
In the SQL-VB Listing 15.7, the index SKDeptSort is a special index key. This is a sort key index. Sort key indexes can be used to speed data access while performing single-record lookups (using the Visual Basic Find method), or for speeding report processing by ordering the data before running a list report. Sort key indexes are not used to enforce data integrity rules or perform data entry validation.
Although sort key indexes are very common in non-relational databases, they are not often used in relational databases. All the related indexes in a database must be updated by the database engine each time a data table is updated. If you have created several sort key indexes, you might begin to see a performance degradation when dealing with large data files or when dealing with remote (ODBC-connected) databases. For this reason, we do not recommend extensive use of sort key indexes in your database.
You have just about all the possible indexes created in the SQLVB03.SQV example. Many of the indexes serve as database integrity enforcers. In fact, only one of the indexes is meant to be used as a tool for ordering the data (SKDeptSort). All the other indexes in SQLVB03.SQV add database integrity features to the table. This is an important point. In SQL databases, you have much more opportunity to build database editing and field-level enforcement into your database structures than you do with non-relational desktop databases. When you use the database enforcement options of SQL databases, you can greatly decrease the amount of Visual Basic code you need to write to support data entry routines. Also, by storing the database integrity enforcement in the database itself, all other programs that access and update the database have to conform to the same rules. The rules are no longer stored in your program; they're stored in the database itself!
The PRIMARY KEY index (PKEmployeeID) is familiar to you by now. By defining the index as the primary key, no record is allowed to contain a NULL value in the column EmployeeID, and every record must contain a unique value in the EmployeeID column.
The index key UKLoginName allows records in the table that have this field blank (IGNORE NULL). However, if a user enters data into this column, the database checks the other records in the table to make sure that the new entry is unique(UNIQUE keyword). This shows an excellent method for enforcing uniqueness on columns that are not required to have input. For example, if you have an input form that allows users to enter their social security number, but does not require that they do so, you can ensure that the value for the field is unique by using the IGNORE NULL and UNIQUE keywords in the INDEX definition.
The index key IKJobTitle is another example of using the SQL database engine to enforce data integrity rules. By defining the IKJobTitle index as DISALLOW NULL, you have set a data rule that defines this field as a required field. No record can be saved to the data table unless it has a valid value in the JobTitle column. Notice that you have not required that the value be unique. That would require every person in the database to have a unique job title. Instead, you allow duplicate job titles in this column. In real life, you would probably want to check the value entered here against a list of valid job titles. That involves creating a foreign key relationship using the CONSTRAINT keyword. Read the next section for more on CONSTRAINTS.
CONSTRAINTS are really the same as indexes from the standpoint of SQL statements. The CONSTRAINT keyword is used to create indexes that add data integrity to your database. You must use the CONSTRAINT keyword with the CREATE TABLE or ALTER TABLE SQL statement. There is no such thing in Microsoft Access JET SQL as CREATE CONSTRAINT.
There are three forms of the CONSTRAINT clause:
Microsoft Access SQL syntax does not allow you to use the IGNORE NULL or DISALLOW NULL keywords within the CONSTRAINT clause. If you want to create data integrity indexes that include the IGNORE NULL or DISALLOW NULL keywords, you have to use the CREATE INDEX keyword to define your index.
The most commonly used CONSTRAINT clause is the PRIMARY KEY CONSTRAINT. This is used to define the column (or set of columns) that contains the primary key for the table. The SQL-VB script in Listing 15.8 creates a new database and a single table that contains two fields, one of which is the primary key column for the table. The other field is a MEMO field. MEMO fields can contain any type of free-form text and cannot be used in any CONSTRAINT or INDEX definition.
Listing 15.8. Testing the PRIMARY KEY CONSTRAINT.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, JobDesc MEMO ); // show the table SELECT * FROM JobsTable; // eof
Enter this code into the SQL-Visual Basic editor, save the script as SQLVB04.SQV, and execute it. You will see a simple table that shows two fields. Refer to Figure 15.12 as an example.
Defining the PRIMARY KEY CONSTRAINT.
The SQL script in Listing 15.9 performs the same task, except it uses the CREATE INDEX keyword to define the primary key index.
Listing 15.9. Using CREATE INDEX to define the PRIMARY KEY.
// // create index using CREATE INDEX keywords // // create database dbMake sqlvb04.mdb; // create table CREATE TABLE JobsTable (JobTitle TEXT(20), JobDesc MEMO ); // create index CREATE INDEX PKJobTitle ON JobsTable(JobTitle) WITH PRIMARY; // eof
Although the code examples in Listing 15.8 and Listing 15.9 both perform the same task, the first code example (15.9) is the preferred method for creating primary key indexes. Listing 15.9 documents the creation of the index at the time the table is created. This is easier to understand and easier to maintain over time. It is possible to create primary key indexes using the CREATE INDEX statement, but this can lead to problems. If you attempt to use the CREATE INDEX...PRIMARY KEY statement on a table that already has a primary key index defined, you get a database error. It is best to avoid this error by limiting the creation of primary key indexes to CREATE TABLE statements.
Another common use of the CONSTRAINT clause is in the creation of UNIQUE indexes. By default, the index key created using the UNIQUE CONSTRAINT clause allows null entries in the identified columns. However, when data is entered into the column, that data must be unique or the database engine returns an error message. This is the same as using the IGNORE NULL keyword in the CREATE INDEX statement. You should also note that you cannot use the DISALLOW NULL keywords when creating a UNIQUE CONSTRAINT clause. By default, all keys created using the UNIQUE CONSTRAINT are IGNORE NULL index keys.
The SQL script in Listing 15.10 shows a new column in the JobsTable data table that was created in the last SQL-VBscript. The new column, BudgetCode, is defined as an optional column data column that must contain unique data. Update your version of the SQLVB04.SQV script, save it, and execute it. Your result set should resemble the one shown in Figure 15.13.
Listing 15.10. Adding a UNIQUE CONSTRAINT.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetCode TEXT(10) CONSTRAINT UKJobCode UNIQUE, JobDesc MEMO ); // show table SELECT * FROM JobsTable; // eof
Defining a UNIQUE CONSTRAINT index.
You can use the UNIQUE CONSTRAINT clause in a multicolumn index. This is especially handy if you have a data table containing more than one field that must be evaluated when deciding uniqueness. For example, what if the preceding data table in addition to BudgetCode, had BudgetPrefix and BudgetSuffix, too? You can make sure that the combination of the three fields is always unique by building a multicolumn CONSTRAINT clause. Use the code sample in Listing 15.11 as a guide. Update your SQLVB04.SQV script to match the example in Listing 15.11 and execute it to make sure you have written the syntax correctly.
Listing 15.11. Defining a multicolumn UNIQUE CONSTRAINT.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT UKBudget UNIQUE (BudgetPrefix,BudgetCode,BudgetSuffix) ); // show table SELECT * FROM JobsTable; // eof
Once the script has executed, your screen should look similar to the one in Figure 15.14.
The results of a multicolumn CONSTRAINT clause.
You should also be aware of an important difference between the single-column and multicolumn CONSTRAINT clause formats. Notice that when you are defining a single-column CONSTRAINT, you place the CONSTRAINT clause directly after the column definition without a comma between the column type and the CONSTRAINT keyword. In the multicolumn CONSTRAINT clause, you separate the CONSTRAINT clause with a comma and enclose the column names within parentheses. Mixing these two formats can lead to frustration when you are trying to debug an SQL script!
TIP: Think of it this way. In the case of a single-column CONSTRAINT, these are additional qualifiers of the column; the constraint belongs within the column definition. A multicolumn CONSTRAINT, however, is a standalone definition that is not an extension of any one column definition. For this reason, multicolumn constraints are treated as if they are on an equal level as a column definition. They stand alone in the column list.
The most powerful of the CONSTRAINT formats is the FOREIGN KEY...REFERENCES format. This format is used to establish relationships between tables. Most commonly, a FOREIGN KEY relationship is established between a small table containing a list of valid column entries (usually called a validation table) and another table. The second table usually has a column defined with the same name as the primary key column in the validation table. By establishing a foreign key relationship between the two files, you can enforce a database rule that says that the only valid entries in a given table are those values that already exist in the primary key column of the validation table. Once again, you are using the database engine to store data integrity rules. This reduces your volume of Visual Basic code and increases database integrity.
Let's use the previous script from Listing 15.11 (SQLVB04.SQV) to create a foreign key relationship. You already have a table definedJobsTable. This is an excellent example of a validation table. It has few fields and has a single column defined as the primary key. Now let's add another tablethe EmpsTable. This table holds basic information about employees, including their respective job titles. Listing 15.12 shows modifications to SQLVB04.SQV that include the definition of the EmpsTable data table.
Listing 15.12. Adding a PRIMARY KEY CONSTRAINT to the EmpsTable.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT UKBudget UNIQUE (BudgetPrefix,BudgetCode,BudgetSuffix) ); // create a test table to work with CREATE TABLE EmpsTable (EmployeeID TEXT(10) CONSTRAINT PKEmployeeID PRIMARY KEY, LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20), Department TEXT(10) ); // show empty table SELECT * FROM JobsTable; SELECT * FROM EmpsTable; // eof
The SQL-VB script in Listing 15.12 defines the EmpsTable with only one CONSTRAINTthat of the PRIMARY KEY index. Now let's define a relationship between the EmpsTable.JobTitle column and the JobsTable.JobTitle column. You do this by using the FOREIGN KEY CONSTRAINT syntax. The modified SQLVB04.SQV is shown in Listing 15.13.
Listing 15.13. Adding the FOREIGN KEY...REFERENCES CONSTRAINT.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT UKBudget UNIQUE (BudgetPrefix,BudgetCode,BudgetSuffix) ); // create a test table to work with CREATE TABLE EmpsTable (EmployeeID TEXT(10) CONSTRAINT PKEmployeeID PRIMARY KEY, LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20) CONSTRAINT FKJobTitle REFERENCES JobsTable(JobTitle), Department TEXT(10) ); // show empty table SELECT * FROM JobsTable; SELECT * FROM EmpsTable; // eof
Notice that the exact SQL syntax for single-column foreign key indexes is
CONSTRAINT IndexName REFERENCES Tablename(ColumName)
As long as the column name you are referencing defines the PRIMARY KEY of the referenced table, you can omit the (ColumnName) portion of the CONSTRAINT clause. However, it is good programming practice to include the column name for clarity.
Use the SQL-VB editor window to load SQLVB04.SQV. Modify the script to match the code in Listing 15.13, save it, and run the script. Your screen should resemble Figure 15.15.
A foreign key constraint cascades the related tables on-screen.
What you have defined here is a rule that tells the Microsoft JET database engine that, any time a user enters data into the EmpsTable.JobTitle column, it should refer to the JobsTable.JobTitle column to make sure that the value entered in EmpsTable.JobTitle can be found in one of the rows of JobsTable.JobTitle. If not, return an error message to the user and do not save the record to the data table. All of that is done without writing any input validation code at all!
You can set up foreign key relations between any two columns in any two tables. They need not have the same column name, but they must have the same data type. For example, you can add a table to the SQLVB04.MDB database that holds information about job titles and pay grades. But in this table the column that holds the job title will be called JobName. Enter the script in Listing 15.14, save it, and execute it. Refer to Figure 15.16 as a guide.
Listing 15.14. Creating a foreign key relationship on unmatched field names.
// // sqlvb04.sqv - Test CONSTRAINT SQL keyword // // create a database dbMake sqlvb04.mdb; // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT UKBudget UNIQUE (BudgetPrefix,BudgetCode,BudgetSuffix) ); // create job pay grade table CREATE TABLE PayGrades (GradeID TEXT(5) CONSTRAINT PKGradeID PRIMARY KEY, JobName TEXT(20) CONSTRAINT FKJobName REFERENCES JobsTable(JobTitle), PayMin CURRENCY, PayMax CURRENCY ); // create a test table to work with CREATE TABLE EmpsTable (EmployeeID TEXT(10) CONSTRAINT PKEmployeeID PRIMARY KEY, LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20) CONSTRAINT FKJobTitle REFERENCES JobsTable(JobTitle), Department TEXT(10) ); // show empty table SELECT * FROM JobsTable; SELECT * FROM PayGrades; SELECT * FROM EmpsTable; // eof
Results of a FOREIGN KEY CONSTRAINT on unmatched column names.
Notice that the column PayGrades.JobName does not have the same name as its referenced column (JobsTable.JobTitle). You can still define a foreign key relationship for these columns. This relationship will operate exactly the same as the one defined for EmpsTable.JobTitle and JobsTable.JobTitle.
It is also important to point out the order in which you must create tables when you are establishing foreign key constraints. You must always create the referenced table before you refer to it in a CONSTRAINT clause. Failure to adhere to this rule will result in a database error when you run your SQL-VB script. SQL must see that the table exists before a foreign key reference to it can be established.
It is also possible to create a multicolumn foreign key constraint. When you create multicolumn foreign key constraints, you must reference the same number of columns on each side of the relationship. For example, if you have a primary key index called PKBudgetCode that contains three columns, any foreign key constraint you define in another table that references PKBudgetCode must also contain three columns.
The example in Listing 15.15 shows an added FOREIGN KEY CONSTRAINT in the JobsTable. This constraint sets up a relationship between the Budget columns in the BudgetTrack table and JobsTable. Make the changes to the SQLVB04.SQV script and execute it to check for errors. Refer to Figure 15.17 to compare your results.
Listing 15.15. Creating a multicolumn FOREIGN KEY CONSTRAINT.
// create a database dbMake sqlvb04.mdb; // create budget tracking file CREATE TABLE BudgetTrack (BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), CONSTRAINT PKBudgetCode PRIMARY KEY (BudgetPrefix,BudgetCode,BudgetSuffix), AnnBudgetAmt CURRENCY, YTDActualAmt CURRENCY ); // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT FKBudget FOREIGN KEY (BudgetPrefix,BudgetCode,BudgetSuffix) REFERENCES BudgetTrack ); // create job pay grade table CREATE TABLE PayGrades (GradeID TEXT(5) CONSTRAINT PKGradeID PRIMARY KEY, JobName TEXT(20) CONSTRAINT FKJobName REFERENCES JobsTable(JobTitle), PayMin CURRENCY, PayMax CURRENCY ); // create a test table to work with CREATE TABLE EmpsTable (EmployeeID TEXT(10) CONSTRAINT PKEmployeeID PRIMARY KEY, LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20) CONSTRAINT FKJobTitle REFERENCES JobsTable(JobTitle), Department TEXT(10) ); // show empty table SELECT * FROM JobsTable; SELECT * FROM EmpsTable; SELECT * FROM PayGrades; SELECT * FROM BudgetTrack; // eof
The results of adding a multicolumn FOREIGN KEY CONSTRAINT.
Notice that the syntax for adding multicolumn foreign key constraints differs from that used when creating single-column foreign key relationships. When creating multicolumn foreign key relationships, you have to actually use the keywords FOREIGN KEY. Also, you list the columns in parentheses in the same order in which they are listed in the referenced key for the referenced table.
You can also use the ALTER TABLE statement to add constraints or drop constraints from existing data tables. The code example in Listing 15.16 adds a new constraint to an existing table, and then removes it. You should be careful adding or dropping constraints outside of the CREATE TABLE statement. While SQL allows you to do this, it can often lead to data integrity errors if data already exists within the target table. We recommend that you only establish CONSTRAINTS at the time you create the table using the CREATE TABLE statement.
Listing 15.16. Using ALTER TABLE to ADD and DROP CONSTRAINTS.
// create a database dbMake sqlvb04.mdb; // create budget tracking file CREATE TABLE BudgetTrack (BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), CONSTRAINT PKBudgetCode PRIMARY KEY (BudgetPrefix,BudgetCode,BudgetSuffix), AnnBudgetAmt CURRENCY, YTDActualAmt CURRENCY ); // create jobs title table CREATE TABLE JobsTable (JobTitle TEXT (20) CONSTRAINT PKJobTitle PRIMARY KEY, BudgetPrefix TEXT(5), BudgetCode TEXT(10), BudgetSuffix TEXT(5), JobDesc MEMO, CONSTRAINT FKBudget FOREIGN KEY (BudgetPrefix,BudgetCode,BudgetSuffix) REFERENCES BudgetTrack ); // create job pay grade table CREATE TABLE PayGrades (GradeID TEXT(5) CONSTRAINT PKGradeID PRIMARY KEY, JobName TEXT(20) CONSTRAINT FKJobName REFERENCES JobsTable(JobTitle), PayMin CURRENCY, PayMax CURRENCY ); // create a test table to work with CREATE TABLE EmpsTable (EmployeeID TEXT(10) CONSTRAINT PKEmployeeID PRIMARY KEY, LastName TEXT(30), FirstName TEXT(30), LoginName TEXT(15), JobTitle TEXT(20) CONSTRAINT FKJobTitle REFERENCES JobsTable(JobTitle), Department TEXT(10) ); // use alter table to add and drop a constraint ALTER TABLE EmpsTable ADD CONSTRAINT FKMoreJobs FORGEIN KEY (JobTitle) REFERENCES JobsTable(JobTitle); ALTER TABLE EmpsTable DROP CONSTRAINT FKMoreJobs; // show empty table SELECT * FROM JobsTable; SELECT * FROM EmpsTable; SELECT * FROM PayGrades; SELECT * FROM BudgetTrack; // eof
In today's lesson, you have seen SQL keywords that create and alter tables and establish table indexes and relationship constraints. Now you are ready tomorrow's lesson where you'll learn the SQL keywords that you can use to add data to the tables you have created. You'll also see keywords that you can use to copy tables, including the data.
In today's lesson you've learned how to create, alter, and delete database table structures using DDL (Data Definition Language) SQL keywords. You've also learned that using DDL statements to build tables, create indexes, and establish relationships is an excellent way to automatically document table layouts. You learned how to maintain database structures using the following DDL keywords:
Table | Name | Field Type |
---|---|---|
CustomerType | CustomerType Description | TEXT(6) TEXT(30) |
Customers | CustomerID Name CustomerType Address City State Zip Phone FAX | TEXT(10) TEXT(30) TEXT(6) TEXT(30) TEXT(30) TEXT(30) TEXT(10) TEXT(14) TEXT(14) |
USE SQL-VB to build this structure. Include a primary key for each table and an index on Zip in the Customers table. Include any foreign key relationships that you think would increase database integrity. Name your database CH15EX.MDB. (You can use any path that you like for the .MDB file.)