SQL select statement to a parameter - sql-server

I am trying to build a parameter, that contains a comma separated list columns, so I can use this dynamically later on. However i'm having an issue writing my select query, into a parameter that can be used to a separate select.
This is my parameter, to define the comma seperated list;
DECLARE #sqlSrcCols VARCHAR(MAX)
SET #sqlSrcCols = '
SELECT STUFF((SELECT '','' + a.sourceName
FROM #matchedFields a
FOR XML PATH('''')), 1, 1, '''') AS listStr'
This outputs a comma seperated list like so;
abc,cde,dfg,thy
I want to then reference that list, in a query like below:
DECLARE #sqlSelect VARCHAR(MAX) = 'SELECT ' + #sqlSrcCols + 'FROM a.TableName WHERE ExpDat = ''2020-01-01'' '
EXEC(#sqlSelect)
Although I can't use #sqlSrcCols within my query, and I can't use EXEC(#sqlSrcCols).
Any help welcomed.

At a guess, but I think what you actually mean to do is:
DECLARE #sqlSrcCols nvarchar(MAX); --Fixed datatype
SET #sqlSrcCols = STUFF((SELECT N',' + QUOTENAME(mF.sourceName) --Quoting is important
FROM #matchedFields mF --"a" for matchedFields?
FOR XML PATH(N''), TYPE).value('.', 'nvarchar(MAX)'),1,1,''); --used TYPE, in case of any odd characters
DECLARE #sqlSelect nvarchar(MAX);
SET #sqlSelect = N'SELECT ' + #sqlSrcCols + N' FROM a.TableName WHERE ExpDat = ''20200101'';'; --ISO dates are better, they won't fail depending on language/data type
--PRINT #SQL; --You debugging friend
EXEC sp_executesql #SQL; --Don't use EXEC(#SQL), use sp_executesql so you can parametrise when needed.
You weren't setting the value of #sqlSrcCols to be the names of the columns, you were setting it to the SQL statement. Then, when you concatenated the 2 variables, you ended up with a statement that would have looked like this:
SELECT
SELECT STUFF((SELECT ',' + a.sourceName
FROM #matchedFields a
FOR XML PATH('')), 1, 1, '') AS listStrFROM a.TableName WHERE ExpDat = '2020-01-01'
Which, as you can see, doesn't make any sense.
Bonus: I would actually write the above like you see below. The reason being is that then you have formatting in your dynamic SQL too; making it easier to debug. I have also parametrised #ExpDat, incase it needs to be (and this therefore shows you have to parametrise a dynamic statement):
DECLARE #sqlSrcCols nvarchar(MAX), --Fixed datatype
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #sqlSrcCols = STUFF((SELECT N',' + #CRLF +
N' ' + QUOTENAME(mF.sourceName) --Quoting is important
FROM #matchedFields mF --"a" for matchedFields?
FOR XML PATH(N''), TYPE).value('.', 'nvarchar(MAX)'),1,10,N''); --used TYPE, in case of any odd characters
DECLARE #sqlSelect nvarchar(MAX);
SET #sqlSelect = N'SELECT ' + #sqlSrcCols + #CRLF +
N'FROM a.TableName' + #CRLF +
N'WHERE ExpDat = #ExpDat;'; --ISO dates are better, they won't fail depending on language/data type
--PRINT #SQL; --You debugging friend
DECLARE #ExpDat date = '20200101';
EXEC sys.sp_executesql #SQL, N'#ExpDat date', #ExpDat; --Don't use EXEC(#SQL), use sp_executesql so you can parametrise when needed.
If you're interested, you can find some of pointers on Dynamic SQL, what to do and not to do, in my article on SSC.

Related

Passing database name as a parameter in sp_executesql to be replaced in the query

I am trying to execute a SQL statement joining tables from a different database. I would like to know if it is possbile in any way to replace the database name passed as parameter through sp_executesql?
exec sp_executesql N'SELECT
A.* FROM TableA A
INNER JOIN #dbName..TableB B ON A.C1 = B.C1
WHERE B.C2 = #ptr',N'#BillingRunID int,#AccountID int,#dbName nvarchar(4000)',#ptr=1001,#dbName=N'Student'
The problem I am having is that I have the query in a .net project and it is called using sp_executesql during other process because of which the SQL can't be moved to the database as a proc or any other means. Is there any way to get around this issue?
You can't parametrise an object name, it must be injected securely. You could achieve this like so:
DECLARE #dbname sysname, --Obviously this'll be assigned somewhere
#SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT A.*' + #CRLF +
N'FROM dbo.Table A' + #CRLF +
N' INNER JOIN ' + QUOTENAME(#dbName) + N'.dbo.TableB B On A.C1 = B.C1' + #CRLF +
N'WHERE B.C2 = #ptr;';
--PRINT #SQL; --Your best friend
--Oddly, only #ptr is in your script, but it isn't defined in the second parameter.
--I therefore don't know what data type it needs to be.
EXEC sys.sp_executesql #SQL, N'#prt {Your Datatype}', #ptr;

SQL Server Concatenate JSON For Dynamic Queries

I have a dynamic (I am passing parameters in a stored procedure) query in a stored procedure which results in a JSON string. Similar to this:
#PropertyPK uniqueidentifier (Stored Procedure Parameter)
#search_term Varchar(50)
#limit int
#offset int
Declare #test Varchar(1000)
SELECT #test = '
SELECT Cast((
SELECT *
FROM Contacts
Where Address like ''%' + #search_term + '%''' + ' Order By '
+ #sort_by + ' ' + #sort_order + ' OFFSET '
+ Cast(#offset as varchar) +
' ROWS
FETCH NEXT '
+ Cast(#limit as varchar) +
' ROWS ONLY
For JSON Path, INCLUDE_NULL_VALUES )
as varchar(max))'
EXEC(#test)
I have been asked to return results from 2 queries in a JSON format but in one string. Basically run one query into a variable and the second into another and concatenate them together and then deliver the results.. Can someone help me putting the result JSON from the above query into a variable so I can do the same with my second query and concatenate them? Can I do anything after the Exec(#test) to get the result into a variable?
Thank you..
The latest query you've posted isn't dynamic either, so I'm unsure why you're using EXEC. As i mentioned in the comments, therefore, this is as simple as using SET:
DECLARE #PropertyPK uniqueidentifier; --SP parameter
DECLARE #JSON nvarchar(MAX);
SET #JSON = (SELECT *
FROM Contacts
WHERE PropertyPK = #PropertyPK
FOR JSON PATH, INCLUDE_NULL_VALUES);
There's no need for #PropertyPK to be cast as a varchar to a dynamic SQL statement; just use proper parametrised SQL.
This is based on guesswork and the OP's latest (but clearly incomplete) question. If this isn't correct, this should get you on the right path, however, having information drip fed makes the question difficult to answer properly.
DECLARE #PropertyPK uniqueidentifier,
#SearchTerm varchar(50),
#Limit int,
#Offset int,
#SortBy sysname, --objects have the data type sysname, aka nvarchar(128)
#SortOrder nvarchar(4); --Guess datatype as it was missing in your sample
DECLARE #JSON nvarchar(MAX);
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SET #JSON = (SELECT {Columns}' + NCHAR(10) + --Replace {Columns} with an actual list of the required columns (NOT *)
N' FROM dbo.Contacts' + NCHAR(10) +
N' WHERE Address LIKE #Search' + NCHAR(10) +
N' AND PropertyPK = #PropertyPK' + NCHAR(10) + --I ASSUME that WHERE is still needed
N' ORDER BY ' + QUOTENAME(#SortBy) + N' ' + CASE #SortOrder WHEN N'ASC' THEN N'ASC' WHEN N'DESC' THEN 'DESC' END + NCHAR(10) + --The CASE stops invalid sort orders
N' OFFSET #Offset FEETCH NEXT #Limit ROWS ONLY' + NCHAR(10) +
N' FOR JSON PATH, INCLUDE_NULL_VALUES);';
PRINT #SQL; --Your best friend
EXEC sp_executesql #SQL,
N'#JSON nvarchar(MAX) OUTPUT, #Search varchar(50), #PropertyPK uniqueidentifier, #Offset int, #Limit int',
#JSON = #JSON OUTPUT,
#Search = #SearchTerm,
#PropertyPK = #PropertyPK,
#Offset = #Offset,
#Limit = #Limit;
One of the biggest things you need to note here is I have made the SQL SAFE. Your SQL was wide open to SQL injection, which is a huge security flaw. If you don't know/understand SQL injection, I suggest reading about it now. SQL like you have above is a huge problem, and raw string concatenation is an awful idea waiting to be exploited.

Concatenating nvarchar(max) values doesn't seem to work (+= working as =)

Using SQL Server 2012, I've found that trying to build up a string based on an nvarchar(max) column in a table doesn't seem to work correctly. It seems to overwrite, instead of append. Arbitrary Example:
DECLARE #sql nvarchar(max);
SELECT #sql = N'';
SELECT #sql += [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT #sql;
This SHOULD print out all the SQL module definitions for all the various dt_ tables in the database, separated by GO, as a script that could then be run. However... this prints out only the LAST module definition, not the sum of all of them. It's behaving as if the "+=" were just an "=".
If you change it just slightly... cast [definition] to an nvarchar(4000) for example, it suddenly works as expected. Also, if you choose any other column that is NOT an nvarchar(max) or varchar(max) type, it works as expected. Example:
DECLARE #sql nvarchar(max);
SELECT #sql = N'';
SELECT #sql += CAST([definition] AS nvarchar(4000)) + '
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT #sql;
Is this a known bug? Or is this working as expected? Am I doing something wrong? Is there any way for me to make this work correctly? I've tried a dozen different things, including ensuring every expression in the concatenation is the same nvarchar(max) type, including the string literal.
NOTE: The example is just an example that shows the problem, and not exactly what I'm trying to do in real life. If your database doesn't have the "dt*" tables defined, you can change the WHERE clause to specify any group of tables or stored procedures in any database you want, you'll get the same result... only the last one shows up in the #sql string, as if you just did "=" instead of "+=". Also, explicitly stating "#sql = #sql + " behaves the same way... works correctly with every string type EXCEPT nvarchar(max) or varchar(max).
I've verified that none of the [definition] values is NULL as well, so there are no NULL shenanigans going on.
The += operator only applies to numeric data types in SQL Server. Microsoft documentation here
For string concatenation, you need to write the assignment and concatenation separately.
DECLARE #sql nvarchar(max);
SELECT #sql = N'';
SELECT #sql = #sql + [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id);
PRINT #sql;
Also, if you are running this query in Management Studio, keep in mind that there is a limit to the size of the data that it will return (including in a print statement). So if the definitions of your modules exceed this limit, they will be truncated in the output.
To add on to #HABO's comment, the behavior of aggregate string concatenation is undefined and results are plan dependent. Use FOR XML to provide deterministic results and honor the ORDER BY clause:
DECLARE #sql nvarchar(max);
SET #sql =
(SELECT [definition] + N'
GO
'
FROM sys.sql_modules
WHERE OBJECT_NAME(object_id) LIKE 'dt%'
ORDER BY OBJECT_NAME(object_id)
FOR XML PATH(''), TYPE).value('(./text())[1]', 'nvarchar(MAX)');
PRINT #sql; --SSMS will truncate long strings
It works as expected:
DECLARE #sql nvarchar(max);
SELECT #sql =COALESCE(#sql + N' GO ','')+[definition]
FROM sys.sql_modules
Print #sql;
The following code demonstrates taking a column value from a set of ordered rows and, using for xml, creating an aggregated string with separators, including line breaks, between values.
-- Carriage return and linefeed characters.
declare #CRLF as NVarChar(16) = NChar( 13 ) + NChar( 10 );
-- Carriage return and linefeed characters XML encoded.
declare #EncodedCRLF as NVarChar(16) = N'
';
-- Separator to insert between source rows in the accumulated string.
declare #Separator as NVarChar(64) = ';' + #EncodedCRLF + 'go;' + #EncodedCRLF + 'do something with ';
-- Characters in #Separator to remove from the first element in #Result .
declare #SkipCount as Int = 4 + 2 * Len( #EncodedCRLF );
-- The result.
declare #Result as NVarChar( max ) = '';
-- Do it.
select #Result =
Replace(
Stuff(
-- Specify the sorting order of the XML elements in the following select statement.
( select #Separator + name from sys.tables order by name desc for XML path(''), type).value('.[1]', 'VarChar(max)' ),
1, #SkipCount, '' ),
#EncodedCRLF, #CRLF );
-- Output the result.
select #Result as [Result];
-- Output the result with line breaks shown.
print #Result; -- Switch to Messages tab in SSMS to see result.
I´m pretty sure the PRINT command is the reason for the troubles.
When I try:
DECLARE #SQL NVARCHAR(MAX) = N'';
SELECT #sql += SUBSTRING( [definition], 1, 100 ) + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
FROM SYS.SQL_MODULES
PRINT #sql;
I get all my procedures (the first 100 chars ...). So it seems to be a limitation to the PRINT command.
Second try, another database, without "substring":
DECLARE #SQL NVARCHAR(MAX) = N'';
SELECT #sql += [definition] + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10)
FROM SYS.SQL_MODULES
PRINT #sql;
Now I got 1,5 procedures: The 1st one complete, the second stopped in the middle of on line in the proc. There is a limit in PRINT
Third try, to proof my assumption:
DECLARE #SQL NVARCHAR(MAX) = N'';
SELECT #sql += [definition] + 'GO' FROM SYS.SQL_MODULES
PRINT #sql;
Now I got one line of code more! So for me, this is the proof, that PRINT has somehow a limit.
A last attempt:
DECLARE #SQL NVARCHAR(MAX) = N'';
SELECT #sql += [definition] + N'GO' FROM SYS.SQL_MODULES
PRINT SUBSTRING(#SQL,1,4000)
PRINT SUBSTRING(#SQL,4001,4000)
The result: Now I got more then 2 procedures, although there now is a line break after 4K, this may occur on ... bad places.

Dynamic Pivot with column names from dynamic tables

I am working on a project with SSRS and Rockwell Software's RSView32. Basically this software project logs manufacturing data to individual tables as such:
One table (Machine1_TagTable) has Tag names which describe the data as such: TagName, TagIndex. The name provides a human-understandable reference to the information contained in a second table. Example : Part Number, 1
The second table (Machine1_FloatTable) contains raw data with nothing more than a timestamp, TagIndex and value.
Example : 2013-12-10 15:44:11.322, 1, 12345(value)
I have a dynamic pivot which works for ONE table; however, I would like to use a variable parameter passed from SSRS to select both the TagTable and FloatTable.
This works with the Machine1_FloatTable as part of the dynamic statement, but not in the XML path building. I understand this is a scope issue, so I'm looking for creative ways to allow me to pass the table names from SSRS into this stored procedure.
Here's what I have now:
DECLARE #FLOATTABLE NVARCHAR(MAX), #TAGTABLE NVARCHAR(MAX), #startdate NVARCHAR(MAX),
#enddate NVARCHAR(MAX), #cols as NVARCHAR(MAX), #query as NVARCHAR(MAX)
SET #TAGTABLE ='dbo.Machine1_TagTable'
SET #FLOATTABLE = 'dbo.Machine1_FloatTable'
SELECT #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(CONVERT(VARCHAR,TagName),'"')
FROM #tagtable
FOR XML PATH('')),1,1,'')
Set #query = 'SELECT DISTINCT DateAndTime, Millitm, ' + #cols + ' FROM ( select
T.DateAndTime, T.Millitm, N.TagName, T.Val from ' + #FLOATTABLE + ' T LEFT JOIN ' +
#TAGTABLE + ' N ON T.TagIndex=N.TagIndex WHERE T.DateAndTime Between '''+ #startdate +
''' AND '''+ #enddate +''') x PIVOT (MAX(Val) for TagName IN (' + #cols + ')) p'
PRINT (#query)
Any help or suggestions would be greatly appreciated. Thanks!
Well, it's possible, if I understand your question. You have to build a dynamic SQL string, and execute that to populate #cols.
declare #string nvarchar(MAX)
set #String = 'SELECT STUFF((SELECT DISTINCT '','' + QUOTENAME(CONVERT(VARCHAR,ActualDate),'
+ '''"'') FROM ' + #TAGTABLE + ' FOR XML PATH ('''')),1,1,'''')'
EXECUTE sp_executeSQL #String, #Cols OUTPUT
print #cols
It's pretty hacky, but I think it should work.

Error While Executing sp_executesql

exec sp_executesql N'SELECT (STUFF (( SELECT '',''+ NAME from Project WHERE ProjectTypeID=1 and OutputHierarchyID IN (SELECT DISTINCT HierarchyID from HierarchyNode'+ #TextVal +''''
+ N')FOR XML PATH('''')), 1, 1, ''''))AS INDUSTRIES',
N'#Industries NVARCHAR(100) output', #Industries output;
I am getting error
Incorrect syntax near '+'.
You have extra single quotes. Try this
exec sp_executesql N'SELECT (STUFF (( SELECT '',''+ NAME from Project WHERE ProjectTypeID=1 and OutputHierarchyID IN (SELECT DISTINCT HierarchyID from HierarchyNode'+ #TextVal +''
+ N')FOR XML PATH('''')), 1, 1, ''''))AS INDUSTRIES',
N'#Industries NVARCHAR(100) output', #Industries output;
Also it is good pratice to assign dynamic query into a variable and use that varialbe for execution
You can't have an expression in the parameter to any procedure, including sp_executesql (this means you can't do things there like concatenate strings). Try the following, which also allows you to debug the statement in a more simple way (I'm not convinced your query is right, because I don't know why you want to add ' after #TextVal, which I assume is some table suffix):
DECLARE #sql NVARCHAR(MAX), #Industries NVARCHAR(100);
SET #sql = N'SELECT #Industries = STUFF((SELECT '','' + NAME
from Project WHERE ProjectTypeID = 1
and OutputHierarchyID IN
(
SELECT DISTINCT HierarchyID from HierarchyNode' + #TextVal + '
) FOR XML PATH('''')), 1, 1, '''');';
PRINT #sql;
--EXEC sp_executesql #sql, N'#Industries NVARCHAR(100) output', #Industries output;
Though I think this version will be slightly more efficient:
DECLARE #sql NVARCHAR(MAX), #Industries NVARCHAR(100);
SET #sql = N'SELECT #Industries = STUFF((SELECT '','' + NAME
from Project AS p WHERE ProjectTypeID = 1
AND EXISTS
(
SELECT 1 from HierarchyNode' + #TextVal + '
WHERE HierarchyID = p.OutputHierarchyID
) FOR XML PATH('''')), 1, 1, '''');';
PRINT #sql;
--EXEC sp_executesql #sql, N'#Industries NVARCHAR(100) output', #Industries output;
You can inspect the statement and be sure it's correct instead of just blindly throwing it at SQL Server and trying to figure out what the error messages might mean. If you copy and paste the output into the top pane, the error message will point to a line number that you can actually see and will make it easier to troubleshoot your syntax. When you believe it's producing correct output, comment the PRINT and uncomment the EXEC.
If you think the +''''+ after #TextVal is necessary, please tell us the value of #TextVal and the name of the table you expect that query to run against.

Resources