Week 7
Database Programming: Cursors,
Triggers and Stored Procedures
Cursors
CURrent Set Of Rows: a set of records
returned from a query
Represent the entire set of records
Support navigation and iteration through
records, one at a time
Record based solution vs. set-based
solution
Forward-only and updatable
Make certain columns of a cursor updatable
and the rest being read-only
Creating a cursor
Declare a cursor-type variable
– Special type of non-scalar object
– Variable name isn't prefixed with an at symbol (@)
– The cursor variable can be declared and defined on the same line
with the SELECT statement used to populate the cursor
DECLARE curProduct INSENSITIVE CURSOR FOR
SELECT ProductID, Name FROM Product
To create a cursor that allows updates only to and from the
Name column of the Product table, declare the cursor like
this:
DECLARE curProduct CURSOR FOR
SELECT ProductID, Name FROM Product FOR UPDATE OF Name
Loading a cursor
The cursor isn't actually populated until it is opened.
Executing the OPEN command loads the cursor structure and data into
memory:
OPEN curProduct
At this point, the record pointer is positioned before the first row. The
FETCH NEXT command navigates the pointer to the next record and
returns a comma-delimited list of column values. In this case, the
pointer is moved to the first row. Individual variables can be used to
capture the values of the current row's column values:
DECLARE @ProdID Int
DECLARE @ProdName VarChar(100)
FETCH NEXT FROM curProduct INTO @ProdID, @ProdName
Capturing column value from cursor
into variables
After FETCH NEXT is executed, one of two things will
happen: the record pointer will either be positioned on a
valid record or it will navigate beyond the last row.
The state of the pointer can be determined using the global
variable @@Fetch_Status.
On a valid row, it returns 0, otherwise it returns –1 or –2.
It returns –1 if there is no next row to fetch. If a –2 is
returned it means that the next row was deleted in the
underlying table when using an updatable cursor.
Using this variable, create a simple loop, navigating to the
next record as long as @@Fetch_Status is equal to 0:
WHILE @@Fetch_Status = 0 BEGIN PRINT @ProdName
FETCH NEXT FROM curProduct INTO @ProdID,
@ProdName END
Close the cursor
Closing the cursor and free up the memory:
CLOSE curProduct
DEALLOCATE curProduct
Complete Codes
DECLARE curProduct INSENSITIVE CURSOR FOR
SELECT ProductID, Name FROM Product
DECLARE @ProdID Int
DECLARE @ProdName VarChar(100)
OPEN curProduct
FETCH NEXT FROM curProduct INTO @ProdID, @ProdName
WHILE @@Fetch_Status = 0
BEGIN
PRINT @ProdName
FETCH NEXT FROM curProduct INTO @ProdID, @ProdName
END
CLOSE curProduct
DEALLOCATE curProduct
Use of Cursors
Use conditional statements to decide
whether to perform related operations, such
as inserting or deleting records.
Use cursors to conditionally call different
stored procedures under different
conditions, to perform practically any
combination of operations.
User-Defined Functions
Scalar functions
Multi-statement table-valued functions
Inline table-valued functions
Scalar functions
A scalar function accepts any number of parameters and returns one value.
Input parameters are declared within parentheses followed by the return value
declaration.
All statements must be enclosed in a BEGIN. . . END block.
Calculate the age by getting the number of days between the birth date and
today's date. Because my function can't call the nondeterministic GETDATE()
function, this value must be passed into the function using the @Today
parameter.
The number of days is divided by the average number of days in a year to
determine the result:
CREATE FUNCTION fnGetAge (@BirthDate DateTime, @Today DateTime)
RETURNS Int
AS
BEGIN
RETURN DateDiff(day, @BirthDate, @Today) / 365.25
END
User-defined scalar functions are always called using multi-part names,
prefixed at least with the owner or schema name:
SELECT dbo.fnGetAge('1/4/1962', GetDate())
More example of User-defined Function
CREATE FUNCTION fnGetLastName
(@FullName VarChar(100))
RETURNS VarChar(100)
AS
BEGIN
DECLARE @CommaPosition Int
DECLARE @LastName VarChar(100)
SET @CommaPosition = CHARINDEX(‘,’)
SET @LastName = SUBSTRING(@FullName, 1,
@CommaPosition - 1)
RETURN @LastName
END
Multi-statement Function
CREATE FUNCTION fnProductListBySubCategory (@SubCategoryID
Int)
RETURNS @ProdList Table ( ProductID Int , Name nVarChar(50) ,
ListPrice Money )
AS BEGIN
IF @SubCategoryID IS NULL
BEGIN
INSERT INTO @ProdList (ProductID, Name, ListPrice)
SELECT ProductID, Name, ListPrice FROM Product
END
ELSE
BEGIN
INSERT INTO @ProdList (ProductID, Name, ListPrice)
SELECT ProductID, Name, ListPrice FROM Product
WHERE ProductSubCategoryID = @SubCategoryID
END
RETURN
END
Stored Procedures
stored procedures can be used to do the
following:
– Implement parameterized views
– Return scalar values
– Maintain records
– Process business logic
Stored Procedures as
Parameterized Views
SP returns a result set based on a SELECT
statement
SP is executed, not selected
Creating a view:
CREATE VIEW vProductCosts AS SELECT
ProductID, ProductSubcategoryID, Name,
ProductNumber, StandardCost FROM Product
Creating a SP:
CREATE PROCEDURE spProductCosts AS SELECT
ProductID, ProductSubcategoryID, Name,
ProductNumber,
StandardCost FROM Product
Using Parameters
A parameter is a special type of variable used to pass
values into an expression.
Named parameters are used for passing values into and
out of stored procedures and user-defined-functions.
Parameters are most typically used to input, or pass values
into, a procedure, but can also be used to return values.
Parameters are declared immediately after the procedure
definition and before the term AS.
Parameters are declared with a specific data type and are
used as variables in the body of a SQL statement.
Parameterised Stored Procedure
ALTER/Create PROCEDURE spProductCosts
@SubCategoryID Int
AS SELECT ProductID, Name, ProductNumber,
StandardCost FROM Product WHERE
ProductSubCategoryID = @SubCategoryID
To execute the procedure and pass the parameter
value, append the parameter value to the end of
the EXEC statement, like this:
EXECUTE spProductCosts 1 or
EXECUTE spProductCosts @SubCategory = 1
SP with Multiple Parameters
CREATE PROCEDURE spProductsByCost
@SubCategoryID Int, @Cost Money
AS
SELECT ProductID, Name, ProductNumber, StandardCost
FROM Product WHERE ProductSubCategoryID =
@SubCategoryID AND StandardCost > @Cost
Using SQL, the multiple parameters can be passed in a
comma-delimited list in the order they were declared:
EXECUTE spProductsByCost 1, $1000.00 or
EXECUTE spProductsByCost @Cost = $1000.00,
@SubCategoryID = 1
Use SP with Views
ALTER / Create PROCEDURE spProductCosts
@SubCategoryID Int
AS
SELECT ProductID, Name, ProductNumber,
StandardCost FROM vProductCosts WHERE
ProductSubCategoryID = @SubCategoryID
EXECUTE spProductCosts 1 or
EXECUTE spProductCosts @SubCategory = 1
Use Parameters to Return Values
This example shows how to pass the SubCategoryID using
an input parameter and return the record count using an
output parameter:
CREATE PROCEDURE spProductCountBySubCategory
@SubCategoryID Int, @ProdCount Int OUTPUT
AS
SELECT @ProdCount = COUNT(*) FROM Product
WHERE ProductSubCategoryID = @SubCategoryID
To test a stored procedure with output parameters in the
Management Studio or Query Analyzer environments, it is
necessary to explicitly use these parameters by name.
DECLARE @Out Int
EXECUTE spProductCountBySubCategory
@SubCategoryID = 2, @ProdCount = @Out OUTPUT
SELECT @Out AS ProductCountBySubCategory
ProductCountBySubCategory
-------------------------
184
An Alternative Method for Return a
Value from SP
CREATE PROCEDURE
spProductCountBySubCategory
@SubCategoryID Int
AS
DECLARE @Out Int
SELECT @Out = Count(*) FROM Product WHERE
ProductSubCategoryID = @SubCategoryID
RETURN @Out
Use SPs to Manage Records
Any program code written to perform record
operations should do so using stored
procedures and not ad-hoc SQL
expressions
– Insert, Update, Delete, and Select
Insert Procedure
Define parameters for all non-default or auto-populated
columns.
In the case of the Product table, the ProductID primary key
column will automatically be incremented because it's
defined as an identity column; the rowguid and ModifiedDate
columns have default values assigned in the table definition.
The MakeFlag and FinishedGoodsFlag columns also have
default values assigned in the table definition, but it may be
appropriate to set these values differently for some records.
For this reason, these parameters are set to the same
default values in the procedure.
Several columns are nullable and the corresponding
parameters are set to a default value of null.
If a parameter with a default assignment isn't provided when
the procedure is executed, the default value is used.
Otherwise, all parameters without default values must be
supplied
Declaring Variables for Insert
Procedure
CREATE PROCEDURE spProduct_Insert
@Name nVarChar(50) , @ProductNumber nVarChar(25) ,
@MakeFlag Bit = 1 , @FinishedGoodsFlag Bit = 1 ,
@Color nVarChar(15) = Null , @SafetyStockLevel SmallInt ,
@ReorderPoint SmallInt ,
@StandardCost Money , @ListPrice Money ,
@Size nVarChar(5) = Null ,
@SizeUnitMeasureCode nChar(3) = Null ,
@WeightUnitMeasureCode nChar(3) = Null ,
@Weight Decimal = Null , @DaysToManufacture Int ,
@ProductLine nChar(2) = Null , @Class nChar(2) = Null ,
@Style nChar(2) = Null , @ProductSubcategoryID SmallInt = Null ,
@ProductModelID Int = Null , @SellStartDate DateTime ,
@SellEndDate DateTime = Null , @DiscontinuedDate DateTime = Null
Insert Statements
AS
INSERT INTO Product ( Name , ProductNumber ,
MakeFlag , FinishedGoodsFlag , Color ,
SafetyStockLevel , ReorderPoint , StandardCost ,
ListPrice , Size , SizeUnitMeasureCode ,
WeightUnitMeasureCode , Weight ,
DaysToManufacture , ProductLine , Class , Style ,
ProductSubcategoryID , ProductModelID ,
SellStartDate , SellEndDate , DiscontinuedDate )
Select Statements
SELECT @Name , @ProductNumber ,
@MakeFlag , @FinishedGoodsFlag , @Color ,
@SafetyStockLevel , @ReorderPoint ,
@StandardCost , @ListPrice , @Size ,
@SizeUnitMeasureCode ,
@WeightUnitMeasureCode , @Weight ,
@DaysToManufacture , @ProductLine , @Class ,
@Style , @ProductSubcategoryID ,
@ProductModelID , @SellStartDate ,
@SellEndDate , @DiscontinuedDate
Execute the SP
EXECUTE spProduct_
Insert
@Name = 'Widget' , @ProductNumber = '987654321' ,
@SafetyStockLevel = 10 , @ReorderPoint = 15 ,
@StandardCost = 23.50 , @ListPrice = 49.95 ,
@DaysToManufacture = 30 , @SellStartDate = '10/1/04'
Or
EXECUTE spProduct_
Insert 'Widget', '987654321', 1, 1, Null, 10, 15, 23.50,
49.95, Null, Null, Null, Null, 30, Null, Null, Null, Null, Null,
'10/1/04'
Update Procedure: Declare all Variables
CREATE PROCEDURE spProduct_Update
@ProductID Int , @Name nVarChar(50) ,
@ProductNumber nVarChar(25) , @MakeFlag Bit = 1 ,
@FinishedGoodsFlag Bit = 1 ,
@Color nVarChar(15) = Null , @SafetyStockLevel SmallInt
, @ReorderPoint SmallInt , @StandardCost Money ,
@ListPrice Money , @Size nVarChar(5) = Null ,
@SizeUnitMeasureCode nChar(3) = Null ,
@WeightUnitMeasureCode nChar(3) = Null ,
@Weight Decimal = Null , @DaysToManufacture Int ,
@ProductLine nChar(2) = Null , @Class nChar(2) = Null ,
@Style nChar(2) = Null , @ProductSubcategoryID SmallInt
= Null , @ProductModelID Int = Null , @SellStartDate
DateTime , @SellEndDate DateTime = Null ,
@DiscontinuedDate DateTime = Null
Update Statements
AS
UPDATE Product
SET
Name = @Name , ProductNumber = @ProductNumber ,
MakeFlag = @MakeFlag , FinishedGoodsFlag = @FinishedGoodsFlag ,
Color = @Color , SafetyStockLevel = @SafetyStockLevel ,
ReorderPoint = @ReorderPoint , StandardCost = @StandardCost ,
ListPrice = @ListPrice , Size = @Size ,
SizeUnitMeasureCode = @SizeUnitMeasureCode ,
WeightUnitMeasureCode = @WeightUnitMeasureCode ,
Weight = @Weight , DaysToManufacture = @DaysToManufacture ,
ProductLine = @ProductLine , Class = @Class , Style = @Style ,
ProductSubcategoryID = @ProductSubcategoryID ,
ProductModelID = @ProductModelID , SellStartDate = @SellStartDate ,
SellEndDate = @SellEndDate , DiscontinuedDate = @DiscontinuedDate
WHERE ProductID = @ProductID
Exec spProduct_Update 1;
Delete Procedure
CREATE PROCEDURE spProduct_Delete
@ProductID Int
AS
DELETE FROM Product
WHERE ProductID = @ProductID
Exec spProduct_Delete 2
Triggers
DML Triggers:
– A special type of SP, executed automatically
based on occurrence of a database event.
– Data manipulation: insert, update, delete
DDL triggers: Also on DDL events in 2005
DML Triggers
Compare before and after versions of data.
Roll back invalid modifications.
Read from other tables, including those in
other databases.
Modify other tables, including those in other
databases.
Execute local and remote stored
procedures.
Creating DML Triggers
Using system trigger templates
– SELECT * FROM sys.triggers
AFTER trigger
– Invoked when any data modification takes place but before data
are committed to the database
Basic Syntax
CREATE TRIGGER trigger_name
ON table_name
AFTER { INSERT | UPDATE | DELETE }
AS
SQL statements
An example of AFTER Trigger
This trigger prints a message, stating the
number of rows updated by an UPDATE
statement. You then execute a couple
UPDATE statements to see whether the
trigger works.
Codes
CREATE TRIGGER tr_au_upd ON authors
AFTER UPDATE
AS
Create View vOldStockLevel AS Select ProdID, Stocklevel from Products
Print ‘Your old stocklevel data are as follows:’
Select * from vOldStockLevel
PRINT ‘TRIGGER OUTPUT: ‘ +CONVERT(VARCHAR(5), @@ROWCOUNT)
+ ‘ rows were updated.’
GO
UPDATE products
SET stock_level = stock_level + 3
WHERE state = ‘UT’
GO
Select ProdID, stock_leve from Products
Your old stocklevel data are as follows:
ProdID Stock_Level
1 2
You new stocklevel data are as follows:
ProdID Stock_Level
1 5
--TRIGGER OUTPUT: 1 rows were updated.
UPDATE authors
SET au_fname = au_fname
WHERE state = ‘CA’
GO
--TRIGGER OUTPUT: 37 rows were updated.
Limitations of AFTER Triggers
The following restrictions apply to AFTER
triggers:
AFTER triggers can be placed only on
tables, not on views.
An AFTER trigger cannot be placed on more
than one table.
The text, ntext, and image columns cannot
be referenced in the AFTER trigger logic.
Displaying Deleted or Inserted
Records within an AFTER Trigger
Using Inserted table and Deleted table
Both tables are available to the trigger.
The changes displayed are not committed
until the trigger completes.
Codes
--Create a copy of the titles table in the BigPubs2005
database
SELECT *
INTO titles_copy
FROM titles
GO
--add an AFTER trigger to this table for testing purposes
CREATE TRIGGER tc_tr ON titles_copy
FOR INSERT, UPDATE, DELETE
AS
PRINT ‘Inserted:’
SELECT title_id, type, price FROM inserted
PRINT ‘Deleted:’
SELECT title_id, type, price FROM deleted
ROLLBACK TRANSACTION
Results of Firing the Trigger
UPDATE titles_copy
SET price = $15.05
WHERE type LIKE ‘%cook%’
Inserted:
title_id type price
-------- ------------ ---------------------
TC7777 trad_cook 15.05
TC4203 trad_cook 15.05
TC3218 trad_cook 15.05
MC3021 mod_cook 15.05
MC2222 mod_cook 15.05
Deleted:
title_id type price
-------- ------------ ---------------------
TC7777 trad_cook 14.3279
TC4203 trad_cook 14.595
TC3218 trad_cook 0.0017
MC3021 mod_cook 15.894
MC2222 mod_cook 14.9532
References
http://msdn.microsoft.com/en-us/library/ms1
89799(classic).aspx
http://msdn.microsoft.com/en-us/library/ms3
45415.aspx
http://msdn.microsoft.com/en-us/library/ms1
80169.aspx