Insert into #temp_table from stored procedure with dynamic columns - sql

I have a stored procedure which can return a variety of field names in its output. Something like this:
create or alter procedure dbo.my_prc (#return_format as int ) as
create table #all_data (id int ,chr varchar(10))
insert into #all_data
values (1,'a')
,(2,'b')
,(3,'c')
--return id -> id_2 only
if #return_format = 1
select id * 2 as 'id_2' from #all_data
--return chr -> chrq only
if #return_format = 2
select chr + '?' as 'chrq' from #all_data
--return everything
if #return_format = 3
select * from #all_data
Here are possible outputs:
exec my_prc #return_format = 1 --id_2
exec my_prc #return_format = 2 --chrq
exec my_prc #return_format = 3 --everything
How can I dynamically create a #temp_table (or a set of temp tables for 1-3) which will capture my field names and data?
This obviously doesn't work but something like this is what I'm thinking:
/*
exec my_prc #return_format = 1 into #temp_table
--or maybe
select top 0 from (exec my_prc #return_format = 1) into #temp_table --create a #temp_table with the field names
insert into #temp_table exec my_prc #return_format = 1
*/
For more context, I am trying to avoid simply declaring a static #temp_table and inserting into as such. I don't want to get into the weeds, but that is likely not a good option for me.
create table #temp_table (id int ,chr varchar(10))
insert into #temp_table
exec my_prc #return_format = 3

Here is a solution which writes to the database then creates a #temp_table from that newly created table
The stored procedure can look like this
create or alter procedure dbo.my_prc (#return_format as int ) as
create table #all_data (id int ,chr varchar(10))
insert into #all_data
values (1,'a')
,(2,'b')
,(3,'c')
drop table if exists dbo.output_table --drop so that you can reset the field names
--return id -> id_2 only
if #return_format = 1
select id * 2 as 'id_2' into dbo.output_table from #all_data
--return chr -> chrq only
if #return_format = 2
select chr + '?' as 'chrq' into dbo.output_table from #all_data
--return everything
if #return_format = 3
select * into dbo.output_table from #all_data
select * from dbo.output_table --if you still need to see the output in the SP
And then you can create your #temp_table off dbo.output_table
select * into #temp_table from dbo.output_table
Regardless of which value you choose for #return_format, dbo.output_table will have those fields

Have you tried to use Dynamic SQL?
It is a way to write your SQL in a string and then execute it.
e.g.
v_sql = "create table " + v_table_name + for loop with columns... ";
execute immediate v_sql;

Related

SQL: Insert Query Result into Temp Table inside While Loop

I am trying to insert select query result into a temporary table inside the while loop. But below query gives me this error:
There is already an object named '#TT' in the database.
DECLARE #V_START INT = 1;
DROP TABLE #TT
WHILE(#V_START < 4)
BEGIN
SELECT * INTO #TT
FROM Table
WHERE Column = #V_START;
SET #V_START = #V_START + 10;
END
SELECT * FROM #TT
every time your loop is executed a temporary table will create, but first-time execution the temp table does not exist command run successfully and you give the error in the second round of loop execution.
You can try it like below :
DECLARE #V_START INT = 1;
DROP TABLE IF EXISTS #TT;
CREATE TABLE #TT
(
--your columns
)
WHILE(#V_START < 40)
BEGIN
INSERT INTO #TT
(
--your columns
)
SELECT --your columns
FROM Table
WHERE Column = #V_START;
SET #V_START = #V_START + 10;
END
SELECT * FROM #TT
but better solution is using select into with condition for inserting data to temp table:
DROP TABLE IF EXISTS #TT
SELECT * INTO #TT
FROM Table
WHERE Column % 10 = 0 AND Id < 10
SELECT * FROM #TT

There is already an object named '#BaseData' in the database

Below is a snippet of my code.
I am wanting to filter my data based upon a variable.
When I try to run the code, it returns an error of "There is already an object named '#BaseData' in the database.". I am not sure as to why this is the case; I have put extra checks within the IF statements to drop the temp table if it already exists but to no avail.
Are you able to help or provide an alternative solution please?
DECLARE #Variable AS VARCHAR(20) = 'Example1'
IF OBJECT_ID(N'TEMPDB..#BaseData') IS NOT NULL
DROP TABLE #BaseData
IF #Variable = 'Example1'
BEGIN
SELECT
*
INTO
#BaseData
FROM
[Database].[schema].[table]
END
IF #Variable = 'Example2'
BEGIN
SELECT
*
INTO
#BaseData
FROM
[Database].[schema].[table]
WHERE
[column] = 1
END
IF #Variable = 'Example3'
BEGIN
SELECT
*
INTO
#BaseData
FROM
[Database].[schema].[table]
WHERE
[column] = 0
END
While code is compiled by SQL, creation of same #table is found in each condition so it doesn't work.
One possible solution would be to create table and than insert data conditionally.
-- DROP TEMP TABLE IF EXISTS
IF OBJECT_ID(N'TEMPDB..#BaseData') IS NOT NULL
DROP TABLE #BaseData
GO
-- CRATE TEMP TABLE WITH TempId, AND SAME STRUCTURE AS YOUR TABLE
SELECT TOP 0 CONVERT(INT, 0)TempId, * INTO #BaseData FROM TestTable
-- DECLARE VARIABLE
DECLARE #Variable AS VARCHAR(20)= 'Example1'
-- INSERT DATA IN TABLE DEPENDING FROM CONDITION
IF (#Variable = 'Example1')
BEGIN
INSERT INTO #BaseData SELECT * FROM TestTable
END
IF (#Variable = 'Example2')
BEGIN
INSERT INTO #BaseData SELECT * FROM TestTable WHERE Id = 1
END
IF (#Variable = 'Example3')
BEGIN
INSERT INTO #BaseData SELECT * FROM TestTable WHERE Id = 2
END

Update a column with the result-dataset-count of a stored procedure

I have a table #data which has the columns Id and Count. In addition I have a stored procedure MyProc which accepts a parameter #id (equals the Id column) and returns a dataset (the count equals the Count column).
My goal is to assign the Count column from Id with MyProc without a cursor.
I know, something like this does not work:
UPDATE d
SET Count = (SELECT COUNT(*) FROM (EXEC MyProc d.Id))
FROM #data AS d
Is there a syntax I do not know or is a cursor the only option to achieve this?
PS: It is a code quality and performance problem for me. Calling the stored procedure would be the easiest way without repeating 50 lines of SQL but a cursor slows it down.
I believe you can make use of the below query :
IF OBJECT_ID('dbo.data') IS NOT NULL DROP TABLE data;
IF OBJECT_ID('dbo.MyFunct') IS NOT NULL DROP FUNCTION dbo.MyFunct;
GO
CREATE TABLE data
(
ID int,
[Count] int
);
INSERT data VALUES (1,5), (1,10), (2,3), (4,6);
GO
UPDATE d
SET d.[Count] = f.CNT
FROM
(SELECT ID,COUNT(id) AS CNT FROM data GROUP BY ID) f
INNER JOIN data d ON f.ID = d.ID
I couldn't find a way to use Stored procedure. Needed you can use Table valued function:
CREATE FUNCTION dbo.MyFunct(#id INT)
RETURNS #i TABLE
(ID INT , CNT INT)
AS
BEGIN
INSERT INTO #i
SELECT ID,COUNT(id) AS CNT FROM data GROUP BY ID
RETURN
END;
GO
UPDATE d
SET d.[Count] = f.CNT
FROM dbo.MyFunct(1) f INNER JOIN data d ON f.ID = d.ID
To do what you say, you need a function, not a procedure.
CREATE FUNCTION dbo.myFunc (#Id INT)
RETURNS INT
AS
BEGIN
UPDATE someTable
SET someCol = 'someValue'
WHERE id = #Id;
RETURN ##ROWCOUNT;
END
GO
Then call the function in your update statement;
UPDATE d
SET d.Count = dbo.myFunc(d.Id)
FROM #data AS d;
However, row-based operations is bad practice. You should always strive to perform set-based operations, but as I don't know what your procedure does, I cannot provide more than a wild guess to what you should do (not using a procedure at all):
DECLARE #data TABLE (Id INT);
UPDATE x
SET x.someCol = 'SomeVal'
OUTPUT INSERTED.id INTO #data
FROM someTable AS x
INNER JOIN #data AS d
ON d.Id = x.Id;
WITH cte (Id, myCount) AS (
SELECT d.Id
,COUNT(d.Id) AS myCount
FROM #data AS d
GROUP BY d.Id
)
UPDATE d
SET d.[Count] = c.myCount
FROM #data AS d
INNER JOIN cte AS c
ON c.Id = d.Id;
I don't fully understand what you're trying to do but I think your solution will involve ##ROWCOUNT; Observe:
-- Sample data and proc...
----------------------------------------------------------------------
IF OBJECT_ID('tempdb..#data') IS NOT NULL DROP TABLE #data;
IF OBJECT_ID('dbo.MyProc') IS NOT NULL DROP PROC dbo.MyProc;
GO
CREATE TABLE #data
(
id int,
[Count] int
);
INSERT #data VALUES (1,5), (1,10), (2,3), (4,6);
GO
CREATE PROC dbo.MyProc(#id int)
AS
BEGIN
SELECT 'some value'
FROM #data
WHERE #id = id;
END;
GO
Data BEFORE:
id Count
----------- -----------
1 5
1 10
2 3
4 6
A routine that uses ##ROWCOUNT
DECLARE #someid int = 1; -- the value you're passing to your proc
EXEC dbo.MyProc 1;
DECLARE #rows int = ##ROWCOUNT; -- this is what you need.
UPDATE #data
SET [Count] = #rows
WHERE id = #someid;
Data AFTER
id Count
----------- -----------
1 2
1 2
2 3
4 6

Insert Query to insert multiple rows in a table via select and output clause. SQL Server 2008

I have a created a stored procedure (please ignore syntax errors)
alter proc usp_newServerDetails
(#appid int, #envid int, #serType varchar(20), #servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_details(envid, servertype, servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid, #serType, #servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid, servertype, configpath, configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1, cm.servertype, cm.configpath, cm.configtype
from configpthmaster cm
where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid, keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select #outID2, cm.key
from configpthmaster cm
where cm.appid = #appid)
begin
commit
end
server_details table has an identity column ID with is auto-generated ie. #outID1 and first insert query inserts only 1 row.
configpthmaster table is not related to any other table directly and has 2 unique data rows, which I want to fetch to insert data into other tables, one by one during insertion.
The second insert query fetch data from configpthmaster table
and insert 2 rows in configdetails while generating (auto-generated) ID ie. #outID2.
It also has a FK mapped to server_details.
The problem is "#outID2" giving last inserted ID only (ie. if two id generated 100,101 i am getting 101) which eventually on 3rd insertion, inserting 2 rows with same id 101 only but i want the insertion should be linear. i.e one for 100 and other for 101.
If zero rows affected while insertion how to rollback the transaction?
How can I achieve these requirements? Please help.
Change your procedure like below,and try again.
ALTER PROCEDURE usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
AS
BEGIN
BEGIN TRY
DECLARE #Output TABLE (ID int,TableName VARCHAR(50),cmKey VARCHAR(50)) --table variable for keeping Inserted ID's
BEGIN TRAN
IF EXISTS ( SELECT 1 FROM configpthmaster cm WHERE cm.appid = #appid )
AND ( SELECT 1 FROM configkeydetails ck WHERE ck.appid = #appid ) --add a conditon to satisfy the valid insertions
BEGIN
INSERT INTO server_detials(envid,servertype,servername)
OUTPUT inserted.serverid,'server_detials',NULL INTO #Output(ID,TableName,cmKey )
VALUES(#envid ,#serType ,#servName)
INSERT INTO configdetails(serverid,servertype,configpath,configtype)
OUTPUT inserted.configid,'configdetails',cm.Key INTO #Output(ID,TableName,cmKey )
SELECT t.ID,cm.servertype,cm.configpath,cm.configtype
FROM configpthmaster cm
CROSS APPLY (SELECT ID FROM #Output WHERE TableName='server_detials')t
WHERE cm.appid = #appid
INSERT INTO configkeydetails(configId,keyname)
SELECT ID,cmKey FROM #Output
WHERE TableName='configdetails'
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
END
Could you try this solution?
alter proc usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_detials(envid,servertype,servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid ,#serType ,#servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid,servertype,configpath,configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1 ,cm.servertype,cm.configpath,cm.configtype from configpthmaster cm where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid,keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select isnull(replace(stuff((SELECT inserted.configid FOR xml path('')), 1, 1, ''), '&', '&'), '') ,cm.key, from configpthmaster cm where cm.appid = #appid )
begin
commit
end
I just added STUFF in your code.
The STUFF function inserts a string into another string.
Do take note that using STUFF drastically slows the processing time of the code.
for more information about STUFF

T-SQL If Else condition on the same Temp Table

Here is what I am trying to do:
IF len(Variable) > 1
BEGIN
SELECT * INTO #TEMPTAB FROM multiple joins
END
ELSE
BEGIN
SELECT * INTO #TEMPTAB FROM different multiple joins
END
SELECT * FROM #TEMPTAB more large number of multiple joins & where & groupby
ERROR: There is already an object #TEMPTAB defined
-- Because of select * into in IF and ELSE both
I don't want to create a temp table prior cause it has a lot of columns to be defined.
Is there a way around it?
This was a fun problem for me that is... Well I figured out four ways to do it. One is with a view, one with a temp Table, one with a physical table, and one with a stored procedure and global temp table. Let me know if you have any questions.
View
DECLARE #Variable VARCHAR(10) = 'aa';
IF LEN(#Variable) > 1
BEGIN
EXEC('CREATE VIEW yourView AS SELECT ''Greater than 1'' col')
END
ELSE
BEGIN
EXEC('CREATE VIEW yourView AS SELECT ''Less than 1'' col')
END
SELECT *
FROM yourView;
DROP VIEW yourView;
Temp Table
DECLARE #Variable VARCHAR(100) = 'aa',
--Default value is 0
#percent INT = 0;
--If the length > 1, then change percent to 100 as to return the whole table
IF LEN(#Variable) > 1
SET #percent = 100;
--If the length <=1, then #percent stays 0 and you return 0 percent of the table
SELECT TOP(#percent) PERCENT 'Greater than 1' col INTO #TEMPTAB
--If you didn't populate the table with rows, then use this query to populate it
IF(#percent = 0)
BEGIN
INSERT INTO #TEMPTAB
SELECT 'Less than 1' col
END
/*your 1k lines of code here*/
SELECT *
FROM #TEMPTAB
--Cleanup
DROP TABLE #tempTab
Physical Table
DECLARE #Variable VARCHAR(10) = 'A';
IF len(#Variable) > 1
BEGIN
SELECT 'Greater than 1' col INTO TEMPTAB
END
ELSE
BEGIN
SELECT 'Less than 1' col INTO TEMPTAB2
END
IF OBJECT_ID('TEMPTAB2') IS NOT NULL
--SP_Rename doesn't work on temp tables so that's why it's an actual table
EXEC SP_RENAME 'TEMPTAB2','TEMPTAB','Object'
SELECT *
FROM TEMPTAB
DROP TABLE TEMPTAB;
Stored Procedure with Global Temp Table
IF OBJECT_ID('yourProcedure') IS NOT NULL
DROP PROCEDURE yourProcedure;
GO
CREATE PROCEDURE yourProcedure
AS
IF OBJECT_ID('tempdb..##TEMPTAB') IS NOT NULL
DROP TABLE ##tempTab;
SELECT 'Greater than 1' col INTO ##TEMPTAB
GO
DECLARE #Variable VARCHAR(10) = 'aaa';
IF LEN(#Variable) > 1
BEGIN
EXEC yourProcedure;
END
ELSE
BEGIN
SELECT 'Less than 1' col INTO ##TEMPTAB
END
SELECT *
FROM ##TEMPTAB
IF OBJECT_ID('tempdb..##TEMPTAB') IS NOT NULL
DROP TABLE ##TEMPTab;
Didn't you consider dynamic query with global temporary tables? This works for me:
DECLARE #sql NVARCHAR(MAX) = CASE WHEN 1 = 2
THEN 'SELECT * INTO ##TEMPTAB FROM dbo.SomeTable1'
ELSE 'SELECT * INTO ##TEMPTAB FROM dbo.SomeTable2'
END
EXEC (#sql)
SELECT * FROM ##TEMPTAB
DROP TABLE ##TEMPTAB
The first time you ran this code it created the table #TEMPTAB. The next time you ran SQL Server is telling you the table already exists. You should precede your code with the following:
if object_ID('tempdb..#TEMPTAB','U') is not null
drop table #TEMPTAB
This will drop (delete the table if it already exists) and the code that follows will always be able to recreate(or create) the table.