Advanced Query Techniques
Advanced Query Techniques
Chapter 9 from Microsoft SQL Server 7.0 Database Implementation Training Kit, published by Microsoft
Press
On This Page
This chapter adds to the query techniques described in Chapter 8, "Querying Multiple Indexes," and
Appendix D, "Basic Queries." Subqueries make it possible to use queries inside of other queries to create
calculated values and specify advanced selection criteria. Cursors are used to perform row-based
processing on SQL result sets. The final lesson explains how to use the techniques, which you have
learned to use with SELECT queries, when modifying data.
• Know the basics of SQL. Appendix C covers basic SQL queries, including exercises.
• Have installed the exercise files from the Supplemental Course Materials CD-ROM to your hard
drive using the Setup.exe discussed in the "Getting Started" section of the book.
Top Of Page
Lesson 1: Subqueries
A subquery is a SELECT statement nested inside a SELECT, INSERT, UPDATE, or DELETE statement or
inside another subquery. Subqueries can be nested or correlated; nested subqueries execute once when
the outer query is executed, and correlated subqueries execute once for every row that is returned when
the outer query is executed.
Use subqueries to break down a complex query into a series of logical steps and solve a problem with a
single statement. Subqueries are useful when your query relies on the results of another query.
When to Use Joins Rather Than Subqueries
Often, a query that contains subqueries can be written as a join. Query performance may be similar with a
join and a subquery. The difference is that a subquery may require the query optimizer to perform
additional steps, such as sorting, which may influence the processing strategy.
Using joins typically allows the query optimizer to retrieve data in the most efficient way. If a query does
not require multiple steps, it may not be necessary to use a subquery.
Using Subqueries
When you decide to use subqueries, consider the following facts and guidelines:
• You can only use one expression or column name in the select list of a subquery that returns a
value.
• You can use subqueries in place of an expression as long as a single value or list of values is
returned.
• You cannot use subqueries on columns that contain text and image data types.
• The query that contains the subquery is usually called the outer query and a subquery is called
an inner query.
Nested Subqueries
Nested subqueries return either a single value or a list of values. SQL Server evaluates a nested subquery
once and then uses the value or list returned by the subquery in the outer query.
Use a nested subquery that returns a single value to compare to a value in the outer query. When you use
nested subqueries to return a single value, consider the following guidelines:
• If a nested subquery returns more than one value when the outer query expects only one value,
• You can use an aggregate function or specify an expression or column name to return a single
value.
Example 1
This example uses a single-value nested SELECT statement in the WHERE clause to return all customers in
the Orders table who placed orders on the most recent recorded day.
USE Northwind
SELECT OrderID, CustomerID
FROM Orders
WHERE OrderDate = (SELECT MAX(OrderDate) FROM Orders)
Result
OrderID CustomerID
————— —————
11074 SIMOB
11075 RICSU
11076 BONAP
11077 RATTC
(4 row(s) affected)
To fully understand this example, consider the result if the subquery is executed on its own:
—————————————
1998-05-06 00:00:00.000
(1 row(s) affected)
Therefore, the outer query is effectively:
USE Northwind
SELECT OrderID, CustomerID
FROM Orders
WHERE OrderDate = '1998-05-06 00:00:00.000'
Example 2
This example contains two single-value subqueries. For each sale of a particular product, the query lists
the quantity sold, the total sales of all products and the percentage of total sales for that particular sale.
USE Northwind
SELECT
ProductID,
Quantity AS Number_Sold,
(SELECT SUM(Quantity) FROM [Order Details]) AS Total_Sold,
((CONVERT(Money, Quantity) /
(SELECT SUM(Quantity) FROM [Order Details])) * 100)
AS Percent_Of_Sales
FROM [Order Details]
Result
In this exercise, you will write and execute a query in the library database that returns member.firstname,
member.lastname, loanhist.isbn, and loanhist.fine_paid for members who have paid the highest recorded
fines for all books. The c:\sqlimpl\exercise\ch09\highpay.sql script file contains a completed script for this
exercise.
2. Write a query that returns the largest recorded value in the loanhist.fine_paid column.
3. Write a query that joins the member and loanhist tables and returns the firstname, lastname,
isbn, and fine_paid for each row.
4. Use the first query as selection criteria in the WHERE clause of the second query to return only
those rows from the join in which the fine that is paid equaled the largest value that was ever
recorded for all books.
5. Include the DISTINCT keyword in your query to eliminate entries for members who have paid this
fine on several occasions.
6. USE library
7. SELECT DISTINCT firstname, lastname, isbn, fine_paid
8. FROM member m JOIN loanhist lh
9. ON m.member_no = lh.member_no
10. WHERE lh.fine_paid = (SELECT MAX(fine_paid) FROM loanhist)
Result
To test for membership in a generated list of values, use a nested subquery that returns a list of values.
When you use nested subqueries to return a list of values, consider the following facts:
• In a WHERE clause, use the IN operator with subqueries that return a list of values.
Example 1
This example generates a list of companies that made purchases after the date 1/1/95.
USE Northwind
SELECT CompanyName
FROM Customers
WHERE CustomerID IN
(SELECT CustomerID FROM Orders WHERE OrderDate > '1/1/95')
Result
CompanyName
————————————————————
Alfreds Futterkiste
Ana Trujillo Emparedados y helados
Antonio Moreno Taquería
…
Wilman Kala
Wolski Zajazd
(89 row(s) affected)
To fully understand this example, consider the result if the subquery is executed on its own:
CustomerID
—————
VINET
TOMSP
HANAR
…
BONAP
RATTC
(830 row(s) affected)
Therefore, the outer query is effectively:
USE Northwind
SELECT CompanyName
FROM Customers
WHERE CustomerID IN
('VINET', 'TOMSP', 'HANAR', … , 'BONAP', 'RATTC')
To use a multiple-value subquery
In this exercise, you will write and execute a query in the library database on the reservation table that
returns two columns, member_no, and DateReserved. The query returns a list of member numbers that
have books on reserve with a title that contains the word First. The c:\sqlimpl\exercise\ch09\subquin.sql
script file contains a completed script for this exercise.
1. Write a subquery that joins the item and title tables and returns a list of ISBN numbers for all
2. Write an outer query that returns two columns: member_no and DateReserved, where
DateReserved is the log_date converted using the CONVERT function and date style 1.
3. Restrict the rows that form the groups in the outer query by comparing reservation.isbn against
the list of values that were generated by the subquery that you wrote in step 1. Use the IN
keyword as part of the WHERE clause.
4. USE library
5. SELECT member_no, CONVERT(char(8), log_date, 1) AS DateReserved
6. FROM reservation
7. WHERE isbn IN (SELECT it.isbn
8. FROM item it INNER JOIN title ti
9. ON it.title_no = ti.title_no
10. WHERE ti.title LIKE '%First%')
Result
member_no DateReserved
———— ——————
35 01/12/99
86 01/12/99
137 01/12/99
…
9929 01/12/99
9980 01/12/99
(196 row(s) affected)
Correlated Subqueries
For correlated subqueries, the inner query uses information from the outer query and executes once for
every row in the outer query.
When you use correlated subqueries, consider the following facts and guidelines:
• You must use aliases to distinguish table names.
• Correlated subqueries can usually be rephrased as joins. Using joins rather than correlated
subqueries enables SQL Server query optimizer to determine how to correlate the data in the
most efficient way.
Example 1
This example returns a list of customers who ordered more than 20 items of product number 23 on a
single order.
USE Northwind
SELECT OrderID, CustomerID
FROM Orders o
WHERE 20 < (SELECT Quantity
FROM [Order Details] od
WHERE o.OrderID = od.OrderID AND od.ProductID = 23)
Result
OrderID CustomerID
————— —————
10337 FRANK
10348 WANDK
10396 FRANK
…
10869 SEVES
10880 FOLKO
(11 row(s) affected)
Figure 9.1 and the following steps describe how the correlated subquery is evaluated in Example 1:
1. The outer query passes a column value to the inner query. The outer query passes the value
from the OrderID column to the inner query.
2. The inner query uses the value that the outer query passes. The OrderID from the Orders table is
used in the search condition of the WHERE clause of the inner query. The inner query tries to find
a row in the [Order Details] table that matches the OrderID from the outer query and the
ProductID 23.
3. The inner query returns a value back to the outer query. If a row was found in the inner query,
the value from the Quantity column of that row is returned to the outer query; otherwise, NULL is
returned to the outer query. The WHERE clause of the outer query then evaluates its search
condition to determine whether the Quantity exceeds 20, in which case the order is included in
the result set of the outer query.
4. Move on to the next row in the Orders table. The outer query moves on to the next row and SQL
Server repeats the evaluation process for that row.
Figure 9.1: Evaluating a correlated subquery.
Example 2
This example returns a list of products and the largest order ever placed for each product. Notice that this
correlated subquery uses a second alias to reference the same table as the outer query. The Products
table joined simply to look up the product name; it is not involved in the subquery.
USE Northwind
SELECT DISTINCT ProductName, Quantity
FROM [Order Details] od1 JOIN Products p
ON od1.ProductID = p.ProductID
WHERE Quantity = (SELECT MAX(Quantity)
FROM [Order Details] od2
WHERE od1.ProductID = od2.ProductID)
Result
ProductName Quantity
———————————————————— ————
Alice Mutton 100
Aniseed Syrup 60
Boston Crab Meat 91
…
Wimmers gute Semmelknödel 130
Zaanse koeken 55
(77 row(s) affected)
To use a correlated subquery
In this exercise, you will create a query that uses a correlated subquery to calculate a value based on data
from the outer query, and then uses that value as part of a comparison. You will query the member and
loanhist tables to return a list of library members who have assessed fines that total more than $5.00. A
correlated subquery calculates the fines that are assessed for each member. The
c:\sqlimpl\exercise\ch09\fineof5.sql script file contains a completed script for this exercise.
1. Write a query that returns the member_no and lastname columns of the member table. Use a
table alias for the member table.
2. Write a subquery that calculates the total fines that are assessed for each member as recorded in
the loanhist table. Use an alias for the loanhist table. Correlate the member.member_no column
of the outer query to the loanhist.member_no column of the inner query.
3. Use a comparison operator in the WHERE clause of the outer query to select those members who
have fines that total more than $5.00.
4. USE library
5. SELECT member_no, lastname
6. FROM member m
7. WHERE (SELECT SUM(fine_assessed)
8. FROM loanhist lh
9. WHERE m.member_no = lh.member_no) > 5
Result
member_no lastname
———— ———————
203 Graff
617 Valentine
778 Chen
…
9676 Harui
9806 Martin
(41 row(s) affected)
Using the EXISTS and NOT EXISTS Keywords
You can use the EXISTS and NOT EXISTS keywords to determine whether a subquery returns rows.
Use the EXISTS and NOT EXISTS keywords with correlated subqueries to restrict the result set of an outer
query to rows that satisfy the subquery. The EXISTS and NOT EXISTS keywords return TRUE or FALSE,
based on whether rows are returned by the subquery. The subquery is tested to see if it will return rows;
it does not return any data. Use * (asterisk) in the select list of the subquery as there is no reason to use
column names.
Example 1
This example uses a correlated subquery with an EXISTS keyword in the WHERE clause to return a list of
employees who received orders on 9/2/94.
USE Northwind
SELECT LastName, EmployeeID
FROM Employees e
WHERE EXISTS (SELECT * FROM Orders
WHERE e.EmployeeID = Orders.EmployeeID
AND OrderDate = '9/5/97')
Result
LastName EmployeeID
—————————— —————
Peacock 4
King 7
(2 row(s) affected)
Example 2
This example returns the same result set as Example 1 to show that you could use a join operation rather
than a correlated subquery.
USE Northwind
SELECT LastName, e.EmployeeID
FROM Orders INNER JOIN Employees e
ON Orders.EmployeeID = e.EmployeeID
WHERE OrderDate = '9/5/97'
Result
LastName EmployeeID
—————————— —————
Peacock 4
King 7
(2 row(s) affected)
Lesson Summary
Subqueries allow you to use queries within other queries anywhere that you can use an expression or in
WHERE clause search conditions. Nested subqueries are executed once, when the outer query executes
and can return either a single value or a list of values for use by the outer query. Correlated subqueries
are passed a value and then executed for each row in the outer query. The EXISTS and NOT EXISTS
keywords are used in WHERE clauses to test for the existence of rows in the result set of a subquery.
Top Of Page
Cursors offer the ability to navigate forward and backward through a result set one row at a time in order
to view or process data. Think of a cursor as a pointer to a current position or a specific row within a result
set.
• Determine which cursor type and characteristics are appropriate for a cursor.
• Retrieving and modifying one row or block of rows from the current position in the result set.
• Supporting different levels of sensitivity to changes to the data underlying the result set.
Requesting Cursors
Microsoft SQL Server version 7.0 supports two methods for requesting a cursor:
• Transact-SQL
SQL Server supports the cursor functionality of the following database APIs and object interfaces:
• OLE DB
• DB-Library
All cursors require temporary resources to cache data. These resources can be in the form of RAM, a
paging file (such as the virtual memory feature of Microsoft Windows), temporary files, or databases. You
should not create large cursors or use cursors unnecessarily because excessive use of these temporary
resources may degrade the performance of SQL Server and user applications.
Cursors are cached on the client or the server under the following circumstances:
• When you request a client cursor, SQL Server sends the entire result set across the network to
the client.
• When you request a server cursor, SQL Server uses its own resources to manage temporary
objects. Only rows that are selected within the cursor are returned to the client over the network.
API cursors may be cached on the client or the server. Transact-SQL cursors are always cached on the
server.
Transact-SQL Cursors
This lesson discusses the uses of Transact-SQL cursors, how Transact-SQL cursors work, the types and
characteristics of Transact-SQL cursors, and the syntax for using Transact-SQL cursors. It also describes
how to work with data from cursors and how to gather information about cursors.
You can use Transact-SQL cursors to build command strings that include parameters such as database
object names or data from user tables. You might use a cursor in a stored procedure to execute a
Transact-SQL statement (such as UPDATE STATISTICS) on many related objects in a database.
Using a cursor rather than a nested SELECT statement can sometimes improve the readability of a stored
procedure. If this is the case and if the performance of the two queries is comparable, using a Transact-
SQL cursor is appropriate.
You can pass local cursors between stored procedures when you want to generate a result set once and
perform unrelated manipulations on the result set. For example, you can use a cursor to offer users
multiple ways to view the data in a result set.
You also can use cursors to compensate for database and application limitations. An example of this is the
sp_helptext system stored procedure. This system stored procedure uses a Transact-SQL cursor to break
down the text of a database object into multiple rows because previous versions of SQL Server had a 255-
character limit on the char data type.
Follow the steps illustrated in Figure 9.2 when you work with Transact-SQL cursors.
1. Declare the cursor. This includes the SELECT statement that generates a result set and defines
the characteristics of the cursor, such as whether the rows in the cursor can be updated.
2. Execute the OPEN statement to generate the result set and populate the cursor.
3. Retrieve rows from the cursor result set. The operation to retrieve one row or one block of rows
4. Use values from or perform operations on the row at the current position of the cursor.
This example, used in Figure 9.2, creates a cursor based on a query that returns the names of the tables
in the Northwind database from the sysobjects system table. After the cursor is opened, the script
navigates through the cursor result set, executing the sp_spaceused system stored procedure for each
table. When the cursor processing is complete, the cursor is closed and deallocated.
USE Northwind
DECLARE tbl_space CURSOR
FOR
SELECT name FROM sysobjects
WHERE type = 'U' ORDER BY name
OPEN tbl_space
DECLARE @tbl_name sysname
FETCH NEXT FROM tbl_space INTO @tbl_name
WHILE (@@FETCH_STATUS = 0)
BEGIN
EXEC sp_spaceused @tbl_name
FETCH NEXT FROM tbl_space INTO @tbl_name
END
CLOSE tbl_space
DEALLOCATE tbl_space
Results
SQL Server supports four types of cursors: forward-only, static, dynamic, and keyset-driven. The different
types of cursors vary in their ability to detect changes to the result set and in the resources, such as
memory and space in the tempdb database, that they use.
If you have a cursor open and the data in the underlying table, on which the cursor is based, is changed
by other connections, different cursor types may or may not reflect those changes. The following table
shows how the cursor type that you choose determines whether your cursor result set reflects changes
that are made to the membership, order, or values of the data in the underlying table:
A forward-only cursor only supports fetching rows serially from the first to the last row of the cursor. It
does not retrieve rows from the database until the rows are fetched from the cursor. The effects of all
INSERT, UPDATE, and DELETE statements that any other connection makes before a row is fetched are
visible when the row is fetched.
Static
A static cursor fixes a result set when the cursor is opened and the result set is always read-only. Thus, it
is not possible to update the underlying tables of a static cursor through the cursor. The result set is
stored in tempdb when the static cursor is opened. Changes made by other connections after the cursor is
opened are never reflected by the cursor. Static cursors are also called insensitive or snapshot cursors.
Dynamic
Dynamic cursors are the opposite of static cursors. A dynamic cursor reflects all changes that are made to
the data values, order, and membership of rows in the result set when you scroll through a cursor result
set. The effects of all UPDATE, INSERT, and DELETE statements that any user makes are visible as rows
are fetched through the cursor. You can fetch rows randomly from anywhere in the cursor. Dynamic
cursors are also called sensitive cursors.
Keyset-driven
The membership and order of rows in a keyset-driven cursor are fixed when the cursor is opened. Keyset-
driven cursors are controlled by a unique set of identifiers (keys), known as a keyset. The keys are built
from a set of columns that uniquely identify a set of rows in the result set. The keyset is the set of key
values from all the rows that qualified for the SELECT statement when the cursor was opened, and the
membership and order of rows is never updated.
Inserts and updates that the user makes through the cursor are visible, as are changes that other
connections make to data values in nonkeyset columns. The keyset for a keyset-driven cursor is stored in
tempdb when it is the cursor opened.
Note: Cursors with fixed characteristics of membership, order, and values perform faster than cursors
with dynamic characteristics, although they may be slower to open initially.
Consider the following facts when you determine which type of cursor to use:
• In joins, keyset-driven and static cursors can be faster than dynamic cursors.
• Static and keyset-driven cursors increase the use of the tempdb database. Static server cursors
build the entire cursor in tempdb; keyset-driven cursors build the keyset in tempdb. For this
reason, if a large number of users each open a static or keyset-driven cursor, tempdb may run
out of space.
You can define the behavior and locking characteristics of cursors. Transact-SQL cursors and ODBC API
cursors support specifying scrollability and sensitivity. Not all API cursors support specifying behaviors. All
cursors support varying levels of locking.
Scrollability
Scrollability defines the fetch options that the cursor supports. Cursors can be scrollable or forward-only. A
scrollable cursor supports all fetch options; a forward-only cursor supports only the fetch next option.
Sensitivity
Sensitivity defines whether updates that are made against the base rows are visible through the cursor.
Sensitivity also defines whether you can make updates through the cursor. If you make an update to the
current row of a cursor, the actual update is made to the underlying table; this is called a positioned
update.
A sensitive cursor reflects data modifications that anyone makes. Positioned updates can be made through
the cursor, except when a read-only sensitive cursor is used. An insensitive cursor is read-only and does
not support updates.
Locking
Because SQL Server must acquire an exclusive lock before it can update a row, updates that are made
through a cursor can be blocked by other connections that hold a shared lock on a row. The transaction
isolation level of a cursor allows a programmer to determine the full locking behavior of a specific cursor
environment.
When any row in a cursor is updated, SQL Server locks it with an exclusive lock. Locks are held for varying
lengths of time, depending on the situation:
• If the update is performed within a transaction, the exclusive lock is held until the transaction is
terminated.
• If the update is performed outside of a transaction, the update is committed automatically and
You use five statements when you work with Transact-SQL cursors. The following table summarizes these
five statements:
Statement Description
CLOSE Releases the current result set and frees any cursor locks held on the rows on which
the cursor is positioned
The DECLARE CURSOR statement defines the characteristics of the cursor, the query that the cursor uses,
and any variables that the cursor uses.
There are two forms of the DECLARE CURSOR syntax. The first form of the DECLARE CURSOR statement
uses the ANSI SQL-92 syntax. The second form uses the extended Transact-SQL cursor syntax.
• The select_statement is a standard SELECT statement. Certain keywords, such as DISTINCT and
• LOCAL or GLOBAL specifies the scope of the cursor. The cursor can be local to a stored
procedure, trigger, or Transact-SQL script in which case it can be used only by that code and is
removed when the code is finished executing. If the cursor is global, then it may be used by
subsequent stored procedures, triggers or Transact-SQL scripts, executed on the connection, and
is not removed until the connection is closed or until the cursor is closed and deallocated. The
default is global.
• The current user must have SELECT permission on the table and the columns that are used in the
cursor.
OPEN
The OPEN statement opens and populates the cursor by executing the SELECT statement. If the cursor is
declared with the INSENSITIVE or STATIC options, OPEN creates a temporary table to hold the rows of the
cursor. If the cursor is declared with the KEYSET option, the OPEN statement creates a temporary table to
hold the keyset.
Syntax
FETCH
Syntax
FETCH
[ [NEXT | PRIOR | FIRST | LAST
| ABSOLUTE {n | @nvar}
| RELATIVE {n | @nvar}]
FROM ]
{{[GLOBAL] cursor_name} | @cursor_variable_name}
[INTO @variable_name[,...n]]
Consider the following facts when you use the FETCH statement:
• The type of FETCH option that is supported within a cursor depends on the type of declared
cursor.
• The @@FETCH_STATUS function is updated at every execution of the FETCH statement. Use
@@FETCH_STATUS before you attempt to operate on the data to determine whether a fetch was
a success or a failure.
CLOSE
The CLOSE statement releases the current result set. It releases any locks that are held on the row on
which the cursor is positioned but leaves the data structures accessible for reopening. Modifications and
fetches are not allowed until the cursor is reopened. Closed cursors may be reopened.
Syntax
The DEALLOCATE statement removes the association between a cursor and a cursor name or cursor
variable that references the cursor. If the cursor name or cursor variable is the last reference to the
cursor, the data structures used by the cursor are released.
Syntax
In addition to navigating through data in cursors, you can modify data through a cursor or share the data.
You can modify data through local and global Transact-SQL cursors. You must declare the cursor as
updateable and you must not declare it as read-only. In an updateable cursor, you can use UPDATE or
DELETE statements with the WHERE CURRENT OF cursor_name clause to modify the current row. In the
following example a cursor called ShipCurs has been created on the Shippers table in the Northwind
database and the CompanyName column of the shipper in the current row is updated:
USE Northwind
UPDATE Shippers
SET CompanyName = N'Speedy Express, Inc.'
WHERE CURRENT OF ShipCurs
Passing Local Cursors between Stored Procedures
Stored procedures that declare and open local cursors can pass the cursors out for use by the calling
stored procedure, trigger, or batch. Passing a cursor is done by declaring a variable with the cursor
varying data type as an output parameter. Variables that are used to pass cursors are called cursor
variables.
• Cursor variables can only be used as output parameters; they cannot be used as input
parameters.
• Local variables can also be declared with the cursor data type to hold a reference to a local
cursor.
Pass local cursors between stored procedures when you want to generate a result set once and perform
unrelated manipulations on the result set. For example, you could pass a local cursor to allow users to
choose from a list of manipulations to perform on the cursor result set.
cursors.
When a cursor has been declared, you can use the following system stored procedures to determine the
characteristics of the cursor. These system stored procedures do not return a standard result set. They all
report their output as an output cursor, so you have to declare a cursor variable and then fetch the rows
from the cursor in order to use them.
sp_cursor_list Returns a list of cursors that are currently opened by the connection and
the attributes of the cursors
sp_describe_cursor_columns Describes the attributes of the columns in the cursor result set
Many functions return information about the status of a cursor. The following table describes three of the
more commonly used functions:
@@FETCH_STATUS Returns the status of the last cursor FETCH statement that was issued against any
cursor that is currently opened on the connection
@@CURSOR_ROWS Returns the number of qualifying rows in the last cursor that is opened on the
connection
CURSOR_STATUS After calling a stored procedure that returns a cursor as an output parameter, the
function allows you to determine the status of the cursor that was returned.
To execute a script that uses a cursor to find the median unitprice in the Products table
In this exercise, you will execute a script that creates a cursor to find the median unit price of all products
in the Northwind database. If the cursor holds an odd number of products, the median is the middle row.
If the cursor holds an even number of products, the median is the average of the two middle rows. A
scrollable cursor is used so that the middle row(s) can be retrieved directly using an absolute fetch.
2. Open the c:\sqlimpl\exercise\ch09\cursor.sql script file. Review and then execute the script.
Lesson Summary
Cursors make it possible to perform row-based operations on SQL result sets. Transact-SQL provides
syntax for creating cursors on SQL Server; you can also use API-based client and server cursors with SQL
Server 7.0. Different types of cursors are available. The different types vary in their scrollability,
sensitivity, and locking characteristics.
When you declare and open a Transact-SQL cursor, a temporary structure is created in the tempdb
database to cache some or all the cursor data. The FETCH statement is then used to position the row
pointer in the cursor and retrieve individual rows.
Top Of Page
You can modify data with subqueries by using the INSERT…SELECT statement and delete or update data
in a table based on data from another table by using the DELETE or UPDATE statements.
• Insert data into an existing table from another table using the INSERT…SELECT statement.
• Use subqueries and the FROM clause to use other tables when specifying criteria for the DELETE
Using the INSERT…SELECT statement is more efficient than writing multiple, single-row INSERT
statements. When you use the INSERT…SELECT statement, consider the following facts and guidelines:
• All rows that satisfy the SELECT statement are inserted into the outermost table of the query.
• The table that receives the new rows must already exist in the database.
• The columns of the table that receives the new rows must have data types that are compatible
• Determine whether a default value exists or whether a null value is allowed for any columns that
are omitted from the SELECT statement. If there is no default and null values are not allowed,
you must provide values for these columns.
Syntax
This example adds new customers to the Customers table. Employees of Northwind Traders are eligible to
buy company products. This query contains an INSERT statement with a SELECT clause that adds
employee information to the Customers table.
The value for the CustomerID column will be generated from the first three letters of the employee's first
name, concatenated with the first two letters of the last name. The employee's last name is used in the
CompanyName column, and the first name is used in the ContactName column.
USE Northwind
INSERT Customers
SELECT UPPER(SUBSTRING(FirstName, 1, 3) + SUBSTRING(LastName, 1, 2)),
LastName, FirstName, Title, Address, City,
Region, PostalCode, Country, HomePhone, NULL
FROM Employees
To insert values from one table into an existing table
In this exercise, you will insert rows into the adult table of the library database for all juvenile members
over age 18 by using the SELECT clause of the INSERT statement. You will delete the rows from the
juvenile table in another exercise. The c:\sqlimpl\exercise\ch09\juv2adlt.sql script file contains a
completed script for this exercise.
1. Write a SELECT statement that returns the member_no column from the juvenile table and the
street, city, state, zip, and phone_no columns from the adult table. Also include in the select list
today's date plus one year by using the following expression:
2. (DATEADD(YY, 1, GETDATE())
This last column will be used later, to provide a value for the adult.expr_date column. This
SELECT statement joins the juvenile table with the adult table; the join condition is
juvenile.adult_member_no = adult.member_no.
3. Write an INSERT statement that incorporates the SELECT statement that you created in step 1 in
order to add rows to the adult table. Include a WHERE clause to limit the rows that are added to
those members in the juvenile table who are over age 18 by using the following expression:
4. (DATEADD(YY, 18, ju.birth_date) < GETDATE())
5. USE library
6. INSERT adult
7. SELECT
8. ju.member_no,
9. ad.street,
10. ad.city,
11. ad.state,
12. ad.zip,
13. ad.phone_no,
14. DATEADD(YY, 1, GETDATE())
15. FROM juvenile ju JOIN adult ad
16. ON ju.adult_member_no = ad.member_no
17. WHERE (DATEADD(YY, 18, ju.birth_date) < GETDATE())
18. Execute the query.
19. Write a query to verify that juvenile members were added to the adult table. The
c:\sqlimpl\exercise\ch09\juv_test.sql script file is an example of this query.
Use the DELETE statement with joins or subqueries to remove rows from a table based on data stored in
other tables. This is more efficient than writing multiple, single-row DELETE statements.
In a DELETE statement, the WHERE clause references values in the table itself and is used to decide which
rows to delete. If you use a FROM clause, you can reference other tables to make this decision. When you
use the DELETE statement with a FROM clause, consider the following facts:
• You can use the optional FROM keyword before the table name from which the rows are deleted.
• The FROM clause may introduce a join and acts as the restricting criteria for the DELETE
statement.
Partial Syntax
This example uses a join operation with the DELETE statement to remove customers who reside in the
United States (USA) from the Orders table of the Northwind database. (You cannot actually execute this
example, as it will violate referential integrity constraints.)
USE Northwind
DELETE FROM Orders
FROM Customers c INNER JOIN Orders o
ON c.CustomerID = o.CustomerID
WHERE Country = 'USA'
Specifying Conditions in the WHERE Clause
You also can use subqueries to determine which rows to delete from a table based on rows of another
table. You can specify the conditions in the WHERE clause rather than using an additional FROM clause.
Use a nested or correlated subquery in the WHERE clause to determine which rows to delete.
Example 2
This example removes the same customers as Example 1 and shows that you can convert a join operation
to a nested subquery. (You cannot actually execute this example, as it will violate referential integrity
constraints.)
USE Northwind
DELETE FROM Orders
WHERE CustomerID IN
(SELECT CustomerID
FROM Customers
WHERE Country = 'USA')
To delete rows from a table based on criteria in other tables
In this exercise, you will create a query that deletes rows from the juvenile table that have matching rows
in the adult table of the library database. After juvenile members are converted to adult members, those
members must be deleted from the juvenile table. The c:\sqlimpl\exercise\ch09\del_juv1.sql script file
contains a completed script for this exercise.
1. Write a SELECT statement that joins the adult and juvenile tables so that juvenile.member_no =
adult.member_no.
2. Write a DELETE statement that uses the SELECT statement that you created in step 1 with an
additional FROM clause to delete these rows from the juvenile table.
3. USE library
4. DELETE FROM juvenile
5. FROM juvenile INNER JOIN adult
6. ON juvenile.member_no = adult.member_no
7. Execute the query.
8. Write a SELECT statement to verify that the juveniles that were added to the adult table have
been removed from the juvenile table. The c:\sqlimpl\exercise\ch09\del_test.sql script file is an
example of this query.
Use the UPDATE statement with a FROM clause or a subquery to modify a table based on values from
other tables.
When you use joins and subqueries with the UPDATE statement, consider the following facts and
guidelines:
• SQL Server never updates the same row twice in a single UPDATE statement. This built-in
• Use the SET keyword to introduce the list of columns or variable names to be updated. Any
column prefix that is specified in the SET clause must match the table or view name that is
specified after the UPDATE keyword.
• Tables introduced by subqueries or the FROM clause cannot be updated in the UPDATE
statement.
Partial Syntax
Use the FROM clause to specify tables and joins that are used to provide the criteria for the UPDATE
statement.
Example 1
This example uses a join to update the number of sales-to-date of each product in the Products table.
Assume that a ToDateSales column has been added to the Products table. This query does not generate
the expected results. Each row in the Products table is only updated once, so only the first order of each
product in the Order Details table is added to the ToDateSales column.
USE Northwind
UPDATE Products
SET ToDateSales = ToDateSales + od.Quantity
FROM Products INNER JOIN [Order Details] od
ON Products.ProductID = od.ProductID
Using Subqueries
When you use subqueries to update rows, consider the following facts and
guidelines:
• SQL Server executes the subquery once for each row in the table to be updated. Therefore, the
• If the subquery does not return a single value, the results are unspecified.
• Consider using aggregate functions with subqueries because SQL Server never updates the same
Example 2
This example solves the problem of Example 1, where only the first order of each product in the Products
table was updated. Many orders for each product may exist. Because SQL Server never updates the same
row twice, you can use an aggregate function with a correlated subquery to update the total number of
sales-to-date of each product.
USE Northwind
UPDATE Products
SET ToDateSales = (SELECT SUM(Quantity)
FROM [Order Details] od
WHERE Products.ProductID = od.ProductID)
To update rows in a table based on criteria in other tables
In this exercise, you will create a query that updates rows in the copy table to set the on_loan column to Y
for those books that are represented by a copy_no/isbn that are on loan. Because the loan table contains
a list of all copy_no/isbn pairs that are on loan, it is referenced to update the copy table.
1. Review and execute the c:\sqlimpl\exercise\ch09\updsetup.sql script, which adds a new title and
copy of a book to the library database. At this point, the copy is not on loan.
2. Review and execute the c:\sqlimpl\exercise\ch09\loan_out.sql script, which places this copy on
loan. Notice that this script updates the loan table but does not update the copy table.
3. Write and execute a SELECT statement that joins the copy and loan tables and returns all
matching rows. Use a WHERE clause to limit the rows to those for which the copy.on_loan is not
Y.
Be aware that both tables have a compound primary key, so you must reference both columns in
the JOIN clause. The c:\sqlimpl\exercise\ch09\upd_cpy1.sql script file contains a completed script
for this step.
USE library
SELECT *
FROM copy JOIN loan
ON copy.isbn = loan.isbn AND copy.copy_no = loan.copy_no
WHERE copy.on_loan <> 'Y'
4. Write and execute an UPDATE statement that uses the FROM and JOIN clauses of the SELECT
statement that you created in step 3 to update the copy.on_loan column to Y for all copies that
are on loan but do not have a copy.on_loan value of Y.
The c:\sqlimpl\exercise\ch09\upd_cpy2.sql script file contains a completed script for this step.
USE library
UPDATE copy
SET on_loan = 'Y'
FROM copy JOIN loan
ON copy.isbn = loan.isbn AND copy.copy_no = loan.copy_no
WHERE copy.on_loan <> 'Y'
5. Execute the query.
6. Verify that the copy.on_loan column was changed from N to Y for copy_no = 1 and isbn = 10005.
The c:\sqlimpl\exercise\ch09\upd_test.sql script file is an example of this query.
Lesson Summary
The INSERT, UPDATE, and DELETE statements all support advanced options for modifying data in a table
based on the data from other tables.
The INSERT…SELECT statement allows you to insert data into an existing table from tables in the same or
other databases.
Subqueries can be used in the SET clause of the UPDATE statement to calculate column values.
Subqueries can also be used in the WHERE clause of the UPDATE and DELETE statements to specify the
criteria for which rows should be updated or deleted.
The FROM clause of the UPDATE and DELETE statements is used to join other tables to the table being
modified. The joined table(s) are then available for specifying the criteria for which rows should be
updated or deleted.
Top Of Page
Review
The following questions are intended to reinforce key information presented in this chapter. If you are
unable to answer a question, review the appropriate lesson and then try the question again. Answers to
the questions can be found in Appendix A, "Questions and Answers," located at the back of the book.
The Duluth Mutual Life health care organization has a database that tracks information about doctors and
their patients. The database includes the following tables:
Doctors table
fname Char(20)
lname Char(25)
specialty Char(25)
phone Char(10)
Patients table
fname Char(20)
lname Char(25)
insurance_company Char(25)
phone Char(10)
Cases table
Diagnosis Varchar(150)
Based on this table structure, answer the following questions:
1. How, with a single query, can you produce a list of all cases that were admitted on the first
chronological date in the database?
2. You want to know the total number of hospital admissions, listed by patient name. How can you
determine this? What are the advantages or disadvantages of your method?
You have numerous, user-defined databases that you administer. On a periodic basis, you want
to be able to determine the amount of space that each table uses in a specified database. You
plan to execute the sp_spaceused stored procedure against every table in the specified database.
6. Does your answer depend on the number of tables or size of the database? For example, if you
increase the number of tables or the size of the database, will your answer change?
The above article is courtesy of Microsoft Press. Copyright 1999, Microsoft Corporation.
We at Microsoft Corporation hope that the information in this work is valuable to you. Your use of the
information contained in this work, however, is at your sole risk. All information in this work is provided
"as -is", without any warranty, whether express or implied, of its accuracy, completeness, fitness for a
particular purpose, title or non-infringement, and none of the third-party products or information
mentioned in the work are authored, recommended, supported or guaranteed by Microsoft Corporation.
Microsoft Corporation shall not be liable for any damages you may sustain by using this information,
whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility
of such damages. All prices for products mentioned in this document are subject to change without notice.