I am wanting to create a dynamic delete and insert stored procedure for my archiving task in SQL Server.
Here are the temp tables I created:
enter image description here
The source table contains the table names where the records came from. Those records will be inserted in the tables listed in table 2 (the destination table names). Countdays table contains the days for the records I want to archive.
Here's my code below.
DECLARE #i int
DECLARE #destinationTbl NVARCHAR(2048)
DECLARE #sourcetbl NVARCHAR(2048)
DECLARE #cntdays INT
DECLARE #monthAgo dateTime
SET #i = 1
SET #destinationTbl = (SELECT Destination FROM #Table2 ID = #i)
SET #sourcetbl = (SELECT Source FROM #Table1 WHERE ID = #i)
SET #cntdays = (SELECT CountDays FROM Table3 WHERE ID = #i)
SET #monthAgo = DATEADD(month,-#cntdays, GETDATE())
SELECT #arctable
SELECT #sourcetable
SELECT #cntdays
-------ERROR PART----------------------------------------------
INSERT INTO #destinationTbl
SELECT TOP 10 * fROM #sourcetbl Where Createdttm < #monthAgo
---------------------------------------------------------------
After running I keep getting error
Must declare the scalar variable for #destinationTbl, #sourcetbl and "#monthAgo".
I'm still stuck in the inserting part. I have not yet created a script for Deleting the records. I think if someone can help me with the insert script, deleting will be easy.
SQL isn't a scripting language; you can't use a variable to replace something that need to be a literal. The statement INSERT INTO #destinationTbl will try to INSERT values into the table variable #destinationTbl not the table that has the same name as the value in the scalar variable #destinationTbl.
When using dynamic SQL, you need to safely inject the values into the query and pass the other variables as parameters:
DECLARE #i int;
DECLARE #destinationTbl sysname; --Changed datatype, an object cannot have 2048 characters in its name
DECLARE #sourcetbl sysname; --Changed datatype, an object cannot have 2048 characters in its name
DECLARE #cntdays INT; --This isn't used, only SET, is it needed?
DECLARE #monthAgo datetime;
SET #i = 1;
SET #destinationTbl = (SELECT Destination FROM #Table2 ID = #i);
SET #sourcetbl = (SELECT Source FROM #Table1 WHERE ID = #i);
SET #cntdays = (SELECT CountDays FROM Table3 WHERE ID = #i);
SET #monthAgo = DATEADD(month,-#cntdays, GETDATE());
PRINT #arctable;
PRINT #sourcetable;
PRINT #cntdays;
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'INSERT INTO dbo.' + QUOTENAME(#destinationTbl) + #CRLF + --Assumed schema
N'FROM dbo.' + QUOTENAME(#sourcetbl) + #CRLF + --Assumed schema
N'WHERE Createdttm < #monthAgo;';
--PRINT #SQL; --Your best friend.
EXEC sys.sp_executesql #SQL, N'#monthAgo datetime', #monthAgo;
I'm trying to build a stored procedure that will query multiple database depending on the databases required.
For example:
SP_Users takes a list of #DATABASES as parameters.
For each database it needs to run the same query and union the results together.
I believe a CTE could be my best bet so I have something like this at the moment.
SET #DATABASES = 'DB_1, DB_2' -- Two databases in a string listed
-- I have a split string function that will extract each database
SET #CURRENT_DB = 'DB_1'
WITH UsersCTE (Name, Email)
AS (SELECT Name, Email
FROM [#CURRENT_DB].[dbo].Users),
SELECT #DATABASE as DB, Name, Email
FROM UsersCTE
What I don't want to do is hard code the databases in the query. The steps I image are:
Split the parameter #DATABASES to extract and set the #CURRENT_DB Variable
Iterate through the query with a Recursive CTE until all the #DATABASES have been processed
Union all results together and return the data.
Not sure if this is the right approach to tackling this problem.
Using #databases:
As mentioned in the comments to your question, variables cant be used to dynamically select a database. Dynamic sql is indicated. You can start by building your template sql statement:
declare #sql nvarchar(max) =
'union all ' +
'select ''#db'' as db, name, email ' +
'from [#db].dbo.users ';
Since you have sql server 2016, you can split using the string_split function, with your #databases variable as input. This will result in a table with 'value' as the column name, which holds the database names.
Use the replace function to replace #db in the template with value. This will result in one sql statement for each database you passed into #databases. Then, concatenate the statements back together. Unfortunately, in version 2016, there's no built in function to do that. So we have to use the famous for xml trick to join the statements, then we use .value to convert it to a string, and finally we use stuff to get rid of the leading union all statement.
Take the results of the concatenated output, and overwrite the #sql variable. It is ready to go at this point, so execute it.
I do all that is described in this code:
declare #databases nvarchar(max) = 'db_1,db_2';
set #sql = stuff(
(
select replace(#sql, '#db', value)
from string_split(#databases, ',')
for xml path(''), type
).value('.[1]', 'nvarchar(max)')
, 1, 9, '');
exec(#sql);
Untested, of course, but if you print instead of execute, it seems to give the proper sql statement for your needs.
Using msForEachDB:
Now, if you didn't want to have to know which databases had 'users', such as if you're in an environment where you have a different database for every client, you can use sp_msForEachDb and check the structure first to make sure it has a 'users' table with 'name' and 'email' columns. If so, execute the appropriate statement. If not, execute a dummy statement. I won't describe this one, I'll just give the code:
declare #aggregator table (
db sysname,
name int,
email nvarchar(255)
);
insert #aggregator
exec sp_msforeachdb '
declare #sql nvarchar(max) = ''select db = '''''''', name = '''''''', email = '''''''' where 1 = 2'';
select #sql = ''select db = ''''?'''', name, email from ['' + table_catalog + ''].dbo.users''
from [?].information_schema.columns
where table_schema = ''dbo''
and table_name = ''users''
and column_name in (''name'', ''email'')
group by table_catalog
having count(*) = 2
exec (#sql);
';
select *
from #aggregator
I took the valid advice from others here and went with this which works great for what I need:
I decided to use a loop to build the query up. Hope this helps someone else looking to do something similar.
CREATE PROCEDURE [dbo].[SP_Users](
#DATABASES VARCHAR(MAX) = NULL,
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)
)
BEGIN
SET NOCOUNT ON;
--Local variables
DECLARE
#COUNTER INT = 0,
#SQL NVARCHAR(MAX) = '',
#CURRENTDB VARCHAR(50) = NULL,
#MAX INT = 0,
#ERRORMSG VARCHAR(MAX)
--Check we have databases entered
IF #DATABASES IS NULL
BEGIN
RAISERROR('ERROR: No Databases Provided,
Please Provide a list of databases to execute procedure. See stored procedure:
[SP_Users]', 16, 1)
RETURN
END
-- SET Number of iterations based on number of returned databases
SET #MAX = (SELECT COUNT(*) FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i)X)
-- Build SQL Statement
WHILE #COUNTER < #MAX
BEGIN
--Set the current database
SET #CURRENTDB = (SELECT X.Value FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i
ORDER BY RowNumber OFFSET #COUNTER
ROWS FETCH NEXT 1 ROWS ONLY) X);
SET #SQL = #SQL + N'
(
SELECT Name, Email
FROM [' + #CURRENTDB + '].[dbo].Users
WHERE
(Name = #PARAM1 OR #PARAM1 IS NULL)
(Email = #PARAM2 OR #PARAM2 IS NULL)
) '
+ N' UNION ALL '
END
PRINT #CURRENTDB
PRINT #SQL
SET #COUNTER = #COUNTER + 1
END
-- remove last N' UNION ALL '
IF LEN(#SQL) > 11
SET #SQL = LEFT(#SQL, LEN(#SQL) - 11)
EXEC sp_executesql #SQL, N'#CURRENTDB VARCHAR(50),
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)',
#CURRENTDB,
#PARAM1 ,
#PARAM2
END
Split Variable Function
CREATE FUNCTION [dbo].[udf_SplitVariable]
(
#List varchar(8000),
#SplitOn varchar(5) = ','
)
RETURNS #RtnValue TABLE
(
Id INT IDENTITY(1,1),
Value VARCHAR(8000)
)
AS
BEGIN
--Account for ticks
SET #List = (REPLACE(#List, '''', ''))
--Account for 'emptynull'
IF LTRIM(RTRIM(#List)) = 'emptynull'
BEGIN
SET #List = ''
END
--Loop through all of the items in the string and add records for each item
WHILE (CHARINDEX(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT Value = LTRIM(RTRIM(SUBSTRING(#List, 1, CHARINDEX(#SplitOn, #List)-1)))
SET #List = SUBSTRING(#List, CHARINDEX(#SplitOn,#List) + LEN(#SplitOn), LEN(#List))
END
INSERT INTO #RtnValue (Value)
SELECT Value = LTRIM(RTRIM(#List))
RETURN
END
How to get a list of all tables (which already have Change Tracking enabled) which have any tracked changes after given version?
This will return a list of all the tables that have changed since the previous tracking version:
set nocount on;
-- We want to check for changes since the previous version
--declare #prevTrackingVersion int = INSERT_YOUR_PREV_VERSION_HERE
-- Comment out this line if you know the previous version
declare #prevTrackingVersion int = CHANGE_TRACKING_CURRENT_VERSION() - 1
-- Get a list of table with change tracking enabled
declare #trackedTables as table (name nvarchar(1000));
insert into #trackedTables (name)
select sys.tables.name from sys.change_tracking_tables
join sys.tables ON tables.object_id = change_tracking_tables.object_id
-- This will be the list of tables with changes
declare #changedTables as table (name nvarchar(1000));
-- For each table name in tracked tables
declare #tableName nvarchar(1000)
while exists(select top 1 * from #trackedTables)
begin
-- Set the current table name
set #tableName = (select top 1 name from #trackedTables order by name asc);
-- Determine if the table has changed since the previous version
declare #sql nvarchar(250)
declare #retVal int
set #sql = 'select #retVal = count(*) from changetable(changes ' + #tableName + ', ' + cast(#prevTrackingVersion as varchar) + ') as changedTable'
exec sp_executesql #sql, N'#retVal int output', #retVal output
if #retval > 0
begin
insert into #changedTables (name) select #tableName
end
-- Delete the current table name
delete from #trackedTables where name = #tableName;
end
select * from #changedTables;
Well to get a list of all tables that have change tracking enabled you would perform a query like
SELECT sys.tables.name FROM sys.change_tracking_tables
JOIN sys.tables ON tables.object_id = change_tracking_tables.object_id
Then you can add a where condition for the version if you'd like to. I believe that answers your question.
Also if you'd like to see some info on the change you can run a query like the one below for a specific table using the changetable function.
DECLARE #synchronization_version NVARCHAR(MAX),#last_synchronization_version NVARCHAR(MAX)
SET #synchronization_version = CHANGE_TRACKING_CURRENT_VERSION();
SELECT
CT.*
FROM
CHANGETABLE(CHANGES Sales.CreditCard, #last_synchronization_version) AS CT
UPDATE
I updated the original query to perform a look and print the results, you'l be able to review the tables before you exec the query since you have over 1000 tables per your comment you might want to remove some.
SET NOCOUNT ON;
DECLARE #Views as TABLE (name nvarchar(200));
INSERT INTO #Views (name)
SELECT sys.tables.name FROM sys.change_tracking_tables
JOIN sys.tables ON tables.object_id = change_tracking_tables.object_id
DECLARE #viewName nvarchar(200) = (select top 1 name from #Views);
DECLARE #sql nvarchar(max) = '';
DECLARE #union NVARCHAR(20)
DECLARE #sql1 NVARCHAR(max)
SET #sql1 = 'DECLARE #synchronization_version NVARCHAR(MAX),#last_synchronization_versionNVARCHAR(MAX)
SET #synchronization_version = CHANGE_TRACKING_CURRENT_VERSION();'
PRINT(#sql1)
WHILE(Exists(select 1 from #Views)) BEGIN
SET #union = '';
SET #sql = '
SELECT
CT.*
FROM
CHANGETABLE(CHANGES ' + #ViewName +', #last_synchronization_version) AS CT'
IF (SELECT COUNT(name) FROM #Views) > 2
BEGIN
SET #union = ' UNION'
END
Print (#sql+#union);
DELETE FROM #Views where name = #viewName;
SET #ViewName = (select top 1 name from #Views);
END;
I am trying to write a windows service, which will send automatic emails. all the tables which require email sending have common columns 'templateid' and 'emailstatus'. I want to iterate through all the tables and get the tables which has column name 'templateid'.
Now that i have the list of tables with column name 'templateid' get the data from each table whose email status is 'false' and save it in a temporary table.
if 'table1' has 4 rows of data, the temporary table should have 4 rows. after iterating through the next table the row collection should be added to the same temporary table.
IF (SELECT object_id('TempDB..#TEMPTABLE')) IS NOT NULL
BEGIN
DROP TABLE #TEMPTABLE
END
CREATE TABLE #TEMPTABLE(
[ID] INTEGER IDENTITY(1,1) NOT NULL,
[TABLE_NAME] VARCHAR(1000)
)
INSERT INTO #TEMPTABLE(TABLE_NAME)
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'TEMPLATEID'
SELECT * FROM #TEMPTABLE
DECLARE #ROWCOUNT INT
SET #ROWCOUNT = (SELECT COUNT(ID) FROM #TEMPTABLE)
DECLARE #I INT
SET #I=1
WHILE(#I<=#ROWCOUNT)
BEGIN
DECLARE #TABLENAME VARCHAR(500)
SELECT #TABLENAME=TABLE_NAME FROM #TEMPTABLE WHERE ID=#I
EXEC('SELECT * FROM '+#TABLENAME)
SET #I=#I+1
END
i found the above query which is giving me all the tables. after that i am clueless how to proceed further as i am not good with sql server.
Not sure you are still looking for an answer but the following code will give you a temporary table with just the tables that have a column named 'templateid'. From here you would need a cursor as Tanner suggested to loop through each table and then insert records from each table (into your final target table) where email status = 'false'.
Here is the code that gets you the temp tables with columns named 'templateid' along with a sample for your cursor:
declare #max_tables int
declare #max_columns int
declare #sql nvarchar(400)
declare #x int
declare #y int
declare #table varchar(50)
declare #columns varchar(800)
declare #tablename varchar(100)
create table #c ([Table] varchar(50),[Columns] varchar(800))
select ROW_NUMBER() OVER(ORDER BY name) AS Row, name
into #table_list
from sys.objects
where type_desc = 'USER_TABLE'
order by name
set #max_tables = (select count(*) from sys.objects where type_desc = 'USER_TABLE')
set #y = 0
while #y < #max_tables
begin
set #y = #y + 1
set #table = (select name from #table_list where row = #y)
create table #t (c int)
set #sql = 'select count(*) as c from Information_schema.Columns where table_name = ''' + #table + ''''
insert into #t exec sp_executesql #sql
set #max_columns = (select top 1 c from #t)
DROP TABLE #t
set #x = 0
set #columns = ''
while #x < #max_columns
begin
set #x = #x + 1
set #columns = #columns + (select column_name from Information_schema.Columns where table_name = #table and ordinal_position = #x)
if #x < #max_columns set #columns = #columns + ', '
end
insert into #c select #table,#columns
end
select * into #tables from #c c
where c.Columns like '%templateid%'
declare my_cursor cursor for
select table from #t
open my_cursor
fetch next from my_cursor into #tablename
while ##fetch_status = 0
begin
--do something here to retrieve your data from each table and
--insert into target table
end
close my_cursor
deallocate my_cursor
DROP TABLE #c,#tables,#table_List
We're maintaining (and occasionally debugging) a large in-house system. The system has 20+ databases, and a number of servers interfacing to other systems, processing data, etc. Not all is in-house developed - i.e. we don't always have access to source code.
At one place, we can see the system creating a #temp table - and then, in the next step, failing due to a data-error. We can see the existence of the #temp table in Management Studio - it exists in tempdb --> Temporary Tables as something like
#MyStuff________________________________________________________________________________________________________000000A65029
Obviously, the context menu here doesn't offer the full functionality (with Create table, select top 1000, etc.) - but only Reportsand Refresh.
I can find the table in sys.objects, sys.tables and even its column definition in sys.columns.
The question is: Is it in any way possible to access the data in the table? We can break execution so as to ensure that the table stays in scope, so the table vanishing shouldn't be an issue. This is not something that is to be done regularly or in code - it's more or less a one-shot deal. (I hope).
Unwieldy but you can examine the tables pages from an admin logon.
Get object id;
select object_id from tempdb.sys.tables where name like '#mystuff%'
Get a list of allocated pages;
dbcc ind('tempdb', <object id>, -1)
for each of the PageFID / PagePID (file/page IDs)
dbcc traceon(3604);
dbcc page(tempdb, <PageFID>, <PagePID>, 3) with tableresults
If I create #mystuff from another session I can see in a dbcc page view from my own session:
Slot 0 Offset 0x60 Length 18 Slot 0 Column 1 Offset 0xb Length 7 Length (physical) 7 myFieldName MyValue
If you prefix a temporary table name with two octothorpes, e.g. ##mystuff, it creates a global temporary table that exists outside session scope.
That does require you to be able to alter the query text, which may or may not be accessible in this specific case.
Based on Alex K's answer I wrote up a script. I didn't actually end up with all the rows from the table, so there's likely room for improvement.
Disclaimer: As with any code you find online... understand it before you run it and use at your own risk. Ends with DBCC TRACEOFF(3604).
/* Run this first to get your object_id. In the case of > 1, choose one based on date and rowcount. */
DECLARE #tableName NVARCHAR(100) = '#conv'
SELECT #tableName TableName,
o.object_id,
SUM(p.rows) TableRows,
o.Create_Date CreateDatetime,
o.name TempDBFullName
FROM tempdb.sys.objects o
LEFT JOIN tempdb.sys.partitions p ON o.object_id = p.object_id
AND p.index_id <= 1
WHERE o.name LIKE #tableName+'\_\_\_%' ESCAPE '\'
GROUP BY o.name, o.object_id,o.Create_Date
--(-1074969413)
GO
/* Run this using the object_id from above to get table contents */
DECLARE
#object_id INT = -1074969413, --from above
#tableName NVARCHAR(100),
#rows BIGINT,
#created DATETIME,
#msg NVARCHAR(MAX),
--Set this to NULL for all pages
#max_pages INT = 2 --set this to restrict how many pages are read. Must be > 1.
IF #max_pages < 2
RAISERROR('You''re going to need some more pages there, Sport. Try at least 2.',16,1)
RAISERROR('
**** This code uses an undocumented feature and is for informational purposes only. Do not assume results are complete or correct. ****
',0,1)
SET NOCOUNT ON
--declare #tablename nvarchar(100), #msg nvarchar(max), #rows bigint, #created datetime
SELECT #rows = SUM(rows) FROM tempdb.sys.partitions WHERE object_id = #object_id AND index_id <= 1
SELECT #rows = ISNULL(#rows,0)
SELECT #tableName = SUBSTRING(o.name,1,CHARINDEX('____',o.name,1)-1),
#created = o.create_date
FROM tempdb.sys.objects o
WHERE o.object_id = #object_id
SELECT #msg = 'Object name '+QUOTENAME(#tableName)+' is expected to contain up to '+CAST(#rows AS NVARCHAR(20))+' rows and was created # '+CONVERT(NVARCHAR,#created,113)+'.'
RAISERROR(#msg,0,1) WITH NOWAIT
DROP TABLE IF EXISTS #dbccind
CREATE TABLE #dbccind(PageFID NVARCHAR(100),PagePID NVARCHAR(100),IAMFID NVARCHAR(100),IAMPID NVARCHAR(100),ObjectID NVARCHAR(100),IndexID NVARCHAR(100),PartitionNumber NVARCHAR(100),PartitionID NVARCHAR(100),iam_chain_Type NVARCHAR(100),PageType NVARCHAR(100),IndexLevel NVARCHAR(100),NextPageFID NVARCHAR(100),NextPagePID NVARCHAR(100),PrevPageFID NVARCHAR(100),PrevPagePID NVARCHAR(100))
DECLARE #SQL NVARCHAR(MAX) = N'dbcc ind(''tempdb'', '+CAST(#object_id AS NVARCHAR(20))+', -1) WITH NO_INFOMSGS'
--Get a list of pages for this object
INSERT #dbccind
EXEC sp_executesql #SQL
--add an iterative counter for following loop
ALTER TABLE #dbccind ADD ID INT IDENTITY NOT NULL
--select '#dbccind' [#dbccind], * FROM #dbccind
--DECLARE #SQL nvarchar(max), #msg nvarchar(max)
DROP TABLE IF EXISTS #TempTableUnpivoted
CREATE TABLE #TempTableUnpivoted(ParentObject NVARCHAR(100), Object NVARCHAR(100), Field NVARCHAR(100), Value NVARCHAR(1000))
DBCC TRACEON(3604) WITH NO_INFOMSGS;
--Use DBCC PAGE to dump TEMPDB pages to a table as name/value pairs
IF #max_pages IS NOT NULL
BEGIN
SELECT #msg = 'Max pages set. This process will read (at most) '+CAST(#max_pages AS NVARCHAR(20))+' data page(s).'
RAISERROR(#msg,0,1) WITH NOWAIT
END
DECLARE #i INT = 1, #j INT = (SELECT MAX(id) FROM #dbccind)
WHILE #i <= #j
AND (#i <= #max_pages OR #max_pages IS NULL)
BEGIN
SELECT #SQL = 'INSERT #TempTableUnpivoted EXEC SP_EXECUTESQL N''DBCC PAGE(TEMPDB, '+CAST(PageFID AS NVARCHAR(20))+', '+CAST(PagePID AS NVARCHAR(20))+', 3) WITH TABLERESULTS, NO_INFOMSGS'''
FROM #dbccind
WHERE id = #i
SELECT #msg = 'Reading page '+CAST(#i AS NVARCHAR(20))+' of '+CAST(#j AS NVARCHAR(20))+' # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
--raiserror(#sql,0,1) with nowait
IF #SQL IS NULL RAISERROR('Unable to read pages.',16,1) WITH NOWAIT
EXEC SP_EXECUTESQL #SQL
SELECT #i += 1--, #SQL = NULL, #msg = NULL
END
IF #max_pages <= #i
BEGIN
SELECT #msg = 'Max pages reached. This process has read data from up to '+CAST(#max_pages AS NVARCHAR(20))+' data page(s).'
RAISERROR(#msg,0,1) WITH NOWAIT
END
--SELECT '#TempTableUnpivoted' [#TempTableUnpivoted], * FROM #TempTableUnpivoted
--get a list of fields from the page results. Assume all occur withing the first 1000 results.
SELECT #msg = 'Identify fields # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
DROP TABLE IF EXISTS #TableFields
CREATE TABLE #TableFields(FieldName NVARCHAR(100) NOT NULL PRIMARY KEY, ColumnId INT NOT NULL)
INSERT #TableFields(FieldName,ColumnId)
SELECT DISTINCT Field,ColumnId
FROM (SELECT TOP 1000 *, SUBSTRING(Object,CHARINDEX('Column ',Object,1)+7,CHARINDEX(' Offset ',Object,1)-CHARINDEX('Column ',Object,1)-7) ColumnId
FROM #TempTableUnpivoted
WHERE ParentObject LIKE 'Slot %'
AND Object LIKE 'Slot%Column%offset%length%') a
--Set up variables to hold a list of column names
SELECT #msg = 'Compile list of field names # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
--declare #sql nvarchar(max)
DECLARE #columnlist NVARCHAR(MAX) = (SELECT STRING_AGG(QUOTENAME(FieldName,''''),',') WITHIN GROUP ( ORDER BY ColumnId) FROM #TableFields)
DECLARE #columnlistquoted NVARCHAR(MAX) = (SELECT STRING_AGG(QUOTENAME(FieldName),',') WITHIN GROUP ( ORDER BY ColumnId) FROM #TableFields)
DECLARE #columnlistTyped NVARCHAR(MAX) = (SELECT STRING_AGG(QUOTENAME(FieldName),' nvarchar(1000),') WITHIN GROUP ( ORDER BY ColumnId) FROM #TableFields)+' nvarchar(1000)'
IF #columnlist IS NULL OR #columnlistquoted IS NULL OR #columnlistTyped IS NULL
BEGIN
SELECT #columnlist [#columnlist], #columnlistquoted [#columnlistquoted], #columnlistTyped [#columnlistTyped]
RAISERROR('Unable to compile columns. You might need to read more pages to get a solid list. Try incrementing #max_pages or clear #max_pages to do a full read.',16,1) WITH NOWAIT
END
--Create a list of unpivoted name/value pairs (NVP) for just the columns we need. This _may_ be able to be cut out with implicit restrictions in the pivot.
SELECT #msg = 'Reduce unpivoted data to name/value pairs # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
DROP TABLE IF EXISTS #TempTableNVP
CREATE TABLE #TempTableNVP(ParentObject NVARCHAR(100),Field NVARCHAR(100),Value NVARCHAR(1000))
SELECT #SQL = N'
SELECT ParentObject,Field,Value
FROM #TempTableUnpivoted
WHERE Field in ('+#columnlist+')'
INSERT #TempTableNVP(ParentObject,Field,Value)
EXEC sp_executesql #SQL
--Pivot data to final form to match temp table
SELECT #msg = 'Add fields to working table 1 # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
DROP TABLE IF EXISTS #TempTable
CREATE TABLE #TempTable(id INT IDENTITY NOT NULL)
SELECT #SQL = 'ALTER TABLE #TempTable ADD '+#columnlistTyped
IF #SQL IS NULL
RAISERROR('Unable to alter working table 1.',16,1) WITH NOWAIT
--raiserror(#sql,0,1) with nowait
EXEC sp_executesql #SQL
--select '#TempTable' [#TempTable], * from #TempTable
SELECT #msg = 'Pivot data to get original table # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
SELECT #SQL = 'INSERT #TempTable('+#columnlistquoted+')
SELECT '+#columnlistquoted+'
FROM (SELECT Field,Value,ParentObject FROM #TempTableNVP) a
PIVOT (MIN(Value) FOR Field IN ('+#columnlistquoted+')) PVT'
--raiserror(#sql,0,1) with nowait
IF #SQL IS NULL RAISERROR('Unable to populate working table 1.',16,1) WITH NOWAIT
EXEC sp_executesql #SQL
--Return results
SELECT #msg = 'Return results # '+CONVERT(NVARCHAR,GETDATE(),113)
RAISERROR(#msg,0,1) WITH NOWAIT
--SELECT * FROM #TempTable
SELECT #SQL = 'SELECT '+#columnlistquoted+' FROM #TempTable'
EXEC sp_executesql #SQL
DBCC TRACEOFF(3604) WITH NO_INFOMSGS;