Dynamic SQL (saving result table) - sql

I have the following table, used in dynamic pivot for PORT_NAME:
DECLARE #BaseQuery TABLE
(
PORT_NAME NVARCHAR(50),
BILL_ACCOUNT_NAME NVARCHAR(50),
AVERAGE float
);
I need to save the output as a table, I tried to add the following code, since the columns need to be dynamic to schedule a SQL job:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (BILL_ACCOUNT_NAME NVARCHAR(50),' + #Columns + ' float)'
EXEC(#sql)
INSERT INTO ##T1 VALUES (select #Query)
SELECT * FROM ##T1
got (Incorrect syntax near the keyword 'select'.)
I am working in SQL Server Management Studio 18 (2019 version), I tried to read the code in SSIS was not able to read the results.

You can insert your pivot result into a new table object using INTO.
Quick Example:
Executes the #Query string which creates the dbo.RESULT_PIVOT_TABLE object.
SET #Query = 'SELECT
BILL_ACCOUNT_NAME,
' + #Columns + N'
INTO dbo.RESULT_PIVOT_TABLE -- INSERT PIVOT RESULT INTO TABLE OBJECT
FROM (SELECT
PORT_NAME,
BILL_ACCOUNT_NAME,
AVERAGE
FROM #BaseQuery) AS a PIVOT (MAX(AVERAGE) FOR a.PORT_NAME IN (' + #Columns + ')) AS P';
EXEC sp_executesql #Query;
SELECT * FROM dbo.RESULT_PIVOT_TABLE;
Full Example:
DROP TABLE IF EXISTS dbo.RESULT_PIVOT_TABLE;
DROP TABLE IF EXISTS #BaseQuery;
CREATE TABLE #BaseQuery
(
PORT_NAME NVARCHAR(50),
BILL_ACCOUNT_NAME NVARCHAR(50),
AVERAGE FLOAT
);
DECLARE #Query NVARCHAR(MAX);
DECLARE #Columns NVARCHAR(MAX);
/* POPULATE TEMP DUMMY TABLE WITH FAKE DATA */
INSERT INTO #BaseQuery
SELECT
a.PORT_NAME,
a.BILL_ACCOUNT_NAME,
a.AVERAGE
FROM (SELECT
'PORT 1' AS PORT_NAME,
'NAME 1' AS BILL_ACCOUNT_NAME,
1.23 AS AVERAGE
UNION ALL
SELECT
'PORT 2' AS PORT_NAME,
'NAME 2' AS BILL_ACCOUNT_NAME,
2.34 AS AVERAGE
UNION ALL
SELECT
'PORT 3' AS PORT_NAME,
'NAME 3' AS BILL_ACCOUNT_NAME,
3.45 AS AVERAGE) a;
/* SET COLUMNS FOR PIVOT */
SELECT
#Columns = STRING_AGG(a.PORT_NAME, ',')
FROM (SELECT DISTINCT
QUOTENAME(PORT_NAME) AS PORT_NAME
FROM #BaseQuery) a;
/* SET DYNAMIC QUERY STRING THAT INSERTS PIVOT RESULT INTO TABLE OBJECT */
SET #Query = 'SELECT
BILL_ACCOUNT_NAME,
' + #Columns + N'
INTO dbo.RESULT_PIVOT_TABLE -- INSERT PIVOT RESULT INTO TABLE OBJECT
FROM (SELECT
PORT_NAME,
BILL_ACCOUNT_NAME,
AVERAGE
FROM #BaseQuery) AS a PIVOT (MAX(AVERAGE) FOR a.PORT_NAME IN (' + #Columns + ')) AS P';
EXEC sp_executesql #Query;
SELECT * FROM dbo.RESULT_PIVOT_TABLE;
Result:
BILL_ACCOUNT_NAME
[PORT 1]
[PORT 2]
[PORT 3]
NAME 1
1.23
null
null
NAME 2
null
2.34
null
NAME 3
null
null
3.45

Related

How to iterate in SQL Server without while loop

I have a temp table with few table names as below:
Id TableName
-------------
1 TableA
2 TableB
3 TableC
These table names should be replaced in the query shown, and should get executed. I can achieve it with a while loop, but I don't want to use while. Is there any alternative concept?
I have the following SQL statement:
SELECT
TDU.ColHeader, TDU.ColValues
FROM
(SELECT
' + #ColumnsCasted + '
FROM
' + #TableName + ' ' +
#WhereCondition + ') TD
UNPIVOT
(ColValues FOR ColHeader IN (' + #ColumnsUnpivot + ')
) AS TDU;
The #TableName, #ColumnsCasted, #ColumnsUnpivot are based upon the table name which is stored in a temp table. I used while loop to iterate each table name and replace it here in the statement.
Can anyone suggest a method without using while loop?
You may try to generate and execute dynamic SQL. Next example is just for SELECT * FROM Table statement, you must change it for your query:
-- Create table
CREATE TABLE #TempTable (
Id int,
TableName nvarchar(50)
)
INSERT INTO #TempTable (Id, TableName)
VALUES
(1, 'TableA'),
(2, 'TableB'),
(3, 'TableC')
-- Dynamic SQL
DECLARE #stm nvarchar(max)
DECLARE #err int
SET #stm = N''
SELECT #stm =
#stm +
'SELECT * FROM ' +
QUOTENAME(TableName) +
'; '
FROM #TempTable
-- Statement execution
PRINT #stm
EXEC #err = sp_executesql #stm
IF #err <> 0 PRINT 'Error'
ELSE PRINT 'OK'
Generated statement:
SELECT * FROM [TableA]; SELECT * FROM [TableB]; SELECT * FROM [TableC];
There is non-documented function sp_MSforeachtable that executes query for each table in databse, maybe it can help you:
EXEC sp_MSforeachtable 'SELECT COUNT(*) FROM ?'

Pivot Dynamic row to Column with output parameter?

There is no problem for me to pivot statistic columns in my query e.g Column 1 , Column 2 , Column 3.... and so on.
But i would like to do this dynamic instead.
My data looks like this:
i want to be able to EXECUTE a store procedure to get the output result:
Exec sp_output 1 (from another window where '1' represents the PoolID (#AppPool)) to look like this:
This is my SP:
create PROCEDURE [dbo].[sp_test]
#Query as nvarchar(100) -- OUTPUT?,
#AppPool AS nvarchar(50)
AS
SELECT #Query = Attribute FROM [dbo].[Vy_UserAccess] WHERE PoolID = #AppPool
SELECT [Users],'+ #Query +' FROM
(SELECT [Pool],[Users],[RecNum],[Attribute],[Values] FROM [dbo].[Vy_UserAccess] ) AS T1
PIVOT (MAX([Values]) FOR [ATTRIBUTE] IN ('+ #Query +')) AS T2
Is this possible to achieve by just fine tuning my code or do i have to go on another direction?
You can do It in following:
QUERY
CREATE PROCEDURE [dbo].[sp_test]
#AppPool AS NVARCHAR(60)
AS
DECLARE #cols AS NVARCHAR(MAX) = '',
#sql AS NVARCHAR(MAX)
SELECT #cols += QUOTENAME([Name]) + ','
FROM (SELECT DISTINCT Attribute as Name
FROM [dbo].[Vy_UserAccess]
WHERE PoolID = #AppPool
) a
ORDER BY Name DESC
SET #cols = LEFT(#cols, LEN(#cols) - 1)
SET #sql = 'SELECT Users, ' + #cols + ' FROM
(
SELECT [Pool],[Users],[RecNum],[Attribute],[Values]
FROM [dbo].[Vy_UserAccess]
) AS T1
PIVOT (MAX([Values]) FOR [ATTRIBUTE] IN ('+ #cols +')) AS T2'
EXEC sp_executesql #sql, N'#AppPool NVARCHAR(60)', #AppPool
EXECUTION
Exec sp_test 1
QUERY WITH SAMPLE DATA
CREATE PROCEDURE sp_test
#AppPool AS NVARCHAR(60)
AS
CREATE TABLE #test
(
PoolId NVARCHAR(60),
Pool NVARCHAR(40),
Users NVARCHAR(60),
RecNum INT,
Attribute NVARCHAR(40),
[Values] NVARCHAR(20)
)
INSERT INTO #test VALUES
('1', 'FINANCE', 'User1', 2, 'DIVISION', '010'),
('1', 'FINANCE', 'User1', 1, 'COMPANY', '1'),
('1', 'FINANCE', 'User1', 1, 'DIVISION', '050')
DECLARE #cols AS NVARCHAR(MAX) = '',
#sql AS NVARCHAR(MAX)
SELECT #cols += QUOTENAME([Name]) + ','
FROM (SELECT DISTINCT Attribute as Name
FROM #test
WHERE PoolID = #AppPool
) a
ORDER BY Name DESC
SET #cols = LEFT(#cols, LEN(#cols) - 1)
SET #sql = 'SELECT Users, ' + #cols + ' FROM
(
SELECT [Pool],[Users],[RecNum],[Attribute],[Values]
FROM #test
) AS T1
PIVOT (MAX([Values]) FOR [ATTRIBUTE] IN ('+ #cols +')) AS T2'
EXEC Sp_executesql #sql, N'#AppPool NVARCHAR(60)', #AppPool
DROP TABLE #test
OUTPUT
Users DIVISION COMPANY
User1 050 1
User1 010 NULL

How to insert into a table variable with a dynamic query?

I tried to develop this stored procedure using a temp table but that wouldn't work so I switched to using a table variable. I need to execute an interim dynamic query into the table variable and then I use that table variable to execute the final query. The problem is that I receive an error "must declare scalar variable #clms". I assume that Exec doesn't have scope for the table variable?
DECLARE #qry nvarchar(4000)
DECLARE #clms TABLE (mastcatname nvarchar(50),engdtlbeta decimal (18,4))
SET #qry='INSERT INTO #clms
SELECT distinct replace(mastcatname, '' '', '''') as mastcatname,
engdtlbeta
FROM vw_Scorecard
WHERE empsurveyid=' + cAST(#EmpSurveyID AS nvarchar(10)) + '
AND UnitID IN (' + #UnitIDs + ')
ORDER BY engdtlbeta desc, MastCatName'
EXEC(#qry)
DECLARE #cols nvarchar(1000)
SELECT #cols=COALESCE (#cols + ',[' + mastcatname + ']', '[' + mastcatname + ']')
FROM #clms
SET #qry='SELECT UnitName ,
ParentName, ' + #cols + '
FROM (
SELECT UnitName,
ParentName,
ScoreAvg,
replace(mastcatname, '' '','''') as mastcatname
FROM vw_Scorecard
WHERE UnitID IN (' + #UnitIDs + ')
AND EmpSurveyID=' + cast(#EmpSurveyID as nvarchar(5)) + ' ) p
PIVOT
(SUM(ScoreAvg) FOR mastcatname in (' + #cols + ')) as pvt'
EXEC (#qry)
This is simple minimal example. You can use INSERT EXEC statement. The key is to have table variable declared inside and outside dynamic query. At the end of dynamic query just select from table variable and insert resultset into outside table variable:
DECLARE #t TABLE ( id INT )
DECLARE #q NVARCHAR(MAX) = 'declare #t table(id int)
insert into #t values(1),(2)
select * from #t'
INSERT INTO #t
EXEC(#q)
SELECT * FROM #t
I found this attempting to do basically the same thing. I altered my SQL, and yes, it works! But then I thought, this is overcomplicating things. Why declare the table variable, insert, then select all in the dynamic SQL? Why not just select...
DECLARE #t TABLE ( id INT )
DECLARE #q NVARCHAR(MAX) = 'select 1 union select 2'
INSERT INTO #t
EXEC(#q)
SELECT * FROM #t

Dynamic SQL Result INTO Temporary Table

I need to store dynamic sql result into a temporary table #Temp.
Dynamic SQL Query result is from a pivot result, so number of columns varies(Not fixed).
SET #Sql = N'SELECT ' + #Cols + ' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
--#Cols => Column Names which varies in number
Now I have to insert dynamic sql result to #Temp Table and use this #Temp Table with another existing table to perform joins or something else.
(#Temp table should exist there to perform operations with other existing tables)
How can I Insert dynamic SQL query result To a Temporary table?
Thanks
Can you please try the below query.
SET #Sql = N'SELECT ' + #Cols + '
into ##TempTable
FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
You can then use the ##TempTable for further operations.
However, do not forget to drop the ##TempTable at the end of your query as it will give you error if you run the query again as it is a Global Temporary Table
As was answered in (https://social.msdn.microsoft.com/Forums/sqlserver/en-US/144f0812-b3a2-4197-91bc-f1515e7de4b9/not-able-to-create-hash-table-inside-stored-proc-through-execute-spexecutesql-strquery?forum=sqldatabaseengine),
you need to create a #Temp table in advance:
CREATE TABLE #Temp(columns definition);
It seems that the task is impossible, if you know nothing about the dynamic list of columns in advance. But, most likely you do know something.
You do know the types of dynamic columns, because they come from PIVOT. Most likely, you know the maximum possible number of dynamic columns. Even if you don't, SQL Server has a limit of 1024 columns per (nonwide) table and there is a limit of 8060 bytes per row (http://msdn.microsoft.com/en-us/library/ms143432.aspx). So, you can create a #Temp table in advance with maximum possible number of columns and use only some of them (make all your columns NULLable).
So, CREATE TABLE will look like this (instead of int use your type):
CREATE TABLE #Temp(c1 int NULL, c2 int NULL, c3 int NULL, ..., c1024 int NULL);
Yes, column names in #Temp will not be the same as in #Cols. It should be OK for your processing.
You have a list of columns in your #Cols variable. You somehow make this list of columns in some external code, so when #Cols is generated you know how many columns there are. At this moment you should be able to generate a second list of columns that matches the definition of #Temp. Something like:
#TempCols = N'c1, c2, c3, c4, c5';
The number of columns in #TempCols should be the same as the number of columns in #Cols. Then your dynamic SQL would look like this (I have added INSERT INTO #Temp (#TempCols) in front of your code):
SET #Sql = N'INSERT INTO #Temp (' + #TempCols + N') SELECT ' + #Cols + N' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
Then you execute your dynamic SQL:
EXEC (#Sql) OR sp_executesql #Sql
And then do other processing using the #Temp table and temp column names c1, c2, c3, ...
MSDN says:
A local temporary table created in a stored procedure is dropped
automatically when the stored procedure is finished.
You can also DROP the #Temp table explicitly, like this:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp'
All this T-SQL code (CREATE TABLE, EXEC, ...your custom processing..., DROP TABLE) would naturally be inside the stored procedure.
Alternative to create a temporary table is to use the subquery
select t1.name,t1.lastname from(select * from table)t1.
where "select * from table" is your dyanmic query. which will return result which you can use as temp table t1 as given in example .
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
CREATE TABLE ##TmepTable (TmpCol CHAR(1))
DECLARE #SQL NVARCHAR(max) =' IF OBJECT_ID(''tempdb..##TmepTable'') IS NOT
NULL DROP TABLE ##TmepTable
SELECT * INTO ##TmepTable from [MyTableName]'
EXEC sp_executesql #SQL
SELECT Alias.* FROM ##TmepTable as Alias
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
Here is step by step solution for your problem.
Check for your temporary tables if they exist, and delete them.
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
IF OBJECT_ID('tempdb..##abc') IS NOT NULL
DROP TABLE ##abc
Store your main query result in first temp table (this step is for simplicity and more readability).
SELECT *
INTO #temp
FROM (SELECT ResourceKey, ResourceValue
FROM LocaleStringResources
where StateId ='+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =' + LTRIM(RTRIM(#CultureCode)) + ') AS S
Write below query to create your pivot and store result in another temp table.
DECLARE #str NVARCHAR(1000)
DECLARE #sql NVARCHAR(1000)
SELECT #str = COALESCE(#str+',', '') + ResourceKey FROM #temp
SET #sql = N'select * into ##abc from (select ' + #str + ' from (SELECT ResourceKey, ResourceValue FROM #temp) as A
Pivot
(
max(ResourceValue)
for ResourceKey in (' + #str + ')
)as pvt) as B'
Execute below query to get the pivot result in your next temp table ##abc.
EXECUTE sp_executesql #sql
And now you can use ##abc as table where-ever you want like
select * from ##abc
Hope this will help you.

using temp tables in SQL Azure

I am writing a query to pivoting table elements where column name is generated dynamically.
SET #query = N'SELECT STUDENT_ID, ROLL_NO, TITLE, STUDENT_NAME, EXAM_NAME, '+
#cols +
' INTO ##FINAL
FROM
(
SELECT *
FROM #AVERAGES
UNION
SELECT *
FROM #MARKS
UNION
SELECT *
FROM #GRACEMARKS
UNION
SELECT *
FROM #TOTAL
) p
PIVOT
(
MAX([MARKS])
FOR SUBJECT_ID IN
( '+
#cols +' )
) AS FINAL
ORDER BY STUDENT_ID ASC, DISPLAYORDER ASC, EXAM_NAME ASC;'
EXECUTE(#query)
select * from ##FINAL
This query works properly in my local database, but it doesn't work in SQL Azure since global temp tables are not allowed there.
Now if i change ##FINAL to #FINAL in my local database, but it gives me error as
Invalid object name '#FINAL' .
How can I resolve this issue?
Okay, after saying I didn't think it could be done, I might have a way. It's ugly though. Hopefully, you can play with the below sample and adapt it to your query (without having your schema and data, it's too tricky for me to attempt to write it):
declare #cols varchar(max)
set #cols = 'object_id,schema_id,parent_object_id'
--Create a temp table with the known columns
create table #Boris (
ID int IDENTITY(1,1) not null
)
--Alter the temp table to add the varying columns. Thankfully, they're all ints.
--for unknown types, varchar(max) may be more appropriate, and will hopefully convert
declare #tempcols varchar(max)
set #tempcols = #cols
while LEN(#tempcols) > 0
begin
declare #col varchar(max)
set #col = CASE WHEN CHARINDEX(',',#tempcols) > 0 THEN SUBSTRING(#tempcols,1,CHARINDEX(',',#tempcols)-1) ELSE #tempcols END
set #tempcols = CASE WHEN LEN(#col) = LEN(#tempcols) THEN '' ELSE SUBSTRING(#tempcols,LEN(#col)+2,10000000) END
declare #sql1 varchar(max)
set #sql1 = 'alter table #Boris add [' + #col + '] int null'
exec (#sql1)
end
declare #sql varchar(max)
set #sql = 'insert into #Boris (' + #cols + ') select ' + #cols + ' from sys.objects'
exec (#sql)
select * from #Boris
drop table #Boris
They key is to create the temp table in the outer scope, and then inner scopes (code running within EXEC statements) have access to the same temp table. The above worked on SQL Server 2008, but I don't have an Azure instance to play with, so not tested there.
If you create a temp table, it's visible from dynamic sql executed in your spid, if you create the table in dynamic sql, it's not visible outside of that.
There is a workaround. You can create a stub table and alter it in your dynamic sql. It requires a bit of string manipulation but I've used this technique to generate dynamic datasets for tsqlunit.
CREATE TABLE #t1
(
DummyCol int
)
EXEC(N'ALTER TABLE #t1 ADD foo INT')
EXEC ('insert into #t1(DummyCol, foo)
VALUES(1,2)')
EXEC ('ALTER TABLE #t1 DROP COLUMN DummyCol')
select *from #t1