Committing a nested transaction does not write the changes from that transactionpermanently to the database; it merely makes them available to the outer transaction.Suppose you have the
Trang 1Committing a nested transaction does not write the changes from that transactionpermanently to the database; it merely makes them available to the outer transaction.Suppose you have the following SQL batch:
BEGIN TRANSACTIONUPDATE titlesSET price = 20.00WHERE title_id = ‘TC7777’
BEGIN TRANSACTIONUPDATE titlesSET type = ‘potboiler’
WHERE title_id = ‘TC7777’
COMMIT TRANSACTIONROLLBACK TRANSACTION
In this case, the COMMIT TRANSACTION statement tells SQL Server that you’refinished with the second transaction that you started However, the ROLLBACKTRANSACTION then rolls back all the work since the first BEGIN TRANSACTION,including the inner nested transaction
Although transaction names appear to offer increased readability for your code,they interact poorly with nested transactions In fact, you can refer to a transaction
by name only if it’s the outermost transaction in a batch Our recommendation is toavoid naming transactions if you plan to ever nest transactions
if there’s only one transaction open, the changes are written immediately
It’s your responsibility to make sure you’ve made all the changes you want beforeissuing a COMMIT TRANSACTION statement Once a transaction has been commit-ted, it can’t be rolled back
Although you can use a name in the COMMIT TRANSACTION statement, SQLServer makes no attempt to match this to a name in a BEGIN TRANSACTION state-ment The name’s purely for your convenience in making your code more readable
Trang 2COMMIT, with or without the optional keyword WORK, is exactly synonymous toCOMMIT TRANSACTION with no transaction name This form of the statement isANSI SQL-92 compatible
@savepoint_variable]
ROLLBACK [WORK]
ROLLBACK TRANSACTION throws away all changes since the most recent BEGINTRANSACTION Again, you can supply a transaction name as either a constant or avariable, but SQL Server ignores this name
You can also roll back part of a transaction by supplying a savepoint name We’lltalk about savepoints in the next section If a transaction is a distributed transaction(one that affects databases on multiple servers), you can’t roll back to a savepoint
ROLLBACK, with or without the optional WORK keyword, is the SQL-92 ant form of the statement However, in this form, you can’t roll back only one of a set
compli-of nested transactions ROLLBACK WORK always rolls back to the outermost (first)transaction in a batch
WAR N I N G ROLLBACK WORK rolls back all nested transactions and sets
@@TRANCOUNT to zero
If you call ROLLBACK TRANSACTION as part of a trigger, subsequent SQL ments in the same batch are not executed On the other hand, if you call ROLLBACKTRANSACTION in a stored procedure, subsequent SQL statements in the same batchare executed
Trang 3Note that when you issue SAVE TRANSACTION, you must name it This name vides a reference point for a subsequent COMMIT TRANSACTION or ROLLBACKTRANSACTION statement.
pro-An example will make the use of SAVE TRANSACTION more clear Consider thefollowing T-SQL batch:
BEGIN TRANSACTIONUPDATE titlesSET price = 20.00WHERE title_id = ‘TC7777’
SAVE TRANSACTION pricesavedUPDATE titles
SET type = ‘potboiler’
@@TRANCOUNT
The @@TRANCOUNT system global variable tells you the number of nested tions that are currently pending If no transactions are pending, this variable will con-tain zero This is useful for determining whether a trigger, for example, is executing inthe middle of a transaction already started by a T-SQL batch
transac-@@ERROR
The @@ERROR system global variable holds the most recent error number from any T-SQL statement Whenever a statement is executed that does not cause an error, thisvariable will contain zero That is, it’s reset to zero every time you successfully execute
a statement So if you want to check at some later point whether a statement hascaused an error, you need to save the value of @@ERROR to a local variable
Trang 4UPDATE titlesSET price = 20.00WHERE title_id = ‘TC7777’
SET @price_err = @@ERRORSAVE TRANSACTION pricesavedUPDATE titles
SET type = ‘potboiler’
WHERE title_id = ‘TC7777’
SET @type_err = @@ERROR
IF @type_err <> 0 ROLLBACK TRANSACTION pricesaved
IF @price_err = 0 AND @type_err = 0 BEGIN
COMMIT TRANSACTIONPRINT ‘Changes were successful’
ENDELSEROLLBACK TRANSACTION
Here’s a blow-by-blow account of this batch:
1 The DECLARE statement sets up two local variables.
2 The BEGIN TRANSACTION statement starts a transaction.
3 The first UPDATE statement makes a change to the price column.
4 The first SET statement is used to save the value of @@ERROR so that you can
check later whether the first UPDATE statement was successful Note that thisstatement must immediately follow the UPDATE statement
5 The SAVE TRANSACTION statement sets a savepoint.
6 The second UPDATE statement makes a change to the type column.
7 The second SET statement is used to save the value of @@ERROR so you can tell
whether the second UPDATE statement succeeded
8 If there was an error on the second UPDATE statement, the first ROLLBACK
TRANSACTION statement undoes the transaction back to the savepoint
9 If there are no errors at all, the transaction is committed, and a message is
printed Note the use of BEGIN and END to group two T-SQL statements intoone logical statement This is necessary because the IF statement refers only tothe following statement
Trang 510 If there are any errors, the second ROLLBACK TRANSACTION statement undoes
all of the work
Distributed Transactions
So far, we’ve been discussing local transactions: those that make changes in a singledatabase SQL Server also supports distributed transactions: transactions that makechanges to data stored in more than one database These databases need not be SQLServer databases; they can be databases on other linked servers
NOTE For more information on linked servers, see Chapter 6
A distributed transaction can be managed in code using exactly the same SQLstatements as you’d use for a local transaction However, when you issue a COMMITTRANSACTION on a distributed transaction, SQL Server automatically invokes a pro-
tocol called two-phase commit (sometimes referred to as 2PC) In the first phase, SQL
Server asks every database involved to prepare the transaction The individual bases verify that they can commit the transaction and set aside all the resources nec-essary to do so It’s only if every involved database tells SQL Server that it’s OK tocommit the transaction that the second phase starts In this phase, SQL Server tellsevery involved database to commit the transaction If any of the databases involvedare unable to commit the transaction, SQL Server tells all of the databases to roll backthe transaction instead
data-Microsoft DTC
Distributed transactions are managed by a SQL Server component called the
Distrib-uted Transaction Coordinator (DTC) This is a separate service that’s installed at the
same time as SQL Server If you’re going to use distributed transactions, you shouldset this service to autostart Figure 8.1 shows this service selected in the SQL ServerService Manager
Trang 6BEGIN DISTRIBUTED TRANSACTION
You can tell SQL Server explicitly to start a distributed transaction with the BEGINDISTRIBUTED TRANSACTION statement:
BEGIN DISTRIBUTED TRANS[ACTION]
Transaction Tips
Transactions consume resources on the server In particular, when you change datawithin a transaction, that data must be locked to ensure that it’s available if you com-mit the transaction So, in general, you need to make transactions efficient to avoidcausing problems for other users Here are a few points to consider:
• Don’t do anything that requires user interaction within a transaction, becausethis can cause locks to be held for a long time while the application is waitingfor the user
• Don’t start transactions for a single SQL statement
• Change as little data as possible when in a transaction
Trang 7• Don’t start a transaction while the user is browsing through data Wait untilthey’re actually ready to change the data.
• Keep transactions as short as possible
Rowset Functions
Rowset functions are functions that return an object that can be used in place of a table
in another SQL statement For example, as you saw in Chapter 7, some rowset tions can be used to provide the rows to be inserted with an INSERT statement Thereare five rowset functions in SQL Server 2000:
of most of the statements we’ve examined so far:
CONTAINSTABLE (table_name, {column_name | *},
<search_condition> […n]
}
Trang 8<simple_term> ::=
word | “phrase”
<weighted_term> ::=
ISABOUT ({{
CON-• Using the asterisk (*) to specify columns tells CONTAINSTABLE to search allcolumns that have been registered for full-text searching, which might not beall the columns in the table
• Weight values are numbers between zero and one that specify how importanteach match is considered to be in the final virtual table
Trang 9• You can limit the number of results returned by specifying an integer in the
top_n parameter This is useful when you’re searching a very large source table
and want to see only the most important matches
The CONTAINSTABLE statement returns a virtual table containing two columns,always named KEY and RANK For example, consider the following statement:
SELECT * FROMCONTAINSTABLE(Products, ProductName,
‘ISABOUT(mix WEIGHT(.8), sauce WEIGHT(.2))’)
Assuming that you’ve enabled the Product table in the Northwind sample databasefor full-text searching on the ProductName column, this statement returns the resultsshown in Figure 8.2 The ISABOUT search condition here specifies that results con-
taining the word mix should be rated as more important than those containing the word sauce.
FIGURE 8.2
Using CONTAINSTABLE
to generate a virtual table
The KEY column will always contain values from the column that you identified asthe primary key to the full-text indexing service To make this statement more useful,you’ll probably want to use this column to join back to the original table Figure 8.3shows the results of the following statement:
SELECT ProductName, RANK FROMCONTAINSTABLE(Products, ProductName,
‘ISABOUT(mix WEIGHT(.8), sauce WEIGHT(.2))’)
Trang 10AS CINNER JOIN Products
ON Products.ProductID = C.[KEY]
FIGURE 8.3
Using CONTAINSTABLE joined to the original search table
N OTE The virtual table needs to be aliased to be included in a join, and you mustinclude the square brackets around the joining name because KEY is a SQL Server keyword
Trang 11You can think of FREETEXTTABLE as being like a black-box version of TABLE Internally, SQL Server breaks the freetext string up into words, assigns aweight to each word, and then looks for similar words For example, the followingstatement could be used to retrieve items whose description looks somehow similar
Trang 12OPENQUERY
The OPENQUERY statement lets you use any query (SQL statement that returns rows)
on a linked server to return a virtual table The syntax of OPENQUERY is as follows:
OPENQUERY(linked_server, ‘query’)
NOTE For more information on creating linked servers, see Chapter 6
Figure 8.5 shows in SQL Server Enterprise Manager that the MOOCOW serverknows about a linked server named BIGREDBARN, which is also a Microsoft SQLServer If you connected to the BIGREDBARN server directly, you could run a querylike the following:
SELECT * FROM Northwind.dbo.Customers
FIGURE 8.5
Inspecting properties for a linked server
This query would return all of the rows in the Customers table owned by dbo inthe Northwind database So far, there’s no need for OPENQUERY However, suppose
ROWSET FUNCTIONS
P A R TII
Trang 13you want to join the Customers table from the BIGREDBARN server to the Orderstable from the MOOCOW server In this case, you might connect to the MOOCOWserver and run the following statement instead:
SELECT CompanyName, OrderID, OrderDate FROMOPENQUERY(BIGREDBARN, ‘SELECT * FROM Northwind.dbo.Customers’)
AS CustomersINNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerIDORDER BY OrderID
Note that the original query that retrieved the records in the context of theBIGREDBARN server has been incorporated as one of the parameters of the OPEN-QUERY statement
OPENQUERY is the easiest tool that you can use to perform distributed queriesusing SQL Server By using OPENQUERY, you can join any number of tables from dif-ferent data sources These data sources don’t even need to be SQL Server tables; aslong as they’re data sources that you can represent as linked servers (basically, anydata source that you have an OLE DB provider to connect with), you can use themwith OPENQUERY
OPENROWSET
OPENROWSET also provides a way to use data from a different server in a SQL Serverstatement In the case of OPENROWSET, you supply the information needed to con-nect via OLE DB directly:
par-SELECT CompanyName, OrderID, OrderDate FROMOPENROWSET(‘SQLOLEDB’, ‘BIGREDBARN’;’sa’;’’,
‘SELECT * FROM Northwind.dbo.Customers’)
AS CustomersINNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerIDORDER BY OrderID
Trang 14OPEN-OPENDATASOURCE(provider_name, connection_string)
OPENDATASOURCE is more flexible than OPENROWSET in that SOURCE can be used in place of a linked server name, so it need not refer to any par-ticular database or table on the other server You can use OPENDATASOURCE to refer
Trang 15For example, you could perform the same query that was shown in the ROWSET example with the following OPENDATASOURCE statement:
OPEN-SELECT CompanyName, OrderID, OrderDate FROMOPENDATASOURCE(‘SQLOLEDB’,
‘Data Source=BIGREDBARN;User ID=sa;Password=’
).Northwind.dbo.Customers
AS CustomersINNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerIDORDER BY OrderID
TI P OPENROWSET and OPENDATASOURCE should be used only for data sources thatyou need to query on an infrequent basis If you need to regularly connect to a particulardata source, it’s more efficient to use a linked server for that connection
Cursors
Traditionally, SQL provides a set-oriented look for your data For example, when youexecute a SELECT statement, it returns a set of rows This set is all one thing, not aselection of individual rows Although this is a useful view for many traditionalbatch-processing applications, it’s less appealing for interactive applications where auser might want to work with rows one at a time
What Are Cursors?
SQL Server’s solution to this problem is to introduce cursors If you’ve worked withrecordsets in a product such as Access or Visual Basic, you can understand cursors as a
server-side recordset A cursor is a set of rows together with a pointer that identifies a
current row T-SQL provides statements that allow you to move the pointer and towork with the current row In the remainder of this section, you’ll learn about the fol-lowing statements:
Trang 16DECLARE CURSOR
The DECLARE CURSOR statement is used to set aside storage for a cursor and to setthe basic properties of the cursor Actually, there are two different forms of theDECLARE CURSOR statement The first form is the ANSI standard DECLARE CURSOR:
DECLARE cursor_name [INSENSITIVE][SCROLL] CURSOR FOR select_statement
[FOR {READ ONLY | UPDATE [OF column_name [,…n]]}]
In this form of the DECLARE CURSOR statement:
• The DECLARE and CURSOR keywords are required to declare a cursor
• The cursor_name is an arbitrary SQL identifier that will identify this cursor in
subsequent T-SQL statements
• INSENSITIVE tells SQL Server to establish a temporary table just for this cursor
Modifications that other users make while the cursor is open will not bereflected in the cursor’s data, and you won’t be able to make any modificationsthrough the cursor
• SCROLL specifies that all of the options of the FETCH statement should be ported If you omit SCROLL, only FETCH NEXT is supported
• The select_statement argument is a standard T-SQL SELECT statement that
sup-plies the rows for the cursor This statement cannot use the COMPUTE, PUTE BY, FOR BROWSE, or INTO options
COM-• READ ONLY prevents any updates through the cursor By default, the cursor willallow updating (unless it was opened with the INSENSITIVE option)
• UPDATE specifies explicitly that the cursor should allow updating If you use UPDATE OF with a list of column names, only data in those columns can
be updated
There’s also an extended form of DECLARE CURSOR that is not ANSI SQL compatible:
DECLARE cursor_name CURSOR
[LOCAL | GLOBAL]
[FORWARD_ONLY | SCROLL]
[STATIC | KEYSET | DYNAMIC | FAST_FORWARD]
[READ_ONLY | SCROLL_LOCKS | OPTIMISTIC]
[TYPE_WARNING]
FOR select_statement [FOR UPDATE [OF column_name [ , n]]]
In this form of the DECLARE CURSOR statement:
• The DECLARE and CURSOR keywords are required to declare a cursor
Trang 17• The cursor_name is an arbitrary SQL identifier that will identify this cursor in
subsequent T-SQL statements
• The LOCAL keyword limits the use of the cursor to the batch, stored procedure,
or trigger where it was created
• The GLOBAL keyword makes the cursor available to any statement on the rent connection
cur-• FORWARD_ONLY specifies that only the NEXT option of the FETCH statement
• KEYSET specifies that the cursor should be updateable, both by the connectionand by other users However, new rows added by other users won’t be reflected
in the cursor
• DYNAMIC specifies that the cursor should be fully updateable and that itshould reflect new rows
• READ_ONLY specifies that the cursor should be read-only
• SCROLL_LOCKS specifies that updates or deletions made through the cursorshould always succeed SQL Server ensures this by locking the rows as soon asthey’re read into the cursor
• OPTIMISTIC uses optimistic locking when you attempt to change a rowthrough the cursor
• TYPE_WARNING tells SQL Server to send a warning if the selected cursoroptions can’t all be fulfilled
• The select_statement argument is a standard T-SQL SELECT statement that
sup-plies the rows for the cursor This statement cannot use the COMPUTE, PUTE BY, FOR BROWSE, or INTO options
COM-• FOR UPDATE specifies explicitly that the cursor should allow updating If youuse UPDATE OF with a list of column names, only data in those columns can beupdated
Trang 18OPEN and @@CURSOR_ROWS
The OPEN statement is used to populate a cursor with the records to which it refers:
OPEN {{[GLOBAL] cursor_name} | cursor_variable_name}
You must use the GLOBAL keyword if you’re referring to a cursor declared with theGLOBAL keyword You can use either the name of a cursor directly or the name of acursor variable (one declared with the DECLARE statement and set equal to a cursorwith the SET statement)
Of course, the cursor must be declared before you issue the OPEN statement
If the cursor was declared with the INSENSITIVE or STATIC keywords, the OPENstatement will create a temporary table in the tempdb database to hold the records Ifthe cursor was declared with the KEYSET keyword, the OPEN statement will create atemporary table in the tempdb database to hold the keys You don’t need to worryabout these tables; SQL Server will delete them when the cursor is closed
Once a cursor has been opened, you can use the @@CURSOR_ROWS global able to retrieve the number of rows in this cursor For example, consider the followingT-SQL batch:
vari-DECLARE customer_cursor CURSORLOCAL SCROLL STATIC
FORSELECT * FROM CustomersOPEN customer_cursorPRINT @@CURSOR_ROWS
As you can see in Figure 8.7, the PRINT statement shows that all 91 rows of theCustomers table are in the cursor
Trang 19WAR N I N G The @@CURSOR_ROWS variable always refers to the most recentlyopened cursor You may want to store the value of this variable directly after the OPENstatement so that you can refer to it later.
You need to be a bit cautious about using @@CURSOR_ROWS, because under somecircumstances, it won’t reflect the actual number of rows in the cursor That’s becauseSQL Server might decide to fetch data into the cursor asynchronously, so that process-ing can continue while the cursor is still being populated
SQL Server will fill a cursor asynchronously if the cursor is declared with the TIC or KEYSET parameters and SQL Server estimates that the number of rows will begreater than a certain threshold value You can set this value with the sp_configure
STA-system stored procedure; the name of the option is cursor threshold By default, the
value is set to –1, which tells SQL Server to always populate cursors synchronously
NOTE See Chapter 14 for more information on sp_configure
Depending on the circumstances, @@CURSOR_ROWS might return one of the lowing values:
fol-• A negative number indicates that the cursor is being populated asynchronouslyand shows the number of rows retrieved so far The value –57, for example, indi-cates that the cursor has 57 rows, but that SQL Server has not finished populat-ing the cursor
• The value –1 is a special case that’s always returned for dynamic cursors Becauseother users can be adding or deleting data, SQL Server can’t be sure about thenumber of rows in a dynamic cursor, or whether it’s fully populated
• Zero indicates that there isn’t an open cursor
• A positive number indicates that the cursor is fully populated with that number
of rows
FETCH and @@FETCH_STATUS
The FETCH statement is used to retrieve data from a cursor to variables so that youcan work with the data This statement has a number of options:
FETCH[[ NEXT | PRIOR | FIRST | LAST
Trang 20| ABSOLUTE {n | @n_variable}
| RELATIVE {n | @n_variable}
]FROM]
{{[GLOBAL] cursor_name} | @cursor_variable_name}
[INTO @variable_name [,…n]]
If you keep in mind that a cursor is a set of records with a pointer to a particularrecord, it’s pretty easy to understand the FETCH statement FETCH is used to movethe record pointer
• NEXT is the default option and fetches the next row in the cursor If FETCH NEXT
is the first statement issued, it fetches the first row from the cursor
• PRIOR fetches the previous row in the cursor
• FIRST fetches the first row in the cursor
• LAST fetches the last row in the cursor
• ABSOLUTE fetches the particular record specified For example, ABSOLUTE 5fetches the fifth record If you use a variable to hold the number, the variablemust be of type int, smallint, or tinyint
• RELATIVE fetches a record ahead or behind the current record by the specifiedamount For example, RELATIVE 5 fetches the record five past the currentrecord, and RELATIVE –5 fetches the record five before the current record If youuse a variable to hold the number, the variable must be of type int, smallint, ortinyint
• INTO lets you specify variables that will hold the fetched data You must supplyenough variables to hold all the columns from the cursor The variables will befilled in column order, and the datatypes must match those in the cursor or bedatatypes that can be implicitly converted from those in the cursor
Not all FETCH options are supported by all cursors, depending on how the cursorwas declared Here are the rules:
• If the cursor was declared with SQL-92 syntax without SCROLL, only NEXT issupported
• If the cursor was declared with SQL-92 syntax with SCROLL, all options are ported
sup-• If the cursor was declared with SQL Server syntax with FORWARD_ONLY orFAST_FORWARD, only NEXT is supported
Trang 21• If the cursor was declared with SQL Server syntax with DYNAMIC SCROLL, alloptions except ABSOLUTE are supported.
• If the cursor was declared with SQL Server syntax and doesn’t fall into one ofthe above two categories, all options are supported
The @@FETCH_STATUS global variable contains information on the most recentFETCH operation If the value is zero, the fetch was successful If the value is not zero,the FETCH statement failed for some reason
As a simple example of FETCH, here’s how you might print the data from the firstrow of a cursor:
DECLARE @customerid nchar(5), @companyname nvarchar(100)DECLARE customer_cursor CURSOR
LOCAL SCROLL STATICFOR
SELECT CustomerID, CompanyName FROM CustomersOPEN customer_cursor
FETCH NEXT FROM customer_cursorINTO @customerid, @companynamePRINT @customerid + ‘ ‘ + @companyname
More often, you’ll want to do something that moves through an entire cursor Youcan do this by using the @@FETCH_STATUS variable with the WHILE statement Wehaven’t discussed the WHILE statement yet, but it’s similar to WHILE in most otherprogramming languages It performs the next statement repeatedly as long as somecondition is true Figure 8.8 shows an example of using FETCH to retrieve multiplerows by executing the following T-SQL batch:
DECLARE @customerid nchar(5), @companyname nvarchar(100)DECLARE customer_cursor CURSOR
LOCAL SCROLL STATICFOR
SELECT CustomerID, CompanyName FROM CustomersOPEN customer_cursor
FETCH NEXT FROM customer_cursorINTO @customerid, @companynamePRINT @customerid + ‘ ‘ + @companynameWHILE @@FETCH_STATUS = 0
BEGINFETCH NEXT FROM customer_cursorINTO @customerid, @companynamePRINT @customerid + ‘ ‘ + @companynameEND
Trang 22FIGURE 8.8
Fetching multiple rows of data with a WHILE loop
CLOSE
The CLOSE statement is the reverse of the OPEN statement Its syntax is similar tothat of OPEN:
CLOSE {{[GLOBAL] cursor_name} | cursor_variable_name}
When you’re done with the data in a cursor, you should execute a CLOSE ment This frees up the rows that are being held in the cursor, but it does not destroythe cursor itself The cursor could be reopened by executing the OPEN statementagain While a cursor is closed, of course, you can’t execute a FETCH statement on it
state-DEALLOCATE
The DEALLOCATE statement is the reverse of the DECLARE CURSOR statement:
DEALLOCATE {{[GLOBAL] cursor_name} | cursor_variable_name}
CURSORS
P A R TII
Trang 23When you’re done with a cursor, you should use DEALLOCATE to destroy the sor data structures and remove the name from the SQL Server namespace.
PRINT ‘Results for ‘ + CAST(@@CURSOR_ROWS AS varchar) +
‘ customers’
Print ‘—————————————’
FETCH NEXT FROM customer_cursorINTO @customerid, @companynameSELECT @norders = (
SELECT COUNT(*) FROM ORDERS WHERE CustomerID = @customerid)PRINT @companyname + ‘ (‘ + @customerid + ‘) has ‘ +CAST(@norders AS varchar) + ‘ orders’
WHILE @@FETCH_STATUS = 0BEGIN
FETCH NEXT FROM customer_cursorINTO @customerid, @companynameSELECT @norders = (
SELECT COUNT(*) FROM ORDERS WHERE CustomerID = @customerid)PRINT @companyname + ‘ (‘ + @customerid + ‘) has ‘ + CAST(@norders AS varchar) + ‘ orders’
ENDCLOSE customer_cursorDEALLOCATE customer_cursor
Trang 24Let’s look at the statements in this batch, step by step:
• The first DECLARE statement sets aside storage for two variables
• The second DECLARE statement sets aside storage for one more variable
• The third DECLARE statement declares a static cursor to hold information fromtwo columns in the Customers table
• The OPEN statement gets the rows that the cursor declares
• The first PRINT statement uses the @@CURSOR_ROWS global variable to printthe number of records in the cursor Note the use of the CAST statement to con-vert this numeric value to character format before the value is concatenatedwith other strings
• The first FETCH NEXT statement gets the first row from the cursor
• The SELECT statement uses some of the data from the cursor together with theCOUNT function to count the rows in the Orders table for the first customer
• The PRINT statement formats the selected data for the user
• The WHILE statement tells SQL Server to continue until it’s exhausted the cursor
• The BEGIN statement marks the start of the statements controlled by theWHILE statement
• The FETCH NEXT, SELECT, and PRINT statements within the WHILE loop tellSQL Server to continue fetching rows and printing the results
• The END statement marks the end of the statements controlled by the WHILEstatement
• The CLOSE statement removes the records from the cursor
• The DEALLOCATE statement removes the cursor from memory
Can you visualize the results of running this batch of T-SQL statements? You canrefer to Figure 8.9 to confirm your results
Trang 25FIGURE 8.9
Running a batch in SQL Query Analyzer
NOTE If you’re working with a client data-access library such as ADO, you may neverneed to deal with SQL Server’s cursor functions directly You’ll be able to get the same ben-efits by opening a recordset on the client Behind the scenes, ADO will be using the cursorfunctions itself, freeing you from managing the cursors See Chapter 19 for more informa-tion on ADO