Insert into distinct columns from a stored procedure - sql-server

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.

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.

Calling two levels of stored procedure

In SQL Server 2014 I have two stored procedures. One calls the other with the INSERT command.
I want to call the upper stored procedure to get the result table of it. However, I get an error:
Msg 213, Level 16, State 7, Procedure HelloWorld, Line 23
Column name or number of supplied values does not match table definition.
This is an example of my completed code:
1st stored procedure:
ALTER PROCEDURE [dbo].[HelloWorld_2]
#aaa VARCHAR(20)
AS
BEGIN
SELECT 1 AS col1, #aaa AS col2
END
2nd stored procedure (it calls the 1st stored procedure)
ALTER PROCEDURE [dbo].[HelloWorld]
#aaa VARCHAR(20)
AS
BEGIN
DECLARE #temp TABLE(col1 INT, col2 VARCHAR(20))
INSERT INTO #temp
EXEC [HelloWorld_2] #aaa
SELECT *, 'patata'
FROM #temp
END
SQL that I need to execute (it calls the 2nd stored procedure)
DECLARE #temp TABLE(col1 INT, col2 VARCHAR(20), col3 VARCHAR(20))
INSERT INTO #temp
EXEC HelloWorld 3
The return values has to be a table with 3 columns and 1 row. But this error appears:
Column name or number of supplied values does not match table definition
However the temp table is correct with the column order and values...
Thanks for all replies.
Finally, I create a function of 'HelloWorld_2', so the stored procedure 'HelloWorld' and the SQL code works correctly.

Options besides table valued parameters for passing table variable?

We have many stored procedures that are used for reports. All these procedures follow the following format. In essence, the SP does work, and the final result is inserted into a #table variable:
ALTER procedure dbo.usp_GetComplexData
as
declare #MyTable table
(
Col1 varchar(20),
Col2 varchar(20)
)
-- Here the SP is doing lots of work with lots of tables.
-- The result inserted in #MyTable
SELECT Col1, Col2 from #MyTable
Now I need to send via email (in html format) the results of these stored procedures.
I also have SpCustomTable2HTML (found at Symantec) that converts any table into an html table. It doesn't need the table schema to do its work; it simply takes the table and returns an html table.
So here's the stored procedure:
ALTER procedure usp_sendHtmlReportViaEmail
as
DECLARE #HTML1 NVARCHAR(MAX)
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
drop TABLE #results
select top 50 * into #results From MyTable
EXEC SpCustomTable2HTML '#results', #HTML1 OUTPUT, '', ''
EXEC sp_send_dbmail #profile_name='My profile',
#recipients='test#Example.com',
#subject='Test message',
#body_format = 'HTML',
#body=#HTML1
I would like to somehow call usp_sendHtmlReportViaEmail from usp_GetComplexData by sending it #MyTable as parameter. I was reading about table valued parameters, but that requires to create a TVP for each table that I would pass. I don't want to create a specific TVP for each table that will be passed to usp_sendHtmlReportViaEmail.
Are there any other options?
Thanks.
If you're determined to use SQL, then you should look into using a global temporary table. You have to make sure you clean up after your code executes to avoid using too many resources, but it might be a valid approach for you.
At the end of your usp_GetComplexData procedure, just insert the data into a ##TemporaryTable and use that as the parameter to usp_sendHtmlReportViaEmail. Since I don't know what exactly you do with the table variable, I won't replace it, but you could potentially replace it with the temporary table instead.
ALTER PROCEDURE usp_GetComplexData
AS BEGIN
DECLARE #MyTable TABLE
(/*... Columns ...*/);
-- Do complex data stuff...
SELECT
*
INTO
##MyTempTable
FROM
#MyTable;
EXECUTE usp_sendHtmlReportViaEmail '##MyTempTable';
SELECT
*
FROM
#MyTable;
END

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

Can I insert into temp table from SP without declaring table?

Here is my code:
alter procedure test1 as
select DeptID,DeptName from Department
go
alter procedure test2 as
--Create Table #tab (DeptID INT, DeptName VARCHAR(255))
INSERT INTO #tab
exec test1
select * from #tab
drop table #tab
go
exec test2
I am getting an error like "Invalid object name #tab"
If I add at the begining Create Table #tab (DeptID INT, DeptName VARCHAR(255)) then I do not get any error.
What is wrong in my code? Can I populate a temp table from the results of a stored procedure without declaring the temp table and its column definitions?
When loading a temp table from a stored procedure, then you have to CREATE the table first.
There is no straightforward equivalent of
SELECT * INTO #temptable FROM AnotherTable
The non-straightforward version (read all about the bad stuff on "How to Share Data Between Stored Procedures". And simpler) would be
SELECT * INTO #temptable FROM OPENQUERY(Loopback, 'exec test1')
It's because the Local Temporary table #tab which you are expecting does not existing in the session.
So the table creation should not be commented line.
Create Table #tab (DeptID INT, DeptName VARCHAR(255))
Moreover, if you want to do without creating the table then it should be like below
Alter procedure test2
As
Set NoCount ON
IF OBJECT_ID('tempdb..#tab') IS NOT NULL
Begin
Drop table #temp
End
SELECT DeptID, DeptName INTO #tab from Department
Select * from #tab
Drop table #tab
Go

Resources