Dynamic temp table - sql

I have to create a dynamic temp table in sql server, based in a list of columns, for example, I have a table ListOfColumns that has many names of columns inside that refers to a real table
ListOfColumns
ColumnNameA
ColumnNameB
ColumnNameC
I create a function to get a string with all these columns in this format:
"ColumnNameA, ColumnNameB, ColumnNameC"
Now I need to create my temp table based on those columns that are in a real table called Report. I can have more or less columns (Its a dynamic report column generator)
I need to do a dynamic SQL, I don't have the datatypes for each column, I would like to create a temp table with dynamic sql and inherit the datatypes of my Report table. Is there any way of doing this?
By the way... I don't want to use global variables.
Thank you.

If, for example, your Report table looks something like this:
create table Report (
ColumnNameA varchar(4),
ColumnNameB integer,
ColumnNameC integer,
ColumnNameD varchar(8),
ColumnNameE bit,
ColumnNameF integer
);
You can create the following procedure:
create proc copy_table #col_names varchar(128)
as
DECLARE #SQLQuery AS NVARCHAR(500)
SET #SQLQuery = 'SELECT ' + #col_names + ' into ReportTemp from Report where 1 = 0'
EXEC(#SQLQuery);
and call it using the comma separated columns string you have, as below:
exec copy_table #col_names = 'ColumnNameA, ColumnNameB, ColumnNameC'
and you should have your table created with the correct column types. To test it, insert one row and then query it.
insert into ReportTemp values ('abc', 1, 2 );
select * from ReportTemp;
To see this in action, check out this sql fiddle.
Note that, this will not carry over constraints, indices, etc -- just the column names and types.

You can't create a dynamic temporary table.
The reason is that the temporary table is associated with the SQL session. When you execute dynamic SQL, it creates a new session that terminates at the end of the session. So, the table gets created during the exec. Then it gets dropped (or out of context) when control moves back to the calling procedure.
Here are some work-arounds, none of which you might like:
Create a table with a canonical prefix, such as "_" to represent a working table. Then drop this table in the stored procedure and catch exceptions to drop it in almost all cases.
Create a temporary table with all possible column values.
Create your own "temporary" database for such working tables.
Use generic column names and keep the correspondence from these generic columns to your columns somewhere else.

Related

Dynamic Schema name in SQL View

I have two datasets:
one is data about dogs [my data]
the second is a lookup table of matching keys [I have no control over this data]
The matching keys are updated regularly, and I want to create a View (or something that fulfills the same purpose) of the Dog dataset, which always joins on the most recent matching keys. Furthermore, I need to be able to reference it inline - as though it was a table.
The match updates in the lookup table are differentiated by their schema names, so to get the most recent, I just have to identify the latest schema name and swap it out of the query.
Given that both Views and Table Valued Functions prohibit dynamic SQL, and Stored Procedures can't be referenced like a table can be how can I achieve this in just SQL?
The match updates in the lookup table are differentiated by their schema names, so to get the most recent, I just have to identify the latest schema name and swap it out of the query.
You can use a view to solve this problem, but you need some way of altering it whenever new data is entered into the database.
I'm assuming that whenever a new schema is created, a new table is also created in that schema, but the table name and it's column names are always the same. Note that this assumption is critical to the solution I'm about to propose - and that solution is to use a DDL trigger listening to the create_table event on the database level to alter your view so that it will reference the schema of the newly created table.
Another assumption I'm making is that you either already have the initial view, or that you are working with SQL Server 2016 or higher (that allows create or alter syntax).
So first, let's create the initial view:
CREATE VIEW dbo.TheView
AS
SELECT NULL As Test
GO
Then, I've added the DML trigger, which creates and executes a dynamic alter view statement based on the schema of the newly created table:
CREATE TRIGGER AlterViewWhenSchemaChanges
ON DATABASE
FOR CREATE_TABLE
AS
DECLARE #Sql nvarchar(max),
#NewTableName sysname,
#NewSchemaName sysname;
SELECT #NewSchemaName = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)'),
#NewTableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)');
-- We only want to alter the view when this specific table is created!
IF #NewTableName = 'TableName'
BEGIN
SELECT #Sql =
'ALTER VIEW dbo.TheView
AS
SELECT Col as test
FROM '+ #NewSchemaName +'.'+ #NewTableName
EXEC(#Sql)
END
GO
This way, whenever a new table with the specific name (TableName in my example) is created, the view gets altered to reference the last TableName created (which is obviously created in the newest schema).
Testing the script:
SELECT * FROM dbo.TheView;
GO
Results:
Test
NULL
Create a new schema with the table TableName
CREATE SCHEMA SchemaName
CREATE TABLE SchemaName.TableName (Col int);
GO
-- insert some data
INSERT INTO SchemaName.TableName(Col) VALUES (123);
-- get the data from the altered view
SELECT * FROM dbo.TheView
Results:
test
123
You can see a live demo on Rextester.

Passing temp table from one execution to another

I want to pass a temp table from one execution path to another one nested in side it
What I have tried is this:
DECLARE #SQLQuery AS NVARCHAR(MAX)
SET #SQLQuery = '
--populate #tempTable with values
EXECUTE('SELECT TOP (100) * FROM ' + tempdb..#tempTable)
EXECUTE sp_executesql #SQLQuery
but it fails with this error message:
Incorrect syntax near 'tempdb'
Is there a another\better way to pass temporary table between execution contexts?
You can create a global temp table using the ##tablename syntax (double hash). The difference is explained on the TechNet site:
There are two types of temporary tables: local and global. They differ from each other in their names, their visibility, and their availability. Local temporary tables have a single number sign (#) as the first character of their names; they are visible only to the current connection for the user, and they are deleted when the user disconnects from the instance of SQL Server. Global temporary tables have two number signs (##) as the first characters of their names; they are visible to any user after they are created, and they are deleted when all users referencing the table disconnect from the instance of SQL Server.
For example, if you create the table employees, the table can be used by any person who has the security permissions in the database to use it, until the table is deleted. If a database session creates the local temporary table #employees, only the session can work with the table, and it is deleted when the session disconnects. If you create the global temporary table ##employees, any user in the database can work with this table. If no other user works with this table after you create it, the table is deleted when you disconnect. If another user works with the table after you create it, SQL Server deletes it after you disconnect and after all other sessions are no longer actively using it.
If a temporary table is created with a named constraint and the temporary table is created within the scope of a user-defined transaction, only one user at a time can execute the statement that creates the temp table. For example, if a stored procedure creates a temporary table with a named primary key constraint, the stored procedure cannot be executed simultaneously by multiple users.
The next suggestion may be even more helpful:
Many uses of temporary tables can be replaced with variables that have the table data type. For more information about using table variables, see table (Transact-SQL).
Your temp table will be visible inside the dynamic sql with no problem. I am not sure if you are creating the temp table inside the dynamic sql or before.
Here it is with the table created BEFORE the dynamic sql.
create table #Temp(SomeValue varchar(10))
insert #Temp select 'made it'
exec sp_executesql N'select * from #Temp'
The reason for your syntax error is that you are doing an unnecessary EXECUTE inside an EXECUTE, and you didn't escape the nested single-quote. This would be the correct way to write it:
SET #SQLQuery='
--populate #tempTable with values
SELECT TOP 100 * FROM tempdb..#tempTable'
However, I have a feeling that the syntax error is only the beginning of your problems. Impossible to tell what you're ultimately trying to do here, only seeing this much of the code, though.
Your quotations are messed up. Try:
SET #SQLQuery='
--populate #tempTable with values
EXECUTE(''SELECT TOP 100 * FROM '' + tempdb..#tempTable + '') '

StoredProc manipulating Temporary table throws 'Invalid column name' on execution

I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more

stored procedures and temp tables

I have to create a 10 stored procedure as follows:
In stored procedure # 1 I create temp table 1 and this temp table is used in stored procedure 2 to create another temp table and this new tem table is used in another STORED PROCEDURE and so on.
I am not sure how to create these stored procedure, because for these stored procedure I need to have temporary tables present in temdb.
Any help
Can you user Global Temporary Tables?
SELECT * INTO ##Users FROM UserTable
The Global temp tables will remain in tempdb until deleted and can be used across different stored procs.
Assuming you want to name the table (or some of its columns) that's about to be created based on the data present in the temp table, you might want to resort to dynamic SQL, since you can't use variables like this:
declare #foo varchar(50)
select #foo = tableName from #tempTable
create table #foo (fooColumn int)
But before you even think of using dynamic SQL, you've got to ask yourself whether you really need this solution.

How can I conditionally construct a table name for an SQL CREATE TABLE statement?

Within an SQL stored procedure, I would like to have the ability to construct a table name and create it.
Example: I just logged into my database under company 03 and a customer table does not exist, so I would like for the proc to CREATE TABLE CUSTOMER03.
Is there a way to append company_id char(2) to CUSTOMER and feed it to the CREATE TABLE statement? maybe like
CREATE TABLE $tablename or $tablename+company_id?
In Oracle the syntax would be something like
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE CUSTOMER_'||v_company_id||' (..)';
END;
However this is probably a really bad idea. Six months down the line you'll want to add a column to the table and you'll need to work out which tables you need to add it to.
Also, stored procedures in Oracle need a fixed table name (of an existing table) or you'd have to reference everything through dynamic SQL which is a pain.
Better to have a single customer table with the company_id as an attribute. Then use Fine Grained Access Control to securely filter on the company_id to control who see's what company's data.
You would need to use dynamic SQL eg:
DECLARE #company_id char(2)
SET #company_id = '02'
EXEC('CREATE TABLE CUSTOMER' + #company_id + ' (id int PRIMARY KEY)')
(tested)
Use the IF NOT EXISTS modifier to the CREATE TABLE statement. This will cause the table to be created only if it does not already exist.