Call dynamic SQL from function - sql

I am writing a function that returns a table. There are two parameters that are passed to the function and a query is built and executed and inserted into the returning table. However I am receiving this error.
Only functions and some extended stored procedures can be executed from within a function.
I would like to not use a stored procedure as this is a simple utility function. Does anyone know if this can be done. My function is coded below, it checks for dupes for a certain column within a certain table.
-- =============================================
-- AUTHOR: JON AIREY
-- THIS FUNCTION WILL RETURN A COUNT OF HOW MANY
-- TIMES A CERTAIN COLUMN VALUE APPEARS IN A
-- TABLE. THIS IS HELPFUL FOR FINDING DUPES.
-- THIS FUNCTION WILL ACCEPT A COLUMN NAME, TABLE
-- NAME (MUST INCLUDE SCHEMA), AND OPTIONAL
-- DATABASE TO USE. RESULTS WILL BE RETURNED AS
-- A TABLE.
-- =============================================
ALTER FUNCTION [dbo].[fn_FindDupe]
(
-- Add the parameters for the function here
#Column VARCHAR(MAX),
#Table VARCHAR(100),
#Database VARCHAR(100) = ''
)
RETURNS
#TempTable TABLE
([Column] varchar(100)
,[Count] int)
AS
BEGIN
DECLARE #SQL VARCHAR(MAX)
SET #Table = CASE
WHEN #Database = ''
THEN #Table
ELSE #Database + '.' + #Table
END
SET #SQL =
'
INSERT INTO #TempTable
SELECT ' + #Column + '
,COUNT(' + #Column + ') AS CNT
FROM ' + #Table + '
GROUP BY ' + #Column + '
ORDER BY CNT DESC
'
EXEC SP_EXECUTESQL #SQL
RETURN
END
GO

You can't use dynamic sql in a udf:
This very simple: you cannot use dynamic SQL from used-defined
functions written in T-SQL. This is because you are not permitted do
anything in a UDF that could change the database state (as the UDF may
be invoked as part of a query). Since you can do anything from dynamic
SQL, including updates, it is obvious why dynamic SQL is not
permitted.
...
In SQL 2005 and later, you could implement your function as a CLR
function. Recall that all data access from the CLR is dynamic SQL.
(You are safe-guarded, so that if you perform an update operation from
your function, you will get caught.) A word of warning though: data
access from scalar UDFs can often give performance problems.

Related

how to pass table name as parameter to sql table valued function?

I want to pass the table name as a parameter to table_valued function in MS SQL Server
CREATE FUNCTION maxid
(
-- Add the parameters for the function here
#tblname sysname,
#feild nvarchar(max),
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
select ISNULL(max(#feild),0)+1 from #tblname
)
You cannot use dynamic SQL in table valued or any other TSQL function. However, the code you provide seems to be used to obtain the next value of some identifier or counter field. The way you want to do it is highly deprecated and leads to concurrency problems.
Indeed, SQL Server can do it using at least two standard methods:
using a sequences
creating an identity column
I have done it but with store procedure
CREATE procedure [dbo].[get_maxid]
#tblname nvarchar(max),
#col nvarchar(max)
as
Begin
declare #sql nvarchar(max);
set #sql='select ISNULL(MAX('+#col+'),0)+1 as id from '+ QUOTENAME( #tblname)
execute sp_executesql #sql
End
Now Execute Store Procedure
exec get_maxid #col='col_name',#tblname='tbl_name'

How to call table variables outside stored procedure

I want to access outside the scope of a stored procedure a table variable that it defines. I have tried this one, but my case is different because my procedure receives a varchar as a parameter and then it creates a varchar that is executed, someway like this:
create procedure p_x
#Table varchar(250)
as
begin
declare #Sql varchar(450)
select #sql = 'declare ' + #Table + 'table(col1 varchar(10), col2 float, col3 float, col4 float)'
select #sql = #sql + ' insert' + #Table + ' values('a', 1,1,1)'
select #sql = #sql + ' insert' + #Table + ' values('b', 2,2,2)'
select #sql = #sql + ' select * from'
execute(#sql)
end
go
The solution the other question does is to pass as a parameter the new table to be processed. But in my case, because the code is written in a decisive part inside a varchar concatenation (and also it creates auxiliary tables concatenating at the end of the varchar #Table), I don't know what to do.
To give some context: there is this procedure that uses global temporary tables, which were called inside queries. Everything was working fine until we need to change the query to transform it into a table-valued function. The query just access the tables defined inside the procedure through global scope, but the table-valued function doesn't allow to access these global temporary tables. It seems that table variables can't be global.
In short, to change the query to a table-valued function, I need to change the procedure's temporary tables into table variables that I can access inside the table-valued function.
The big picture: 1) Today I have a query that works and this query calls a procedure.
2) I want to be able to call this query from a API without having to paste 100 lines of query
3) I received the suggestion of converting the query into a TFV
4) I did it, but it doesn't work, because TFV can't use temporary tables
5) I want a workaround to create a copy of the procedure with some minor changes that I can call from the TVF.
You cannot do this with a table variable. You can do it with a Temp Table, but table variables are automatically out of scope and inaccessible outside of the procedure that creates them.

Creating SQL Function to Create Multiple Tables

I am currently moving a SAS process to SQL. Within the SAS process, I leverage macros to create a multitude of tables.
I am trying to leverage the CREATE FUNCTION function within SQL to mimic this process, however I am stuck. I have three arguments, the server name, the name of the new table and the table where it should select from. I'm not sure what I should specify as what I am returning as I'm not looking to return anything, just create tables.
CREATE FUNCTION dbo.functionname (#server VARCHAR(250), #name VARCHAR(250), #table VARCHAR(250))
RETURN (???)
AS BEGIN
SELECT *
INTO #server.dbo.#nm
FROM #table
RETURN
END
This is what I have come up with so far. My SELECT statement wouldn't actually be *, I just put that for simplicity sake for this question.
UPDATE: In this instance, using a stored procedure is not an option as permissions have been limited.
You can create a dynamic SQL script as follows
declare #newtable sysname = 'T003',
#sourcetable sysname = 'sys.tables'
declare #sql nvarchar(max)
set #sql = N'select * into ' + #newtable + ' from ' + #sourcetable + ';'
set #sql = #sql + N'select * from ' + #newtable
exec sp_executesql #sql
Then you can use it in a stored procedure
To return data from new table, the table type must be known before. In this case it is not possible, so developer cannot create the function return type
Or create a function just to create the table and insert data into it. But return fail or success, etc
Then select from the new table using a dynamic SQL again

T-SQL: Variable Scope

I am trying to store the results of an SQL query into a variable.The query simply detects the datatype of a column, hence the returned result is a single varchar.
SET #SQL =
'declare ##x varchar(max) SET ##x = (select DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = ' +char(39)+#TabName+char(39) +
' AND column_name = ' +char(39)+#colName+char(39) + ')'
EXECUTE (#SQL)
Anything within the 'SET declaration' cannot access any variables outside of it and vice versa, so I am stuck on how to store the results of this query in a varchar variable to be accessed by other parts of the stored procedure.
You dont need a dynamic query to achieve what you want, below query will give the same result as yours.
declare #x varchar(max)
declare #tableName varchar(100), #ColumnName varchar(50)
set #tableName = 'Employee'
set #ColumnName = 'ID'
select #x = DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
where
Table_Name = #tableName
and column_name = #ColumnName
select #x
All user-defined variables in T-SQL have private local-scope only. They cannot be seen by any other execution context, not even nested ones (unlike #temp tables, which can be seen by nested scopes). Using "##" to try to trick it into making a global-variable doesn't work.
If you want to execute dynamic SQL and return information there are several ways to do it:
Use sp_ExecuteSQL and make one of the parameters an OUTPUT parameter (recommended for single values).
Make a #Temp table before calling the dynamic SQL and then have the Dynamic SQL write to the same #Temp table (recommended for multiple values/rows).
Use the INSERT..EXEC statement to execute your dynamic SQL which returns its information as the output of a SELECT statement. If the INSERT table has the same format as the dynamic SQL's SELECT output, then the data output will be inserted into your table.
If you want to return only an integer value, you can do this through the RETURN statement in dynamic SQL, and receive it via #val = EXEC('...').
Use the Session context-info buffer (not recommended).
However, as others have pointed out, you shouldn't actually need dynamic SQL for what you are showing us here. You can do just this with:
SET #x = ( SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = #TabName
AND column_name = #colName )
You may want to consider using the sp_executesql stored procedure for dynamic sql.
The following link provides a good usage example of sp_executesql procedure with output parameters:
http://support.microsoft.com/kb/262499

Passing SQL stored procedure entirety of WHERE clause

I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'