Tải bản đầy đủ (.pdf) (71 trang)

Microsoft SQL Server 2000 Programming by Example phần 7 pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (525.27 KB, 71 trang )

Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
411

USE Northwind
GO

DROP FUNCTION dbo.Today
Caution
Before dropping a user-defined function, as with any other database object, check its
dependencies.
You cannot drop a user-defined function if it is used in a constraint definition. If you drop a user-
defined function and it is used in other functions, views, triggers, or stored procedures, those
functions will produce an error on next execution.
Preventing the Alteration of Dependent Objects:The SCHEMABINDING
Option
You can prevent changes on the dependent objects of a user-defined function by using the SCHEMABINDING
option. Using this option, you cannot modify the definition of the dependent objects using any of the ALTER
statements, and you cannot drop dependent objects using any of the DROP statements. This link disappears
when the function is dropped or when you alter the function definition without using the SCHEMABINDING
option.
To use this option, you must ensure that the following conditions are met:
• Every function and view referenced in the function must be defined as SCHEMABINDING as well.
• Every object referenced in the function must be referenced using two-part names
(owner.objectname).
• Every object referenced in the function belongs to the same database as the function.
• The user who creates the function (not necessarily the owner) has REFERENCES permissions on
every object referenced inside the function. It is recommended that only members of the db_owner
role execute the CREATE FUNCTION statement.
Listing 10.22 shows how to use the SCHEMABINDING option and the effect when you try to modify a
dependent object. The process is as follows:
1. You create the NewCustomers table with data coming from the Customers table.


2. You create the GetCustomers table-valued function, reading the CustomerID and CompanyName
fields from the NewCustomers table.
3. You try to alter the NewCustomers table, dropping the CompanyName column, and it is successful
because the GetCustomers function was created without the SCHEMABINDING option.
4. Trying to use the GetCustomers function, you get error message 207 because the column
CompanyName does not exist.
5. You start all over, with the creation of the NewCustomers table.
6. Create the GetCustomers function with the SCHEMABINDING option, and use the NewCustomers
table without specifying the owner, and you get error 4512, because to use SCHEMABINDING you
must use two part names.
7. Create the GetCustomers function with the SCHEMABINDING option and use two-part names this
time. The operation succeeds.
8. Try to alter the NewCustomers table, dropping the CompanyName column. You get errors 5074 and
4922 because the function is created with the SCHEMABINDING option.
Listing 10.22 Effect of SCHEMABINDING on the Dependent Objects
Microsoft SQL Server 2000 Programming by Example

412


USE Northwind
GO

IF OBJECT_ID('GetCustomers') IS NOT NULL
DROP FUNCTION GetCustomers
GO

IF OBJECT_ID('NewCustomers') IS NOT NULL
DROP TABLE NewCustomers
GO


SELECT *
INTO NewCustomers
FROM Customers
GO

CREATE FUNCTION dbo.GetCustomers()
RETURNS @List TABLE
(CustomerID nchar(5),
CompanyName nvarchar(40))
AS
BEGIN

INSERT @List
SELECT CustomerID, CompanyName
FROM NewCustomers

RETURN
END
GO

ALTER TABLE NewCustomers
DROP COLUMN CompanyName

PRINT CHAR(10)
+ 'ALTER TABLE statement successful without SCHEMABINDING'
+ CHAR(10)
GO

SELECT *

FROM GetCustomers()
GO

PRINT CHAR(10)
+ 'Execution of the GetCustomers table was unsuccessful'
+ CHAR(10)
+ 'because it references a non-existing field'
+ CHAR(10)
GO

IF OBJECT_ID('GetCustomers') IS NOT NULL
DROP FUNCTION GetCustomers
GO
Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
413

IF OBJECT_ID('NewCustomers') IS NOT NULL
DROP TABLE NewCustomers
GO

SELECT *
INTO NewCustomers
FROM Customers
GO

CREATE FUNCTION dbo.GetCustomers()
RETURNS @List TABLE
(CustomerID nchar(5),
CompanyName nvarchar(40))
WITH SCHEMABINDING

AS
BEGIN

INSERT @List
SELECT CustomerID, CompanyName
FROM NewCustomers

RETURN
END
GO

PRINT CHAR(10)
+ 'CREATE FUNCTION failed with SCHEMABINDING'
+ CHAR(10)
+ 'because it did not use two part names'
+ CHAR(10)
GO

CREATE FUNCTION dbo.GetCustomers()
RETURNS @List TABLE
(CustomerID nchar(5),
CompanyName nvarchar(40))
WITH SCHEMABINDING
AS
BEGIN

INSERT @List
SELECT CustomerID, CompanyName
FROM dbo.NewCustomers
RETURN

END
GO

PRINT CHAR(10)
+ 'CREATE FUNCTION was successful with SCHEMABINDING'
+ CHAR(10)
+ 'because it did use two part names'
+ CHAR(10)
GO

ALTER TABLE NewCustomers
DROP COLUMN CompanyName
GO

PRINT CHAR(10)
+ 'ALTER TABLE statement failed with SCHEMABINDING'
Microsoft SQL Server 2000 Programming by Example

414
+ CHAR(10)
GO


(91 row(s) affected)


ALTER TABLE statement successful without SCHEMABINDING

Server: Msg 207, Level 16, State 3, Procedure GetCustomers, Line 12
Invalid column name 'CompanyName'.


Execution of the GetCustomers table was unsuccessful
because it references a non-existing field


(91 row(s) affected)

Server: Msg 4512, Level 16, State 3, Procedure GetCustomers, Line 14
Cannot schema bind function 'dbo.GetCustomers'because name NewCustomers'is
invalid for
schema binding. Names must be in two-part format and an object cannot reference
itself.

CREATE FUNCTION failed with SCHEMABINDING
because it did not use two part names


CREATE FUNCTION was successful with SCHEMABINDING
because it did use two part names
Server: Msg 5074, Level 16, State 3, Line 2
The object 'GetCustomers'is dependent on column 'CompanyName'.
Server: Msg 4922, Level 16, State 1, Line 2
ALTER TABLE DROP COLUMN CompanyName failed because one or more objects access
this column.

ALTER TABLE statement failed with SCHEMABINDING
Deterministic and Nondeterministic Functions
Some functions always return the same value when called with the same set of arguments. These functions
are called deterministic. This is important if you want to create a clustered index on a view or any index on a
computed column, because you can create these indexes only if they use deterministic functions.

Most of the built-in functions are deterministic, such as
ABS DATEDIFF PARSENAME
ACOS DAY POWER
ASIN DEGREES RADIANS
ATAN EXP ROUND
ATN2 FLOOR SIGN
CEILING ISNULL SIN
COALESCE ISNUMERIC SQUARE
Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
415
COS LOG SQRT
COT LOG10 TAN
DATALENGTH MONTH YEAR
DATEADD NULLIF

Some built-in functions are deterministic or nondeterministic, depending on the way you use them:
• CAST is deterministic for every type of value except for conversion from datetime,
smalldatetime, and sql_variant containing a date value, because the final results depend on
regional settings.
• CONVERT is deterministic in the same cases as CAST and nondeterministic in the same cases as
CAST, except if you specify a style when converting datetime and smalldatetime data, the result
is always predictable and the function is deterministic in that case.
• CHECKSUM is deterministic if you specify the list of columns or an expression; it is nondeterministic if
you specify CHECKSUM(*).
• ISDATE is nondeterministic unless it is used with CONVERT and with a predictable style different from
0, 100, 9, or 109.
• RAND is deterministic if a seed value is specified; it is nondeterministic without a seed value.
Most of the other built-in functions are nondeterministic. For a full list, you can search for the "Deterministic
and Nondeterministic Functions" topic in Books Online.
A user-defined function is deterministic only if

• Every function— built-in or user-defined— referenced in the function is deterministic.
• The function is defined with the SCHEMABINDING option.
• The function does not references objects not defined inside the function itself, such as tables, views,
extended stored procedures.
Note
Creating a nondeterministic user-defined function is fine, as long as you are aware of their
limitations. Books Online incorrectly says that you cannot use built-in nondeterministic functions
inside a user-defined function. The only functions you cannot use inside a user-defined function are
contained in the list following this note.

Built-in functions that use the current time are not valid inside a user-defined function:
CURRENT_TIMESTAMP GETDATE
GetUTCDate IDENTITY
NEWID TEXTPTR
@@DBTS @@MAX_CONNECTIONS
Other functions that are not valid inside user-defined functions are the System Statistical functions:
@@CONNECTIONS @@PACK_RECEIVED
@@CPU_BUSY @@PACK_SENT
fn_virtualfilestats @@TIMETICKS
@@IDLE @@TOTAL_ERRORS
@@IO_BUSY @@TOTAL_READ
Microsoft SQL Server 2000 Programming by Example

416
@@PACKET_ERRORS @@TOTAL_WRITE
Altering User-Defined Functions Definition
To modify the definition of a user-defined function, you can use the ALTER FUNCTION statement in exactly
the same way you use the CREATE FUNCTION statement. In this case, the new definition replaces the old
definition of the user-defined function with the same name.
Listing 10.23 shows an example of how to use the ALTER FUNCTION statement to modify a preexisting

user-defined function and encrypt the definition with the WITH ENCRYPTION option.
Listing 10.23 Use ALTER FUNCTION to Modify a User-Defined Function


USE Northwind
GO

Returns the maximum ProductID from Products

ALTER FUNCTION dbo.MaxProductID
()
RETURNS int
WITH ENCRYPTION
AS
BEGIN

RETURN (
SELECT MAX(ProductID)
FROM dbo.Products
)

END
GOa user-defined function, you can use the ALTER FUNCTION statement
Caution
Not using the SCHEMABINDING option when you execute the ALTER FUNCTION statement
unbinds the dependent objects from the function.

Caution
Before encrypting a user-defined function definition, make sure you have a copy in a safe place,
because it will be impossible to decrypt it.

Security Implications of Using User-Defined Functions
Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
417
You can grant or deny the permissions to use user-defined functions depending on the type of function:
• For scalar user-defined functions, you can grant or deny permissions on EXECUTE and REFERENCES.
• For inline user-defined functions, you can grant or deny permissions on SELECT, UPDATE, INSERT,
DELETE, or REFERENCES.
• For multistatement table-values user-defined functions, you can grant or deny permissions to SELECT
and REFERENCES.
As in stored procedures and views, if every object referenced in a user-defined function belongs to the same
owner as the user-defined function, and a user tries to use the function, permissions will be checked only on
the function, not on every object referenced in the function.
Applying User-Defined Functions
You convert commonly used formulas into scalar user-defined functions. In this case, the function's compiled
query plan remains in memory, as does any built-in function.
You can call user-defined functions from inside other user-defined functions, but only up to 32 levels, and this
limit applies to the total of stored procedures, triggers, and scalar or table-valued user-defined functions you
use.
Note
Use the @@NESTLEVEL system function to know how many nested levels you are using.

A good approach would be to create user-defined functions in a short number of layers, so the limit for nesting
levels will never be surpassed. This contributes to the clarity of your database design as well.
Be aware that modifying underlying objects could affect the result of a user-defining function, unless you
create the function with the SCHEMABINDING option.
This is still a new feature for Transact-SQL programmers, but client- application programmers will find user-
defined functions very close to their normal programming methods.
Converting Stored Procedures into User-Defined Functions
If the only reason for a stored procedure is to supply an output parameter, you can create a scalar user-
defined function instead. In this way, you can use this function in a more natural way than a stored procedure.

Listing 10.24 shows an example of converting the fn_FV function into the sp_FV stored procedure and how
to call them.
Listing 10.24 Comparing a Stored Procedure with a Scalar User-Defined Function

Microsoft SQL Server 2000 Programming by Example

418

USE Northwind
GO

sp_fv with the same functionality
as the fn_fv function

CREATE PROCEDURE sp_fv
@rate float, @nper int, @pmt money,
@pv money = 0.0, @type bit = 0,
@FV money output
AS

IF @rate = 0
SET @fv = @pv + @pmt * @nper
ELSE
SET @fv = @pv * POWER(1 + @rate, @nper) +
@pmt * (((POWER(1 + @rate, @nper + @type) - 1) / @rate) - @type)

SET @fv = -@fv
GO

Call the sp_fv stored procedure


DECLARE @fv money

EXECUTE sp_fv 0.10, 24, 1000, 10000, 0, @fv OUTPUT

SELECT @fv 'From sp_fv'
GO

Call the sp_fv stored procedure

SELECT dbo.fn_fv(0.10, 24, 1000, 10000, 0) as 'From fn_fv'
GO


From sp_fv

-186994.6535

From fn_fv

-186994.6535
If a stored procedure returns a single read-only result set, you can convert it into a table-valued function with a
similar code, and you can use the function in the FROM clause of any DML statement, providing a better
programming flexibility. Listing 10.25 shows an example of a stored procedure with the same functionality as
the tv_TopTenOrders and how to call them.
If you have a stored procedure that provides read/write access to a table through a client library, you can
convert this procedure into an inline user-defined function.
Listing 10.25 Comparing Stored Procedures and Table-Valued User-Defined Functions
Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
419



USE Northwind
GO

CREATE PROCEDURE sp_TopTenOrders
AS

DECLARE @list TABLE
(OrderID int,
CustomerID nchar(5),
OrderDate datetime,
TotalValue money)

INSERT @List
SELECT O.OrderID, CustomerID, OrderDate, TotalValue
FROM Orders O
JOIN (
SELECT OrderID, SUM(dbo.TotalPrice(Quantity, UnitPrice, Discount))
AS TotalValue
FROM [Order Details]
GROUP BY OrderID) AS OD
ON O.OrderID = OD.OrderID

SELECT TOP 10 WITH TIES
OrderID, CustomerID, OrderDate, TotalValue
FROM @List
ORDER BY TotalValue DESC
GO


EXECUTE sp_TopTenOrders
GO

SELECT *
FROM tv_TopTenOrders()
GO


OrderID CustomerID OrderDate TotalValue

10865 QUICK 1998-02-02 00:00:00.000 16387.5000
10981 HANAR 1998-03-27 00:00:00.000 15810.0000
11030 SAVEA 1998-04-17 00:00:00.000 12615.0500
10889 RATTC 1998-02-16 00:00:00.000 11380.0000
10417 SIMOB 1997-01-16 00:00:00.000 11188.4000
10817 KOENE 1998-01-06 00:00:00.000 10952.8450
10897 HUNGO 1998-02-19 00:00:00.000 10835.2400
Microsoft SQL Server 2000 Programming by Example

420
10479 RATTC 1997-03-19 00:00:00.000 10495.6000
10540 QUICK 1997-05-19 00:00:00.000 10191.7000
10691 QUICK 1997-10-03 00:00:00.000 10164.8000
OrderID CustomerID OrderDate TotalValue

10865 QUICK 1998-02-02 00:00:00.000 16387.5000
10981 HANAR 1998-03-27 00:00:00.000 15810.0000
11030 SAVEA 1998-04-17 00:00:00.000 12615.0500
10889 RATTC 1998-02-16 00:00:00.000 11380.0000
10417 SIMOB 1997-01-16 00:00:00.000 11188.4000

10817 KOENE 1998-01-06 00:00:00.000 10952.8450
10897 HUNGO 1998-02-19 00:00:00.000 10835.2400
10479 RATTC 1997-03-19 00:00:00.000 10495.6000
10540 QUICK 1997-05-19 00:00:00.000 10191.7000
10691 QUICK 1997-10-03 00:00:00.000 10164.8000
Converting Views into User-Defined Functions
You can convert views into inline user-defined functions very easily, but in this case, the only benefit you will
get is the possibility of having parameters. However, if you use a view to read data only, you will benefit from
converting this view into a table-valued function because it will be optimized and compiled on the first
execution, providing performance gains over a view.
Listing 10.26 shows the fv_TopTenOrders converted into a view, and how you call the view and the user-
defined function. The output is the same as the one for Listing 10.25.
Listing 10.26 Comparing Views and Table-Valued User-Defined Functions


USE Northwind
GO

CREATE VIEW vw_TopTenOrders
AS

SELECT TOP 10 WITH TIES
O.OrderID, CustomerID, OrderDate, TotalValue
FROM Orders O
JOIN (
SELECT OrderID, SUM(dbo.TotalPrice(Quantity, UnitPrice, Discount))
AS TotalValue
FROM [Order Details]
GROUP BY OrderID) AS OD
ON O.OrderID = OD.OrderID

ORDER BY TotalValue DESC

GO

SELECT *
FROM tv_TopTenOrders()
GO

SELECT *
Chapter 10. Enhancing Business Logic: User-Defined Functions (UDF)
421
FROM vw_TopTenOrders
GO
Using User-Defined Functions in Constraints
You can use a scalar user-defined function anywhere an expression is allowed, and that includes
• DEFAULT constraints
• CHECK constraints
• DEFAULT objects
• RULE objects
• A PRIMARY KEY constraint defined in a computed column using a user-defined function, as long as
the returned values are unique
• A UNIQUE constraint defined in a computed column with a user-defined function, as long as the
returned values are unique
Therefore, it is possible to access values from other tables from inside a constraint, as long as the constraint
uses a user-defined function that searches for external data to produce its result.
The only place where you can use a table-valued user-defined function or an inline user-defined function is as
a subquery in a CHECK constraint but, unfortunately, CHECK constraints do not support subqueries.
What's Next?
This chapter covered the creation and use of user-defined functions— an exciting new feature that provides
extra programmability to the Transact-SQL language. The more you practice with user-defined functions, the

more you will wonder how you could have survived without them before SQL Server 2000 offered this feature.
Chapter 11 teaches you how to write complex queries, and in some cases, using user-defined functions that
could solve similar situations with less complexity.
In Chapter 12, you learn how to work with result sets row by row, using cursors. You can use cursors inside
user-defined functions to achieve complex operations that are impossible using rowset-oriented programming.

Chapter 11. Using Complex Queries and Statements
423
Chapter 11. Using Complex Queries and Statements
In previous chapters, you learned how to execute queries to retrieve and modify data in SQL Server. You also
learned how to create and use database objects, such as tables, views, stored procedures, user-defined
functions, and triggers. Transact-SQL provides extended structures that can simplify the process of writing
queries to solve complex requests.
This chapter teaches you the following:
• How to create subqueries, which are queries inside other queries, to solve complex problems
• How to use the EXISTS keyword to test for existence of rows in a subquery
• How to use the IN operator to check for values returned from a subquery
• How to use derived tables, which are subqueries that can be used as virtual tables in the FROM clause,
to simplify complex queries
• How to use the CASE function to retrieve conditional values
• How to produce summary reports using the COMPUTE clause
• How to produce summary result sets using the CUBE and ROLLUP operators
• How to use optimizer hints to modify the way the query will be processed
Subqueries
A subquery is just a query contained inside another query. You can call the subquery an inner query
contained within an outer query, which in turn can be a standard query or another subquery.
If you think about standard queries, you can define three kinds of queries, according to the type of result they
provide:
• Scalar— Queries that produce a single value (one single row with only one column)
• List— Queries that produce a list of values (one or more rows with a single column only)

• Array— Queries that return a result set (one or more rows with one or more columns)
List queries can be considered single-column array queries. Scalar queries can be used as single-column,
single-row array queries as well.
Listing 11.1 shows different scalar queries that return a single value. This value can be a single constant,
the result of a system function, or the result of a standard query, as long as the query returns a single column
and a single row.
Listing 11.1 Scalar Queries Return a Single Value


USE Northwind
GO

SET NOCOUNT ON
GO

Select a single constant

SELECT 1

Select a scalar system niladic function
Microsoft SQL Server 2000 Programming by Example

424

SELECT SYSTEM_USER

Select a scalar system function

SELECT db_ID('Northwind')


Select the result of a User-Defined Function
Note this function does not exist

SELECT fn_getProductNameFromID(123)

Select the result of an aggregate function applied to a number of rows

SELECT COUNT(*) as NRows
FROM Northwind.dbo.Products

Select a single column from a single row in a table

SELECT ProductName
FROM Northwind.dbo.Products
WHERE ProductID = 5




1.00



SQLBYEXAMPLE\GuestUser



6.00

NRows


77.00

ProductName

Chef Anton's Gumbo Mix
In Listing 11.2, you can see three examples of queries that provide a list of values. In the first example, you
select values from a single column. In the second example, you aggregate data, grouping the results by
another field. In the third example, you create a list query by combining several scalar queries using the
UNION operator.
Listing 11.2 List Queries Return a List of Values
Chapter 11. Using Complex Queries and Statements
425


USE Northwind
GO

SET NOCOUNT ON
GO

Selecting a single column from a table

SELECT CategoryName
FROM Northwind.dbo.Categories

Selecting aggregate values from a single column from a table using GROUP BY

SELECT COUNT(*) AS "Products per Supplier"
FROM Northwind.dbo.products

GROUP BY SupplierID

Selecting different constant values using the UNION operator

SELECT 1 AS "Numbers"
UNION
SELECT 2
UNION
SELECT 3


CategoryName

Beverages
Condiments
Confections
Dairy Products
Grains/Cereals
Liquors
Liquors
Meat/Poultry
Produce
Seafood

Products per Supplier

3.00
4.00
3.00
3.00

Microsoft SQL Server 2000 Programming by Example

426
2.00
3.00
5.00
4.00
2.00
1.00
3.00
5.00
1.00
3.00
3.00
3.00
3.00
2.00
2.00
3.00
2.00
2.00
3.00
3.00
2.00
2.00
1.00
2.00
2.00

Numbers


1.00
2.00
3.00
Listing 11.3 shows several array query examples that return result sets with multiple columns. The first
example selects two columns from a table. The second example selects several constants. The third example
selects the results of several scalar system functions. The last example combines the results of two array
queries to produce a single array query, using the UNION operator.
Listing 11.3 Array Queries Return a Complete Result Set


USE Northwind
GO

SET NOCOUNT ON
GO

Selecting multiple columns from a table

SELECT ProductName, UnitPrice
FROM Northwind.dbo.Products
WHERE CategoryID = 1

Selecting multiple constants

Chapter 11. Using Complex Queries and Statements
427
SELECT 1 AS 'Lower',
2 AS 'Higher',
'Peter'AS 'Responsible'


Selecting values from system functions
SELECT CURRENT_TIMESTAMP AS 'Now',
CURRENT_USER AS 'Database User',
SYSTEM_USER AS 'System Login'

Selecting data from multiple tables using the UNION operator

SELECT CompanyName, ContactName
FROM Northwind.dbo.Customers
WHERE Country = 'Brazil'
UNION
SELECT CompanyName, ContactName
FROM Northwind.dbo.Suppliers
WHERE Country = 'Brazil'


ProductName UnitPrice

Chai $18.00
Chang $19.00
Guaraná Fantástica $4.50
Sasquatch Ale $14.00
Steeleye Stout $18.00
Côte de Blaye $263.50
Chartreuse verte $18.00
Ipoh Coffee $46.00
Laughing Lumberjack Lager $14.00
Outback Lager $15.00
Rhönbräu Klosterbier $7.75

Lakkalikööri $18.00

Lower Higher Responsible

1.00 2.00 Peter

Now Database User System Login

1/21/2001 4:38:42 PM dbo SQLBYEXAMPLE\AdminUser

CompanyName ContactName

Comércio Mineiro Pedro Afonso
Familia Arquibaldo Aria Cruz
Gourmet Lanchonetes André Fonseca
Hanari Carnes Mario Pontes
Que Delícia Bernardo Batista
Queen Cozinha Lúcia Carvalho
Refrescos Americanas LTDA Carlos Diaz
Ricardo Adocicados Janete Limeira
Microsoft SQL Server 2000 Programming by Example

428
Tradição Hipermercados Anabela Domingues
Wellington Importadora Paula Parente
Most of the queries that use subqueries can be rewritten as simple queries without subqueries to produce the
same results. Actually, the Query Optimizer can decide to apply the same query plan regardless of the way
the query is written.
In the following sections, you will see the same solution with and without subqueries. In some cases, using a
subquery makes the query easier to read.

Scalar Subqueries
A scalar query can be used as a subquery anywhere in a Transact-SQL statement where an expression is
accepted:
• As part of any expression, because the result of the subquery is a scalar value.
• In the SELECT clause of a SELECT statement, as part of the output list.
• In the SET clause of an UPDATE statement, specifying the value to assign to a field.
• In the FROM clause of a SELECT statement, as a single row and single column derived table.
• In the WHERE clause, as a condition to test the value of a field, constant, variable, or the result of
another scalar subquery.
• In the HAVING clause, in the same cases as in the WHERE clause.
Listing 11.4 shows several examples of how to use scalar subqueries in the SELECT, SET, FROM, WHERE,
and HAVING clauses, inside other queries. The purpose of every query is documented throughout the code.
You can see in Listing 11.5 how to solve the same queries from Listing 11.4, without using any subquery.
Note that we do not show the output of Listing 11.5 because it is the same as for Listing 11.4.
Note
To use a query as a subquery inside another query, you must enclose the subquery in parentheses.

Listing 11.4 Use Scalar Subqueries Inside Other Queries


USE Northwind
GO

SET NOCOUNT ON
GO

In this case we combine the values returned by two subqueries
to get the medium unit price
SELECT (
(SELECT MIN(Unitprice)

FROM Products) +
(SELECT MAX(Unitprice)
FROM Products))/2 as NewPrice

Chapter 11. Using Complex Queries and Statements
429
This query is not practically useful,
but it shows more choices on designing subqueries
SELECT 1, 2,
(SELECT 3)
GO

This query uses two subqueries to retrieve one single row
with the Maximum and Average UnitPrice

SELECT (
SELECT AVG(Unitprice)
FROM Products
) as AvgPrice
, (
SELECT MAX(Unitprice)
FROM Products
) as MaxPrice
GO

Compare the UnitPrice of every product
with the Average UnitPrice, produced by a subquery

SELECT ProductName, UnitPrice, (
SELECT AVG(Unitprice)

FROM Products
) as AvgPrice
FROM Products
WHERE CategoryID = 2
GO

Updates the UnitPrice of the product 11 to
20% more than the maximum UnitPrice.

UPDATE Products
SET UnitPrice = (
SELECT MAX(Unitprice)
FROM Northwind Products
) * 1.2
WHERE ProductID = 11
Show the product with maximum UnitPrice

SELECT ProductName, UnitPrice
FROM Products P
WHERE Unitprice = (
SELECT Max(UnitPrice) MPrice
FROM Products
)

You want to retrieve the Categories with average Unitprice
greater than the overall products average price

SELECT CategoryID, AVG(UnitPrice) AS 'Average Price'
FROM Products P
GROUP BY CategoryID

HAVING AVG(UnitPrice) > (
SELECT AVG(UnitPrice) MPrice
FROM Products
)
Microsoft SQL Server 2000 Programming by Example

430


NewPrice

$190.97



1.00 2.00 3.00

AvgPrice MaxPrice

$33.88 $379.44

ProductName UnitPrice AvgPrice

Aniseed Syrup $11.00 $33.88
Chef Anton's Cajun Seasoning $24.20 $33.88
Chef Anton's Gumbo Mix $23.49 $33.88
Grandma's Boysenberry Spread $27.50 $33.88
Northwoods Cranberry Sauce $44.00 $33.88
Genen Shouyu $17.05 $33.88
Gula Malacca $21.40 $33.88

Sirop d'érable $31.35 $33.88
Vegie-spread $48.29 $33.88
Louisiana Fiery Hot Pepper Sauce $23.16 $33.88
Louisiana Hot Spiced Okra $18.70 $33.88
Original Frankfurter grüne Soße$14.30 $33.88
ProductName UnitPrice

Queso Cabrales $455.33

CategoryID Average Price

1.00 $37.98
4.00 $72.16
6.00 $54.01
Listing 11.5 Solving the Scalar Subqueries Examples from Listing 11.4 Without Subqueries


USE Northwind
GO

SET NOCOUNT ON
Chapter 11. Using Complex Queries and Statements
431
GO

Get the medium unit price

SELECT (MIN(UnitPrice) +
MAX(UnitPrice))/2 AS NewPrice
FROM Products


Selects three constants

SELECT 1, 2, 3
GO

Retrieve one single row with the Maximum and Average UnitPrice

SELECT AVG(Unitprice) as AvgPrice,
MAX(Unitprice) as MaxPrice
FROM Products

GO

Compare the UnitPrice of every product
with the Average UnitPrice

DECLARE @UP money

SELECT @UP = AVG(Unitprice)
FROM Products

SELECT ProductName, UnitPrice, @UP AS AvgPrice
FROM Products
WHERE CategoryID = 2
GO

Updates the UnitPrice of the product 11 to
20% more than the maximum UnitPrice.


DECLARE @UP money

SELECT @UP = MAX(Unitprice)
FROM Products

UPDATE Products
SET UnitPrice = @UP * 1.2
WHERE ProductID = 11

You want to show the product with maximum UnitPrice

DECLARE @UP money

SELECT @UP = MAX(Unitprice)
FROM Products

SELECT ProductName, UnitPrice
FROM Products P
WHERE Unitprice = @UP

You want to retrieve the Categories with average Unitprice
greater than the overall products average price

DECLARE @UP money
Microsoft SQL Server 2000 Programming by Example

432

SELECT @UP = AVG(Unitprice)
FROM Products


SELECT CategoryID, AVG(UnitPrice)
FROM Products P
GROUP BY CategoryID
HAVING AVG(UnitPrice) > @UP
List Subqueries
A List query can be used as a subquery inside a query in the following cases:
• In the WHERE clause of any query using the IN operator to specify the List query as a list of possible
values.
• In the WHERE clause when using any comparison operator with the SOME, ANY, or ALL operators.
• In the FROM clause of a SELECT statement, as a multirow and single- column derived table.
• In the WHERE clause, using the EXISTS or NOT EXISTS keywords to test for existence of values in
the List.
Listing 11.6 contains some examples of subqueries that produce lists of values. The first example uses a list
subquery in the WHERE clause introduced with the IN operator. The second example uses a list subquery in
the WHERE clause as well, with the ALL operator. The third example uses a list subquery as a derived table in
the FROM clause. The last example shows a subquery in the WHERE clause using the EXISTS operator.
Listing 11.7 contains the same examples, but without using list subqueries. The output is the same as in
Listing 11.6.
Listing 11.6 Using List Queries As Subqueries


USE Northwind
GO

SET NOCOUNT ON
GO

Orders placed by clients from London
and EmployeeID = 1


SELECT OrderID, CustomerID, EmployeeID, OrderDate
FROM Orders
WHERE CustomerID IN (
SELECT CustomerID
FROM Customers
WHERE City = 'London'
)
AND EmployeeID = 1

Select all the products with the UnitPrice
greater than all the products from Category 2

Chapter 11. Using Complex Queries and Statements
433
SELECT ProductID, ProductName, UnitPrice
FROM Products
WHERE UnitPrice > ALL (
SELECT UnitPrice
FROM Products
WHERE CategoryID = 2
)
Select all the order details related to products from category 2
and OrderID between 10250 and 10300

SELECT OD.OrderID, OD.ProductID, OD.UnitPrice
FROM [Order Details] OD
JOIN (
SELECT ProductID
FROM Products

WHERE CategoryID = 2
) AS P
ON P.ProductID = OD.ProductID
WHERE OrderID BETWEEN 10250 AND 10300

List all the products only if there are any products never ordered

SELECT ProductID, ProductName
FROM Products
WHERE EXISTS (
SELECT Products.ProductID
FROM Products
LEFT OUTER JOIN [Order Details]
ON Products.ProductID = [Order Details].ProductID
WHERE [Order Details].ProductID IS NULL
)


OrderID CustomerID EmployeeID OrderDate

10,364.00 EASTC 1.00 11/26/1996 12:00:00 AM
10,377.00 SEVES 1.00 12/9/1996 12:00:00 AM
10,400.00 EASTC 1.00 1/1/1997 12:00:00 AM
10,453.00 AROUT 1.00 2/21/1997 12:00:00 AM
10,558.00 AROUT 1.00 6/4/1997 12:00:00 AM
10,743.00 AROUT 1.00 11/17/1997 12:00:00 AM
10,800.00 SEVES 1.00 12/26/1997 12:00:00 AM
11,023.00 BSBEV 1.00 4/14/1998 12:00:00 AM

ProductID ProductName UnitPrice


9.00 Mishi Kobe Niku $97.00
11.00 Queso Cabrales $455.33
18.00 Carnarvon Tigers $62.50
20.00 Sir Rodney's Marmalade $81.00
29.00 Thüringer Rostbratwurst $123.79
38.00 Côte de Blaye $263.50
51.00 Manjimup Dried Apples $53.00
Microsoft SQL Server 2000 Programming by Example

434
59.00 Raclette Courdavault $55.00
62.00 Tarte au sucre $49.30
OrderID ProductID UnitPrice

10,250.00 65.00 $16.80
10,251.00 65.00 $16.80
10,256.00 77.00 $10.40
10,257.00 77.00 $10.40
10,258.00 5.00 $17.00
10,262.00 5.00 $17.00
10,278.00 44.00 $15.50
10,278.00 63.00 $35.10
10,283.00 15.00 $12.40
10,284.00 44.00 $15.50
10,289.00 3.00 $8.00
10,290.00 5.00 $17.00
10,290.00 77.00 $10.40
10,291.00 44.00 $15.50
10,293.00 63.00 $35.10

10,300.00 66.00 $13.60

ProductID ProductName

Listing 11.7 Solving the List Subqueries Examples from Listing 11.6 Without Subqueries


USE Northwind
GO

SET NOCOUNT ON
GO

Orders placed by clients from London
and EmployeeID = 1

SELECT O.OrderID, O.CustomerID, O.EmployeeID, O.OrderDate
FROM Orders O
JOIN Customers C
ON O.CustomerID = C.CustomerID
WHERE City = 'London'
AND EmployeeID = 1

Select all the products with the UnitPrice
greater than all the products from Category 2

DECLARE @MP money

SELECT @MP = MAX(Unitprice)
FROM Products

WHERE CategoryID = 2
SELECT ProductID, ProductName, UnitPrice
FROM Products
Chapter 11. Using Complex Queries and Statements
435
WHERE UnitPrice > @MP

Select all the order details related to products from category 2
and OrderID between 10250 and 10300

SELECT OD.OrderID, OD.ProductID, OD.UnitPrice
FROM [Order Details] OD
JOIN Products P
ON P.ProductID = OD.ProductID
WHERE CategoryID = 2
AND OrderID BETWEEN 10250 AND 10300

List all the products only if there are any products never ordered

DECLARE @n int


SELECT @n = COUNT(*)
FROM Products
LEFT OUTER JOIN [Order Details]
ON Products.ProductID = [Order Details].ProductID
WHERE [Order Details].ProductID IS NULL

SELECT ProductID, ProductName
FROM Products

WHERE ISNULL(@n, 0) > 0

OR

IF EXISTS(
SELECT Products.ProductID
FROM Products
LEFT OUTER JOIN [Order Details]
ON Products.ProductID = [Order Details].ProductID
WHERE [Order Details].ProductID IS NULL
)
SELECT ProductID, ProductName
FROM Products
Array Subqueries
An Array query, or standard query, can be used as a subquery inside a query in the following cases:
• In the FROM clause of a SELECT statement, as a multirow and multicolumn derived table.
• In the WHERE clause, using the EXISTS or NOT EXISTS keywords to test for existence of values in
the List. The EXISTS function does not return any rows, it evaluates to TRUE if the subquery returns
at least one row, and it evaluates to FALSE otherwise.
Listing 11.8 shows two examples of using array subqueries. The first example uses an array subquery in the
FROM clause as a derived table. The second example combines two result sets with the UNION operator,
introducing two array subqueries with the EXISTS operator.
Listing 11.9 solves the same problems from Listing 11.8 without using subqueries. Note that we do not
show the output for Listing 11.9 because it is exactly the same as for Listing 11.8.
Listing 11.8 Using Array Queries As Subqueries

×