Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 71 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
71
Dung lượng
525,27 KB
Nội dung
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 * [...]... 65.00 $16.80 77 .00 $10.40 77 .00 $10.40 5.00 $ 17. 00 5.00 $ 17. 00 44.00 $15.50 63.00 $35.10 15.00 $12.40 44.00 $15.50 3.00 $8.00 5.00 $ 17. 00 77 .00 $10.40 44.00 $15.50 63.00 $35.10 66.00 $13.60 $55.00 $49.30 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... P.ProductName (Same output for every query) ProductID 1.00 2.00 24.00 34.00 35.00 38.00 39.00 43.00 67. 00 70 .00 75 .00 76 .00 Caution UnitPrice $18.00 $19.00 $4.50 $14.00 $18.00 $263.50 $18.00 $46.00 $14.00 $15.00 $7. 75 $18.00 AvgPrice $ 17. 15 $ 17. 88 $4.24 $12. 97 $ 17. 00 $245.93 $16.68 $43.04 $13 .72 $14.15 $7. 38 $16.98 MaxPrice $14.40 $15.20 $3.60 $11.20 $14.40 $210.80 $14.40 $36.80 $11.20 $12.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... OrderID 10,364.00 10, 377 .00 10,400.00 10,453.00 10,558.00 10 ,74 3.00 10,800.00 11,023.00 CustomerID -EASTC SEVES EASTC AROUT AROUT AROUT SEVES BSBEV EmployeeID 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 OrderDate -11/26/1996 12:00:00 AM 12/9/1996 12:00:00 AM 1/1/19 97 12:00:00 AM 2/21/19 97 12:00:00 AM 6/4/19 97 12:00:00 AM 11/ 17/ 19 97 12:00:00 AM 12/26/19 97 12:00:00 AM 4/14/1998 12:00:00... $ 17. 15 $14.40 Chai 2.00 $19.00 $ 17. 88 $15.20 Chang 24.00 $4.50 $4.24 $3.60 Guaraná Fantástica 34.00 $14.00 $12. 97 $11.20 Sasquatch Ale 35.00 $18.00 $ 17. 00 $14.40 Steeleye Stout 38.00 $263.50 $245.93 $210.80 Côte de Blaye 39.00 $18.00 $16.68 $14.40 Chartreuse verte 43.00 $46.00 $43.04 $36.80 Ipoh Coffee 67. 00 $14.00 $13 .72 $11.20 Laughing Lumberjack Lager 70 .00 $15.00 $14.15 $12.00 Outback Lager 75 .00... CategoryID 1.00 4.00 6.00 UnitPrice $11.00 $24.20 $23.49 $ 27. 50 $44.00 $ 17. 05 $21.40 $33.88 $48.29 $23.16 $18 .70 $33.88 UnitPrice $455.33 AvgPrice $33.88 $33.88 $33.88 $33.88 $33.88 $33.88 $33.88 $33.88 $33.88 $33.88 Average Price $ 37. 98 $72 .16 $54.01 Listing 11.5 Solving the Scalar Subqueries Examples from Listing 11.4 Without Subqueries USE Northwind GO SET NOCOUNT... best selling product by number of units and by revenue This solution uses subqueries SELECT 'By units'AS Criteria, ProductName as 'Best Selling' FROM Products 439 WHERE ProductID = ( SELECT ProductID FROM [Order Details] GROUP BY ProductID HAVING SUM(Quantity) = ( SELECT MAX(SQ) FROM ( SELECT SUM(Quantity) as SQ FROM [Order Details] GROUP BY ProductID ) AS OD)) UNION SELECT 'By revenue'AS Criteria,... ProductName UnitPrice Mishi Kobe Niku $ 97. 00 Queso Cabrales $455.33 Carnarvon Tigers $62.50 Sir Rodney's Marmalade $81.00 Thüringer Rostbratwurst $123 .79 Côte de Blaye $263.50 Manjimup Dried Apples $53.00 433 59.00 62.00 OrderID 10,250.00 10,251.00 10,256.00 10,2 57. 00 10,258.00 10,262.00 10, 278 .00 10, 278 .00 10,283.00 10,284.00 10,289.00 10,290.00 10,290.00 10,291.00 10,293.00... Klosterbier Lakkalikööri UnitPrice $18.00 $19.00 $4.50 $14.00 $18.00 $263.50 $18.00 $46.00 $14.00 $15.00 $7. 75 $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... 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, . 11188.4000 108 17 KOENE 1998-01-06 00:00:00.000 10952.8450 108 97 HUNGO 1998-02-19 00:00:00.000 10835.2400 Microsoft SQL Server 2000 Programming by Example 420 10 479 RATTC 19 97- 03-19 00:00:00.000. SCHEMABINDING' Microsoft SQL Server 2000 Programming by Example 414 + CHAR(10) GO (91 row(s) affected) ALTER TABLE statement successful without SCHEMABINDING Server: Msg 2 07, Level. 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