Dynamic sql and spexecutesql behavior - sql-server

I have a table like
c1 c2 c3 c4
-----------------
2 1 7 13
9 2 8 14
1 3 9 15
5 4 10 16
2 5 11 17
11 6 12 18
As in general I would not know the number of columns (in the code #d here 4) to get a string in the form:
2,9,1,5,2,11, 1,2,3,4,5,6, 7,8,9,10,11,12, 13,14,15,16,17,18
To do so I am doing:
DECLARE #d INT
,#counterI INT
,#template AS nvarchar(max)
SET #d = 4;
SET #counterI = 1;
Set #template = 'SELECT STUFF(
( SELECT '','' + CAST([col] AS VARCHAR) FROM (';
WHILE (#counterI < #d) BEGIN
SET #template += ' SELECT [c'+CAST(#counterI-1 AS VARCHAR)+'] AS col FROM [MyTable] UNION ALL ';
SET #counterI = #counterI + 1;
END
Set #template += ' SELECT [c'+CAST(#counterI-1 AS VARCHAR)+'] AS col FROM [MyTable] '
Set #template += ') alldata FOR XML PATH('''') ) , 1 , 1 , '''' )';
declare #CommaString varchar(max)
set #CommaString = ''
exec sp_executesql #template, N'#CommaString varchar(max) out', #CommaString out
So if I do
select #CommaString;
Why is not #CommaString at the moment of selecting it returning the string if when doing the sp_executesql it is printing it right?

I may be missing something about how sp_executesql works, but don't you need something like 'SELECT #CommaString = ...' in #template, so that it assigns the comma string to the out parameter?
Just to clarify, I think you need something like:
DECLARE #d INT
,#counterI INT
,#template AS nvarchar(max)
SET #d = 4;
SET #counterI = 1;
Set #template = 'SELECT #CommaString = STUFF(
( SELECT '','' + CAST([col] AS VARCHAR) FROM (';
WHILE (#counterI < #d) BEGIN
SET #template += ' SELECT [c'+CAST(#counterI-1 AS VARCHAR)+'] AS col FROM [MyTable] UNION ALL ';
SET #counterI = #counterI + 1;
END
Set #template += ' SELECT [c'+CAST(#counterI-1 AS VARCHAR)+'] AS col FROM [MyTable] '
Set #template += ') alldata FOR XML PATH('''') ) , 1 , 1 , '''' )';
declare #CommaString varchar(max)
set #CommaString = ''
exec sp_executesql #template, N'#CommaString varchar(max) out', #CommaString = #CommaString out
As a simpler example, something like this is perhaps easier to read/see what I mean:
declare #CommaString varchar(max)
set #CommaString = ''
exec sp_executesql 'SELECT #CommaString = ''1,2,3''', '#CommaString varchar(max) out', #CommaString = #CommaString out
Incidentally, I've usually seen this kind of thing for string concatenation:
DECLARE #MyString varchar(max)
SET #MyString = ''
SELECT #MyString = #MyString + ',' + MyColumn
FROM MyTable

Related

I am getting an error saying Msg 137, Level 15, State 2, Line 8 Must declare the scalar variable "#UtilityID"

IF EXISTS (SELECT * FROM SYSOBJECTS WHERE [Type] = 'P' AND [Name] = 'GetSiteSetupPagedList')
BEGIN
DROP PROCEDURE [GetSiteSetupPagedList]
END
GO
CREATE PROCEDURE [GetSiteSetupPagedList]
(#UtilityID VARCHAR(6),
#ActiveOnly BIT = 1,
--#UtilityID VARCHAR(6),
-- Table Search Parameters
#PageSize INT = 10,
#RequestedPage INT = 0, -- 0-indexed
#SearchText VARCHAR(MAX) = '',
#UseSelectedIDs BIT = 0,
#SelectedIDs IntList READONLY ,
#OrderBy VARCHAR(MAX) = NULL,
#TotalPages INT OUT)
AS
BEGIN
SET #TotalPages = -1; -- -1 shall be interpreted as "use current value"
IF #UseSelectedIDs = 1
BEGIN
SELECT
[SiteID],
[Site].[Name],
[Site].[DefaultUtilityID],
[Utility].[Name] AS 'DefaultUtilityName'
FROM
#SelectedIDs
JOIN [Site] ON [Int] = [Site].[SiteID] AND [Site].AmendedTime IS NULL
JOIN [Utility] ON [Utility].[UtilityID] = [Site].[DefaultUtilityID] AND [Utility].AmendedTime IS NULL
RETURN
END
DECLARE #SQL NVARCHAR(MAX),
#From VARCHAR(MAX),
#Where VARCHAR(MAX),
#RowsToSkip INT,
#TotalRecords INT
-- Set the default order by
IF ISNULL(#OrderBy, '') = ''
SET #OrderBy = '[Site].[Name], [Site].[CreatedTime]'
-- Calculate Rows to Skip for paging
SET #RowsToSkip = #PageSize * #RequestedPage
-- Create the WHERE clause
SET #Where = ' WHERE [Site].AmendedTime IS NULL
AND [Utility].AmendedTime IS NULL
'
-- Active filter
IF #ActiveOnly = 1
BEGIN
SET #Where = #Where + ' AND [Site].[Active] = 1
AND [Utility].[UtilityID] = #UtilityID
'
END
-- Generic search filter
IF LEN(#SearchText) > 0
BEGIN
SET #Where = #Where + ' AND
convert(varchar(10), [SiteID]) +
[Site].[Name] +
ISNULL([Site].[DefaultUtilityID], '''') +
[Utility].[Name]
LIKE ''%'' + #SearchText + ''%''
'
END
-- Create combined FROM/WHERE clause
SET #From = ' FROM [Site]
JOIN [Utility] ON [Utility].[UtilityID] = [Site].[DefaultUtilityID]
'
+ #Where
-- Calculate total pages if we're loading the first page and not grabbing specific IDs
IF #RequestedPage = 0
BEGIN
SET #SQL = 'SELECT #TotalRecords = COUNT(*) ' + #From + ' '
--print #SQL
EXEC SP_EXECUTESQL #SQL,
N'#TotalRecords INT OUT, #SearchText VARCHAR(MAX)',
#TotalRecords = #TotalRecords OUT, #SearchText = #SearchText
SET #TotalPages = CEILING(CONVERT(DEC(10,2), #TotalRecords) / CONVERT(DEC(10,2), #PageSize))
END
-- Final select
SET #SQL = 'SELECT
[SiteID],
[Site].[Name],
[Site].[DefaultUtilityID],
[Utility].[Name] AS ''DefaultUtilityName''
' + #From + '
ORDER BY ' + #OrderBy + '
OFFSET ' + CONVERT(VARCHAR, #RowsToSkip) + ' ROWS
FETCH NEXT ' + CONVERT(VARCHAR, #PageSize) + ' ROWS ONLY
'
--print #SQL
EXEC SP_EXECUTESQL #SQL,
N'#SearchText VARCHAR(MAX)',
#SearchText = #SearchText
END
`
When I was using:
`
EXECUTE SP_EXECUTESQL #SQL,
N'#UtilityID varchar(6)', -- this is an argument #1 with 1 parameter
N'#SearchText VARCHAR(MAX)', -- this is an argument #2 with 1 parameter
-- that's why we get an error saying too
-- many arguments
#UtilityID = #UtilityID,
#SearchText = #SearchText
`
`
I get the message saying:
Msg 137, Level 15, State 2, Line 8
Must declare the scalar variable "#UtilityID".
Msg 8144, Level 16, State 2, Line 2
Procedure or function has too many arguments specified.
It took me awhile to figure out but this is the correct syntax:
`
`
`
EXECUTE SP_EXECUTESQL #SQL,
N'#UtilityID varchar(6),
#SearchText VARCHAR(MAX)',
`
#UtilityID = #UtilityID,
#SearchText = #SearchText
`

Storing FOR XML PATH results into a variable

Below code works fine and convert table to HTML. It gives the results as HTML tables but I want to assign this to a variable
How can we assign the output to a variable in below code.
CREATE PROC dbo.usp_ConvertQuery2HTMLTable (
#SQLQuery NVARCHAR(3000))
AS
BEGIN
DECLARE #columnslist NVARCHAR (1000) = ''
DECLARE #restOfQuery NVARCHAR (2000) = ''
DECLARE #DynTSQL NVARCHAR (3000)
DECLARE #FROMPOS INT
DECLARE #out table
(
out nvarchar(max)
)
SET NOCOUNT ON
SELECT #columnslist += 'ISNULL (' + NAME + ',' + '''' + ' ' + '''' + ')' + ','
FROM sys.dm_exec_describe_first_result_set(#SQLQuery, NULL, 0)
SET #columnslist = left (#columnslist, Len (#columnslist) - 1)
SET #FROMPOS = CHARINDEX ('FROM', #SQLQuery, 1)
SET #restOfQuery = SUBSTRING(#SQLQuery, #FROMPOS, LEN(#SQLQuery) - #FROMPOS + 1)
SET #columnslist = Replace (#columnslist, '),', ') as TD,')
SET #columnslist += ' as TD'
SET #DynTSQL = CONCAT (
'SELECT (SELECT '
, #columnslist
,' '
, #restOfQuery
,' FOR XML RAW (''TR''), ELEMENTS, TYPE) AS ''TBODY'''
,' FOR XML PATH (''''), ROOT (''TABLE'')'
)
PRINT #DynTSQL
EXEC (#DynTSQL)
SET NOCOUNT OFF
END
Generally, you have 2 options.
Via an intermediate temporary table (table variable).
By itself, exec() returns nothing when a literal or variable is executed, but you can use the rowset produced by it as a source for an insert statement:
-- Option 1
declare #t table (X xml);
declare #Ret xml;
insert into #t (X)
exec('select top 1 * from sys.objects o for xml raw(''TR''), elements, type;');
select top (1) #Ret = t.X from #t t;
select #Ret as [Option1];
go
Switching to the sys.sp_executesql
As Peter has suggested in the comments, you can switch from exec to the sp_executesql system stored procedure, which provides an additional functionality of output parameters:
-- Option 2
declare #s nvarchar(max) = N'set #A = (select top 1 * from sys.objects o for xml raw(''TR''), elements, type);';
declare #Ret xml;
exec sys.sp_executesql #s, N'#A xml = null output', #A = #Ret output;
select #Ret as [Option2];
go

SQL - Remove (Eliminate) columns which have no data

I wonder that;
in SQl, is it possible to not bring the columns which have no data (or zero value)?
Select * from PLAYER_TABLE where PLAYER_NAME='cagri'
it is bringing just 1 row. because there is only one player which PLAYER_NAME is "cagri".
And there are 30 columns for example statistics.
Score-Rebound-PlayedMinutes-Fauls etc....
Score=2
Rebound=0
PlayedMinutes=2
Fauls=0
and I want to see only [Score] and [PlayedMinutes] columns when call my query.
is it possible?
You can use this logic over a stored procedure in SQL
DDL
create table usr_testtable
(player varchar(30),col1 float, col2 float, col3 float, col4 float)
insert into usr_testtable
values ('Jordan',10,20,3,0)
Convert to Stored Proc
declare #playername varchar(30) = 'Jordan' --- pass this value
declare #ctr smallint = 2 -- start from ordinal 2
declare #maxctr smallint = (SELECT max(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable')
declare #columns varchar(max) = ''
declare #columnswithvalues varchar(max) = ''
declare #coltocheck varchar(30)
declare #mysql nvarchar(max)
declare #coloutput varchar(30)
while #ctr <= #maxctr
begin
SELECT #coltocheck = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable'
and ORDINAL_POSITION = #ctr
set #mysql = N'select #output = ' + #coltocheck + ' from usr_testTable where player =''' + #playername + ''' and cast(' + #coltocheck +' as float) > 0'
EXECUTE sp_executesql #mysql,N'#output int OUTPUT',#output = #coloutput OUTPUT;
if #coloutput > 0
begin
set #columns = coalesce(#columns + ',' + #coltocheck,#columns)
set #columnswithvalues = coalesce(#columnswithvalues + char(13) + char(10) + #coltocheck + ' : ' + #coloutput,#columnswithvalues) --- text form
end
set #coloutput = ''
set #ctr= #ctr + 1
end
-- final result in table format
set #mysql = N'select player' + #columns + ' from usr_testTable where player =''' + #playername + ''' '
EXECUTE sp_executesql #mysql
-- final result in text format appended with columnname
select #columnswithvalues -- format to display in text
First create dynamic SQL to select all columns names in the table PLAYER_TABLE except PLAYER_NAME, then unpivot data from PLAYER_TABLE into table PLAYER_TABLE1, then you can search values <> 0 and select this column in second dynamic SQL.
DROP TABLE PLAYER_TABLE1
DECLARE #Player NVARCHAR(50);
DECLARE #columns NVARCHAR(max);
DECLARE #sql NVARCHAR(max);
DECLARE #columns2 NVARCHAR(max);
DECLARE #sql2 NVARCHAR(max);
SET #player='cagri'
SET #columns = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(x.columns)))
FROM (SELECT COLUMN_NAME as columns FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME= 'PLAYER_TABLE' and COLUMN_NAME<>'PLAYER_NAME' ) AS x
ORDER BY X.columns
FOR xml path('')), 1, 1, '');
SET #sql = N' SELECT
PLAYER_NAME, Un_Pivot.Field, Un_Pivot.Value
INTO PLAYER_TABLE1
FROM
(
SELECT * FROM PLAYER_TABLE
) Data
UNPIVOT
(
Value FOR Field IN ('+#columns+')
) AS Un_Pivot';
EXECUTE sp_executesql #sql;
SET #columns2 = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(y.Field)))
FROM (SELECT Field FROM PLAYER_TABLE1 WHERE VALUE<>0 AND PLAYER_NAME=#Player) AS y
ORDER BY y.Field
FOR xml path('')), 1, 1, '');
SET #sql2 = N'SELECT PLAYER_NAME,'+#columns2+'FROM PLAYER_TABLE WHERE PLAYER_NAME='+char(39)+#Player+char(39);
EXECUTE sp_executesql #sql2

Merging more than one table into one existing table

This is the table creation and insertion query
If not exists(select * from sysobjects where name='hrs')
Create table hrs(hr int)
declare #cnt int =1
while #cnt <= 12
begin
insert into hrs values(#cnt)
set #cnt=#cnt+1
end
The above code gives the output like
but I just want that
declare #cnt1 int = 1
while #cnt1<=12
begin
EXEC('select he'+#cnt1+' = case when hr = 1 then '+#cnt1+' end from hrs')
set #cnt1=#cnt1+1
end
The above code returns the 12 different table but i just want the all records in one table (without creating any new table).
So, how can i do this?
Please help me.
Thanks.
Here the all column are created dynamically through loop
Here are the full query
declare #s varchar(MAX)=''
declare #j int = 1
while #j<=12
begin
if #j = 12
Set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end)'
else
set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end),'
set #j=#j+1
end
set #s = 'select '+#s+' from hrs'
exec(#s)
Your query doesn't make a lot of sense, but you can build a list of columns and then exec that:
declare #columns nvarchar(max)
declare #cnt int = 1
while #cnt <= 12
begin
set #columns = isnull(#columns + ', ', '') + 'He' + cast(#cnt as nvarchar) +
' = sum(case when hr = ' + cast(#cnt as nvarchar) + ' then hr end)'
end
declare #sql nvarchar(max) = 'select ' + #columns ' + from hr'
exec (#sql)

Assign year variable to column alias for excel sheet

I have an excel sheet which is bound to a stored procedure. In the stored procedure I am selecting the columns that appear in the excel sheet. Now I'm facing an issue when I wanted to add some more columns:
Some value 2016
Some value 2017
Some value 2018
The first column is adding the actual year to the header and the two others the next one and the year after the next one.
My problem is that I don't know how to do this dynamically. I've tried something like this:
DECLARE #actualYear INT = YEAR(GETDATE())
SELECT tab.Name,
myTable.SomeValue [Some value #actualYear],
myTableNext.SomeValue [Some value #actualYear+1],
myTableAfterTheNext.SomeValue [Some value #actualYear+2]
FROM SomeTable tab
LEFT JOIN MyTable myTable ON tab.SomeId = myTable.SomeId
AND myTable.[Year] = #actualYear
LEFT JOIN MyTable myTableNext ON tab.SomeId = myTableNext.SomeId
AND myTable.[Year] = (#actualYear+1)
LEFT JOIN MyTable myTableAfterTheNext ON tab.SomeId = myTableAfterTheNext.SomeId
AND myTable.[Year] = (#actualYear+2)
The output is the following:
+------+------------------------+--------------------------+--------------------------+
| Name | Some value #actualYear | Some value #actualYear+1 | Some value #actualYear+2 |
+------+------------------------+--------------------------+--------------------------+
Second try:
SELECT tab.Name,
myTable.SomeValue ['Some value' + #actualYear]
...
Output:
+------+----------------------------+ ...
| Name | 'Some value' + #actualYear | ...
+------+----------------------------+ ...
How can I get the correct column headers dynamically?
You'll have to create a dynamic sql query like so (short example):
declare #i int;
declare #sql nvarchar(max);
set #i = 2016;
set #sql = N'select 1 as [' + cast(#i as nvarchar) + N']';
exec(#sql);
Translated to your sql query this should be something like this:
declare #sql nvarchar(max);
declare #actualYear int = year(getdate());
set #sql = #sql + N'select tab.Name, '
set #sql = #sql + N' myTable.SomeValue [' + cast(#actualYear as nvarchar) + N'], '
set #sql = #sql + N' myTableNext.SomeValue [' + cast(#actualYear + 1 as nvarchar) + N'], '
set #sql = #sql + N' myTableAfterTheNext.SomeValue [' + cast(#actualYear + 2 as nvarchar) + N'] '
set #sql = #sql + N'from SomeTable tab '
set #sql = #sql + N'left join MyTable myTable '
set #sql = #sql + N'on tab.SomeId = myTable.SomeId '
set #sql = #sql + N' and myTable.Year = #actualYear '
set #sql = #sql + N'left join MyTable myTableNext '
set #sql = #sql + N'on tab.SomeId = myTableNext.SomeId '
set #sql = #sql + N' and myTable.Year = (#actualYear + 1) '
set #sql = #sql + N'left join MyTable myTableAfterTheNext '
set #sql = #sql + N'on tab.SomeId = myTableAfterTheNext.SomeId '
set #sql = #sql + N' and myTable.Year = (#actualYear + 2); '
exec(#sql);
How to easily convert an SQL query into a dynamic SQL query:
Note, within Notepad++ you should replace the regular expression ^(.*)$ with set #sql = #sql + N'\1 '.
Update
Possible implementation of the above into a stored procedure (short example only):
IF OBJECT_ID('procTest', 'P') IS NOT NULL
DROP PROCEDURE procTest;
GO
CREATE PROCEDURE procTest
AS
BEGIN
DECLARE #i INT;
DECLARE #sql NVARCHAR(MAX);
SET #i = 2016;
SET #sql
= N'insert into #t (Column1) VALUES (' + CAST(#i AS NVARCHAR)
+ N'); ' + N'insert into #t (Column1) '
+ N'SELECT cast(1 as nvarchar) as [' + CAST(#i AS NVARCHAR) + N']';
EXEC (#sql);
END;
GO
CREATE TABLE #t
(
Column1 NVARCHAR(MAX)
);
EXEC dbo.procTest;
SELECT *
FROM #t;
DROP TABLE #t;

Resources