6

I'm at a loss as to why some SQL leveraging sp_executesql is working the way it is when temporary tables are involved, along with INSERT and SELECT - INTO. I'm using SQL Server.

Here is the code in question:

-- Insert into CREATE TABLE #results ( rid INT, ); declare @sql nvarchar(max) = ''; set @sql = 'INSERT INTO #results (rid) VALUES (1);'; EXEC sp_executesql @sql; select * from #results; -- Select into CREATE TABLE #results2 ( rid INT, ); declare @sql2 nvarchar(max) = ''; set @sql2 = 'select rid into #results2 from #results;'; EXEC sp_executesql @sql2; select * from #results2; -- Clean up drop table #results; drop table #results2; 

When this runs, #results has a single row where rid = 1, and #results2 has no rows at all, which you can see here:

enter image description here

There are honestly several points of confusion I have with this. To start off, I would expect the SELECT - INTO to fail, since a table called #results2 should already exist in this session.

You can see this error with this similar bit of SQL which doesn't leverage sp_executesql for the SELECT - INTO:

-- Insert into CREATE TABLE #results ( rid INT, ); declare @sql nvarchar(max) = ''; set @sql = 'INSERT INTO #results (rid) VALUES (1);'; EXEC sp_executesql @sql; select * from #results; -- Select into CREATE TABLE #results2 ( rid INT, ); declare @sql2 nvarchar(max) = ''; select rid into #results2 from #results; select * from #results2; -- Clean up drop table #results; drop table #results2; 

This code, as expected, generates the error:

There is already an object named '#results2' in the database.

Based on this answer, I assumed this was happening because sp_executesql has its own session. Due to this, the SELECT - INTO nested inside of sp_executesql doesn't know that #results2 exists, so it creates its own temporary table called #results2, inserts into it, then drops that table.

That makes sense to me, except, that in that very same sp_executesql is accessing the table #results. So sp_executesql knows about the #results table, but not the #results2 table? Plus my first sp_executesql with than INSERT has no trouble accessing the #results table either. Based on this commnet, it seems to that the reason sp_executesql can access #results is because sp_executesql actually creates its own batch, and batches within the same session can see each others temporary tables. Great, except then we loop back to my original confusion of why isn't sp_executesql complaining about the existence of #results2 when running the SELECT - INTO?


If I had to boil it down, my core points of confusion are:

  • Why doesn't the SELECT - INTO nested inside of sp_executesql throw an error that #results2 already exists?
  • Why does the INSERT statement nested inside of sp_executesql have no problem accessing #results and inserting into it?
  • Why does the SELECT - INTO nested inside of sp_executesql have no problem accessing #results, but doesn't seem to recognize the existence of #results2?
0

1 Answer 1

10

Background

I would expect the SELECT - INTO to fail, since a table called #results2 should already exist in this session.

You can create a temporary table of the same name in a nested scope.

A local temporary table created within a stored procedure or trigger can have the same name as a temporary table that was created before the stored procedure or trigger is called. However, if a query references a temporary table and two temporary tables with the same name exist at that time, it isn't defined which table the query is resolved against.

A call to sys.sp_executesql creates a nested scope in the same way as a stored procedure call or trigger execution.

A SELECT...INTO is effectively a CREATE TABLE followed by an INSERT.

I assumed this was happening because sp_executesql has its own session

No, the session ID is the same as you can see using SELECT @@SPID in the different scopes.

-- Same session ID reported for both SELECT @@SPID; EXEC sys.sp_executesql N'SELECT @@SPID;'; 

Questions

  • Why doesn't the SELECT - INTO nested inside of sp_executesql throw an error that #results2 already exists?

Creating a temporary table with the same name as one already existing from an outer scope is allowed.

  • Why does the INSERT statement nested inside of sp_executesql have no problem accessing #results and inserting into it?

A scope can see temporary tables created in a higher scope.

  • Why does the SELECT - INTO nested inside of sp_executesql have no problem accessing #results, but doesn't seem to recognize the existence of #results2?

See above.

Practical

I discourage this practice because the behaviour can be confusing, impact plan cache size, and isn't technically guaranteed to work if a name collision occurs.

My advice is to avoid accessing temporary tables created in a parent scope where possible and use unique names for temporary objects in general.

Additional example

The following mess compiles and runs without error:

DROP TABLE IF EXISTS #Results; SELECT rid = 1 INTO #Results; PRINT OBJECT_ID(N'tempdb..#Results', N'U'); EXEC sys.sp_executesql N' SELECT rid = 1 INTO #Results; PRINT OBJECT_ID(N''tempdb..#Results'', N''U''); EXEC sys.sp_executesql N'' SELECT rid = 1 INTO #Results; PRINT OBJECT_ID(N''''tempdb..#Results'''', N''''U''''); EXEC sys.sp_executesql N'''' SELECT rid = 1 INTO #Results; PRINT OBJECT_ID(N''''''''tempdb..#Results'''''''', N''''''''U''''''''); DROP TABLE #Results; DROP TABLE #Results; DROP TABLE #Results; DROP TABLE #Results; ''''; ''; '; 

Example output:

(1 row affected) -1492493503 (1 row affected) -1476493446 (1 row affected) -1460493389 (1 row affected) -1444493332 

db<>fiddle demo

Notice the temporary tables have different IDs. You can drop the same table name four times in a row. Each drop brings a different temporary table (with the same name) into visibility.

Related reading

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.