In today's lesson, you'll learn about the SQL Data Manipulation Language (DML) keywords you can use to update and modify data in existing tables. Although most of the time you will use Visual Basic data entry forms and Visual Basic program code to perform data table updates, there are often times when it is more desirable to use SQL statements to update your data tables.
When you complete the examples in this chapter, you will be able to do the following:
NOTE: Throughout this chapter, you will use the SQL-VB program to create and run SQL scripts. The lesson in Day 15 contains a short tutorial on where to locate the SQL-VB program and how to use it. If you have not worked through the lesson on Day 15 yet, now is a good time to review at least the first half of the chapter.
The Data Management Language (DML) SQL keywords are used to add new data to existing tables, edit existing table data, append data from one table to another, copy data from one table to an entirely new table, and delete data rows from existing tables.
Most of the time, your Visual Basic programs will use data entry screens to perform these tasks. However, at certain other times, the DML keywords come in handy. In some back end database systems, these SQL keywords are the only way you can add, edit, or delete data from tables. At other times, these SQL keywords will give you the power to produce updates to large tables with very few lines of code and in a relatively short amount of time.
Also, many times you might need to select a small subset of data from your tables for a report or a graphic display. Instead of creating Dynaset views of existing tables, you might want to create a frozen Snapshot of the data to use for this purpose. What you need to do is copy some records from an existing table into a new table for use in reporting and displays. SQL DML keywords can help create these select tables quickly without extensive Visual Basic code.
Another example of using SQL DML keywords is when you want to append a set of records from one table to another. Instead of writing Visual Basic code routines that read a record from one table and then write it to another, you can use SQL DML keywords to perform the table updatemany times with just one line of SQL code.
Finally, SQL DML keywords allow you to quickly delete entire tables or subsets of the data in a single SQL statement. This reduces the amount of Visual Basic code you need to write and also greatly speeds the processing in most cases.
The INSERT statement is used to insert values into data tables. You can use the INSERT statement to populate data tables automaticallywithout the need for data entry screens. Also, you can perform this automatic data entry using very little Visual Basic code.
Even though you will most often perform data entry using Visual Basic coded data entry screens tied to Visual Basic data controls, there are times when using the INSERT statement can prove more efficient.
An excellent example of the use of INSERT statements is the installation of a new database system. Often, several data tables need to be populated with default values before people can start using a system. You can use the INSERT statement to perform the initial data load.
Another use for the INSERT statement is in converting data from one database to another. Often, you can use INSERT statements to load existing data in one format into your newly designed relational database.
Finally, you can use INSERT statements to quickly add data to tables that would be too tedious to enter using data entry screens.
The basic form of the INSERT statement is
INSERT INTO TableName(field1, field2) VALUES (value1, value2);
NOTE: INSERT and INSERT INTO statements are often used interchangebly. For the most part, this book uses the latter term.
The INSERT SQL statement has three parts. The TableName identifies the table that you want to update. The (field1, field2) part of the statement identifies the columns into which you will add data. The (value1, value2) part of the statement identifies the exact values you will be adding to the fields you identified in the statement. You can name a few or as many fields as you like in the field portion of the statement. However, you must supply a list of values that has the same number of values and the same data type as those identified in the field portion of the statement. Also, you must list the values in the same order as the fields. The first value will be placed in the first field, the second value in the second field, and so on.
Let's use SQL-VB to create a working example of the INSERT statement. Open a new .SQV script called SQLVB05.SQV using the File | New command from the main menu. Enter the following script, save it, and execute it using the File | Run menu option. Refer to Figure 16.1 to compare your results.
Listing 16.1. Testing the INSERT INTO keyword.
// // sqlvb05.sqv - Testing the INSERT INTO keyword // // create a new database dbMake sqlvb05.mdb; // create a new table CREATE TABLE JobTitles (JobID TEXT(5) CONSTRAINT PKJobTitle PRIMARY KEY, JobName TEXT(20), JobDesc MEMO ); // insert some data INSERT INTO JobTitles(JobID, JobName, JobDesc) VALUES ('J001', 'President', 'Presides over the company' ); INSERT INTO JobTitles(JobID, JobName, JobDesc) VALUES ('J002', 'Vice President', 'Does what the President tells him to do' ); // display results SELECT * FROM JobTitles; // eof
The results of the INSERT INTO statement.
Notice that you must use a separate INSERT INTO statement for each row you want to add to the table. If you wanted to add 10 more job descriptions to the JobTitles table, you would need to add 10 more INSERT INTO statements to the script.
Also, because you defined the JobsTitles.JobID column as the primary key, you are required to fill that field with unique, non-null data each time you execute the INSERT INTO statement. If you provide a null value, or leave the JobsTitles.JobID field out of the INSERT INTO statement, you get a database error message.
If you use a COUNTER data type field in your table, you can't include that in the field list of the INSERT INTO statement. Visual Basic and the SQL engine will fill the COUNTER field in with an appropriate value. Also, you do not have to add data to every column in the row. If there are fields in the data table that are not required and that can be left null, you can simply omit them from the INSERT INTO statement. The code example in Listing 16.2 illustrates these last two points. Use SQL-VB to edit the SQLVB05.SQV script to match the one in Listing 16.2. Save and execute the script. Check your results against those in Figure 16.2.
Listing 16.2. Handling Counter and blank fields in INSERT statements.
// // sqlvb05.sqv - Testing the INSERT INTO keyword // // create a new database dbMake sqlvb05/mdb; // create a new table CREATE TABLE JobTitles (JobCounter COUNTER, JobID TEXT(5) CONSTRAINT PKJobTitle PRIMARY KEY, JobName TEXT(20), JobPay CURRENCY, JobDesc MEMO ); // insert some data INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J001', 'President', 'Presides over the company', '50000' ); INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J002', 'Vice President', 'Does what the President tells him to do', '40000' ); INSERT INTO JobTitles (JobID, JobPay, JobName) VALUES ('J003', '35000', 'Chief Engineer' ); // display results SELECT * FROM JobTitles; // eof
The results of using INSERT INTO with counter and optional fields.
Notice that the JobTitles.JobCounter column was automatically populated by Visual Basic. Also, you can see that the JobTitles.JobDesc column was left blank for the third record in the table.
Two other interesting things about the INSERT INTO statement are illustrated in the code example in Listing 16.2. Notice that the values for the JobTitles.JobPay column were surrounded by quote marks even though the data type is CURRENCY. When you use the INSERT INTO statement, all values must be surrounded by quote marks. SQL and Visual Basic will handle any type conversions needed to insert the values into the identified fields.
The second interesting thing to note in Listing 16.2 is the order in which columns are listed in the INSERT INTO statements. If you look at each of the statements, you will see that the JobTitles.JobPay column appears in different places within the field list. When you use the INSERT INTO statement, you can list the columns in any order. You only need to make sure that you list the values to be inserted in the same order that you list the columns.
You have learned how to use the INSERT INTO statement to add individual rows to a table. This is commonly called a single-record insert. In the next section, you'll learn about a more powerful version of the INSERT INTO statement, commonly called an append query.
The INSERT INTO...FROM version of the INSERT statement allows you to insert multiple records from one table into another table. This multirecord version of INSERT INTO is called an append query. It is called an append query because it enables you to append rows from one table onto the end of another table. As long as the two tables you are working with have fields with the same name, you can use the INSERT INTO...FROM statement to append records from one table to the other.
The basic format of the INSERT INTO...FROM statement is as follows:
INSERT INTO TargetTable SELECT field1, field2 FROM SourceTable;
There are three important parts of the INSERT INTO...FROM statement. The first part is the TargetTable. This is the table that will be updated by the statement. The second part is the SELECT fields part of the statement. This is a list of the fields that will be updated in the TargetTable. These are also the fields that will be supplied by the third part of the statementthe SourceTable. As you can see, the INSERT INTO...FROM statement is really just a SELECT...FROM query with an INSERT INTO TargetTable in front of it.
Now let's update the SQLVB05.SQV to provide an example of the INSERT INTO...FROM statement. First, use SQL-VB to load and edit the SQLVB05.SQV script. Make changes to the script so that it matches the one shown in the code example in Listing 16.3. Save the script and run it. Check your results against those shown in Figure 16.3.
Listing 16.3 Using the INSERT INTO...FROM statement.
// // sqlvb05.sqv - Testing the INSERT INTO keyword // // create a new database dbMake sqlvb05.mdb; // create a new table CREATE TABLE JobTitles (JobCounter COUNTER, JobID TEXT(5) CONSTRAINT PKJobTitle PRIMARY KEY, JobName TEXT(20), JobPay CURRENCY, JobDesc MEMO ); // insert some data INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J001', 'President', 'Presides over the company', '50000' ); INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J002', 'Vice President', 'Does what the President tells him to do', '40000' ); INSERT INTO JobTitles (JobID, JobPay, JobName) VALUES ('J003', '35000', 'Chief Engineer' ); // create a second table to hold some of the info from JobTitles CREATE TABLE JobReport (JobID TEXT(5) CONSTRAINT PKJobReport PRIMARY KEY, JobName TEXT(20), JobDesc MEMO, DeptID TEXT(5) ); // now append records from JobTitles into JobReport INSERT INTO JobReport SELECT JobID, JobName, JobDesc FROM JobTitles; // display results SELECT * FROM JobTitles; SELECT * FROM JobReport; // eof
The results of the INSERT INTO...FROM statement.
NOTE: You might have noticed in Listing 16.3 that you created two indexes, each on an identical column name, but you named the two indexes different names. SQL will not allow you to use the same name on two different indexes, even if they refer to a different table. Indexes appear as independent data objects in a Microsoft Access database. Each object must have a unique name.
Notice that the INSERT INTO...FROM statement lists only those fields that are present in both tables. You need to list the columns by name in this example because the JobReport table does not contain all the fields that the JobTitles table contains. If both tables were an exact match, you could use the asterisk wildcard (*) character in the SELECT clause. For example, if JobTitles and JobReport shared all the same column names, you could use the following SQL statement to append data from one to the other:
INSERT INTO JobReport SELECT * FROM JobTitles;
You can also use the INSERT INTO statement to append rows to tables in another database. You accomplish this by adding an IN clause to the first part of the statement. For example, you can add rows from the JobTitles table in SQLVB05.MDB to a similar table in another database called SQLVB05B.MDB. The syntax for the IN clause of an INSERT INTO...FROM statement is
IN "DatabaseFileName" "DatabaseFormat"
The DatabaseFileName is the complete database filename including the drive identifier and the path name of the destination (or external) database. The DatabaseFormat is the name of the database format of the destination database, such as FoxPro, dBASE, Paradox, and so on. For example, if you want to update TableOne in the external database called EXTERNAL.MDB on drive C at the directory called DB, you would use the following IN clause for the SELECT INTO statement:
SELECT INTO TableOne IN "c:\db\external.mdb" "access"
Listing 16.4 shows how this is done using a real set of database files. Use SQL-VB to load and edit SQLVB05.SQV to match the modifications outlined in Listing 16.4. Save the script and execute it. Your results should look similar to those in Figure 16.4.
Listing 16.4. Adding the IN clause.
// // sqlvb05.sqv - Testing the INSERT INTO keyword // // create sqlvgb05b database dbMake sqlvb05b.mdb; // make a table CREATE TABLE OtherTitles (JobCounter COUNTER, JobID TEXT(5) CONSTRAINT PKJobTitle PRIMARY KEY, JobName TEXT(20), JobPay CURRENCY, JobDesc MEMO ); // insert some rows INSERT INTO OtherTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J004', 'Line Foreman', 'Supervises production line', '30000' ); INSERT INTO OtherTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J005', 'Line Worker', 'Does what the Line Foreman tells him to do', '25000' ); // show results SELECT * FROM OtherTitles; // now close this database dbClose; // ******************************************************** // create a new database dbMake sqlvb05.mdb; // create a new table CREATE TABLE JobTitles (JobCounter COUNTER, JobID TEXT(5) CONSTRAINT PKJobTitle PRIMARY KEY, JobName TEXT(20), JobPay CURRENCY, JobDesc MEMO ); // insert some data INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J001', 'President', 'Presides over the company', '50000' ); INSERT INTO JobTitles (JobID, JobName, JobDesc, JobPay) VALUES ('J002', 'Vice President', 'Does what the President tells him to do', '40000' ); INSERT INTO JobTitles (JobID, JobPay, JobName) VALUES ('J003', '35000', 'Chief Engineer' ); // create a second table to hold some of the info from JobTitles CREATE TABLE JobReport (JobID TEXT(5) CONSTRAINT PKJobReport PRIMARY KEY, JobName TEXT(20), JobDesc MEMO ); // now append records from JobTitles into JobReport INSERT INTO JobReport SELECT JobID, JobName, JobDesc FROM JobTitles; // display results SELECT * FROM JobTitles; SELECT * FROM JobReport; // now append data from one database to another INSERT INTO OtherTitles IN "sqlvb05b.mdb" "Access" SELECT JobID, JobName, JobDesc, JobPay FROM JobTitles; // close this db dbClose; // open other db dbOpen sqlvb05b.mdb // show updated table SELECT * FROM OtherTitles; // eof
The results of the INSERT INTO...FROM statement with IN clause.
The script in Listing 16.4 first creates a database with a single table (OtherTitles) that has two records in the table. Then the script displays the table for a moment before the database is closed. Notice that the records in the table have OtherTitles.JobCounter values of 1 and 2. Then the script creates the JobTitles table in another database and populates that table with three records. Other tables are populated (this was done in previous examples), and eventually the JobTitles table is displayed. Notice that the three records have JobTitles.JobCounter values of 1, 2, and 3. Finally, the INSERT INTO...FROM...IN statement is executed to update the external data table. Then the external table is opened so that you can view the results.
Now look at the OtherTitles.JobCounter values. What has happened? When you append COUNTER data fields to another table, the new records will be renumbered. This is to ensure unique counter values in the table. If you want to retain the old numbers, you can include the COUNTER field in your INSERT INTO list. To illustrate this, add the JobCounter column name to the field list in the INSERT INTO statement that updated the external table. Now execute the script again to see the results.
The results of the INSERT INTO...FROM...IN with updated counter column.
As you can see in Figure 16.5, you now have duplicate COUNTER values in your table. This can lead to data integrity problems if you are using the COUNTER data type as a guaranteed unique value. You should be careful when you use INSERT INTO statements that contain COUNTER data type columns.
WARNING: The Microsoft Visual Basic documentation for the behavior of INSERT INTO with COUNTER data types states that duplicate counter values will not be appended to the destination table. This is not correct. The only time duplicates are not included in the destination tables is when the counter data type column is defined as the primary key.
We should point out here that if you attempt to append records to a table that has a duplicate primary key value, the new record will not be appended to the tableand you will not receive an error message! If you edit the SQLVB05.SQV script to renumber the OtherTitles.JobID values to J001 and J002, you will see a different set of results when you run the script. Figure 16.6 shows what you get when you attempt to update duplicate primary key rows.
The results of attempting to append duplicate primary key rows.
The fact that SQL will not append records with a duplicate key can be used as an advantage. You can easily merge two tables that contain overlapping data and get a single result set that does not contain duplicates. Anyone who has worked with mailing lists will be able to find a use for this feature of the INSERT INTO statement.
Now that you know how to insert rows into tables, it's time to learn how you can update existing rows using the UPDATE...SET statement.
The UPDATE...SET statement enables you to update a large amount of data in one or more tables very quickly with very little coding. You use the UPDATE...SET statement to modify data already on file in a data table. The advantage of the UPDATE...SET statement is that you can use a single statement to modify multiple rows in the table.
For example, assume that you have a table of 500 employees. You are told by the Human Resource Department that all employees are to be given a 17.5 percent increase in their pay starting immediately (wouldn't it be nice?). You could write a Visual Basic program that opens the table, reads each record, computes the new salary, stores the updated record, and then goes back to read the next record. Your code would look something like the pseudocode sample in Listing 16.5.
NOTE: Listing 16.5 is not a real Visual Basic program; it is just a set of statements that read like program code. This pseudocode is often used by programmers to plan out programs without having to deal with the details of a particular programming language. Another benefit of using pseudocode to plan programs is that people do not need to know a particular programming language to be able to understand the example.
Listing 16.5. Sample code for record-oriented updates.
OPEN EmpDatabase OPEN EmpTable DO UNTIL END-OF-FILE (EmpTable) READ EmpTable RECORD EmpTable.EmpSalary = EmpTable.EmpSalary * 1.175 WRITE EmpTable RECORD END DO CLOSE EmpTable CLOSE EmpDatabase
This is a relatively simple process butdepending on the size of the data table and the speed of your workstation or the database serverthis kind of table update could take quite a bit of time. You can use the SQL UPDATE statement to perform the same task.
OPEN database UPDATE EmpTable SET EmpSalary = EmpSalary * 1.175 CLOSE database
The preceding example shows how you can accomplish the same task with less coding. Even better, this code will run much faster than the walk-through loop shown in Listing 16.5, and this single line of code works for any number of records in the set. Furthermore, if this statement is sent to a back-end database server connected via ODBC and not processed by the local workstation, you could see an ever greater increase in processing speed for your program.
Let's start a new program that will illustrate the UPDATE...SET statement. Use SQL-VB to create a new script file called SQLVB06.SQV and enter the commands in Listing 16.6. After you save the script, execute it and check your results against those in Figure 16.7.
Listing 16.6. Using the UPDATE...SET statement.
// // sqlvb06.sqv - testing the UPDATE ... SET statement // // create a database dbMake sqlvb06.mdb; // create a table CREATE TABLE EmpTable (EmpID TEXT(5) CONSTRAINT PKEmpTable PRIMARY KEY, EmpName TEXT(30), EmpSalary CURRENCY ); // insert some data INSERT INTO EmpTable VALUES ('E001', 'Anderson, Shannon', '35000' ); INSERT INTO EmpTable VALUES ('E002', 'Billings, Jesse', '30000' ); INSERT INTO EmpTable VALUES ('E003', 'Caldwell, Dana', '25000' ); // show first result set SELECT * FROM EmpTable; // now perform update UPDATE empTable SET EmpSalary = EmpSalary * 1.175; // show new results SELECT * FROM EmpTable; // eof
The results of using the UPDATE...SET statement.
NOTE: Notice that you did not include the column names in the INSERT INTO statements in this example. As long as you are supplying all the column values for a table, in the same order that they appear in the physical layout, you can omit the column names from the statement.
As you can see in Figure 16.7, all the records in the table are updated by the UPDATE...SET statement. The set statement works for both numeric and character fields. It can contain any number of column updates, too. For example, if you have a table that has three fields that need to the updated, you can use the following SQL statement:
UPDATE MyTable SET CustType="RETAIL", CustDiscount=10, CustDate=#01/15/96#;
You can also add a WHERE clause to the UPDATE statement to limit the rows that are affected by the SET portion of the statement. What if you want to give anyone whose salary is over $35,000 a 10 percent raise and anyone whose salary is $35,000 or under a 15 percent raise? You could accomplish this with two UPDATE...SET statements that each contain a WHERE clause. Use the code in Listing 16.7 as a guide to modifying the SQLVB06.SQV script. Save your changes and run the script. Check your results against Figure 16.8.
Listing 16.7. Adding the WHERE clause to the UPDATE statement.
// // sqlvb06.sqv - testing the UPDATE ... SET statement // // create a database dbMake sqlvb06.mdb; // create a table CREATE TABLE EmpTable (EmpID TEXT(5) CONSTRAINT PKEmpTable PRIMARY KEY, EmpName TEXT(30), EmpSalary CURRENCY ); // insert some data INSERT INTO EmpTable VALUES ('E001', 'Anderson, Shannon', '35000' ); INSERT INTO EmpTable VALUES ('E002', 'Billings, Jesse', '30000' ); INSERT INTO EmpTable VALUES ('E003', 'Caldwell, Dana', '25000' ); // show first result set SELECT * FROM EmpTable AS FirstPass; // now perform updates UPDATE EmpTable SET EmpSalary = EmpSalary * 1.10 WHERE EmpSalary > 30000; UPDATE empTable SET EmpSalary = EmpSalary * 1.15 WHERE EmpSalary <= 30000; // show new results SELECT * FROM EmpTable AS SecondPass; // eof
The results of the UPDATE QUERY with a WHERE clause.
In Listing 16.7, you use the WHERE clause to isolate the records you want to modify with the UPDATE...SET statement. The WHERE clause can be as simple or as complicated as needed to meet the criteria. In other words, any WHERE clause that is valid within the SELECT...FROM statement can be used as part of the UPDATE...SET statement.
The SELECT...INTO...FROM statement allows you to create entirely new tables, complete with data from existing tables. This is called a Make Table query because it enables you to make a new table. The difference between Make Table queries and the CREATE TABLE statement is that you use the Make Table query to copy both the table structure and the data within the table from an already existing table. Because the Make Table query is really just a form of a SELECT statement, you can use all the clauses valid for a SELECT statement when copying data tables including WHERE, ORDER BY, GROUP BY, and HAVING.
Make Table queries are excellent for making backup copies of your data tables. You can also create static read-only tables for reporting and reviewing purposes. For example, you can create a Make Table query that summarizes sales for the period and save the results in a data table that can be accessed for reports and on-screen displays. Now you can provide summary data to your users without giving them access to the underlying transaction tables. This can improve overall processing speed and help provide data security, too.
The basic form of the Make Table query is
SELECT field1, field2 INTO DestinationTable FROM SourceTable;
In the preceding example, the field1, field2 list contains the list of fields in the SourceTable that will be copied to the DestinationTable. If you want to copy all the columns from the source to the destination, you can use the asterisk wildcard (*) character for the field list. Enter the SQL-VB script in Listing 16.8 as SQLVB07.SQV. Save and execute the script, and check your on-screen results against those in Figure 16.9.
Listing 16.8. Testing Make Table queries.
// // sqlvb07.sqv - Testing Make Table Queries // // create a database dbMake sqlvb07.mdb; // create a base table CREATE TABLE BaseTable (CustID TEXT(10) CONSTRAINT PKBaseTable PRIMARY KEY, CustName TEXT(30), CustBalance CURRENCY, CustType TEXT(10), Notes MEMO ); // add some data INSERT INTO BaseTable VALUES ('CUST01', 'Willingham & Associates', '300.65', 'RETAIL', 'This is a comment' ); INSERT INTO BaseTable VALUES ('CUST02', 'Parker & Parker', '1000.29', 'WHOLESALE', 'This is another comment' ); INSERT INTO BaseTable VALUES ('CUST03', 'Anchor, Smith, & Hocking', '575.25', 'RETAIL', 'This is the last comment' ); // now make a new table from the old one SELECT * INTO CopyTable FROM BaseTable; // show results SELECT * FROM BaseTable; SELECT * FROM CopyTable; // eof
The results of a simple Make Table query.
In Listing 16.8, you created a database with one table, populated the table with some test data, and then executed a Make Table query that copied the table structure and contents to a new table in the same database.
You can use the WHERE clause to limit the rows copied to the new table. Modify SQLVB07.SQV to contain the new SELECT...INTO statement and its corresponding SELECT...FROM as shown in Listing 16.9. Save the script and execute it. Your results should look similar to those in Figure 16.10.
Listing 16.9. Using the WHERE clause to limit Make Table queries.
// // sqlvb07.sqv - Testing Make Table Queries // // create a database dbMake sqlvb07.mdb; // create a base table CREATE TABLE BaseTable (CustID TEXT(10) CONSTRAINT PKBaseTable PRIMARY KEY, CustName TEXT(30), CustBalance CURRENCY, CustType TEXT(10), Notes MEMO ); // add some data INSERT INTO BaseTable VALUES ('CUST01', 'Willingham & Associates', '300.65', 'RETAIL', 'This is a comment' ); INSERT INTO BaseTable VALUES ('CUST02', 'Parker & Parker', '1000.29', 'WHOLESALE', 'This is another comment' ); INSERT INTO BaseTable VALUES ('CUST03', 'Anchor, Smith, & Hocking', '575.25', 'RETAIL', 'This is the last comment' ); // now make a new table from the old one SELECT * INTO CopyTable FROM BaseTable; // select just some of the records SELECT * INTO RetailTable FROM BaseTable WHERE CustType='RETAIL'; // show results SELECT * FROM BaseTable; SELECT * FROM CopyTable; SELECT * FROM RetailTable; // eof
Using the WHERE clause to limit Make Table queries.
As you can see from Figure 16.10, only the rows with where CustType = 'RETAIL' are copied to the new table.
You can also use the GROUP BY and HAVING clauses to limit and summarize data before copying to a new table. Let's modify the SQLVB07.SQV script to produce only one record for each Customer Type, with each new row containing the customer type and total balance for that type. Let's also order the records in descending order by customer balance. Finally, let's rename the CustBalance field to Balance. The modifications to SQLVB07.SQV are shown in Listing 16.10. Make your changes, save and run the script, and compare your results to Figure 16.11.
Listing 16.10. Using GROUP BY and HAVING to summarize data.
// // sqlvb07.sqv - Testing Make Table Queries // // create a database dbMake sqlvb07.mdb; // create a base table CREATE TABLE BaseTable (CustID TEXT(10) CONSTRAINT PKBaseTable PRIMARY KEY, CustName TEXT(30), CustBalance CURRENCY, CustType TEXT(10), Notes MEMO ); // add some data INSERT INTO BaseTable VALUES ('CUST01', 'Willingham & Associates', '300.65', 'RETAIL', 'This is a comment' ); INSERT INTO BaseTable VALUES ('CUST02', 'Parker & Parker', '1000.29', 'WHOLESALE', 'This is another comment' ); INSERT INTO BaseTable VALUES ('CUST03', 'Anchor, Smith, & Hocking', '575.25', 'RETAIL', 'This is the last comment' ); // now make a new table from the old one SELECT * INTO CopyTable FROM BaseTable; // select just some of the records SELECT * INTO RetailTable FROM BaseTable WHERE CustType='RETAIL'; // create a new summary table with fancy stuff added SELECT CustType, SUM(CustBalance) AS Balance INTO SummaryTable FROM BaseTable GROUP BY CustType; // show results SELECT * FROM BaseTable; SELECT * FROM CopyTable; SELECT * FROM RetailTable; SELECT * FROM SummaryTable; // eof
Using GROUP BY and HAVING to summarize data.
In all the examples so far, you have used the SELECT...INTO statement to copy existing tables to another table within the database. You can also use SELECT...INTO to copy an existing table to another database by adding the IN clause. You can use this feature to copy entire data tables from one database to another, or to copy portions of a database or data tables to another database for archiving or reporting purposes.
For example, if you want to copy the entire BaseTable you designed in the previous examples from SQLVB07.MDB to SQLVB07B.mdb, you could use the following SELECT...INTO statement.
SELECT * INTO CopyTable IN sqlvb07b.mdb FROM BaseTable;
You can use all the WHERE, ORDER BY, GROUP BY, HAVING and AS clauses you desire when copying tables from one database to another.
WARNING:
When you copy tables using the SELECT...INTO statement, none of the indexes or constraints are copied to the new table. This is an important point. If you use SELECT...INTO to create tables that you want to use for data entry, you need to reconstruct the indexes and constraints using CREATE INDEX to add indexes and ALTER TABLE to add constraints.
The final SQL statement you'll learn today is the DELETE...FROM statement, commonly called the Delete Table query. Delete Table queries are used to remove one or more records from a data table. The delete query can also be applied to a valid view created using the JOIN keyword. Although it is not always efficient to use the DELETE statement to remove a single record from a table, it can be very effective to use the DELETE statement to remove several records from a table. In fact, when you need to remove more than one record from a table or view, the DELETE statement will outperform repeated uses of the Delete method in Visual Basic code.
In its most basic form, the DELETE statement looks like this:
DELETE FROM TableName;
In the preceding example, the TableName represents the name of the base table from which you are deleting records. In this case, all records in the table would be removed using a single command. If you want to remove only some of the records, you could add an SQL WHERE clause to limit the scope of the DELETE action.
DELETE FROM TableName WHERE Field = value;
This example would only remove the records that meet the criteria established in the WHERE clause.
Now let's create some real DELETE statements using SQL-VB. Start a new script file called SQLVB08.SQV, and enter the script commands in Listing 16.11. Save the script and execute it. Check your results against those shown in Figure 16.12.
Listing 16.11. Using the DELETE statement.
// // sqlvb08.sqv - Testing DELETE statements // // create a new database dbMake sqlvb08.mdb; // create a table to work with CREATE TABLE Table1 (RecID TEXT(10), LastName TEXT(30), FirstName TEXT(30), RecType TEXT(5), Amount CURRENCY, LastPaid DATE ); // add some records to work with INSERT INTO Table1 VALUES ('R01', 'Simmons', 'Chris', 'LOCAL', '3000', '12/15/95' ); INSERT INTO Table1 VALUES ('R02', 'Walters', 'Curtis', 'INTL', '5000', '11/30/95' ); INSERT INTO Table1 VALUES ('R03', 'Austin', 'Moro', 'INTL', '4500', '01/15/96' ); // show loaded table SELECT * FROM Table1; // now delete LOCAL records DELETE FROM Table1 WHERE RecType = 'LOCAL'; // show results SELECT * FROM Table1; // eof
The results of a simple DELETE statement.
The SQLVB08.SQV script in Listing 16.11 creates a database with one table in it, populates that table with test data, and then shows the loaded table. Next, a DELETE statement is executed to remove all records that have a Table1.RecType that contains LOCAL. When this is done, the results are shown on-screen.
You can create any type of WHERE clause you need to establish the proper criteria. For example, what if you want to remove all international (INTL) records where the last payment is after 12/31/95? Edit your copy of SQLVB08.SQV. Then save and run it to check your results against Figure 16.13. Our version of the solution appears in Listing 16.12.
Listing 16.12. Using a complex WHERE clause with a DELETE statement.
// // sqlvb08.sqv - Testing DELETE statements // // create a new database dbMake sqlvb08.mdb; // create a table to work with CREATE TABLE Table1 (RecID TEXT(10), LastName TEXT(30), FirstName TEXT(30), RecType TEXT(5), Amount CURRENCY, LastPaid DATE ); // add some records to work with INSERT INTO Table1 VALUES ('R01', 'Simmons', 'Chris', 'LOCAL', '3000', #12/15/95# ); INSERT INTO Table1 VALUES ('R02', 'Walters', 'Curtis', 'INTL', '5000', #11/30/95# ); INSERT INTO Table1 VALUES ('R03', 'Austin', 'Moro', 'INTL', '4500', #01/15/96# ); // show loaded table SELECT * FROM Table1; // now delete LOCAL records DELETE FROM Table1 WHERE RecType = 'INTL' AND LastPaid > #12/31/95#; // show results SELECT * FROM Table1; // eof
The results of the DELETE statement with a complex WHERE clause.
As you can see from the code in Listing 16.12, you only need to change the WHERE clause (adding the date criteria) in order to make the DELETE statement function as planned.
NOTE: You might have noticed that you enclose date information with the pound symbol (#). This ensures that Microsoft JET handles the data as DATE type values. Using the pound symbols works across language settings within the Windows operating system. This means that if you ship your program to Europe, where many countries use the date format DD/MM/YY (instead of the US standard MM/DD/YY), Windows will convert the date information to display and compute properly for the regional settings on the local PC.
You can also use the DELETE statement to delete records in more than one table at a time. These multitable deletes must be performed on tables that have a one-to-one relationship. The example in Listing 16.13 shows modifications to SQLVB08.SQV to illustrate the use of the JOIN clauses to create a multitable DELETE statement. Use SQL-VB to edit your copy of SQLVB08.SQV to match the one in Listing 16.13. Save and execute the script and refer to Figure 16.14 for comparison.
Listing 16.13. Using JOIN to perform a multitable DELETE.
// // sqlvb08.sqv - Testing DELETE statements // // create a new database dbMake sqlvb08.mdb; // create a table to work with CREATE TABLE Table1 (RecID TEXT(10), LastName TEXT(30), FirstName TEXT(30), RecType TEXT(5), Amount CURRENCY, LastPaid DATE ); // add some records to work with INSERT INTO Table1 VALUES ('R01', 'Simmons', 'Chris', 'LOCAL', '3000', #12/15/95# ); INSERT INTO Table1 VALUES ('R02', 'Walters', 'Curtis', 'INTL', '5000', #11/30/95# ); INSERT INTO Table1 VALUES ('R03', 'Austin', 'Moro', 'INTL', '4500', #01/15/96# ); // create a second table for JOIN purposes CREATE TABLE Table2 (RecID TEXT(10), BizPhone TEXT(20), EMailAddr TEXT(30) ); // load some data INSERT INTO Table2 VALUES ('R01', '(111)222-3333', '[email protected]' ); INSERT INTO Table2 VALUES ('R03', '(777)888-9999', '[email protected]' ); INSERT INTO Table2 VALUES ('R04', '(222)444-6666', '[email protected]' ); // show loaded table SELECT * FROM Table1; SELECT * FROM Table2; // now delete records DELETE Table1.*, Table2.* FROM Table1 INNER JOIN Table2 ON Table1.RecID = Table2.RecID; // show results SELECT * FROM Table1; SELECT * FROM Table2; // eof
Results of a multitable DELETE.
The results of this DELETE query might surprise you. Because there is no WHERE clause in the DELETE statement that could limit the scope of the SQL command, you might think that the statement will delete all records in both tables. In fact, this statement only deletes the records that have a matching RecID in both tables. The reason for this is that you used an INNER JOIN. INNER JOIN clauses operate only on records that appear in both tables. You now have an excellent way to remove records from multiple tables with one DELETE statement! It must be pointed out, however, that this technique only works with tables that have a one-to-one relationship defined. In the case of one-to-many relationships, only the first occurrence of the match on the many-side will be removed.
Here is a puzzle for you. What happens if you only list Table1 in the first part of that last DELETE statement?
DELETE Table1.* FROM Table1 INNER JOIN Table2 ON Table1.RecID = Table2.RecID;
What records (if any) would be deleted from Table1? Edit SQLVB08.SQV, save it, and execute it to find out. Check your results against Figure 16.15.
The results of a one-sided DELETE using an INNER JOIN.
As you can see from Figure 16.15, a DELETE query that contains an INNER JOIN will only remove records from Table1 that have a match in Table2. And the records in Table2 are left intact! This is a good example of using JOIN clauses to limit the scope of a DELETE statement. This technique is very useful when you want to eliminate duplicates in related or identical tables. Note also that this INNER JOIN works just fine without the use of defined constraints or index keys.
You have learned how to add, delete, and edit data within tables using the DML (Data Manipulation Language) SQL keywords. You've learned that, by using DML statements, you can quickly create test data for tables and load default values into startup tables. You also learned that DML statementssuch as Append queries, Make Table queries, and Delete queriescan outperform equivalent Visual Basic code versions of the same operations.
You learned how to manage data within the tables using the following DML keywords:
Data for the CustomerType table
INDV | Individual |
BUS | BusinessNon-Corporate |
CORP | Corporate Entity |
Data for the Customers Table
Field | Customer #1 | Customer #2 | Customer #3 |
---|---|---|---|
CustomerID | SMITHJ | JONEST | JACKSONT |
Name | John Smith | Jones Taxi | Thomas Jackson |
CustomerType | INDV | BUS | INDV |
Address | 160 Main Street | 421 Shoe St. | 123 Walnut St. |
City | Dublin | Milford | Oxford |
State | Ohio | Rhode Island | Maine |
Zip | 45621 | 03215 | 05896 |
Phone | 614-555-8975 | 555-555-5555 | 444-444-4444 |
Fax | 614-555-5580 | 555-555-5555 | 444-444-4444 |