Per-Row UDFs

Nondeterministic functions are functions that are not guaranteed to return the same output when invoked multiple times with the same input. When you invoke nondeterministic built-in functions in a query (such as RAND and GETDATE), those functions are invoked once for the whole query and not once per row. The only exception to this rule is the NEWID function, which generates a globally unique identifier (GUID). NEWID is the only nondeterministic built-in function that will be invoked once per row.

To demonstrate this behavior of nondeterministic functions, run the following code, which queries the Orders table in the Northwind database; invokes the functions RAND, GETDATE, and NEWID; and generates the output shown in abbreviated form in Table 6-11:

USE Northwind;

SELECT RAND() AS rnd, GETDATE() AS dt, NEWID() AS guid, OrderID AS oid
FROM dbo.Orders;

Table 6-11. Output of Query Invoking Nondeterministic Functions (Abbreviated)

rnd

dt

guid

oid

0.23575580157313

2005-12-19 14:18:00.157

52BCB19F-DDA0-4890-AE1C-B7387E2D9E07

10249

0.23575580157313

2005-12-19 14:18:00.157

7D3AAE2B-003F-4DD9-9E2E-F52C108F1ACE

10251

0.23575580157313

2005-12-19 14:18:00.157

A8FFDE94-0160-4AB5-B5B0-5A4A39A05B2F

10258

0.23575580157313

2005-12-19 14:18:00.157

335322D2-16E4-4966-80C0-83C176A86911

10260

0.23575580157313

2005-12-19 14:18:00.157

3AA95970-9AC9-45A6-863F-8E70AD25F5D7

10265

0.23575580157313

2005-12-19 14:18:00.157

3C4A3925-1E37-4617-BC54-B36C66819E6B

10267

0.23575580157313

2005-12-19 14:18:00.157

2730F1A0-222C-4FA9-92B9-3CD7C304C7A7

10269

0.23575580157313

2005-12-19 14:18:00.157

8306A132-9D00-4218-835D-6CCFA07A82A8

10270

0.23575580157313

2005-12-19 14:18:00.157

8D9292A2-3CA5-4A2B-B504-9431C96CA2A6

10274

0.23575580157313

2005-12-19 14:18:00.157

7E59D1A0-ADE6-4DB5-AC53-92CFCA192247

10275

...

...

...

...

You can observe that both RAND and GETDATE were invoked only once for the whole query, and their result values were copied to all rows. On the other hand, NEWID was invoked once per row, generating a different value in each row.

Suppose that you had the need to invoke the RAND function for each row. You might have thought of invoking RAND from a UDF and then invoking the UDF in an outer query, knowing that a UDF is invoked once per row. Here’s an attempt to create such a UDF called fn_rand:

IF OBJECT_ID('dbo.fn_rand') IS NOT NULL
  DROP FUNCTION dbo.fn_rand;
GO
CREATE FUNCTION dbo.fn_rand() RETURNS FLOAT
AS
BEGIN
  RETURN RAND();
END
GO

However, this attempt fails and produces the following error:

Msg 443, Level 16, State 1, Procedure fn_rand, Line 6
Invalid use of side-effecting or time-dependent operator in 'rand' within a function.

The error tells you that your function is not allowed to have side effects, and the RAND function does change an internal state.

There’s a back door that allows you to implicitly invoke RAND from a UDF. Create a view that invokes RAND, and query the view from the UDF, like so:

IF OBJECT_ID('dbo.fn_rand') IS NOT NULL
  DROP FUNCTION dbo.fn_rand;
GO
IF OBJECT_ID('dbo.VRand') IS NOT NULL
  DROP VIEW dbo.VRand;
GO
CREATE VIEW dbo.VRand AS SELECT RAND() AS r;
GO
CREATE FUNCTION dbo.fn_rand() RETURNS FLOAT
AS
BEGIN
  RETURN (SELECT r FROM dbo.VRand);
END
GO

You can test the fn_rand UDF by invoking it in a query against the Orders table, which will generate the output shown in abbreviated form in Table 6-12:

SELECT dbo.fn_rand() AS rnd, OrderID AS oid FROM dbo.Orders;

Table 6-12. Output of Query Invoking the fn_rand UDF (Abbreviated)

rnd

oid

0.126413837261193

10248

0.222567782284458

10249

0.475723707976473

10250

0.57880518253848

10251

0.169390263927576

10252

0.337301740768919

10253

0.489646055111808

10254

0.826464402198423

10255

0.232679419042244

10256

0.152765690787598

10257

...

...

SQL Server 2000 disallowed the invocation of nondeterministic functions from UDFs. SQL Server 2005 is more lenient in the sense that it inspects more properties of a UDF and distinguishes between functions that have side effects and functions that don’t. For example, in SQL Server 2000 you couldn’t invoke the GETDATE function from a UDF, but in SQL Server 2005 you can:

IF OBJECT_ID('dbo.fn_getdate') IS NOT NULL
  DROP FUNCTION dbo.fn_getdate;
GO
CREATE FUNCTION dbo.fn_getdate() RETURNS DATETIME
AS
BEGIN
  RETURN GETDATE();
END
GO

When you’re done, run the following code for cleanup:

IF OBJECT_ID('dbo.fn_rand') IS NOT NULL
  DROP FUNCTION dbo.fn_rand;
GO
IF OBJECT_ID('dbo.VRand') IS NOT NULL
  DROP VIEW dbo.VRand;
GO
IF OBJECT_ID('dbo.fn_getdate') IS NOT NULL
  DROP FUNCTION dbo.fn_getdate;
GO
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.26.185