I am trying to create a function tha开发者_Python百科t returns a comma separated list of values based on a table name and column name parameters.
The idea was to build a dynamic SQL statement on the fly and then return the result. However, I ran into a problem where it won't let me run the execute
statement inside of a function. Here is what I had:
ALTER FUNCTION fn_HearingAttendeesToCSV (@TableName varchar(100),
@ColumnName varchar(100))
RETURNS VARCHAR(MAX)
AS
BEGIN
declare @sql varchar(max)
select @sql =
'DECLARE @listStr VARCHAR(MAX)
SELECT @listStr = COALESCE(@listStr + '', '' ,'''') + '
+ @ColumnName + ' FROM ' + @TableName + ' RETURN @listStr '
Exec (@sql)
END
GO
I am pretty sure that I am going down the wrong road. Can someone suggest how to get around the execute
issue? Or a better way of doing this?
You cannot do this in a function - period. Functions cannot execute dynamic SQL, so using an arbitrary table/column name (non-deterministic) flies in the face of SQL Server functions.
I don't think you can do this via T-SQL, BUT you absolutely can accomplish the same thing via a UDF written in C#. Here's the code:
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(DataAccess = DataAccessKind.Read)]
public static SqlString fn_HearingAttendeesToCSV(SqlString tableName, SqlString columnName) {
System.Text.StringBuilder result = null;
using (var cnn = new SqlConnection("context connection=true")) {
var cmd = new SqlCommand();
cmd.CommandText = String.Format("select [{0}] from [{1}]", SqlEscape(columnName.Value), SqlEscape(tableName.Value));
cmd.Connection = cnn;
cnn.Open();
using (var rdr = cmd.ExecuteReader()) {
while (rdr.Read()) {
if (result == null) {
result = new System.Text.StringBuilder();
}
else {
result.Append(",");
}
result.Append(CsvEscape(rdr[0]));
}
}
cnn.Close();
}
if (result == null) {
return SqlString.Null;
}
else {
return result.ToString();
}
}
private static string SqlEscape(string s) {
s = s ?? "";
return s.Replace("[", "[[").Replace("]", "]]");
}
private static string CsvEscape(object o) {
var s = o == null ? "" : o.ToString();
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
};
In order to use CLR UDFs, you need to be sure you've enabled them on the server like this:
exec sp_configure 'clr enabled', 1
RECONFIGURE
You also need to compile to an older version of the .NET framework (2.0 is what I tested with) as SQL 2005 doesn't allow 4.0 assemblies.
Even though your UDF is written in C#, you still access it via T-SQL the same way you're used to:
select fn_HearingAttendeesToCSV('table', 'column')
If you need to support schemas other than 'dbo', or if you don't need pure CSV, modify to fit your needs but this should get you 99% of the way there. That is, assuming you're the DBA or you're good enough buddies with him/her to get CLR enabled.
Hi I think they're right about functions, but you can use an SP with an output parameter to do this.
Check this code:
create proc DynCsv
@table varchar(100),
@column varchar(100),
@csv varchar(8000) output
as
declare
@sql nvarchar(4000),
@parms nvarchar(100)
-- setup parms
select @csv = '',
@sql = '
select top 10 @csv = @csv +'','' + CAST(' + @column + ' as varchar)
from ' + @table + ' option(maxdop 1)',
@parms = '@csv varchar(8000) output'
-- execute dynamic
exec sp_executesql
@sql,
@parms,
@csv = @csv output
-- loose first comma
set @csv = substring(@csv, 2, 8000)
You can call it with this code:
declare @csv varchar(8000)
exec dynCsv 'tObj', 'ObjId', @csv output
print @csv
Good luck,
GJ
You can't do this in a function but you can do it in a PROCEDURE. I've done that many times to build a dynamic SQL statement.
That's the way to go.
精彩评论