Dynamic SQL error converting nvarchar to int - sql

I have created a procedure in dynamic SQL which has a select statement and the code looks like:
ALTER PROCEDURE cagroup (
#DataID INT ,
#days INT ,
#GName VARCHAR(50) ,
#T_ID INT ,
#Act BIT ,
#Key VARBINARY(16)
)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SchemaName SYSNAME
DECLARE #TableName SYSNAME
DECLARE #DatabaseName SYSNAME
DECLARE #BR CHAR(2)
SET #BR = CHAR(13) + CHAR(10)
SELECT #SchemaName = Source_Schema ,
#TableName = Source_Table ,
#DatabaseName = Source_Database
FROM Source
WHERE ID = #DataID
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ #T_ID + ' AS To_ID ,' + #BR
+ #DataID + ' AS DataSoID ,' + #BR
+ #Act + ' AS Active ,' + #BR
+ Key + ' AS key' + #BR
+ 'R_ID AS S_R_ID' + #BR
+ 'FROM' + #DatabaseName + '.'
+ #SchemaName + '.'
+ #TableName + ' t' + #BR
+ 'LEFT OUTER JOIN Gro g ON g.GName = '
+ #GName + #BR + 'AND g.Data_ID] =' + #DataID + #BR
+ 't.[I_DATE] > GETDATE() -' + #days + #BR
+ 'g.GName IS NULL
AND ' + #GName + ' IS NOT NULL
AND t.[Act] = 1' + #BR
PRINT (#SQL)
END
When I am executing this procedure with this statement:
Exec dbo.cagroup 1,10,'[Gro]',1,1,NULL
I am getting the following error.
Msg 245, Level 16, State 1, Procedurecagroup, Line 33
Conversion failed when converting the nvarchar value 'SELECT [Gro] AS GName ,
' to data type int.
Where am I doing wrong?

You need to CAST all numbers to nvarchar in the concatenation.
There is no implicit VBA style conversion to string. In SQL Server data type precedence means ints are higher then nvarchar: so the whole string is trying to be CAST to int.
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ CAST(#T_ID AS nvarchar(10)) + ' AS To_ID ,' ...
Edit: Will A has a good point: watch for NULLs!

If you have to build this kind of dynamic SQL, it is better to get the column information from the meta-data than to pass it around.
Select * from Information_Schema.Columns Where Table_name=#TableName
The you have to write an ugly cursor to build the SQL. Expect performance problems. I do lots of this during development to write code for me, but I don't dare run it in production.

Related

Passing special character [char(11), char(7)] in stored procedure as string

I want to search in table for all records with a special characters - char(11), char(7) etc.
I have found one stored procedure which helps me to find it. But it is not accepting the input parameters as follows:
EXEC sp_FindStringInTable '%'+char(7)+'%', 'CPOA-TALENTLink-Test-Leeds', 'TALENT_Contact_ChangeLog'
Error:
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '+'.
Stored procedure:
CREATE PROCEDURE dbo.sp_FindStringInTable
#stringToFind NVARCHAR(100),
#schema SYSNAME,
#table SYSNAME
AS
BEGIN TRY
DECLARE #sqlCommand VARCHAR(MAX) = 'SELECT * FROM [' + #schema + '].[' + #table + '] WHERE '
SELECT #sqlCommand = #sqlCommand + '[' + COLUMN_NAME + '] LIKE ''' + #stringToFind + ''' OR '
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = #schema
AND TABLE_NAME = #table
AND DATA_TYPE IN ('char','nchar','ntext','nvarchar','text','varchar')
SET #sqlCommand = LEFT(#sqlCommand, LEN(#sqlCommand) - 3)
EXEC (#sqlCommand)
PRINT #sqlCommand
END TRY
BEGIN CATCH
PRINT 'There was an error. Check to make sure object exists.'
PRINT error_message()
END CATCH
As mentioned in error, I'm unable to search through table for special characters.
In calls to procedures expressions other than literals or variables don't work.
Assign the concatenation to a variable and pass that variable to the procedure.
DECLARE #p varchar(max) = '%' + char(7) + '%';
EXEC sp_FindStringInTable #p, 'CPOA-TALENTLink-Test-Leeds', 'TALENT_Contact_ChangeLog';
Literal string concatenation like '[' + #schema + ']' and ' LIKE ''' + #stringToFind + '''' isn't safe. Far from it. I suspect that parametrising your query is going to fix this:
CREATE PROCEDURE dbo.sp_FindStringInTable #stringToFind NVARCHAR(100), #schema sysname, #table sysname
AS
BEGIN TRY
DECLARE #sqlCommand varchar(max);
SET #sqlCommand = N'SELECT *' + NCHAR(10) + --Formatting yoru dynamic SQL is a very good idea
N'FROM ' + QUOTENAME(#schema) + N'.' + QUOTENAME(#table) + NCHAR(10) +
N'WHERE' +
STUFF((SELECT NCHAR(10) + N' AND ' + QUOTENAME(COLUMN_NAME) + N'LIKE #String'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = #schema
AND TABLE_NAME = #table
AND DATA_TYPE IN ('char','nchar','ntext','nvarchar','text','varchar')
FOR XML PATH(N'')),1,6,N'')
PRINT #sqlCommand; --your best friend
EXEC sp_executesql #sqlCommand, N'String nvarchar(100)', #String = #stringToFind;
END TRY
BEGIN CATCH
PRINT 'There was an error. Check to make sure object exists.'
PRINT error_message()
END CATCH
Note that I have not tested the above. Your best friend is there to help you debug.

Dynamic SQL, Empty Variable, Formatting Issue?

I am using dynamic SQL to build out some statements. Here is a truncated example of a stored proc UpdateFOO. When I debug this stored procedure, the problem is the #SQL variable I am declaring always stay empty! It is supposed to fill with the query. I suspect it has something to do with how I am formatting this, but I cant spot if its a bad formatting error.
CREATE PROC [dbo].[UpdateFOO]
#TEST1 uniqueidentifier,
#TEST2 nvarchar(40),
#TEST3 nvarchar(50),
#TEST4 char(1),
#TEST5 nvarchar(20),
#TEST6 nvarchar(40),
#LINKED_SERVER_NAME nvarchar(max),
#DATABASE_NAME nvarchar(max)
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRAN
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'UPDATE [' + #LINKED_SERVER_NAME + '].[' + #DATABASE_NAME + '].[dbo].[SOME_TABLE]
SET [TEST1]=' + '''' + convert(nvarchar(36), #TEST1) + '''' +',
[TEST2]=' + '''' +#TEST2 + '''' +',
[TEST3]=' + '''' + #TEST3 + '''' +',
[TEST4]='+ '''' + #TEST4 + '''' +',
[TEST5]=' + '''' + #TEST5 + '''' +',
[TEST6]=' + '''' + #TEST6 + '''' +
' WHERE [TEST1] =' + '''' + convert(nvarchar(36), TEST1 )+ '''' +
+ 'SELECT [TEST1] FROM
[' + #LINKED_SERVER_NAME + '].[' + #DATABASE_NAME + '].[Rev].[SOME_TABLE]
WHERE [TEST1] =' + '''' + convert(nvarchar(36), TEST1 )+ '''' +''
PRINT LEN(#SQL)
EXEC (#SQL)
COMMIT
TIA Experts!
Here's how you use ISNULL. If the first value is null, it will return the 2nd value.
SELECT #SQL = 'UPDATE [' + #LINKED_SERVER_NAME + '].[' + #DATABASE_NAME + '].[dbo].[SOME_TABLE]
SET [TEST1]=' + '''' + convert(nvarchar(36), ISNULL(#TEST1, '')) + '''' +',
[TEST2]=' + '''' +ISNULL(#TEST2, '') + '''' +',
[TEST3]=' + '''' + ISNULL(#TEST3, '') + '''' +',
...
etc...
You should use parametrized query. Here's an example:
DECLARE #sql nvarchar(max), #paramlist nvarchar(max)
SELECT #sql= 'UPDATE Table
SET Col1 = #Value1,
Col2 = #Value2
WHERE (1 = 1)'
SELECT #paramlist = '#Value1 nvarchar (256), #Value2 nvarchar (256)'
EXEC sp_executesql #sql, #paramlist, #Value1, #Value2

How to grab the value of the output parameter in execute sp_executesql?

Please forgive newbie's ignorance!
How do I grab the value of the output parameter in execute sp_executesql?
I can see the output but cannot get to it:
DECLARE #LastActivity nvarchar(100)
DECLARE #LastActivityDate datetime
DECLARE #sql nvarchar(MAX)
DECLARE #RowsToProcess int
DECLARE #CurrentRow int
DECLARE #SelectCol1 nvarchar(100)
DECLARE #SelectCol2 nvarchar(100)
DECLARE #SelectCol3 nvarchar(100)
DECLARE #LastDate TABLE (RowID int not null primary key identity(1,1), col4 nvarchar(MAX), col5 sql_variant)
DECLARE #table1 TABLE (RowID int not null primary key identity(1,1), col1 nvarchar(100),col2 nvarchar(100),col3 nvarchar(100))
INSERT into #table1 (col1,col2,col3)(SELECT t.name AS col1, c.name AS col2, m.Field1 as col3
FROM sys.columns c INNER JOIN
sys.tables t ON c.object_id = t.object_id INNER JOIN
sys.schemas s ON t.schema_id = s.schema_id INNER JOIN
dbo.MERGE_TABLES m ON m.Table_Name=t.name
WHERE c.name LIKE '%[_]DATE%' and m.[Enabled]='Y')
SET #RowsToProcess=##ROWCOUNT
SET #CurrentRow=0
WHILE #CurrentRow<#RowsToProcess
BEGIN
SET #CurrentRow=#CurrentRow+1
SELECT #SelectCol1=col1,#SelectCol2=col2,#SelectCol3=col3 FROM #table1 WHERE RowID=#CurrentRow
SET #sql='SELECT ' + '[dbo].[ConvertToDatetime](MAX(' + #SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
Declare #params as nvarchar(MAX)
Set #params = '#date sql_variant output'
Declare #date as sql_variant;
execute sp_executesql
#sql
,#params
,#date output
Select #date
INSERT into #LastDate VALUES (#sql, #date)
end
select col4,col5 from #LastDate
select col4,col5 from #LastDate gives me the SQL script in clo4 but col5 is empty! I need to store the #date as I still need to get the Max(#date)
Thanx a million.
SET #sql='set #date =('SELECT ' + '[dbo].[ConvertToDatetime](MAX(' +
#SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3
+ ' = ' + '''0722607QZ''' ) '
the above sql gives me error: Incorrect syntax near '.'
SET #sql='set #date =(SELECT [dbo].[ConvertToDatetime](MAX( + #SelectCol2 + ))
FROM #SelectCol1 Where #SelectCol3 ''=0722607QZ'' ) '
The above sql gives the error: Must declare the scalar variable "#SelectCol2"
SET #sql='SELECT ' + #date + '=convert(nvarchar(100), [dbo].[ConvertToDatetime](MAX(' + #SelectCol2 + ')))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
the above produces the error : Implicit conversion from data type sql_variant to nvarchar is not
allowed. Use the CONVERT function to run this query.
SET #sql='SELECT ' + #date + '=convert(nvarchar(MAX),(MAX(' + #SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
the above produces no error but all output is NULL, no values.
Your syntax looks ok but you never assign to the output variable #date hence no value.
Instead of;
SET #sql='SELECT ...'
You need;
SET #sql='set #date = (SELECT ...'
Can't you use a better type than sql_variant?

Table Columns as parameters to Stored Procedure [duplicate]

I have created a procedure in dynamic SQL which has a select statement and the code looks like:
ALTER PROCEDURE cagroup (
#DataID INT ,
#days INT ,
#GName VARCHAR(50) ,
#T_ID INT ,
#Act BIT ,
#Key VARBINARY(16)
)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SchemaName SYSNAME
DECLARE #TableName SYSNAME
DECLARE #DatabaseName SYSNAME
DECLARE #BR CHAR(2)
SET #BR = CHAR(13) + CHAR(10)
SELECT #SchemaName = Source_Schema ,
#TableName = Source_Table ,
#DatabaseName = Source_Database
FROM Source
WHERE ID = #DataID
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ #T_ID + ' AS To_ID ,' + #BR
+ #DataID + ' AS DataSoID ,' + #BR
+ #Act + ' AS Active ,' + #BR
+ Key + ' AS key' + #BR
+ 'R_ID AS S_R_ID' + #BR
+ 'FROM' + #DatabaseName + '.'
+ #SchemaName + '.'
+ #TableName + ' t' + #BR
+ 'LEFT OUTER JOIN Gro g ON g.GName = '
+ #GName + #BR + 'AND g.Data_ID] =' + #DataID + #BR
+ 't.[I_DATE] > GETDATE() -' + #days + #BR
+ 'g.GName IS NULL
AND ' + #GName + ' IS NOT NULL
AND t.[Act] = 1' + #BR
PRINT (#SQL)
END
When I am executing this procedure with this statement:
Exec dbo.cagroup 1,10,'[Gro]',1,1,NULL
I am getting the following error.
Msg 245, Level 16, State 1, Procedurecagroup, Line 33
Conversion failed when converting the nvarchar value 'SELECT [Gro] AS GName ,
' to data type int.
Where am I doing wrong?
You need to CAST all numbers to nvarchar in the concatenation.
There is no implicit VBA style conversion to string. In SQL Server data type precedence means ints are higher then nvarchar: so the whole string is trying to be CAST to int.
SET #SQL = 'SELECT ' + #GName + ' AS GrName ,' + #BR
+ CAST(#T_ID AS nvarchar(10)) + ' AS To_ID ,' ...
Edit: Will A has a good point: watch for NULLs!
If you have to build this kind of dynamic SQL, it is better to get the column information from the meta-data than to pass it around.
Select * from Information_Schema.Columns Where Table_name=#TableName
The you have to write an ugly cursor to build the SQL. Expect performance problems. I do lots of this during development to write code for me, but I don't dare run it in production.

Update Statement in a Stored Procedure

I have a stored Procedure called Active and the code is:
CREATE PROCEDURE dbo.Active
(
#ID INT ,
#Source VARCHAR(25)
)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SchemaName SYSNAME
DECLARE #TableName SYSNAME
DECLARE #DatabaseName SYSNAME
DECLARE #BR CHAR(2)
SET #BR = CHAR(13) + CHAR(10)
SELECT #SchemaName = Source_Schema ,
#TableName = Source_Table ,
#DatabaseName = Source_Database
FROM Source
WHERE ID = #ID
SET #SQL = 'UPDATE Source_Table' + #BR
+ 'SET __ACTIVE = CASE WHEN rn = 1 THEN 1 ELSE 0 END' + #BR
+ 'FROM ( ' + #BR + 'SELECT ROW_NUMBER() OVER (PARTITION BY '
+ #Source + ' ORDER BY __REC_ID DESC) AS rn
, * FROM ' + #DatabaseName + '.' + #SchemaName + '.' + #TableName
+ #BR + ') Source_Table' + #BR
EXEC #SQL
END
The problem is I am using thsi procedure in another procedure so Everytime that Procedure runs this procedure also runs and does the update to whole table.
The main reason for that update is to check for the duplicates on the table and set the duplicates to 0 and remaining to 1.
I dont want to run this update for whole table but I want the update to run only for Active duplicates.
Is there a way to do it?
To reiterate your issue. You are calling the above stored procedure from another stored procedure. I assume that the parent procedure is what is determining what you call "Active duplicates". If that is the case, then you have a few options:
1) Temp table, have the first procedure create a global temporary table and use it in nested procedure. Make sure to clean up after.
--Base procedure creates global temp table with proper values;
SELECT ID
INTO ##ActiveDups
FROM DUPTABLE
WHERE SOMECONDITION = SOMECONDITION
--Join global temp table on query
SET #SQL = 'UPDATE Source_Table' + #BR
+ 'SET __ACTIVE = CASE WHEN rn = 1 THEN 1 ELSE 0 END' + #BR
+ 'FROM ( ' + #BR + 'SELECT ROW_NUMBER() OVER (PARTITION BY '
+ #Source + ' ORDER BY __REC_ID DESC) AS rn
, * FROM ' + #DatabaseName + '.' + #SchemaName + '.' + #TableName
+ #BR + ') Source_Table' + #BR
+ ' INNER JOIN ##ActiveDups ad ON ad.ID = Source_Table.ID'
--Drop global temp table
DROP TABLE ##ActiveDups
2) Parameter, pass a comma separated list to the nested procedure and filter with IN or EXISTS clause. Not very scalable. (See added parameter and last line of query)
CREATE PROCEDURE dbo.Active
(
#ID INT ,
#Source VARCHAR(25),
#List VARCHAR(MAX)
)
--...
SET #SQL = 'UPDATE Source_Table' + #BR
+ 'SET __ACTIVE = CASE WHEN rn = 1 THEN 1 ELSE 0 END' + #BR
+ 'FROM ( ' + #BR + 'SELECT ROW_NUMBER() OVER (PARTITION BY '
+ #Source + ' ORDER BY __REC_ID DESC) AS rn
, * FROM ' + #DatabaseName + '.' + #SchemaName + '.' + #TableName
+ #BR + ') Source_Table' + #BR
+ ' WHERE SOMECONDITION IN ' #List
3) Add logic to your dynamic SQL to fetch the proper results. (See last line, which was appended. I cannot determine for you what that logic may be.)
SET #SQL = 'UPDATE Source_Table' + #BR
+ 'SET __ACTIVE = CASE WHEN rn = 1 THEN 1 ELSE 0 END' + #BR
+ 'FROM ( ' + #BR + 'SELECT ROW_NUMBER() OVER (PARTITION BY '
+ #Source + ' ORDER BY __REC_ID DESC) AS rn
, * FROM ' + #DatabaseName + '.' + #SchemaName + '.' + #TableName
+ #BR + ') Source_Table' + #BR
+ ' WHERE SOMECONDITION = SOMECONDITION'