SQL Server 2005: Creating Scalar User-Defined Functions from Assemblies
Scalar functions are not much different from stored procedures. Indeed, for the most part, they have the very same differences that the T-SQL versions. Much as with stored procedures, we utilize the same core CREATE syntax that we used in the T-SQL user-defined functions (UDFs) we created in Chapter 11, "Getting Procedural: Stored Procedures and User-Defined Functions," of the book Professional SQL Server 2005 Programming (Wrox, 2006, ISBN: 0764584340).
CREATE FUNCTION [<schema name>.]<function name>
( [ <@parameter name> [AS]
[<schema name>.]<scalar data type> [ = <default value>]
[ ,...n ] ] )
RETURNS {<scalar type>|TABLE [(<Table Definition>)]}
[ WITH [ENCRYPTION]|[SCHEMABINDING]|
[ RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT ]
| [EXECUTE AS {
CALLER|SELF|OWNER|<'user name'>} ]
]
[AS] { EXTERNAL NAME <<i>external method</i>> |
BEGIN
[<function statements>]
{RETURN <type as defined in RETURNS clause>|RETURN
(<SELECT statement>)}
END }[;]
SQL Server starts you out with a simple template:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString ExampleUDF()
{
// Put your code here
return new SqlString("Hello");
}
};
using System;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;
We're then ready to implement out function with very few changes:
[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static bool RegExIsMatch(string pattern,
string matchString)
{
Regex reg = new Regex(pattern.TrimEnd(null));
return reg.Match(matchString.TrimEnd(null)).Success;
}
I'm not going to review it much here, but take a look at Chapter 11, "Getting Procedural: Stored Procedures and User-Defined Functions" of the book Professional SQL Server 2005 Programming (Wrox, 2006, ISBN: 0764584340). If you need to be reminded of how determinism works The key thing is that, given the same inputs, the function must always yield the same outputs.
Go ahead and compile this, and we're ready to upload the assembly:
USE AdventureWorks
CREATE ASSEMBLY ExampleUDF
FROM '<solution path>\ExampleUDF\bin\Debug\ExampleUDF.dll'
And then create the function reference:
CREATE FUNCTION fCLRExample
(
@Pattern nvarchar(max),
@MatchString nvarchar(max)
)
RETURNS BIT
AS EXTERNAL NAME ExampleUDF.UserDefinedFunctions.RegExIsMatch
Notice the use of the nvarchar type instead of varchar. The string data type is a Unicode data type, and our function data type declaration needs to match.
This done, we're ready to test things out a bit:
SELECT ContactID, FirstName, LastName, EmailAddress, Phone
FROM Person.Contact
WHERE dbo.fCLRExample(
'[a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.)+(com|org|edu|mil|net)',
EmailAddress) = 1
If you have the default data, then this will actually return every row in the table since they all are adventure-works.com addresses. So, let's try a simple test to show what works versus what doesn't:
DECLARE @GoodTestMail varchar(100),
@BadTestMail varchar(100)
SET @GoodTestMail = 'robv@professionalsql.com'
SET @BadTestMail = 'misc. text'
SELECT dbo.fCLRExample(
'[a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.)+(com|org|edu|nz|au)',
@GoodTestMail) AS ShouldBe1
SELECT dbo.fCLRExample(
'[a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.)+(com|org|edu|nz|au)',
@BadTestMail) AS ShouldBe0
For the sake of brevity, I have not built the full e-mail regex string here. It would need to include all of the valid country code top domans such as au, ca, uk, and us. There are a couple hundred of these, so it wouldn't fit all that well. That said, the basic construct is just fine, you can tweak it to meet your particular needs.
This gets us back what we would expect:
ShouldBe1
---------
1
(1 row(s) affected)
ShouldBe0
---------
0
(1 row(s) affected)
But let's not stop there. We have this nice function, let's apply it a little further by actually applying it as a constraint to the table.
ALTER TABLE Person.Contact
ADD CONSTRAINT ExampleFunction
CHECK (dbo.fCLRExample(
'[a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.)+(com|org|edu|nz|au)',
EmailAddress) = 1)
Now we try to update a row to insert some bad data into our column, and it will be rejected:
UPDATE Person.Contact
SET EmailAddress = 'blah blah'
WHERE ContactID = 1
And SQL Server tells you the equivalent of "no way!":
Msg 547, Level 16, State 0, Line 1
The UPDATE statement conflicted with the
CHECK constraint "ExampleFunction".
The conflict occurred in database "AdventureWorks",
table "Person.Contact", column 'EmailAddress'
The statement has been terminated.
This article is adapted from Professional SQL Server 2005 Programming 2.0 by Joe Robert Vieira (Wrox, 2006, ISBN: 0-7645-8434-0), from Chapter 14, "Nothing But NET!"
No comments:
Post a Comment