Logging data changes into table with dynamically changing name in MS SQL - sql-server

I am trying to log data changes in MS SQL with trigger. I want to create a new History table in every month. After I found the answer how to change table name Dynamically I can't access the DELETED and INSERTED tables anymore. It says invalid object name.
ALTER TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
declare #DynamicSql1 nvarchar(2000)
declare #DynamicSql2 nvarchar(2000)
set #DynamicSql1 = N'IF NOT EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N''[History][dbo].[#hist]'')
AND OBJECTPROPERTY(id, N''IsUserTable'') = 1)
CREATE TABLE [History].[dbo].[#hist] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql1, N'#hist nvarchar(40)', #hist=#hist
set #DynamicSql2 = N'INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM DELETED
INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM INSERTED'
Exec sp_executesql #DynamicSql2, N'#hist nvarchar(40)', #hist=#hist
Thanks for the answers in advance.

Dynamic sql is executed in his own scope, so you can't acces inserted/deleted objects.
You could write a SQLCLR trigger in C# look this example SQLCLR Trigger
but I think the easiest way is to use a temp table to write changes to, so the dynamic part is fixed.
Take a look:
DROP TRIGGER [test_history]
GO
CREATE TRIGGER [test_history] ON [test_table]
FOR DELETE, INSERT, UPDATE
AS
BEGIN
declare #date datetime = getdate()
declare #guid uniqueidentifier = newid()
declare #hist nvarchar(40)= 'test_history_' + CAST(YEAR(#date ) as VARCHAR(4))+ '_' + right('0' + CAST(Month(#date) as VARCHAR(2)), 2)
DECLARE #T1 BIT = 0
SELECT top 1 #T1 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = 'test_history_9999_99'
IF #T1 = 1 TRUNCATE table test_history_9999_99
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
IF #T1=0 BEGIN
SELECT ID, [desc], #date DATE_TIME, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
INTO test_history_9999_99
FROM DELETED
END else begin
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM DELETED
end
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('insert' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM inserted
IF #T2 = 0 BEGIN
EXEC sp_rename 'test_history_9999_99', #hist
END ELSE BEGIN
declare #DynamicSql nvarchar(2000)
SET #DynamicSql = 'INSERT INTO ' + #hist + ' SELECT * FROM test_history_9999_99;'
Exec sp_executesql #DynamicSql
END
END
My test_table contains only two columns ID and [Desc].
In the history tables I have added a DATETIME column with change date and a UNIQUEIDENTIFIER column so you can group all changes in a batch if you INSERT/UPDATE many records with a single operation

Tanks for the answer #MtwStark. Now it works, I can check if the table exists, and create it if not. And have eaccess to the DELETED and INSERTED tables.
I'm not sure, if in your solution I have to create the test_history_9999_99 table in advance. Because when I used your trigger I've got an error about column insertion (I didn't understand the error completly).
Now my code looks like this. I'm not sure if it can handle INSERT/UPDATE many records with a single operation. Probably I still need to insert this code for it? CAST(#guid AS varchar(64)) BATCH . I'm not sure what it really does, I have to look into it deeper.
CREATE TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
select * into #ins from inserted
select * into #del from deleted
declare #DynamicSql nvarchar(2000)
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
if #T2=0 begin
set #DynamicSql = N'CREATE TABLE [' + #hist + '] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql
end
set #DynamicSql = N'INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM #del
INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM #ins'
Exec sp_executesql #DynamicSql

Try refreshing intellisense. Ctrl+Shift+R see if that might help. Or do a database table refresh.

If you have SQL Server enterprise (check your version) Then better way will be to enable CDC.
https://msdn.microsoft.com/en-us/library/cc645937(v=sql.110).aspx

Related

Query All Views In Database

I am looking to return a view name and all record ID's where billingAddress != shippingAddress to further review. I need to query all views in one database. This is my thought process and if their is a better way or faster way to write the query by all means help me out!
What I am stuck on is how to return the view name with the recordID?
Create Table #T (ID Int Identity Not Null, ViewNames VARCHAR(1000)
Create Table #2 (viewNames varchar(1000))
Insert Into #T (ViewNames)
Select '['+C.Table_Catalog+'].['+C.Table_Schema+'].['+C.Table_Name+']' TableName
FROM Information_Schema.Columns c
Join information_schema.Tables T on C.Table_Catalog = T.Table_Catalog
AND C.Table_Schema = T.Table_Schema
AND C.Table_Name = T.Table_Name
Where T.Table_Type = 'View'
Group By '['+C.Table_Catalog+'].['+C.Table_Schema+'].['+C.Table_Name+']'
---Now this is the piece that I am stuck on as I do not know how to insert the view name into the table as well on each iteration
Declare #N int, #Str nvarchar(2000), #viewname nvarchar(2000), #MaxID int
Set #N = 1
Select #MaxID = Max(ID)
From #T
While (#N<#MaxID)
Begin
Select #viewname= viewname
From #T
Set #Str = ' Insert Into #2(viewname)
Select Top 1 '''+#viewname+'''
From '+#viewname+'
where exists(Select recordID from '+#viewname+' where [shipaddress] != [billaddress] ) '
Exec sp_eecutesql #str
Set #N = #N + 1
End
Select * from #t
Try changing your dynamic query like this.
You said you wanted the view name, and record id, so you need to add a column to #2
SET #Str = 'INSERT INTO #2(viewname, recordid)
SELECT ''' + quotename(#viewname) + ''', recordID
FROM '+ quotename(#viewname) + '
WHERE [shipaddress] != [billaddress]'
EXEC sp_executesql #str
Unless you're sure of the object names, you should try and use quotename when building up dynamic SQL
You do have a problem in your logic though...
You are missing a where clause in the query that assigns the value to #viewname
Try this...
SELECT #viewname= viewname
FROM #T
WHERE ID = #N
I do not understand sql returns set of rows so your variable #viewname can not be assigned value row by row. By default your #viewname will be assigned last row of table T.

GENERIC STORED PROCEDURE TO AUDIT TABLE UPDATE

Is there a generic stored procedure to audit the table. I have actually made one but I don't think its efficient and the stored procedure is quite long. If someone knows a better way to do it then please help me out...!
This is my table trigger.
ALTER TRIGGER [dbo].[Trigger3]
ON [dbo].[UserInfo]
AFTER Update
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TABLENAME VARCHAR(50)
DECLARE #var varbinary
SELECT * INTO #TEMPINSERTED FROM inserted
SELECT * INTO #TEMPDELETED FROM deleted
SET #var = COLUMNS_UPDATED()
EXEC TetsProc #TEMPINSERTED, #TEMPDELETED, ##PROCID, #var
DROP TABLE #TEMPINSERTED
DROP TABLE #TEMPDELETED
END
This is my stored procedure
ALTER PROCEDURE [dbo].[TetsProc]
( #insertTable varchar(max),
#deleteTable varchar(max),
#IDZ varchar(max),
#var1 varbinary
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #TABLE AS TABLE (COL_NAME NVARCHAR(MAX))
DECLARE #idTable INT
SELECT #idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = #IDZ
declare #q1 nvarchar(max),#q2 nvarchar(max)
set #q1 = 'select * from ' + #insertTable
set #q2 = 'select * from ' + #deleteTable
DECLARE #TABLENAME NVARCHAR(250)
SELECT #TABLENAME = OBJECT_NAME(parent_obj)
FROM sysobjects WHERE ID = #IDZ
----RETURN COLUMNS IF THEY ARE UPDATED----
SELECT #idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = ##procid
DECLARE #Columns_UpdateD VARCHAR(50)
SELECT #Columns_Update = ISNULL(#Columns_Updated + ', ', '') + name
FROM syscolumns
WHERE id = #idTable
AND CONVERT(VARBINARY,REVERSE(#var1)) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0
select status into #TmpcolumnsUpdated from dbo.ParseByComma(#Columns_UpdateD)
DECLARE #QRY1 NVARCHAR(MAX)
DECLARE #QRY2 NVARCHAR(MAX)
declare #column_name varchar(50)
DECLARE cursorColumnName CURSOR FOR
select status from #TmpcolumnsUpdated
OPEN cursorColumnName
FETCH NEXT FROM cursorColumnName INTO #column_name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #QRY1= 'SELECT '+#column_name + ' FROM '+ #insertTable
SET #QRY2= 'SELECT '+#column_name + ' FROM ' + #deleteTable
DECLARE #tab AS TABLE (OLD_COL VARCHAR(10))
DECLARE #tab1 AS TABLE (NEW_COL VARCHAR(10))
INSERT into #tab EXECUTE sp_executesql #QRY2
INSERT into #tab1 EXECUTE sp_executesql #QRY1
DECLARE #OLD_VALUE VARCHAR(MAX)=(SELECT OLD_COL FROM #tab)
DECLARE #NEW_VALUE VARCHAR(MAX)=(SELECT NEW_COL FROM #tab1)
IF(#OLD_VALUE!=#NEW_VALUE)
BEGIN
INSERT INTO UpdateInfo (Table_Name,Col_Name,Old_Value,New_Value,Time)
(
SELECT
#TABLENAME,
#column_name,
#OLD_VALUE,
#NEW_VALUE,
GETDATE()
)
SELECT * FROM UpdateInfo
END
DELETE FROM #tab
DELETE FROM #tab1
FETCH NEXT FROM cursorColumnName INTO #column_name
END
CLOSE cursorColumnName
DEALLOCATE cursorColumnName
drop table #TmpcolumnsUpdated
END
Looks like a sure way to drag down the performance of your server.
If you are using SQL Server 2012, I'd recommend you to check out Sql Server Audit instead.
If you have to stick with your mechanism, at the very least lose the cursor in the PROC.

MERGE without specifying column names in SQL Server 2008

I was looking at the MERGE command which seems cool but still it requires the columns to be specified. I'm looking for something like:
MERGE INTO target AS t
USING source AS s
WHEN MATCHED THEN
UPDATE SET
[all t.fields = s.fields]
WHEN NOT MATCHED THEN
INSERT ([all fields])
VALUES ([all s.fields])
Is it possible?
I'm lazy... this is a cheap proc I wrote that will spit out a general MERGE command for a table. It queries information_schema.columns for column names. I ripped out my source database name - so, you have to update the proc to work with your database (look for #SourceDB... I said it was cheap.) Anyway, I know others could write it much better - it served my purpose well. (It makes a couple assumptions that you could put logic in to handle - namely turning IDENTITY_INSERT OFF - even when a table doesn't have identity columns.) It updates the table in your current context. It was written against sql server 2008 to sync up some tables. Use at your own risk, of course.
CREATE PROCEDURE [dbo].[GenerateMergeSQL]
#TableName varchar(100)
AS
BEGIN
SET NOCOUNT ON
declare #sql varchar(5000),#SourceInsertColumns varchar(5000),#DestInsertColumns varchar(5000),#UpdateClause varchar(5000)
declare #ColumnName varchar(100), #identityColName varchar(100)
declare #IsIdentity int,#IsComputed int, #Data_Type varchar(50)
declare #SourceDB as varchar(200)
-- source/dest i.e. 'instance.catalog.owner.' - table names will be appended to this
-- the destination is your current db context
set #SourceDB = '[mylinkedserver].catalog.myDBOwner.'
set #sql = ''
set #SourceInsertColumns = ''
set #DestInsertColumns = ''
set #UpdateClause = ''
set #ColumnName = ''
set #isIdentity = 0
set #IsComputed = 0
set #identityColName = ''
set #Data_Type = ''
DECLARE #ColNames CURSOR
SET #ColNames = CURSOR FOR
select column_name, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IsIdentity ,
COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsComputed') as IsComputed , DATA_TYPE
from information_schema.columns where table_name = #TableName order by ordinal_position
OPEN #ColNames
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
WHILE ##FETCH_STATUS = 0
BEGIN
if #IsComputed = 0 and #DATA_TYPE <> 'timestamp'
BEGIN
set #SourceInsertColumns = #SourceInsertColumns +
case when #SourceInsertColumns = '' THEN '' ELSE ',' end +
'S.' + #ColumnName
set #DestInsertColumns = #DestInsertColumns +
case when #DestInsertColumns = '' THEN '' ELSE ',' end +
#ColumnName
if #isIdentity = 0
BEGIN
set #UpdateClause = #UpdateClause +
case when #UpdateClause = '' THEN '' ELSE ',' end
+ #ColumnName + ' = ' + 'S.' + #ColumnName + char(10)
END
if #isIdentity = 1 set #identityColName = #ColumnName
END
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
END
CLOSE #ColNames
DEALLOCATE #ColNames
SET #sql = 'SET IDENTITY_INSERT ' + #TableName + ' ON;
MERGE ' + #TableName + ' AS D
USING ' + #SourceDB + #TableName + ' AS S
ON (D.' + #identityColName + ' = S.' + #identityColName + ')
WHEN NOT MATCHED BY TARGET
THEN INSERT(' + #DestInsertColumns + ')
VALUES(' + #SourceInsertColumns + ')
WHEN MATCHED
THEN UPDATE SET
' + #UpdateClause + '
WHEN NOT MATCHED BY SOURCE
THEN DELETE
OUTPUT $action, Inserted.*, Deleted.*;
SET IDENTITY_INSERT ' + #TableName + ' OFF'
Print #SQL
END
Not everything you wanted, but partially:
WHEN NOT MATCHED THEN
INSERT([all fields])
VALUES (field1, field2, ...)
(The values list has to be complete, and match the order of the fields in your table's definition.)
Simple alternative to merge without naming any fields or having to update statement whenever table design changes. This is uni-directional from source to target, but it can be made bi-directional. Only acts on changed records, so it is very fast even with linked servers on slower connection.
--Two statement run as transaction batch
DELETE
C
FROM
productschina C
JOIN
(select * from productschina c except select * from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina
Here is code to setup tables to test above:
--Create a target table
--drop table ProductsUS
CREATE TABLE ProductsUS
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into target table
INSERT INTO ProductsUS
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 20.00),
(3, 'Muffin', 30.00),
(4, 'Biscuit', 40.00)
GO
--Create source table
--drop table productschina
CREATE TABLE ProductsChina
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into source table
INSERT INTO ProductsChina
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 25.00),
(3, 'Muffin', 35.00),
(5, 'Pizza', 60.00)
GO
SELECT * FROM ProductsUS
SELECT * FROM ProductsChina
GO
I think this answer deserves a little more love. It's simple, elegant and works. However, depending on the tables in question, it may be a little bit slow because the except clause is evaluating every column.
I suspect you can save a little bit of time by just joining on the primary key and the last modified date (if one exists).
DELETE
C
FROM
productschina C
JOIN
(select primary_key, last_mod_date from productschina c except select primary_key, last_mod_date from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina

How to copy a row with every column except identity column (SQL Server 2005)

My code:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid --get data, put into a temp table
ALTER TABLE #t
DROP COLUMN CID -- remove primary key column CID
INSERT INTO CTABLE SELECT * FROM #t -- insert record to table
DROP TABLE #t -- drop temp table
The error is:
Msg 8101,
An explicit value for the identity column in table 'CTABLE' can only
be specified when a column list is used and IDENTITY_INSERT is ON.
And I did set
SET IDENTITY_INSERT CTABLE OFF
GO
DECLARE
#cid INT,
#o INT,
#t NVARCHAR(255),
#c NVARCHAR(MAX),
#sql NVARCHAR(MAX);
SELECT
#cid = 10,
#t = N'dbo.CTABLE',
#o = OBJECT_ID(#t);
SELECT #c = STRING_AGG(QUOTENAME(name), ',')
FROM sys.columns
WHERE [object_id] = #o
AND is_identity = 0;
SET #sql = 'SELECT ' + #c + ' INTO #t
FROM ' + #t + ' WHERE CID = #cid;
INSERT ' + #t + '('+ #c + ')
SELECT ' + #c + ' FROM #t;'
PRINT #sql;
-- exec sp_executeSQL #sql,
-- N'#cid int',
-- #cid = #cid;
However it seems much easier to just build the following SQL and avoid the #temp table altogether:
SET #sql = 'INSERT ' + #t + '(' + #c + ')
SELECT ' + #c + ' FROM ' + #t + '
WHERE CID = #cid;';
PRINT #sql;
-- exec sp_executeSQL #sql,
-- N'#cid int',
-- #cid = #cid;
Try this:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid
ALTER TABLE #t
DROP COLUMN CID
INSERT CTABLE --Notice that INTO is removed here.
SELECT top(1) * FROM #t
DROP TABLE #t
Test Script(Tested in SQL 2005):
CREATE TABLE #TestIDNT
(
ID INT IDENTITY(1,1) PRIMARY KEY,
TITLE VARCHAR(20)
)
INSERT #TestIDNT
SELECT 'Cybenate'
Try specifying the columns:
INSERT INTO CTABLE
(col2, col3, col4)
SELECT col2, col3, col4
FROM #t
Seems like it might be thinking you are trying to insert into the PK field since you are not explicitly defining the columns to insert into. If Identity insert is off and you specify the non-pk columns then you shouldn't get that error.
Here's an example to dynamically build a list of columns - excluding the primary key columns - and execute the INSERT
declare #tablename nvarchar(100), #column nvarchar(100), #cid int, #sql nvarchar(max)
set #tablename = N'ctable'
set #cid = 1
set #sql = N''
declare example cursor for
select column_name
from information_schema.columns
where table_name = #tablename
and column_name not in (
select column_name
from information_schema.key_column_usage
where constraint_name in (select constraint_name from information_schema.table_constraints)
and table_name = #tablename
)
open example
fetch next from example into #column
while ##fetch_status = 0
begin
set #sql = #sql + N'[' + #column + N'],'
fetch next from example into #column
end
set #sql = substring(#sql, 1, len(#sql)-1)
close example
deallocate example
set #sql = N'insert into ' + #tablename + '(' + #sql + N') select top(1) ' + #sql + ' from #t'
--select #sql
exec sp_executesql #sql
If using SQL Server Management Studio and your problems you have too many fields to type them all out except the identity column, then right click on the table and click "Script table as" / "Select To" / "New Query Window".
This will provide a list of fields that you can copy & paste into your own query and then just remove the identity column.
Try invoking the INSERT statement with EXEC:
SELECT * INTO #t FROM CTABLE WHERE CID = #cid
ALTER TABLE #t
DROP COLUMN CID
EXEC('INSERT INTO CTABLE SELECT top(1) * FROM #t')
DROP TABLE #t
You can't do this:
INSERT INTO CTABLE SELECT top(1) * FROM #t
Because the column listings aren't the same. You've dropped the PK column from #t, so you have 1 less column in #t than in CTABLE. This is the equivalent of the following:
INSERT INTO CTABLE(pk, col1, col2, col3, ...)
select top(1) col1, col2, col3, ...
from #t
This wouldn't work for obvious reasons. Similarly, you aren't going to be able to specify the * wildcard to do the insert if you're not inserting all of the columns. The only way to do the insert without including the PK is to specify every column. You can generate a list of columns through dynamic sql, but you'll have to specify them one way or another.

Dynamic SQL results into temp table in SQL Stored procedure

The code is as follows:
ALTER PROCEDURE dbo.pdpd_DynamicCall
#SQLString varchar(4096) = null
AS
Begin
create TABLE #T1 ( column_1 varchar(10) , column_2 varchar(100) )
insert into #T1
execute ('execute ' + #SQLString )
select * from #T1
End
The problem is that I want to call different procedures that can give back different columns.
Therefore I would have to define the table #T1 generically.
But I don't know how.
Can anyone help me on this problem?
Try:
SELECT into #T1 execute ('execute ' + #SQLString )
And this smells real bad like an sql injection vulnerability.
correction (per #CarpeDiem's comment):
INSERT into #T1 execute ('execute ' + #SQLString )
also, omit the 'execute' if the sql string is something other than a procedure
You can define a table dynamically just as you are inserting into it dynamically, but the problem is with the scope of temp tables. For example, this code:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE #T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO #T1 (Col1) VALUES ('This will not work.')
SELECT * FROM #T1
will return with the error "Invalid object name '#T1'." This is because the temp table #T1 is created at a "lower level" than the block of executing code. In order to fix, use a global temp table:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO ##T1 (Col1) VALUES ('This will work.')
SELECT * FROM ##T1
Hope this helps,
Jesse
Be careful of a global temp table solution as this may fail if two users use the same routine at the same time as a global temp table can be seen by all users...
create a global temp table with a GUID in the name dynamically. Then you can work with it in your code, via dyn sql, without worry that another process calling same sproc will use it. This is useful when you dont know what to expect from the underlying selected table each time it runs so you cannot created a temp table explicitly beforehand. ie - you need to use SELECT * INTO syntax
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
-- select #TmpGlobalTable
-- build query
SET #Sql =
'SELECT * INTO [##' + #TmpGlobalTable + '] FROM SomeTable'
EXEC (#Sql)
EXEC ('SELECT * FROM [##' + #TmpGlobalTable + '] ')
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
INSERT INTO #TempTable
EXEC(#SelectStatement)
Try Below code for creating temp table dynamically from Stored Procedure Output using T-SQL
declare #ExecutionName varchar(1000) = 'exec [spname] param1,param2 '
declare #sqlStr varchar(max) = ''
declare #tempTableDef nvarchar(max) =
(
SELECT distinct
STUFF(
(
SELECT ','+a.[name]+' '+[system_type_name]
+'
' AS [text()]
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) a
ORDER BY a.column_ordinal
FOR XML PATH ('')
), 1, 1, '') tempTableDef
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) b
)
IF ISNULL(#tempTableDef ,'') = '' RAISERROR( 'Invalid SP Configuration. At least one column is required in Select list of SP output.',16,1) ;
set #tempTableDef='CREATE TABLE #ResultDef
(
' + REPLACE(#tempTableDef,'
','') +'
)
INSERT INTO #ResultDef
' + #ExecutionName
Select #sqlStr = #tempTableDef +' Select * from #ResultDef '
exec(#sqlStr)
DECLARE #EmpGroup INT =3 ,
#IsActive BIT=1
DECLARE #tblEmpMaster AS TABLE
(EmpCode VARCHAR(20),EmpName VARCHAR(50),EmpAddress VARCHAR(500))
INSERT INTO #tblEmpMaster EXECUTE SPGetEmpList #EmpGroup,#IsActive
SELECT * FROM #tblEmpMaster
CREATE PROCEDURE dbo.pdpd_DynamicCall
AS
DECLARE #SQLString_2 NVARCHAR(4000)
SET NOCOUNT ON
Begin
--- Create global temp table
CREATE TABLE ##T1 ( column_1 varchar(10) , column_2 varchar(100) )
SELECT #SQLString_2 = 'INSERT INTO ##T1( column_1, column_2) SELECT column_1 = "123", column_2 = "MUHAMMAD IMRON"'
SELECT #SQLString_2 = REPLACE(#SQLString_2, '"', '''')
EXEC SP_EXECUTESQL #SQLString_2
--- Test Display records
SELECT * FROM ##T1
--- Drop global temp table
IF OBJECT_ID('tempdb..##T1','u') IS NOT NULL
DROP TABLE ##T1
End
Not sure if I understand well, but maybe you could form the CREATE statement inside a string, then execute that String? That way you could add as many columns as you want.

Resources