|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
SQL using INThe SQL It would be nice to be able to pass an array of values as a parameter to a stored procedure or ad hoc statement, but T-SQL doesn't support arrays because it is a set-based language. When I first learned about table variables in SQL 2000, I thought they were the answer. I quickly discovered, however, that you cannot define them as parameters. They only exist as local variables and their scope is limited to the batch where they are declared. However, I did eventually discover I could use table-valued functions to create and return a table variable. Table-valued functionsTable valued functions have been available since SQL 2000. A table-valued function is a user-defined function that returns a result set as a table variable. The output can be the result of some CREATE FUNCTION [dbo].[function_string_to_table]
(
@string VARCHAR(MAX),
@delimiter CHAR(1)
)
RETURNS @output TABLE(
data VARCHAR(256)
)
BEGIN
DECLARE @start INT, @end INT
SELECT @start = 1, @end = CHARINDEX(@delimiter, @string)
WHILE @start < LEN(@string) + 1 BEGIN
IF @end = 0
SET @end = LEN(@string) + 1
INSERT INTO @output (data)
VALUES(SUBSTRING(@string, @start, @end - @start))
SET @start = @end + 1
SET @end = CHARINDEX(@delimiter, @string, @start)
END
RETURN
END
The above function takes --JUST RETURN THE KEYS
SELECT * FROM dbo.function_string_to_table('key1|key2|key3|key4|key5', '|')
--USE THE FUNCTION AS A SUBQUERY
SELECT * FROM table1 WHERE sID IN (SELECT *
FROM dbo.function_string_to_table('key1|key2|key3|key4|key5', '|'))
As an additional benefit, you can now pass an "array" to an SQL server-stored procedure or batch. Take the following stored procedure as an example: CREATE PROCEDURE procedure_takes_array
@string VARCHAR(MAX),
@delimiter CHAR(1)
AS
SELECT * FROM table1 WHERE sID IN
(SELECT * FROM dbo.function_string_to_table(@string, @delimiter))
GO
This can then be called from a client application as follows: void GetData(string[] keys)
{
using (SqlConnection oconn = new SqlConnection
("Application Name=SQLArrayTest;Server=(local);
Database=MaxPreps_v2;Trusted_Connection=Yes;"))
{
oconn.Open();
string SQL = "SELECT * FROM
dbo.function_string_to_table(@keys, '|')";
using (SqlCommand cmd = oconn.CreateCommand())
{
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@keys", SqlDbType.VarChar, -1);
cmd.Parameters["@keys"].Value = string.Join("|", keys);
//execute the command
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Close();
}
}
}
But this functionality does not come without a price. T-SQL is not very efficient at string manipulation because it is largely a procedural operation, not a set-based operation. The above technique is not an expensive one, other than the cost in time due to the manual splitting of the string. So, it will not rob SQL server of its much-needed resources. However, if your process runs frequently, you may want to test and make sure this method has acceptable performance. Using the CLRWhen I first started writing this article, I debated whether to break this into two separate articles instead of one single article. Even though SQL Server 2005 is almost a year old now, it seems as though any article on the topic of the integrated CLR should provide some background first. I suspect that DBAs are still not comfortable with the idea and so it has not been enabled on most systems. What I found when researching the CLR for this article is that a good understanding of where the CLR can help and where it will hurt will go a long way towards reaping great benefits from this new feature. I can't speak for the security implications at this point in time, but as you will see, the CLR can provide huge benefits in performance over T-SQL operations like the one in the previous section. Just make sure that you understand when and where to use the CLR. For some background, check out this whitepaper: Using CLR Integration in SQL Server 2005. First, you'll need to make sure the CLR has been enabled on your test server. If it has not already been enabled, run the following script: EXEC sp_configure 'show advanced options' , '1';
go
reconfigure;
go
EXEC sp_configure 'clr enabled' , '1'
go
reconfigure;
Now to write our CLR table-valued function we'll use Visual Studio 2005. Follow these steps:
Now replace the contents of the file with the following code: using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
public partial class UserDefinedFunctions
{
[SqlFunction(Name="clrfunction_string_to_table",
FillRowMethodName="FillRow", TableDefinition="ID NVARCHAR(50)")]
public static IEnumerable SqlArray(SqlString str, SqlChars delimiter)
{
//return single element array if no delimiter is specified
if(delimiter.Length == 0)
return new string[1] { str.Value };
//split the string and return a string array
return str.Value.Split(delimiter[0]);
}
public static void FillRow(object row, out SqlString str)
{
//set the column value
str = new SqlString( (string) row );
}
}
First, to point out a few things: The method that will be used for the "main" function must return
SqlType types because they support the INullable interface and this allows you to be consistent with SQL standards. Now build the project. To deploy it, select Build –> Deploy SqlArray. Now you can call the table-valued function just like you would any other function: --JUST RETURN THE KEYS
SELECT * FROM dbo.clrfunction_string_to_table
('key1|key2|key3|key4|key5', '|')
--USE THE FUNCTION AS A SUBQUERY
SELECT * FROM table1 WHERE sID IN
(SELECT * FROM dbo.clrfunction_string_to_table
('key1|key2|key3|key4|key5', '|'))
The result is much faster, as we'll see next. Test projectThe test project is just a simple "load test" application that connects to the database and repeatedly calls the function. It does not use the function to return results from any table, however. The reason for this is to demonstrate the raw difference in performance between the T-SQL and the CLR versions of the function. My test sample was 1000 iterations and below are the results: Iterations: 10,000
- T-SQL (avg): 10.3543 sec
- CLR (avg): 7.2886
The CLR version is, on average, several seconds faster. Both versions are certainly viable solutions, though. This means that if you're using SQL 2000 or if the CLR hasn't (or won't) be enabled for your SQL 2005 servers, you can of course use the T-SQL version with little overall difference. As a side note, I accidentally discovered something interesting. Claudius Ceteras pointed out that in my original load test application I had forgotten to reset the Test Project (1000 Iterations)
- T-SQL 17 min 20 sec 765 ms
- CLR 0 min 3 sec 453 ms
As you can see, the difference is drastic. The comparison uses an unlikely scenario of splitting a string of characters much larger than any application would ever submit to our function. However, it highlights the strength of the CLR over T-SQL when performing string manipulations. The CLR version is much faster than the T-SQL version. There are two reasons for this. First, the CLR is much faster at string manipulation. The second reason for the performance improvement is that SQL Server will generate a temp table for the T-SQL version that involves I/O to ConclusionWhile the CLR is obviously the better choice here, you may not have the option of using the CLR on your instance of SQL Server due to the policies of your team. However, this doesn't mean you that can't benefit from the T-SQL version. Either method can conserve both CPU cycles and memory by reducing compiles/recompiles due to differences in values passed to the Resources
Updates5/23/2007
6/04/2007
| ||||||||||||||||||||