Cannot cast int to varchar in STUFF while joining select - tsql

I am using stuff to join results of select. The columns I need to join are not constant and could change. So I put them in a variable and prepare a dynamic query.
SET #sql = N'Select #newvalues = STUFF( ( Select '','' + ' + #columns + ' FROM #MYINSERTED FOR XML PATH(''''),TYPE)
.value(''.'',''VARCHAR(MAX)''),1,2,'''')'
This query transforms into what looks like follows:
Select #newvalues = STUFF(
( Select ',' + ID, CaseID, DocumentType, FileName, FileExtension, FilePath, InsertDate, InsertedBy, ModifiedDate, ModifiedBy, OriginalFileName
FROM #MYINSERTED FOR XML PATH(''),TYPE)
.value(''.'',''VARCHAR(MAX)''),1,2,'')
But the first column ID is an integer and I get the following error.
Conversion failed when converting the varchar value ',' to data type int.
Please guide as to if there could be any workaround and I cannot cast each column individually as columns could change.

No reason to use STUFF, FOR XML PATH, etc. to merge the output of all columns into a single string. Simply make sure that all columns are explicitly cast when you construct your dynamic SQL.
How do you construct your #columns variable? I assume it contains something like "ID, CaseID, DocumentType, ...". You must construct a similar variable that does the explicit casting, or transform your #columns variable like this:
SET #columnsCast = 'CAST(' + REPLACE(#columns, ',', ' AS VARCHAR(MAX)) + '', '' CAST( ') + ' AS VARCHAR(MAX))'
This should make the #columnsCast variable look like this:
CAST(ID AS VARCHAR(MAX)) + ', ' + CAST(CaseID AS VARCHAR(MAX)) + ', ' + CAST(DocumentType AS VARCHAR(MAX)) + ', ' ...
Then simply perform a dynamic SQL statement like this:
SET #sql = N'DECLARE #newvalues AS VARCHAR(MAX) = '''';
SELECT #newvalues = #newvalues + ' + #columnsCast + ' FROM #MYINSERTED'
Then when you execute this expression, make sure to output #newvalues like this:
DECLARE #newvals AS VARCHAR(MAX)
EXECUTE sp_executesql #sql, N'#newvalues VARCHAR(MAX) OUTPUT', #newvalues=#newvals OUTPUT
SELECT #newvals

Related

The name "xxx" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables

I want to separate a table's columns into two set (1st set = bottom 50%, 2nd set = top 50%, there is a reason why I am not using a median formula in this case and I know that there will be a case when the count([ORDINAL_POSITION]) will be an odd number, then I won't get accurate result.) to achieve this I am trying to use INFORMATION_SCHEMA.COLUMNS, but I can't figure it out why I got the following error message:
The name "sometable" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.
DECLARE #table2 NVARCHAR(MAX)
DECLARE #table_op_mid INT
SET #table2 = 'sometable'
SELECT #table_op_mid = 'SELECT ROUND(MAX([ORDINAL_POSITION])/2,0) AS OP FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '+#table2+''
PRINT (table_op_mid)
EXEC (#table_op_mid)
1st problem is that #table_op_mid is declared as INT instead of VARCHAR
2nd problem is that #table2 need extra quotes when used in TABLE_NAME comparision
3rd problem is that table_op_mid is missing # symbol, should be PRINT(#table_op_mid)
DECLARE #table2 NVARCHAR(MAX)
DECLARE #table_op_mid NVARCHAR(MAX)
SET #table2 = 'sometable'
SELECT #table_op_mid = 'SELECT ROUND(MAX([ORDINAL_POSITION])/2,0) AS OP FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '''+#table2+''''
PRINT (#table_op_mid)
EXEC (#table_op_mid)
EDIT
After your comment.. It is the same problem as before.. #SQL_columnnull_part_2 should be VARCHAR instead of INT
declare #db2 varchar(max) = 'MyDb'
declare #table2 varchar(max) = 'sometable'
declare #SQL_columnnull_part_2 varchar(max) = ''
Also, your new query will not work because STRING_AGG doesn't add last separator, so you should move the comparision term in the 1st parameter and keep in separator only the ';'
SELECT #SQL_columnnull_part_2 = STRING_AGG(
'UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET ' + QUOTENAME(COLUMN_NAME,'['']') + ' = NULL WHERE ' + QUOTENAME(COLUMN_NAME,'['']') + ' = ''''',
'; '
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND [ORDINAL_POSITION] > #table_op_mid

SQL Server 2012: Dynamic crosstab in stored procedure

Im trying to use a stored procedure to create a pivot table between two declared variables #rvar (as rowvariable) and #cvar (as columnvariable). The point is to call the stored procedure from VBA using these two as dynamic input when executing the stored procedure.
My code has three parts:
creating test-data
declaring locals
finding names of columns in crosstab an storing in new local #sql1
executing crosstable with the pivotfunction using the names stored in #sql1.
My problem: The code below works but I would like to make it dynamic regarding the variable defining the column structure - currently set to "q10_1_resp" - so that I only have to declare the local #cvar and use that in part 3 (like in part 4). I have succeeded in making part 3 into a sql-string with subsequent execution but then the column names stored in #sql1 cannot be used in the code in part 4 (I guess it is a scope thing).
--Part 1
create table [user].[test]
(rowvar nvarchar(max),
q10_1_resp int,
q10_2_resp int)
GO
INSERT [user].[test]
VALUES ('PH',1,2),
('PH',2,3),
('EA',1,5),
('EA',5,4),
('PH',3,4),
('PH',6,6),
('EA',4,1),
('PH',5,3),
('PH',2,1)
GO
-- Part 2
declare #rvar as nvarchar(max) = 'rowvar'
declare #cvar as nvarchar(max) = 'q10_1_resp' --this input should be dynamic as well
declare #sql1 as nvarchar(max)= ''
declare #sql2 as nvarchar(max)= ''
-- Part 3
select #sql1 = #sql1 + [a].[col] + char(44)
from
(select distinct QUOTENAME(q10_1_resp) as [col]
from [user].[test]
group by q10_1_resp) as a
SET #sql1 = left(#sql1, len(#sql1) - 1)
-- Part 4
SET #sql2 = 'select ' +
+ #rvar + ','
+ #sql1
+ ' from (Select '
+ #rvar + ', '
+ #cvar
+ ' from [user].[test]) sq pivot(count('
+ #cvar
+ ') for '
+ #cvar + ' IN ('
+ #sql1
+ ')) as pt'
exec sp_executesql #sql2
After a great deal of trying to globalize the scalarvariable without success using a temporary table to store the string was the key. The temporary table created at the beginning of the stored procedure can be assigned and referenced the entire time of the procedure. Thus assigning within execution of #sql and then referencing the string at execution of #sql2. I hope it makes sense.
CREATE PROCEDURE [dbo].[sp_crosstab]
-- Add the parameters for the stored procedure here
#rvar nvarchar(max) = '',
#cvar nvarchar(max) = '',
#data nvarchar(max) = '',
#sql nvarchar(max) = '',
#sql2 nvarchar(max) = '',
#sql3 nvarchar(max)=''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
create table #temp_crosstab
(
sqlstr nvarchar(max)
)
set #sql ='
declare #sql1 nvarchar(max) = char(00)
select #sql1 = #sql1 + [a].[col] + char(44)
from
(select distinct QUOTENAME(' + #cvar + ') as [col]
from ' + #data + '
group by ' + #cvar + ') as a
SET #sql1 = left(#sql1, len(#sql1) - 1)
insert into #temp_crosstab values (#sql1)'
execute sp_executesql #sql
select #sql3 = [sqlstr] from #temp_crosstab
set #sql2 = '
select ' + #rvar + char(44) +
#sql3 + 'from (Select '
+ #rvar + char(44) + ' '
+ #cvar
+ ' from ' + #data + ') sq pivot(count('
+ #cvar
+ ') for '
+ #cvar + ' IN ('+#sql3+')) as pt'
exec sp_executesql #sql2
END
GO

Getting Error With GROUP BY when Converting Query to Dynamic SQL

I'm trying to convert the following query to dynamic SQL to allow for variations:
UPDATE T
SET SumCount = J.SUM
FROM #temp T
JOIN (SELECT Count_99221 + COUNT_99222 + Count_99223 [SUM], t2.userID
FROM #temp t2
GROUP BY t2.userID, Count_99221 + COUNT_99222 + Count_99223
) J ON T.userID = J.UserID
This is what I have for the Dynamic SQL:
DECLARE #sql3 nvarchar(2000) =
'UPDATE T ' +
'SET SumCount = J.SumOfColumns ' +
'FROM #temp T ' +
'JOIN (SELECT ' + #columnSumString + ' [SumOfColumns], t2.userID ' +
'FROM #temp t2 ' +
'GROUP BY t2.userID, ' + #columnSumString +
' ) J ON T.userID = J.UserID'
EXEC sp_executesql #sql3
I am receiving the following error only when I run the query as Dynamic SQL:
Each GROUP BY expression must contain at least one column that is not
an outer reference.
Can somebody help explain why this is happening? I am new to Dynamic SQL so I'm not privy to any limitations for running queries this way.
Thank you in advance.
EDIT:
The variable #columnString is a string made by concatenating several other column names, created in the following way:
DECLARE #Cursor Cursor
DECLARE #code varchar(20)
DECLARE #ID INT
SET #cptCursor = CURSOR FOR
SELECT * FROM dbo.Split(#UserInput,CHAR(44))
OPEN #cptCursor
FETCH NEXT FROM #cptCursor INTO #ID, #code
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #colName varchar(50) = 'Count_' + cast(#code as varchar(10))
DECLARE #sql nvarchar(50) = 'ALTER TABLE #temp ADD ' + #colName + ' int'
EXEC sp_executesql #sql
--Code that adds values to each column that is created.....
SET #columnSumString = #colName + ' + ' + #columnSumString
--SET #columnSumString = #code + ' + ' + #columnSumString
FETCH NEXT FROM #cptCursor INTO #ID, #code
END
CLOSE #Cursor
DEALLOCATE #Cursor
SET #columnSumString = SUBSTRING(#columnSumString,1,LEN(#columnSumString)-2)
SELECT #columnSumString
The user input is a comma separated string. "Count_99221 + COUNT_99222 + Count_99223" is just one example of columns created from the user input "99221, 99222, 99223".
I also realized I was concatenating the #code variable into #columnSumString instead of #colName. Now when I run the query I don't get the error (even though I don't understand how the above error message relates to that mistake) but every value of SumCount is NULL.
IMHO you must re-write your query as follow:
UPDATE #temp
SET SumCount =
(SELECT Count_99221 + COUNT_99222 + Count_99223
FROM #temp t2
WHERE t2.userID = #temp.userID)
So the dynamic SQL will become:
DECLARE #columnString varchar(200)
SET #columnString = Count_99221 + COUNT_99222 + Count_99223
DECLARE #sql3 nvarchar(2000) =
N'UPDATE #temp ' +
'SET SumCount = (SELECT ' + #columnString +
' FROM #temp t2 WHERE t2.userID = #temp.userID)'
EXEC sp_executesql #sql3

How can i pass parameters to a Common Table Expression inside a sp?

I want to rewrite a functioning, but slow stored procedure using CTE (common table Expression)
I have a big stored procedure where i build the nececary SQL dynamicaly based on parameter used. Currently there are 27 parameters, i compose the SQL string that i execute at the end it looks like:
DECLARE #SqlWhereClause NVARCHAR(MAX)
SET #SqlWhereClause = ' WHERE ([InTimeStamp] BETWEEN ''' + CONVERT(VARCHAR(19), #fromDate, 120) + ''' AND ''' + CONVERT(VARCHAR(19), #toDate, 120) + ''')'
IF #showOnlyErrors = '1'
BEGIN
SET #SqlWhereClause += ' AND Status = ''Error'''
END
IF LEN(LTRIM(RTRIM(#docNo))) > 0
BEGIN
IF #matchExact = '1'
BEGIN
SET #SqlWhereClause += ' AND DocumentNumber = ''' + #docNo + ''''
END
ELSE
BEGIN
SET #SqlWhereClause += ' AND (contains([DocumentNumber],'''+ #docNo +'''))'
END
END
At the end, i add the pagination and transform it to the final formSQL:
IF CONVERT(int, LTRIM(RTRIM(#takeRows))) > 0
BEGIN
SET #SqlOrderByClause += ' OFFSET ' + #rowNumberToSkip +' ROWS FETCH NEXT '+ #takeRows +' ROWS ONLY '
Set #RowCount = ' Select #totalRecords = count(1) from dbo.Messages WITH (NOLOCK) ' + #SqlWhereClause
END
SET #SQL = #SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause + ' ; ' + #RowCount
PRINT #SQL
EXECUTE sp_executesql #SQL, #params, #totalRecords OUTPUT
Everithing is working like a charm. No problems. Only performance problems. To solve one of it, i would try to use CTE (common table extpression)
But this is not working:
With DataSQL AS
(#SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause),
- incorrect syntax near #SqlSelect - Expecting '(' or Select.
I also tried this one:
WITH DataSQL AS
( Select #SqlSelect From #SqlFromFast
Where #SqlWhereClause Order By #SqlOrderByClause),
here i get:
An expression of non-boolean type specified in a context where a condition is expected, near 'Order'
Any idea? Or is it not possible to use CTE with multiple variables? All i found until now are simply queries with one, maybe two variables.
You could try:
WITH DataSQL AS
(
SELECT #SqlSelect SqlSelect
, #SqlFromFast SqlFromFast
, etcetera
)
Make sure you give aliases, or it won't work. I doubt it will help your performance issues though. You could try to use a temp table, that's usually quicker. you could also try to use inner variables:
DECLARE #Select VARCHAR(MAX) = #SQLSelect
And use those instead, that may help the optimizer, though that depends on your data.

Export query to text file

What I am trying to do is export a Tsql query to a csv file. Simple enough, however I need to be able to specify which fields are wrapped in quotes "". I can get my query to export with all the feilds wrapped.
"SCHEN001","Joe Bloggs Inc","1","1","1","1","1","1","13","6","Mr John Smith"
What I would like to export is
"SCHEN001","Joe Bloggs Inc",1,1,1,1,1,1,13,6,"Mr John Smith"
Is this possible using Tsql?
Any ideas would be greatly appreciated
Take a look at the bcp.exe utility. It is ment for bulk operations and you can specify templates for exports etc.
A link that seems reasonable: http://www.simple-talk.com/sql/database-administration/creating-csv-files-using-bcp-and-stored-procedures/
Another approach is to use SQL Server Integration Services (if you have MS SQL Server Standard or Enterprise edition)
or, alternatively, you can copy grid results into Excel, and export CSV from there :-)
Try to use this script.
Set variable #TblName to the name of your table.
The script uses information_schema.columns
to get the datatypes for every column in selected table.
DECLARE #TblName varchar(128)
DECLARE #WhereClause varchar(255)
DECLARE #cmd varchar(7000)
SET #TblName = '<YOUR TABLENAME>' --TABLENAME
SET #cmd = ''
create table #tableDef (id int identity (1,1), ColType int, ColName varchar(128))
--Fetch table information
insert #tableDef (ColType, ColName)
select case when DATA_TYPE like '%char%' then 1
when DATA_TYPE like '%datetime%' then 2
else 0 end ,
COLUMN_NAME
from information_schema.columns
where TABLE_NAME = #TblName
order by ORDINAL_POSITION
SELECT #cmd = #cmd
+ ' CASE WHEN ' + ColName + ' IS NULL '
+ ' THEN ''NULL'' '
+ ' ELSE '
+ case ColType
when 1 then ''''''''' + ' + ColName + ' + '''''''''
when 2 then ''''''''' + ' + 'CONVERT(VARCHAR(20),' + ColName + ')' + ' + '''''''''
else 'CONVERT(VARCHAR(20),' + ColName + ')' end
+ ' END + '','' + '
from #tableDef
order by id
select #cmd = 'SELECT ' + left(#cmd,len(#cmd)-8) + '+'''' FROM ' + #tblName
exec (#cmd)
drop table #tableDef