Referencing a table variable in a query string - sql

I have created a temprary table variable, which I then need to pivot:
Declare #TempTable TABLE(
Name varchar(150),
CloseDate Date,
Revenue Float)
.... <add data to it> .....
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(CloseDate)
FROM #TempTable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Name, ' + #cols + ' from
(select
t.Name,
t.CloseDate,
t.Revenue
from #TempTable as t
) x
pivot
(SUM(Revenue)
for CloseDate in (' + #cols + ')
) p '
execute(#query)
However, I am getting this error:
Must declare the scalar variable "#TempTable".
When I test the #TempTable variable using a normal SELECT it works fine:
SELECT * from #TempTable
How can I reference the variable successfully in the query string?

Unfortunately when you use execute and sp_executesql they are run within their own context, so they cannot reference table variable objects which are defined outside the scope of the dynamic SQL.
In cases like this, what I have tended to do is create a temp table instead and embed a GUID in the name of the temp table that gets created in tempdb. This ensures the table name is unique for simultaneous operations, and then I place that guid in the dynamic SQL that is created.
Performance wise it's slightly slower but still quick, but whether that is an issue for you or not depends on the number of times this will be executed and the frequency.
After some discussion with Damien in the comments to this answer we've determined that local temp tables (single hashtag) can be used when executing dynamic SQL.
Therefore if you change your declare table variable to CREATE TABLE #TempTable and change the reference in your dynamic SQL this should work properly for you.
The only concern I have about using temp tables is the persistence of objects if your calling code uses some form of connection pooling which doesn't sound like it's the case here.
However, as a general self-paranoid self practice I like to throw one of these before the create table and at the end of the statement to clean up the objects.
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
DROP TABLE #TempTable
CREATE TABLE #TempTable (Value VARCHAR(50));

Related

How to dynamically create and populate a local temp table (when the table headers are unknown)?

I am currently using code similar to the following:
SET #Query = 'SELECT * INTO #Temp FROM MyTable'
EXEC sp_executesql #Query
Obviously, this does not work. Until now, I have been getting around this problem by using global temporary tables within my dynamic sql. However, this is not an option once the stored procedure is released to users, as their global temp tables would conflict with one another.
How might I get around this issue? I must specify that the structure of the table MyTable is unknown, so creating the temp table outside of the dynamic sql is (presumably?) not an option.
Edit: Apologies if I was not specific enough with exactly what I am trying to achieve - I thought it would be best to keep this question as relevent to the actual issue as possible.
Obviously, the string that I am trying to execute is not fixed. It is instead constructed so that the table, from which the data is being retrieved, may be specified. Although still not overly complex, the actual query string that I am using is more along the lines of
#Query = CONCAT('SELECT * INTO #Temp FROM ', #Environment, '.[schema].', #Table)
Hopefully this sheds more light on the problem?
Few points:
1. If you want to store the output of dynamic sql into temp table, you need to create the temp table structure first(with CREATE TABLE #TEMP script) and then you can insert data by doing something like below:
INSERT INTO #TEMP EXEC sp_executesql #Query
This will populate data into temp table with all properties of local temp table.
Seeing your query, it does not look like you have a need of dynamic sql unless you are forming some clause like where clause dynamically.
Let me know if this helps.
Couldn't you do something like this:
declare #query nvarchar(4000) = '
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].[temp]'') AND type in (N''U''))
begin
drop table temp
end
Select top 2 *
into temp
from sys.databases
'
exec sp_executesql #query
Select * from temp
With Select into you don't need to know the structure of your query:
https://www.w3schools.com/sql/sql_select_into.asp
Just as an idea.

Creating SQL Function to Create Multiple Tables

I am currently moving a SAS process to SQL. Within the SAS process, I leverage macros to create a multitude of tables.
I am trying to leverage the CREATE FUNCTION function within SQL to mimic this process, however I am stuck. I have three arguments, the server name, the name of the new table and the table where it should select from. I'm not sure what I should specify as what I am returning as I'm not looking to return anything, just create tables.
CREATE FUNCTION dbo.functionname (#server VARCHAR(250), #name VARCHAR(250), #table VARCHAR(250))
RETURN (???)
AS BEGIN
SELECT *
INTO #server.dbo.#nm
FROM #table
RETURN
END
This is what I have come up with so far. My SELECT statement wouldn't actually be *, I just put that for simplicity sake for this question.
UPDATE: In this instance, using a stored procedure is not an option as permissions have been limited.
You can create a dynamic SQL script as follows
declare #newtable sysname = 'T003',
#sourcetable sysname = 'sys.tables'
declare #sql nvarchar(max)
set #sql = N'select * into ' + #newtable + ' from ' + #sourcetable + ';'
set #sql = #sql + N'select * from ' + #newtable
exec sp_executesql #sql
Then you can use it in a stored procedure
To return data from new table, the table type must be known before. In this case it is not possible, so developer cannot create the function return type
Or create a function just to create the table and insert data into it. But return fail or success, etc
Then select from the new table using a dynamic SQL again

Select from temp table fails

I'm inserting data into a temp table and querying the temp table fails
DECLARE #SQLQuery AS NVARCHAR(500)
SET #SQLQuery = 'SELECT Top 100 *
INTO #tempTable
FROM ' + #origDB + '.dbo.' + #origTable + ' o WITH (NOLOCK) '
EXECUTE sp_executesql #SQLQuery
and when I try to query the temp table , like so
select * from #tempTable
I get the following error :
Invalid object name '#tempTable'.
Courtesy of MSDN
The problem that you have is with the scope. The TEMP table is creatd at the scope of the EXEC() method and hence it is not available after the function returns. To fix this, create your temp table before calling EXEC() and use an INSERT INTO instead of SELECT INTO.
As others have said, the scope of a temporary table is limited to the session context in which it is created - a stored procedure runs in its own context.
You could use a global temporary table ##tempTable, but it's generally a bad idea as it would be available to other sessions than the one creating it.

Dynamic Query not writing into temp table

DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(hi)
FROM #hello
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
--select #cols
The above code for some reason is not entering date into the #storage temp table?
Why is that happening?
select #query = 'SELECT *
into #storage
FROM (
SELECT *
FROM #hello) up
PIVOT ( MAX(AFEAmount) FOR hi IN (' + #cols + ') ) AS pvt'
exec(#query)
The code is crete the temp table #storage. The problem is that the table is not "in scope", so you cannot access it outside the exec() statement. Temporary tables are limited in scope to the particular process that creates them. The temporary table is available when exec() is running but not outside it. There is some explanation of this in the documentation (here).
In some cases, you can get around this by using the insert . . . exec() form of the statement. If the table were permanent, you would construct the create table statement for it, and then do:
insert into storage(...)
exec(#query);
Or, you could even do this inside the dynamic SQL, because the permanent table persists across sessions.
The problem with the temporary table, in this case, is that you don't know its structure. You need to create the table before doing the insert. A variable structure requires dynamic SQL, and you have the same problem when creating the table. It is created at the "wrong" lvel.
You could change the table to a global temporary table (those that begin with ##). This has scoping across different processes.
Or, you could create a persistent table with a funky name (I would use _storage or _storage_YYYYMMDDHHMMSS where the latter has a time stamp at the end). Just remember to delete the table when you are done.

Passing Multiple Values to Variable in Linked Server Connection String

I have the following query, which pulls data from an Oracle DB into SQL Server 2005:
SELECT *
FROM (
SELECT *
FROM OPENQUERY(LINKEDSERVERNAME, 'SELECT FOO, BAR, FROM TABLE
WHERE ID IN(' + #IDs + '
')) AS TMP
WHERE SOME_ID IN
(SELECT DISTINCT ID
FROM LOCALTABLE);
The runtime, however, is very long, as the query from the linked server results in a large number of rows. I am only interested in a small number of these rows, however the criteria limiting my query are held in the destination database.
Via another post on SO, I see I could potentially use a variable in dynamic sql that looks like:
DECLARE #IDs AS NVARCHAR(100);
SET #IDs = (SELECT ID FROM LOCALTABLE)
DECLARE #sql AS NVARCHAR(3000);
SET #sql = 'SELECT * FROM OPENQUERY(LINKEDSERVERNAME, ''SELECT FOO, BAR, FROM TABLE
WHERE ID IN(' + #IDs + '))'
EXEC sp_executesql #sql
However, I obviously cannot assign more than one value to the variable, and so the result set only contains results for the final ID placed in #IDs.
What is the best strategy for accomplishing this task for all distinct IDs in the local table?
Anup Shah has already pointed out what is wrong in his comment. Your SELECT assignment will only ever put one value into your variable. You need a way to convert your table results to a CSV style for the IN statement. Pinal Dave has a good post which shows a well known technique for doing this with XML PATH.
http://blog.sqlauthority.com/2009/11/25/sql-server-comma-separated-values-csv-from-table-column/
Worth noting that SELECT #var = #var + var FROM table IS NOT a valid way of doing this, although it may appear to work in some cases.
James