Teach Yourself SQL in 21 Days, Second Edition
- Day 13 -
Advanced SQL Topics
Objectives
Over the course of the past 12 days, you have examined every major topic used
to write powerful queries to retrieve data from a database. You have also briefly
explored aspects of database design and database security. Today's purpose is to
cover advanced SQL topics, which include the following:
- Temporary tables
- Cursors
- Stored procedures
- Triggers
- Embedded SQL
NOTE: Today's examples use Oracle7's PL/SQL
and Microsoft/Sybase SQL Server's Transact-SQL implementations. We made an effort
to give examples using both flavors of SQL wherever possible. You do not need to
own a copy of either the Oracle7 or the SQL Server database product. Feel free to
choose your database product based on your requirements. (If you are reading this
to gain enough knowledge to begin a project for your job, chances are you won't have
a choice.)
NOTE: Although you can apply most of the
examples within this book to any popular database management system, this statement
does not hold for all the material covered today. Many vendors still do not support
temporary tables, stored procedures, and triggers. Check your documentation to determine
which of these features are included with your favorite database system.
Temporary Tables
The first advanced topic we discuss is the use of temporary tables, which are
simply tables that exist temporarily within a database and are automatically dropped
when the user logs out or their database connection ends. Transact-SQL creates these
temporary tables in the tempdb database. This database is created when you
install SQL Server. Two types of syntax are used to create a temporary table.
SYNTAX:
SYNTAX 1:
create table #table_name (
field1 datatype,
.
.
.
fieldn datatype)
Syntax 1 creates a table in the tempdb database. This table is created
with a unique name consisting of a combination of the table name used in the CREATE
TABLE command and a date-time stamp. A temporary table is available only to
its creator. Fifty users could simultaneously issue the following commands:
1> create table #albums (
2> artist char(30),
3> album_name char(50),
4> media_type int)
5> go
The pound sign (#) before the table's name is the identifier that SQL
Server uses to flag a temporary table. Each of the 50 users would essentially receive
a private table for his or her own use. Each user could update, insert, and delete
records from this table without worrying about other users invalidating the table's
data. This table could be dropped as usual by issuing the following command:
1> drop table #albums
2> go
The table could also be dropped automatically when the user who created it logs
out of the SQL Server. If you created this statement using some type of dynamic SQL
connection (such as SQL Server's DB-Library), the table will be deleted when that
dynamic SQL connection is closed.
Syntax 2 shows another way to create a temporary table on an SQL Server. This
syntax produces a different result than the syntax used in syntax 1, so pay careful
attention to the syntactical differences.
SYNTAX:
SYNTAX 2:
create table tempdb..tablename (
field1 datatype,
.
.
.
fieldn datatype)
Creating a temporary table using the format of syntax 2 still results in a table
being created in the tempdb database. This table's name has the same format
as the name for the table created using syntax 1. The difference is that this table
is not dropped when the user's connection to the database ends. Instead, the user
must actually issue a DROP TABLE command to remove this table from the tempdb
database.
TIP: Another way to get rid of a table
that was created using the create table tempdb..tablename syntax is to shut
down and restart the SQL Server. This method removes all temporary tables from the
tempdb database.
Examples 13.1 and 13.2 illustrate the fact that temporary tables are indeed temporary,
using the two different forms of syntax. Following these two examples, Example 13.3
illustrates a common usage of temporary tables: to temporarily store data returned
from a query. This data can then be used with other queries.
You need to create a database to use these examples. The database MUSIC
is created with the following tables:
Use the following SQL statements to create these tables:
INPUT:
1> create table ARTISTS (
2> name char(30),
3> homebase char(40),
4> style char(20),
5> artist_id int)
6> go
1> create table MEDIA (
2> media_type int,
3> description char(30),
4> price float)
5> go
1> create table RECORDINGS (
2> artist_id int,
3> media_type int,
4> title char(50),
5> year int)
6> go
NOTE: Tables 13.1, 13.2, and 13.3 show
some sample data for these tables.
Table 13.1. The ARTISTS table.
Name |
Homebase |
Style |
Artist_ID |
Soul Asylum |
Minneapolis |
Rock |
1 |
Maurice Ravel |
France |
Classical |
2 |
Dave Matthews Band |
Charlottesville |
Rock |
3 |
Vince Gill |
Nashville |
Country |
4 |
Oingo Boingo |
Los Angeles |
Pop |
5 |
Crowded House |
New Zealand |
Pop |
6 |
Mary Chapin-Carpenter |
Nashville |
Country |
7 |
Edward MacDowell |
U.S.A. |
Classical |
8 |
Table 13.2. The MEDIA table.
Media_Type |
Description |
Price |
1 |
Record |
4.99 |
2 |
Tape |
9.99 |
3 |
CD |
13.99 |
4 |
CD-ROM |
29.99 |
5 |
DAT |
19.99 |
Table 13.3. The RECORDINGS table.
Artist_Id |
Media_Type |
Title |
Year |
1 |
2 |
Hang Time |
1988 |
1 |
3 |
Made to Be Broken |
1986 |
2 |
3 |
Bolero |
1990 |
3 |
5 |
Under the Table and Dreaming |
1994 |
4 |
3 |
When Love Finds You |
1994 |
5 |
2 |
Boingo |
1987 |
5 |
1 |
Dead Man's Party |
1984 |
6 |
2 |
Woodface |
1990 |
6 |
3 |
Together Alone |
1993 |
7 |
5 |
Come On, Come On |
1992 |
7 |
3 |
Stones in the Road |
1994 |
8 |
5 |
Second Piano Concerto |
1985 |
Example 13.1
You can create a temporary table in the tempdb database. After inserting
a dummy record into this table, log out. After logging back into SQL Server, try
to select the dummy record out of the temporary table. Note the results:
INPUT:
1> create table #albums (
2> artist char(30),
3> album_name char(50),
4> media_type int)
5> go
1> insert #albums values ("The Replacements", "Pleased To Meet Me", 1)
2> go
Now log out of the SQL Server connection using the EXIT (or QUIT)
command. After logging back in and switching to the database you last used, try the
following command:
INPUT:
1> select * from #albums
2> go
ANALYSIS:
This table does not exist in the current database.
Example 13.2
Now create the table with syntax 2:
INPUT:
1> create table tempdb..albums (
2> artist char(30),
3> album_name char(50),
4> media_type int)
5> go
1> insert #albums values ("The Replacements", "Pleased To Meet Me", 1)
2> go
After logging out and logging back in, switch to the database you were using when
create table tempdb..albums() was issued; then issue the following command:
INPUT:
1> select * from #albums
2> go
This time, you get the following results:
OUTPUT:
artist album_name media_type
_______________________________________________________________________________________
The Replacements Pleased To Meet Me 1
Example 13.3
This example shows a common usage of temporary tables: to store the results of
complex queries for use in later queries.
INPUT:
1> create table #temp_info (
2> name char(30),
3> homebase char(40),
4> style char(20),
5> artist_id int)
6> insert #temp_info
7> select * from ARTISTS where homebase = "Nashville"
8> select RECORDINGS.* from RECORDINGS, ARTISTS
9> where RECORDINGS.artist_id = #temp_info.artist_id
10> go
The preceding batch of commands selects out the recording information for all
the artists whose home base is Nashville.
The following command is another way to write the set of SQL statements used in
Example 13.3:
1> select ARTISTS.* from ARTISTS, RECORDINGS where ARTISTS.homebase = "Nashville"
2> go
Cursors
A database cursor is similar to the cursor on a word processor screen. As you
press the Down Arrow key, the cursor scrolls down through the text one line at a
time. Pressing the Up Arrow key scrolls your cursor up one line at a time. Hitting
other keys such as Page Up and Page Down results in a leap of several lines in either
direction. Database cursors operate in the same way.
Database cursors enable you to select a group of data, scroll through the group
of records (often called a recordset), and examine each individual line of data as
the cursor points to it. You can use a combination of local variables and a cursor
to individually examine each record and perform any external operation needed before
moving on to the next record.
One other common use of cursors is to save a query's results for later use. A
cursor's result set is created from the result set of a SELECT query. If
your application or procedure requires the repeated use of a set of records, it is
faster to create a cursor once and reuse it several times than to repeatedly query
the database. (And you have the added advantage of being able to scroll through the
query's result set with a cursor.)
Follow these steps to create, use, and close a database cursor:
- 1. Create the cursor.
2. Open the cursor for use within the procedure or application.
3. Fetch a record's data one row at a time until you have reached the end
of the cursor's records.
4. Close the cursor when you are finished with it.
5. Deallocate the cursor to completely discard it.
Creating a Cursor
To create a cursor using Transact-SQL, issue the following syntax:
SYNTAX:
declare cursor_name cursor
for select_statement
[for {read only | update [of column_name_list]}]
The Oracle7 SQL syntax used to create a cursor looks like this:
SYNTAX:
DECLARE cursor_name CURSOR
FOR {SELECT command | statement_name | block_name}
By executing the DECLARE cursor_name CURSOR statement, you have defined
the cursor result set that will be used for all your cursor operations. A cursor
has two important parts: the cursor result set and the cursor position.
The following statement creates a cursor based on the ARTISTS table:
INPUT:
1> create Artists_Cursor cursor
2> for select * from ARTISTS
3> go
ANALYSIS:
You now have a simple cursor object named Artists_Cursor that contains
all the records in the ARTISTS table. But first you must open the cursor.
Opening a Cursor
The simple command to open a cursor for use is
SYNTAX:
open cursor_name
Executing the following statement opens Artists_Cursor for use:
1> open Artists_Cursor
2> go
Now you can use the cursor to scroll through the result set.
Scrolling a Cursor
To scroll through the cursor's result set, Transact-SQL provides the following
FETCH command.
SYNTAX:
fetch cursor_name [into fetch_target_list]
Oracle SQL provides the following syntax:
FETCH cursor_name {INTO : host_variable
[[INDICATOR] : indicator_variable]
[, : host_variable
[[INDICATOR] : indicator_variable] ]...
| USING DESCRIPTOR descriptor }
Each time the FETCH command is executed, the cursor pointer advances
through the result set one row at a time. If desired, data from each row can be fetched
into the fetch_target_list variables.
NOTE: Transact-SQL enables the programmer
to advance more than one row at a time by using the following command: set cursor
rows number for cursor_name. This command cannot be used with the INTO
clause, however. It is useful only to jump forward a known number of rows instead
of repeatedly executing the FETCH statement.
The following statements fetch the data from the Artists_Cursor result
set and return the data to the program variables:
INPUT:
1> declare @name char(30)
2> declare @homebase char(40)
3> declare @style char(20)
4> declare @artist_id int
5> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
6> print @name
7> print @homebase
8> print @style
9> print char(@artist_id)
10> go
You can use the WHILE loop (see Day 12, "Database Security")
to loop through the entire result set. But how do you know when you have reached
the end of the records?
Testing a Cursor's Status
Transact-SQL enables you to check the status of the cursor at any time through
the maintenance of two global variables: @@sqlstatus and @@rowcount.
The @@sqlstatus variable returns status information concerning the last
executed FETCH statement. (The Transact-SQL documentation states that no
command other than the FETCH statement can modify the @@sqlstatus
variable.) This variable contains one of three values. The following table appears
in the Transact-SQL reference manuals:
Status |
Meaning |
0 |
Successful completion of the FETCH statement. |
1 |
The FETCH statement resulted in an error. |
2 |
There is no more data in the result set. |
-
The @@rowcount variable contains the number of rows returned from the
cursor's result set up to the previous fetch. You can use this number to determine
the number of records in a cursor's result set.
The following code extends the statements executed during the discussion of the
FETCH statement. You now use the WHILE loop with the @@sqlstatus
variable to scroll the cursor:
INPUT:
1> declare @name char(30)
2> declare @homebase char(40)
3> declare @style char(20)
4> declare @artist_id int
5> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
6> while (@@sqlstatus = 0)
7> begin
8> print @name
9> print @homebase
10> print @style
11> print char(@artist_id)
12> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
13> end
14> go
ANALYSIS:
Now you have a fully functioning cursor! The only step left is to close the cursor.
Closing a Cursor
Closing a cursor is a very simple matter. The statement to close a cursor is as
follows:
SYNTAX:
close cursor_name
This cursor still exists; however, it must be reopened. Closing a cursor essentially
closes out its result set, not its entire existence. When you are completely finished
with a cursor, the DEALLOCATE command frees the memory associated with a
cursor and frees the cursor name for reuse. The DEALLOCATE statement syntax
is as follows:
SYNTAX:
deallocate cursor cursor_name
Example 13.4 illustrates the complete process of creating a cursor, using it,
and then closing it, using Transact-SQL.
Example 13.4
INPUT:
1> declare @name char(30)
2> declare @homebase char(40)
3> declare @style char(20)
4> declare @artist_id int
5> create Artists_Cursor cursor
6> for select * from ARTISTS
7> open Artists_Cursor
8> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
9> while (@@sqlstatus = 0)
10> begin
11> print @name
12> print @homebase
13> print @style
14> print char(@artist_id)
15> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
16> end
17> close Artists_Cursor
18> deallocate cursor Artists_Cursor
19> go
NOTE: The following is sample data only.
OUTPUT:
Soul Asylum Minneapolis Rock 1
Maurice Ravel France Classical 2
Dave Matthews Band Charlottesville Rock 3
Vince Gill Nashville Country 4
Oingo Boingo Los Angeles Pop 5
Crowded House New Zealand Pop 6
Mary Chapin-Carpenter Nashville Country 7
Edward MacDowell U.S.A. Classical 8
The Scope of Cursors
Unlike tables, indexes, and other objects such as triggers and stored procedures,
cursors do not exist as database objects after they are created. Instead, cursors
have a limited scope of use.
WARNING: Remember, however, that memory
remains allocated for the cursor, even though its name may no longer exist. Before
going outside the cursor's scope, the cursor should always be closed and deallocated.
A cursor can be created within three regions:
- In a session--A session begins when a user logs on. If the user logged on to
an SQL Server and then created a cursor, then cursor_name would exist until the user
logged off. The user would not be able to reuse cursor_name during the current session.
- Stored procedure--A cursor created inside a stored procedure is good only during
the execution of the stored procedure. As soon as the stored procedure exits, cursor_name
is no longer valid.
- Trigger--A cursor created inside a trigger has the same restrictions as one created
inside a stored procedure.
Creating and Using Stored Procedures
The concept of stored procedures is an important one for the professional database
programmer to master. Stored procedures are functions that contain potentially large
groupings of SQL statements. These functions are called and executed just as C, FORTRAN,
or Visual Basic functions would be called. A stored procedure should encapsulate
a logical set of commands that are often executed (such as a complex set of queries,
updates, or inserts). Stored procedures enable the programmer to simply call the
stored procedure as a function instead of repeatedly executing the statements inside
the stored procedure. However, stored procedures have additional advantages.
Sybase, Inc., pioneered stored procedures with its SQL Server product in the late
1980s. These procedures are created and then stored as part of a database, just as
tables and indexes are stored inside a database. Transact SQL permits both input
and output parameters to stored procedure calls. This mechanism enables you to create
the stored procedures in a generic fashion so that variables can be passed to them.
One of the biggest advantages to stored procedures lies in the design of their
execution. When executing a large batch of SQL statements to a database server over
a network, your application is in constant communication with the server, which can
create an extremely heavy load on the network very quickly. As multiple users become
engaged in this communication, the performance of the network and the database server
becomes increasingly slower. The use of stored procedures enables the programmer
to greatly reduce this communication load.
After the stored procedure is executed, the SQL statements run sequentially on
the database server. Some message or data is returned to the user's computer only
when the procedure is finished. This approach improves performance and offers other
benefits as well. Stored procedures are actually compiled by database engines the
first time they are used. The compiled map is stored on the server with the procedure.
Therefore, you do not have to optimize SQL statements each time you execute them,
which also improves performance.
Use the following syntax to create a stored procedure using Transact-SQL:
SYNTAX:
create procedure procedure_name
[[(]@parameter_name
datatype [(length) | (precision [, scale])
[= default][output]
[, @parameter_name
datatype [(length) | (precision [, scale])
[= default][output]]...[)]]
[with recompile]
as SQL_statements
This EXECUTE command executes the procedure:
SYNTAX:
execute [@return_status = ]
procedure_name
[[@parameter_name =] value |
[@parameter_name =] @variable [output]...]]
[with recompile]
Example 13.5
This example creates a simple procedure using the contents of Example 13.4.
INPUT:
1> create procedure Print_Artists_Name
2> as
3> declare @name char(30)
4> declare @homebase char(40)
5> declare @style char(20)
6> declare @artist_id int
7> create Artists_Cursor cursor
8> for select * from ARTISTS
9> open Artists_Cursor
10> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
11> while (@@sqlstatus = 0)
12> begin
13> print @name
14> fetch Artists_Cursor into @name, @homebase, @style, @artist_id
15> end
16> close Artists_Cursor
17> deallocate cursor Artists_Cursor
18> go
You can now execute the Print_Artists_Name procedure using the EXECUTE
statement:
INPUT:
1> execute Print_Artists_Name
2> go
OUTPUT:
Soul Asylum
Maurice Ravel
Dave Matthews Band
Vince Gill
Oingo Boingo
Crowded House
Mary Chapin-Carpenter
Edward MacDowell
Example 13.5 was a small stored procedure; however, a stored procedure can contain
many statements, which means you do not have to execute each statement individually.
Using Stored Procedure Parameters
Example 13.5 was an important first step because it showed the use of the simplest
CREATE PROCEDURE statement. However, by looking at the syntax given here,
you can see that there is more to the CREATE PROCEDURE statement than was
demonstrated in Example 13.5. Stored procedures also accept parameters as input to
their SQL statements. In addition, data can be returned from a stored procedure through
the use of output parameters.
Input parameter names must begin with the @ symbol, and these parameters
must be a valid Transact-SQL data type. Output parameter names must also begin with
the @ symbol. In addition, the OUTPUT keyword must follow the output
parameter names. (You must also give this OUTPUT keyword when executing
the stored procedure.)
Example 13.6 demonstrates the use of input parameters to a stored procedure.
Example 13.6
The following stored procedure selects the names of all artists whose media type
is a CD:
1> create procedure Match_Names_To_Media @description char(30)
2> as
3> select ARTISTS.name from ARTISTS, MEDIA, RECORDINGS
4> where MEDIA.description = @description and
5> MEDIA.media_type = RECORDINGS.media_type and
6> RECORDINGS.artist_id = ARTISTS.artist_id
7> go
1> execute Match_Names_To_Media "CD"
2> go
Executing this statement would return the following set of records:
OUTPUT:
NAME
Soul Asylum
Maurice Ravel
Vince Gill
Crowded House
Mary Chapin-Carpenter
Example 13.7
This example demonstrates the use of output parameters. This function takes the
artist's homebase as input and returns the artist's name as output:
INPUT:
1> create procedure Match_Homebase_To_Name @homebase char(40), @name char(30) output
2> as
3> select @name = name from ARTISTS where homebase = @homebase
4> go
1> declare @return_name char(30)
2> execute Match_Homebase_To_Name "Los Angeles", @return_name = @name output
3> print @name
4> go
OUTPUT:
Oingo Boingo
Removing a Stored Procedure
By now, you can probably make an educated guess as to how to get rid of a stored
procedure. If you guessed the DROP command, you are absolutely correct.
The following statement removes a stored procedure from a database:
SYNTAX:
drop procedure procedure_name
The DROP command is used frequently: Before a stored procedure can be
re-created, the old procedure with its name must be dropped. From personal experience,
there are few instances in which a procedure is created and then never modified.
Many times, in fact, errors occur somewhere within the statements that make up the
procedure. We recommend that you create your stored procedures using an SQL script
file containing all your statements. You can run this script file through your database
server to execute your desired statements and rebuild your procedures. This technique
enables you to use common text editors such as vi or Windows Notepad to create and
save your SQL scripts. When running these scripts, however, you need to remember
to always drop the procedure, table, and so forth from the database before creating
a new one. If you forget the DROP command, errors will result.
The following syntax is often used in SQL Server script files before creating
a database object:
SYNTAX:
if exists (select * from sysobjects where name = "procedure_name")
begin
drop procedure procedure_name
end
go
create procedure procedure_name
as
.
.
.
These commands check the SYSOBJECTS table (where database object information
is stored in SQL Server) to see whether the object exists. If it does, it is dropped
before the new one is created. Creating script files and following the preceding
steps saves you a large amount of time (and many potential errors) in the long run.
Nesting Stored Procedures
Stored procedure calls can also be nested for increased programming modularity.
A stored procedure can call another stored procedure, which can then call another
stored procedure, and so on. Nesting stored procedures is an excellent idea for several
reasons:
- Nesting stored procedures reduces your most complex queries to a functional level.
(Instead of executing 12 queries in a row, you could perhaps reduce these 12 queries
to three stored procedure calls, depending on the situation.)
- Nesting stored procedures improves performance. The query optimizer optimizes
smaller, more concise groups of queries more effectively than one large group of
statements.
When nesting stored procedures, any variables or database objects created in one
stored procedure are visible to all the stored procedures it calls. Any local variables
or temporary objects (such as temporary tables) are deleted at the end of the stored
procedure that created these elements.
When preparing large SQL script files, you might run into table or database object
referencing problems. You must create the nested stored procedures before you can
call them. However, the calling procedure may create temporary tables or cursors
that are then used in the called stored procedures. These called stored procedures
are unaware of these temporary tables or cursors, which are created later in the
script file. The easiest way around this problem is to create the temporary objects
before all the stored procedures are created; then drop the temporary items (in the
script file) before they are created again in the stored procedure. Are you confused
yet? Example 13.8 should help you understand this process.
Example 13.8
INPUT:
1> create procedure Example13_8b
2> as
3> select * from #temp_table
4> go
1> create procedure Example13_8a
2> as
3> create #temp_table (
4> data char(20),
5> numbers int)
6> execute Example13_8b
7> drop table #temp_table
8> go
ANALYSIS:
As you can see, procedure Example13_8b uses the #temp_table.
However, the #temp_table is not created until later (in procedure Example13_8a).
This results in a procedure creation error. In fact, because Example13_8b
was not created (owing to the missing table #temp_table), procedure Example13_8a
is not created either (because Example13_8b was not created).
The following code fixes this problem by creating the #temp_table before
the first procedure is created. #temp_table is then dropped before the creation
of the second procedure:
INPUT:
1> create #temp_table (
2> data char(20),
3> numbers int)
4> go
1> create procedure Example13_8b
2> as
3> select * from #temp_table
4> go
1> drop table #temp_table
2> go
1> create procedure Example13_8a
2> as
3> create #temp_table (
4> data char(20),
5> numbers int)
6> execute Example13_8b
7> drop table #temp_table
8> go
Designing and Using Triggers
A trigger is essentially a special type of stored procedure that can be executed
in response to one of three conditions:
- An UPDATE
- An INSERT
- A DELETE
The Transact-SQL syntax to create a trigger looks like this:
SYNTAX:
create trigger trigger_name
on table_name
for {insert, update, delete}
as SQL_Statements
The Oracle7 SQL syntax used to create a trigger follows.
SYNTAX:
CREATE [OR REPLACE] TRIGGER [schema.]trigger_name
{BEFORE | AFTER}
{DELETE | INSERT | UPDATE [OF column[, column]...]}
[OR {DELETE | INSERT | UPDATE [OF column [, column] ...]}]...
ON [schema.]table
[[REFERENCING { OLD [AS] old [NEW [AS] new]
| NEW [AS] new [OLD [AS] old]}]
FOR EACH ROW
[WHEN (condition)] ]
pl/sql statements...
Triggers are most useful to enforce referential integrity, as mentioned on Day
9, "Creating and Maintaining Tables," when you learned how to create tables.
Referential integrity enforces rules used to ensure that data remains valid across
multiple tables. Suppose a user entered the following command:
INPUT:
1> insert RECORDINGS values (12, "The Cross of Changes", 3, 1994)
2> go
ANALYSIS:
This perfectly valid SQL statement inserts a new record in the RECORDINGS
table. However, a quick check of the ARTISTS table shows that there is no
Artist_ID = 12. A user with INSERT privileges in the RECORDINGS
table can completely destroy your referential integrity.
NOTE: Although many database systems can
enforce referential integrity through the use of constraints in the CREATE TABLE
statement, triggers provide a great deal more flexibility. Constraints return system
error messages to the user, and (as you probably know by now) these error messages
are not always helpful. On the other hand, triggers can print error messages, call
other stored procedures, or try to rectify a problem if necessary.
Triggers and Transactions
The actions executed within a trigger are implicitly executed as part of a transaction.
Here's the broad sequence of events:
- 1. A BEGIN TRANSACTION statement is implicitly issued (for tables
with triggers).
2. The insert, update, or delete operation occurs.
3. The trigger is called and its statements are executed.
4. The trigger either rolls back the transaction or the transaction is
implicitly committed.
Example 13.9
This example illustrates the solution to the RECORDINGS table update
problem mentioned earlier.
INPUT:
1> create trigger check_artists
2> on RECORDINGS
3> for insert, update as
4> if not exists (select * from ARTISTS, RECORDINGS
5> where ARTISTS.artist_id = RECORDINGS.artist_id)
6> begin
7> print "Illegal Artist_ID!"
8> rollback transaction
9> end
10> go
ANALYSIS:
A similar problem could exist for deletes from the RECORDINGS table.
Suppose that when you delete an artist's only record from the RECORDINGS
table, you also want to delete the artist from the ARTISTS table. If the
records have already been deleted when the trigger is fired, how do you know which
Artist_ID should be deleted? There are two methods to solve this problem:
- Delete all the artists from the ARTISTS table who no longer have any
recordings in the RECORDINGS table. (See Example 13.10a.)
- Examine the deleted logical table. Transact-SQL maintains two tables: DELETED
and INSERTED. These tables, which maintain the most recent changes to the
actual table, have the same structure as the table on which the trigger is created.
Therefore, you could retrieve the artist IDs from the DELETED table and
then delete these IDs from the ARTISTS table. (See Example 13.10b.)
Example 13.10a
INPUT:
1> create trigger delete_artists
2> on RECORDINGS
3> for delete as
4> begin
5> delete from ARTISTS where artist_id not in
6> (select artist_id from RECORDINGS)
7> end
8> go
Example 13.10b
1> create trigger delete_artists
2> on RECORDINGS
3> for delete as
4> begin
5> delete ARTISTS from ARTISTS, deleted
6> where ARTIST.artist_id = deleted.artist_id
7> end
8> go
Restrictions on Using Triggers
You must observe the following restrictions when you use triggers:
- Triggers cannot be created on temporary tables.
- Triggers must be created on tables in the current database.
- Triggers cannot be created on views.
- When a table is dropped, all triggers associated with that table are automatically
dropped with it.
Nested Triggers
Triggers can also be nested. Say that you have created a trigger to fire on a
delete, for instance. If this trigger itself then deletes a record, the database
server can be set to fire another trigger. This approach would, of course, result
in a loop, ending only when all the records in the table were deleted (or some internal
trigger conditions were met). Nesting behavior is not the default, however. The environment
must be set to enable this type of functionality. Consult your database server's
documentation for more information on this topic.
Using SELECT Commands with UPDATE and DELETE
Here are some complex SQL statements using UPDATE and DELETE:
INPUT:
SQL> UPPDATE EMPLOYEE_TBL
SET LAST_NAME = 'SMITH'
WHERE EXISTS (SELECT EMPLOYEE_ID
FROM PAYROLL_TBL
WHERE EMPLOYEE_ID = 2);
OUTPUT:
1 row updated.
ANALYSIS:
The EMPLOYEE table had an incorrect employee name. We updated the EMPLOYEE
table only if the payroll table had the correct ID.
INPUT/OUTPUT:
SQL> UPDATE EMPLOYEE_TABLE
SET HOURLY_PAY = 'HOURLY_PAY * 1.1
WHERE EMPLOYEE_ID = (SELECT EMPLOYEE_ID
FROM PAYROLL_TBL
WHERE EMPLOYEE_ID = '222222222');
1 row updated.
ANALYSIS:
We increased the employee's hourly rate by 10 percent.
INPUT/OUTPUT:
SQL> DELETE FROM EMPLOYEE_TBL
WHERE EMPLOYEE_ID = (SELECT EMPLOYEE_ID
FROM PAYROLL_TBL
WHERE EMPLOYEE_ID = '222222222';
1 row deleted.
ANALYSIS:
Here we deleted an employee with the ID of 222222222.
Testing SELECT Statements Before Implementation
If you are creating a report (using SQL*PLUS for an example) and the report is
rather large, you may want to check spacing, columns, and titles before running the
program and wasting a lot of time. A simple way of checking is to add where rownum
< 3 to your SQL statement:
SYNTAX:
SQL> select *
from employee_tbl
where rownum < 5;
ANALYSIS:
You get the first four rows in the table from which you can check the spelling
and spacing to see if it suits you. Otherwise, your report may return hundreds or
thousands of rows before you discover a misspelling or incorrect spacing.
TIP: A major part of your job--probably
50 percent--is to figure out what your customer really wants and needs. Good communication
skills and a knowledge of the particular business that you work for will complement
your programming skills. For example, suppose you are the programmer at a car dealership.
The used car manager wants to know how many vehicles he has for an upcoming inventory.
You think (to yourself): Go count them. Well, he asked for how many vehicles he has;
but you know that for an inventory the manager really wants to know how many types
(cars, trucks), models, model year, and so on. Should you give him what he asked
for and waste your time, or should you give him what he needs?
Embedded SQL
This book uses the term embedded SQL to refer to the larger topic of writing actual
program code using SQL--that is, writing stored procedures embedded in the database
that can be called by an application program to perform some task. Some database
systems come with complete tool kits that enable you to build simple screens and
menu objects using a combination of a proprietary programming language and SQL. The
SQL code is embedded within this code.
On the other hand, embedded SQL commonly refers to what is technically known as
Static SQL.
Static and Dynamic SQL
Static SQL means embedding SQL statements directly within programming code. This
code cannot be modified at runtime. In fact, most implementations of Static SQL require
the use of a precompiler that fixes your SQL statement at runtime. Both Oracle and
Informix have developed Static SQL packages for their database systems. These products
contain precompilers for use with several languages, including the following:
- C
- Pascal
- Ada
- COBOL
- FORTRAN
Some advantages of Static SQL are
- Compile-time error checking
The disadvantages of Static SQL are that
- It is inflexible.
- It requires more code (because queries cannot be formulated at runtime).
- Static SQL code is not portable to other database systems (a factor that you
should always consider).
If you print out a copy of this code, the SQL statements appear next to the C
language code (or whatever language you are using). Program variables are bound to
database fields using a precompiler command. See Example 13.11 for a simple example
of Static SQL code.
Dynamic SQL, on the other hand, enables the programmer to build an SQL statement
at runtime and pass this statement off to the database engine. The engine then returns
data into program variables, which are also bound at runtime. This topic is discussed
thoroughly on Day 12.
Example 13.11
This example illustrates the use of Static SQL in a C function. Please note that
the syntax used here does not comply with the ANSI standard. This Static SQL syntax
does not actually comply with any commercial product, although the syntax used is
similar to that of most commercial products.
INPUT:
BOOL Print_Employee_Info (void)
{
int Age = 0;
char Name[41] = " |