SQL Server Stored Procedure with multiple batches - reuse of temp table - sql-server

I have a short SQL script which has been in use for a bit. It reuses a temp table within a script and has been working nicely.
Recently, I decided that I'm going to stick this whole thing into a procedure, though I had a surprise waiting for me - there's a use of the keyword GO in order to divide the script into two batches (that is how I was able to reuse the temp table) - which is why SQL Server is barking at me.
Here is a dumbed down script displaying the functionality of the script:
DROP TABLE IF EXISTS #temp;
SELECT
'john' AS first_name
,'doe' AS last_name
INTO #temp;
SELECT * FROM #temp
GO
TRUNCATE TABLE #temp;
DROP TABLE #temp;
SELECT
'jane' AS first_name
,'doe' AS last_name
INTO #temp;
Here's what I tried to do in the procedure, albeit unsuccessfully:
CREATE PROCEDURE #temp_proc
AS
BEGIN
DROP TABLE IF EXISTS #temp;
SELECT
'john' AS first_name
,'doe' AS last_name
INTO #temp;
SELECT * FROM #temp
GO
TRUNCATE TABLE #temp;
DROP TABLE #temp;
SELECT
'jane' AS first_name
,'doe' AS last_name
INTO #temp;
END
Here's the error message I get when I attempt to create the procedure:
Msg 102, Level 15, State 1, Procedure #temp_proc, Line 10 [Batch Start Line 0]
Incorrect syntax near '#temp'.
Msg 102, Level 15, State 1, Line 20
Incorrect syntax near 'END'.
Objective: I'd like to continue reusing the same temp table names, though I would like to stick all of this in a procedure. Any ideas?

You can't use GO in a stored procedure. If you remove it, your code should run as expected. Although there is no need to truncate the table if you drop it later.

sorry but no - you do not understand GO. It is a keyword that is understood and implemented by an application. It indicates to the application to take all preceding script text (until the beginning of the script or the previous GO) and send it to the db engine for execution. Your procedure creation script is interpreted and executed as 2 separate batches (which is why GO is called a batch separator). The first one is:
CREATE PROCEDURE #temp_proc
AS
BEGIN
DROP TABLE IF EXISTS #temp;
SELECT
'john' AS first_name
,'doe' AS last_name
INTO #temp;
SELECT * FROM #temp
GO
Followed by:
TRUNCATE TABLE #temp;
DROP TABLE #temp;
SELECT
'jane' AS first_name
,'doe' AS last_name
INTO #temp;
END
And notice that this is merely creating your procedure. It has nothing to do with how the procedure is executed. There is nothing to leverage here - you cannot divide your procedure definition or execution into batches using "go". So your idea and direction is, quite simply, impossible without a change. Dynamic sql is a possibility - but that is a level of complexity that will challenge and tax you.

Aside from removing the GOs in your code, you can't drop and recreate the same temp table in a stored procedure; SQL Server ignores the drop and attempts to create the same # table in the two SELECT INTO statements.

Related

SQL Server : what happens if I run the same stored procedure simultaneously that has a select into same temporary table

Any idea, what happen if I run same stored procedure (using jmeter) simultaneously,and in that stored procedure there are query
SELECT INTO #temp
Will the second stored procedure run after first stored procedure is done?
Or will the temp table be created twice (I heard there is local temp table in SQL Server)?
Sorry for the dumb question, I cannot find any answer on Google.
Thanks
A temporary table only exists in the scope it was created in (and "subscopes" of that scope) and only persist for the duration of that scope.
So, for example. If you were to run the below, you wouldn't get any errors:
EXEC sys.sp_executesql N'CREATE TABLE #temp (ID int);';
EXEC sys.sp_executesql N'CREATE TABLE #temp (ID int);';
That's because the table, #temp would only exist within the scope of the "dynamic" statement, and would cease to as soon as it completes.
On the other hand, something like the below would fail (This is wrong, see my edit at the bottom):
CREATE TABLE #temp (ID int);
EXEC sys.sp_executesql N'CREATE TABLE #temp (ID int);';
DROP TABLE #temp;
That's because the "dynamic" statement has access to the "outer" scope, and so would see that #temp already exists and generate an error.
Running 2 statements at the same time in the same connection isn't possible, so you won't be able to call the same Stored Procedure at the same time. This means that both will have different scopes, and will therefore reference they're own object #temp, that is specific to their scope.
You could again test this with a similar idea. Run the below, and then open a new connection and run it again (before the other is complete). You'll notice that they both succeed:
CREATE TABLE #temp (ID int);
WAITFOR DELAY '00:30'; --Waits 30 seconds
--While the WAITFOR happens, open the another connection and run all this SQL at the same time
DROP TABLE #temp;
Side note, Global Temporary tables do not behave the same way, but I specifically only reference temporary tables here, not global ones.
EDIT: Appears I am wrong, sort of, on inner scopes. You actually get some very odd behaviour. Take the below:
CREATE TABLE #temp (ID int);
INSERT INTO #temp VALUES(1);
EXEC sys.sp_executesql N'CREATE TABLE #temp (ID int); SELECT * FROM #temp;';
SELECT *
FROM #temp
DROP TABLE #temp;
This will return 2 datasets, one with no rows, one with 1 row. If, however, you remove the CREATE in the deferred statement then you get 1 row from both:
CREATE TABLE #temp (ID int);
INSERT INTO #temp VALUES(1);
EXEC sys.sp_executesql N'SELECT * FROM #temp;';
SELECT *
FROM #temp
DROP TABLE #temp;
This occurs on SQL Server 2019, but I sure I recall that this behaviour isn't how it was on previous versions. Perhaps I am recalling (very) old behaviour.

Creating temp tables in sybase

I am running into an issue with creating temp tables in Sybase db. We have a sql where we create a temp table, insert/update it and do a select * from it at the end of get some results. We are invoking this sql from the service layer using spring jdbc tmplate. The first run works fine, but the next subsequesnt runs fails with error
cannot create temporary table <name>. Prefix name is already in use by another temorary table
This is how I am checking if table exists:
if object_id('#temp_table') is not null
drop table #temp_table
create table #temp_table(
...
)
Anything I am missing here?
Might not be a great response, but I also have that problem and I have 2 ways around it.
1. Do the IF OBJECT_ID Drop Table as a separate execute prior to the query
2. Do the Drop Table without the IF OBJECT_ID() right after your query.
You are really close but temp tables require using the db name before too.
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
DROP TABLE #Results
GO
It would be the same if you were checking if a user table in another database existed.
IF OBJECT_ID('myDatabase..myTable') IS NOT NULL
DROP TABLE myDatabase..myTable
GO
NOTE: A bit more info on BigDaddyO's first suggestion ...
The code snippet you've provided, when submitted as a SQL batch, is parsed as a single unit of work prior to the execution. Net result is that if #temp_table already exists when the batch is submitted, then the compilation of the create table command will generate the error. This behavior can be seen in the following example:
create table #mytab (a int, b varchar(30), c datetime)
go
-- your code snippet; during compilation the 'create table' generates the error
-- because ... at the time of compilation #mytab already exists:
if object_id('#mytab') is not NULL
drop table #mytab
create table #mytab (a int, b varchar(30), c datetime)
go
Msg 12822, Level 16, State 1:
Server 'ASE200', Line 3:
Cannot create temporary table '#mytab'. Prefix name '#mytab' is already in use by another temporary table '#mytab'.
-- same issue occurs if we pull the 'create table' into its own batch:
create table #mytab (a int, b varchar(30), c datetime)
go
Msg 12822, Level 16, State 1:
Server 'ASE200', Line 1:
Cannot create temporary table '#mytab'. Prefix name '#mytab' is already in use by another temporary table '#mytab'.
As BigDaddyO has suggested, one way to get around this is to break your code snippet into two separate batches, eg:
-- test/drop the table in one batch:
if object_id('#mytab') is not NULL
drop table #mytab
go
-- create the table in a new batch; during compilation we don't get an error
-- because #mytab does not exist at this point:
create table #mytab (a int, b varchar(30), c datetime)
go

Unexpected error on query

When executing this code on SQL Server 2012 through SQL Server Management Studio:
PRINT 'Begin';
SELECT 1 A INTO #TEST;
DROP TABLE #TEST;
SELECT 1 A INTO #TEST;
I get the error
There is already an object named '#TEST' in the database.
and it doesn't even print 'Begin'.
What I am expecting is that the code prints 'Begin' and I have a #TEST table with one column called 'A' and one row containing '1'. Why is my expectation wrong?
Thanks
--- Update ---
By the way, this code returns the same error message:
IF OBJECT_ID('tempdb..#TEST') IS NOT NULL DROP TABLE #TEST
GO
PRINT 'Begin';
SELECT 1 A INTO #TEST;
DROP TABLE #TEST;
SELECT 1 A INTO #TEST;
Once you run the code once the temp table #TEST already exists. Once created it lasts until your session is over. You would need to drop it before you can run it again.
put this at the top and it will work:
IF OBJECT_ID('tempdb..#Test') IS NOT NULL DROP TABLE #TEST
GO
If this doesn't work then the problem then is that you are using the INTO statement twice. The second call is causing the error because #test would exist after the first into. It's kind of a bug with error checking SQL I guess. In order to get around it you either need to use a different temp table name on the second into or put a GO after the DROP table like this
PRINT 'Begin';
SELECT 1 A INTO #TEST;
DROP TABLE #TEST;
GO
SELECT 1 A INTO #TEST;
Alternatively you could just create the table first and then insert into them but then you lose the performance advantages of into - not sure if that is required or not.
Try this:
DROP TABLE #TEST;
PRINT 'Begin';
SELECT 1 A INTO #TEST;
DROP TABLE #TEST;
SELECT 1 A INTO #TEST;

Temp tables: CREATE vs. SELECT INTO

I've searched and found this article about temporary tables in SQL Server because I've met a line in one of our stored procedures saying:
SELECT Value SomeId INTO #SomeTable FROM [dbo].[SplitIds](#SomeIds, ';')
I know that #SomeTable is stored in tempdb as a temporary table. However, I don't understand why we don't have to use CREATE TABLE #SomeTable first as it is written in the mentioned article. Our code is working fine, I just don't get why it is enough to use SELECT ... INTO #SomeTable. What would be the consequence when I add CREATE TABLE #SomeTable at the beginning? Would we get any differences in performance? Would the table be stored at another location?
Select ... into [table] uses the properties of the dataset generated from the Select statement to create a temporary table and subsequently fill the table.
The alternative to using Select ... into [table] is to use a Create Table statement followed by an Insert Into statement. Explicitly creating the table offers more control and precision.
Using a Select ... into [Table] may seem like a no-brainer, but there are situations where Select ... into [Table] can be problematic.
For instance, when you are going to create a temporary table and insert additional rows at a later time, using the Select ... into [Table] syntax can cause problems, especially with string-based and nullable fields.
As an example of the limitations of the Select ... into [table], the script below creates a temporary table with two fields, First_Name and Last_Name. Next, an Insert statement attempts to add another record to the temporary table, but fails as the values would be truncated.
Select 'Bob' as First_Name
, 'Smith' as Last_Name
Into #tempTable;
Insert into #tempTable (First_Name, Last_Name)
Select 'Christopher' as First_Name
, 'Brown' as Last_Name;
The script fails because the Select ... into [table] statement creates a table equivalent to the following script:
Create Table #tempTable (
First_Name varchar(3) Not Null
Last_Name varchar(5) Not Null
);

Compare tables columns(types,collation,nullable...) and update 2nd one according to found differences

I have two Dbs on the same server named 'DB_prod' and 'DB_test', and they are simply the same.
I need to assume that someone can modify table on 'DB_prod'. The script need to find all columns differences (types,collation,nullable,max length) + find new columns, and alter it to table on the 'DB_test'.
There are no relationships between tables.
First step is to find diffrences and I know how to accomplish this.
The secound step would be to move all chages to 'DB_test'.
The only idea I have for now is to use dynamic sql, so write diffrent table 'alters' and execute them in cursor.
Any other idea?
All work need to be done by procedure(s).
Thanks
A database-scoped trigger is probably what you're looking for. You can use it to record the alter table statements to a table TriggerLog and then run your stored procedure to execute the statements on your test table.
--Table to hold your event data.
CREATE TABLE TriggerLog
(
Event_Data NVARCHAR(MAX),
Username NVARCHAR(250),
Event_Date DATETIME
)
CREATE TRIGGER trg_alter_table ON DATABASE --database level trigger
FOR ALTER_TABLE
AS
INSERT TriggerLog
SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'),
COALESCE(SUSER_SNAME(),USER_NAME()),
GETDATE();
GO
ALTER TABLE ProdTable
ADD column1 VARCHAR(100);
SELECT *
FROM triggerlog
Results:
Event_data Username Event_Date
------------------------------------------------------------------------------------------
ALTER TABLE ProdTable ADD column1 VARCHAR(100); Domain\User 2015-03-16 09:29:47.387

Resources