Temp Table behavior with stored procedures - sql-server

SQL Server 2008
I create a local temp table in a stored procedure that then calls another stored procedure that ALTERs the temp table by adding columns to it. SELECT * behaves as expected/desired in both the calling and the called stored procedure: the modified table columns are returned.
However, if I attempt to SELECT [added column], I get an error 'invalid column'.
CREATE PROCEDURE ProcA
AS
BEGIN
CREATE TABLE #BigTemp
(
BigTempId INT IDENTITY (1,1) NOT NULL
);
execute ProcB;
SELECT * FROM #BigTemp;
END
CREATE PROCEDURE ProcB
AS
BEGIN
ALTER TABLE #BigTemp
ADD ProductId int NULL;
SELECT * FROM #BigTemp;
END
Execution of ProcA returns
BigTempId ProductId
-----------------------
and
BigTempId ProductId
-----------------------
Now, if I modify ProcB:
ALTER PROCEDURE ProcB
AS
BEGIN
ADD ProductId int NULL;
SELECT ProductId FROM #BigTemp;
END
I'm rewarded with:
Invalid column name 'ProductId'
Now, in my actual objects I'm creating the ALTER/ADD statement dynamically and executing it with sp_executesql, but the behavior is the same. It sure doesn't make any sense to me. Ideas?

I am still not getting why this behavior with Temp tables.
But below code do the trick to over come it.
ALTER PROCEDURE PROCA
AS
BEGIN
CREATE TABLE #BIGTEMP
(
BIGTEMPID INT IDENTITY (1,1) NOT NULL
);
EXECUTE PROCB;
SELECT PRODUCTID FROM #BIGTEMP B
END
ALTER PROCEDURE PROCB
AS
BEGIN
ALTER TABLE #BIGTEMP
ADD PRODUCTID INT NULL;
SELECT * INTO #TEMP FROM #BIGTEMP
SELECT PRODUCTID FROM #TEMP
END

Issue is due to pre compilation . add WITH RECOMPILE or exec with recomile

Related

Create / Alter procedure referencing non-existing table or view

According to MSDN:
A procedure can reference tables that do not yet exist. At creation
time, only syntax checking is performed. The procedure is not compiled
until it is executed for the first time. Only during compilation are
all objects referenced in the procedure resolved. Therefore, a
syntactically correct procedure that references tables that do not
exist can be created successfully; however, the procedure fails at
execution time if the referenced tables do not exist.
I was always under the impression that referenced tables are checked. For example if you reference an incorrect column in an existing view it complains at compile time:
So what is going on here, why are columns checked within the procedure but not views / tables?
The document is referencing that, at the time to Stored Procedure is created, it isn't validated. Take this batch:
USE Sandbox;
GO
CREATE PROC dbo.MyProc #ID int AS
BEGIN
SELECT *
FROM dbo.MyTable
WHERE ID = #ID;
END;
GO
CREATE TABLE dbo.MyTable (ID int,
SomeValue varchar(20));
INSERT INTO dbo.MyTable
VALUES(1,'asdjka'),
(2,'asdkj');
GO
EXEC dbo.MyProc 1;
GO
DROP TABLE dbo.MyTable;
DROP PROC dbo.MyProc;
Notice that I CREATE dbo.MyProc before dbo.MyTable, even though it references that object. That's because the validity of the objects in the procedure isn't checked at the point the procedure is created or altered.
A VIEW on the other hand, is checked at the time that it is created.
CREATE TABLE dbo.MyTable (ID int,
SomeValue varchar(20));
INSERT INTO dbo.MyTable
VALUES(1,'asdjka'),
(2,'asdkj');
GO
--Fails
CREATE VIEW dbo.MyView AS
SELECT ID,
SomeValue,
SomeInt
FROM dbo.MyTable;
GO
--Fails
CREATE VIEW dbo.MyOtherView AS
SELECT *
FROM dbo.MyOtherTable;
GO
DROP TABLE dbo.MyTable;
Depending on the object type depends on what is validated and isn't when the DDL statement is issued.
Edit: It seems that what the OP is questioning is why do that get an error if the object does exist, but they reference a column that doesn't. For exmaple take the below batch:
USE Sandbox;
GO
CREATE TABLE dbo.MyTable (ID int,
SomeValue varchar(20));
INSERT INTO dbo.MyTable
VALUES(1,'asdjka'),
(2,'asdkj');
GO
CREATE PROC dbo.MyProc #ID int AS
BEGIN
SELECT ID,
SomeValue,
AnotherValue
FROM dbo.MyTable
WHERE ID = #ID;
END;
GO
DROP TABLE dbo.MyTable;
GO
DROP PROC dbo.MyProc;
GO
This fails as the column AnotherValue does not exist. This is actually covered in the very documentation you quote:
A procedure can reference tables that do not yet exist. At creation time, only syntax checking is performed.
It explicitly states you can reference a table that does not exist. It makes no mention of objects that don't, or (more specifically) columns in a table/object. Referencing a table that does exist will be validated at creation time.

Insert into distinct columns from a stored procedure

How to insert into a temp table that is all ready created inside of a stored procedure
ALTER PROCEDURE [dbo].[Report_1]
BEGIN
CREATE TABLE #Temp
(
col1 INT,
col2 INT,
col3 VARCHAR(50)
)
INSERT INTO #Temp
EXEC [spSelection] #ID
..do stuff
..do stuff
..do stuff
SELECT * FROM #temp
END
The problem I am having is, I will use this stored procedure (spSelection) in the future and if I change this stored procedure to get more columns for a different stored procedure, then Report_1 will fail.
So I need a way to dynamically create the table or be able to only select distinct columns from the output of exec [spSelection] #ID or have Report_1 be able to read from a temp table created in spSelection.
I have tried to use a global and that will not work because it can be used by other stored procedure at the same time, if I create a dynamic SQL.
#sql ='
create table #Temp(
col1 int,col2 int,col3 varchar(50)
) ' exec sp_executesql #sql
I can not access the #temp table outside of the quotes
One alternative is to change your SP to make the insert inside the SP:
ALTER PROCEDURE [spSelection]
AS
BEGIN
-- Validate that your temporary table was created (the insert will fail otherwise)
IF OBJECT_ID('tempdb..#Temp') IS NULL
BEGIN
RAISERROR ('The table #Temp must be created before executing this SP', 16, 1)
RETURN
END
..do stuff
..do stuff
..do stuff
INSERT INTO #Temp (
col1,
col2,
col3)
SELECT
/*Columns*/
END
GO
ALTER PROCEDURE [dbo].[Report_1]
BEGIN
CREATE TABLE #Temp
(
col1 INT,
col2 INT,
col3 VARCHAR(50)
)
EXEC [spSelection] #ID -- Will insert into #Temp
..do stuff
..do stuff
..do stuff
SELECT * FROM #temp
END
This approach will fail if you eventually add new columns to the #Temp table and insert them inside the SP without updating the CREATE TABLE in every SP that calls it.
There is no definitive solution here, please read this excellent paper about all possible ways to share data between SP, with pros and cons of each (the solution I posted here is listed as 4. Using a table).
Instead of creating a stored procedure for selecting results, you can create a view and use SELECT INTO clause to dynamically create temp table at run time.
You can not use stored procedure in select statement.

SQL Insert along with matching audit at same time from app

I've got an app that will insert lines to table [P_R], which has first field being [PrimaryKey].
Now I have to add another table, [Actions] with fields [PrimaryKey],[P_R_PK],[User],[ActionTime].
When the app inserts a line to [P_R], I don't know what the PrimaryKey will be, but I have to simultaneously insert to [Actions] with the value in [P_R_PK] being the PrimaryKey I just added to [P_R]. How do I get this PrimaryKey value to [P_R_PK]?
For reference, I'm using a vb.net windows form with a SQL Server database.
If you're using a stored procedure to add the records to [P_R], you can call another stored procedure in the first that includes the primary key. For example:
CREATE PROC AddToP_R
#field1 varchar(10),
#field2...10
AS
BEGIN
declare #pk int --primary key that's created upon inserting
--insert into [P_R]
INSERT INTO [P_R]
VALUES (#field1,...)
--set the var we created to be the primary key
SET #pk = SCOPE_IDENTITY()
--call second proc
EXEC Second_Proc #pk
END
If you need other fields in the second stored procedure, include them in the first procedure parameter list.
Another way would be to a wrapper stored procedure that calls both the other two. For this to work, you would need an output variable in the first procedure to return the primary key. For example:
CREATE PROC AddWrapper
#fieldsforfirstproc...,
#fieldsforsecondproc...
AS
BEGIN
declare #outputVar int --primary key
EXEC firstproc #fieldsforfirstproc..., #outputvar output --adds the record to the first table and returns #outputvar as the primary key
EXEC secondproc #fieldsforsecondproc..., #outputvar --adds the record to the second table using #output var
END
I prefer the second option because it removes logic from the first procedure that doesn't need to be there. However, the first procedure would be slightly different to how I showed earlier.
CREATE PROC AddToP_R
#field1 varchar(10),
#field2...10,
#pk int OUTPUT --primary key that's created upon inserting
AS
BEGIN
--insert into [P_R]
INSERT INTO [P_R]
VALUES (#field1,...)
--set the var we created to be the primary key
SET #pk = SCOPE_IDENTITY()
END
You can retrieve it by using SELECT SCOPE_IDENTITY() after the INSERT.
For example:
DECLARE #T table (id int PRIMARY KEY IDENTITY, value int)
INSERT INTO #T (value) VALUES (2)
SELECT SCOPE_IDENTITY() new_pk
I would also consider doing it all in one stored procedure within a transaction. Given that you are inserting into more than one table, a transaction would allow you to roll back should anything go wrong.

Truncate existing table within a stored procedure then insert fresh data

I have a stored procedure that returns a result set. After that I insert this result set into created real table. And then I am using that real table create SSRS reports.
So, something like this:
CREATE PROCEDURE Test
AS
DECLARE #TempTable TABLE(..)
INSERT INTO #TempTable
SELECT...
FROM ...
WHERE ...
SELECT * FROM #TempTable
--============================
INSERT INTO RealTable EXEC [dbo].[Test]
How can I modify this stored procedure so every time it executed it will truncate table with existing data and then insert a fresh one?
So I need something like that:
create procedure Test
as
TRUNCATE RealTable
DECLARE #TempTable TABLE(..)
INSERT INTO #TempTable
SELECT...
FROM...
WHERE...
SELECT * FROM #TempTable INTO RealTable
Or should I just create agent job that would run command something like:
Truncate Table RealTable
INSERT INTO RealTable EXEC [dbo].[Test]
Am I on a right way in terms of logic?
Dont TRUNCATE. Use a MERGE Statement.
CREATE PROCEDURE Test
AS
MERGE RealTable TRGT
USING SourceTable SRCE
ON SRCE.[Column] = TRGT.Column --use columns that can be joined together
WHEN MATCHED THEN UPDATE
SET TRGT.Column1 = SRCE.Column1,
TRGT.Column2 = SRCE.Column2
....................
WHEN NOT MATCHED BY TARGET THEN INSERT
VALUES
(
SRCE.Column1,
SRCE.Column2,
.....................
)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
What's the purpose of the truncate if you are inserting the same data?
What should happen if you have more then 1 concurrent user?
another thing you can do:
1.
insert into TargetTable
select * from SourceTable
2.
rebuild indexes on TargetTable
3.
exec sp_rename SourceTable, SourceTable_Old
exec sp_rename TargetTable, SourceTable
drop table SourceTable_Old
this is an old way of entire table data refresh without much impact, when table variable was not an option.
this is what you probably need as you are directly inserting from #TempTable to RealTable.
create procedure Test
as
BEGIN
TRUNCATE TABLE RealTable
INSERT INTO RealTable
SELECT...
FROM someothertable
WHERE...
END

DROP TABLE fails for temp table

I have a client application that creates a temp table, the performs a bulk insert into the temp table, then executes some SQL using the table before deleting it.
Pseudo-code:
open connection
begin transaction
CREATE TABLE #Temp ([Id] int NOT NULL)
bulk insert 500 rows into #Temp
UPDATE [OtherTable] SET [Status]=0 WHERE [Id] IN (SELECT [Id] FROM #Temp) AND [Group]=1
DELETE FROM #Temp WHERE [Id] IN (SELECT [Id] FROM [OtherTable] WHERE [Group]=1)
INSERT INTO [OtherTable] ([Group], [Id]) SELECT 1 as [Group], [DocIden] FROM #Temp
DROP TABLE #Temp
COMMIT TRANSACTION
CLOSE CONNECTION
This is failing with an error on the DROP statement:
Cannot drop the table '#Temp', because it does not exist or you do not have permission.
I can't imagine how this failure could occur without something else going on first, but I don't see any other failures occurring before this.
Is there anything that I'm missing that could be causing this to happen?
possibly something is happening in the session in between?
Try checking for the existence of the table before it's dropped:
IF object_id('tempdb..#Temp') is not null
BEGIN
DROP TABLE #Temp
END
I've tested this on SQL Server 2005, and you can drop a temporary table in the transaction that created it:
begin transaction
create table #temp (id int)
drop table #temp
commit transaction
Which version of SQL Server are you using?
You might reconsider why you are dropping the temp table at all. A local temporary table is automatically deleted when the connection ends. There's usually no need to drop it explicitly.
A global temporary table starts with a double hash (f.e. ##MyTable.) But even a global temp table is automatically deleted when no connection refers to it.
I think you aren't creating the table at all, because the statement
CREATE TABLE #Temp ([Id] AS int)
is incorrect. Please, write it as
CREATE TABLE #Temp ([Id] int)
and see if it works.
BEGIN TRAN
IF object_id('DATABASE_NAME..#TABLE_NAME') is not null
BEGIN
DROP TABLE #TABLE_NAME
END
COMMIT TRAN
Note:Please enter your table name where TABLE_NAME and database name where it says DATABASE_NAME

Resources