How to make table dynamic in sql - sql

Does anyone know how to write a script in stored proc to run the table based on the variable (or will it possible to do so?)?
for example:
I have 3 tables name called customer, supplier, and support
when user input 1, then run table customer, 2 table supplier and 3 table support
declare #input int;
if #input =1
begin
declare #table varchar(50); set #table = 'customer'
end
if #input =2
begin
declare #table varchar(50); set #table = 'supplier '
end
if #input =3
begin
declare #table varchar(50); set #table = 'support'
end
select *
INTO ##test
from #table

IF it really is that simple, why not just repeat the Select?
if #input =1
begin
Select * INTO ##test From customer
end
if #input =2
begin
Select * INTO ##test From supplier
end
if #input =3
begin
Select * INTO ##test From support
end

yes you can do it by using dynamic sql "EXEC" or by "Sp_Executesql" command.
Example :
USE Northwind
GO
CREATE TABLE #MyTemp
( RowID int IDENTITY,
LastName varchar(20)
)
DECLARE #SQL nvarchar(250)
SET #SQL = 'INSERT INTO #MyTemp SELECT LastName FROM Employees;'
EXECUTE sp_executesql #SQL

Why do you want to do this? It seems like a bad idea at first glance.
Can you post what your stored procedure is doing and any relevant tables? I suspect that you may be able to either:
Modify your schema in such a way
that you would no longer to do
this
Create different stored procedures
to do what you want on each table instead of forcing it into one proc.
There are several issues that come up when you use dynamic SQL that you should be aware of. Here is a fairly comprehensive article on the pros and cons.

Related

Getting error "An INSERT EXEC statement cannot be nested"

I've hit a problem with the insert...exec, and I can't find a solution online that will work. I have a stored procedure that retrieves data from an API. It does this by building a command line, running it through xp_cmdshell, and capturing the output to a table (using an insert...exec).
The stored procedure works perfectly, and formats the required data into a nice table
I'm now trying to implement this into my db, but this needs to be called from a number of other stored procedures. They need to be able to see the results of the initial stored procedure, but I've hit a "An INSERT EXEC statement cannot be nested" error, and it won't let me capture the output
I've tried various solutions I've seen suggested online, but so far none of them have worked. The initial stored procedure is calling a command line, so I can't find any other way to call it and capture the output, other than using an insert.....exec, but I still need the formatted output.
I have tried to convert my stored procedure to a function, but I cannot run the xp_cmdshell. I've also looked at getting the initial stored procedure to return the table as an output parameter (even if I create it with a type), but the stored procedure won't allow that
I've also looked at using openset, but I need to be able to pass a parameter to the stored procedure, and I don't think openset will allow this. What could I try next?
EDIT: I've put together a simple example of what I'm trying to do. The stored procedure is retrieving data from a command line. I'm just using an echo command to fudge the data, but in reality, this command line is calling an API, and receiving JSON back. The JSON is then formatted into a SQL table, and output. As this is an API call, I can't see any other way to do it without an insert...exec xp_cmdshell, but this means I cannot capture the output of the stored procedure and use it
create procedure usp_retrieveAPIdata
#inparameter int
as
begin
declare #APIcall varchar(200)
--this would normally be an API call, returning a JSON array
set #APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'
declare #resulttable table
(outputfield varchar(100),ID int identity)
insert into #resulttable
exec xp_cmdshell #APICall
declare #formattedtable table
(field1 varchar(100),field2 varchar(100))
declare #rownum int =0
declare #field1 varchar(100)
declare #field2 varchar(100)
declare #currentfield varchar(100)
while exists (select * from #resulttable where ID>#rownum)
begin
set #rownum=#rownum+1
select #currentfield=outputfield from #resulttable where ID=#rownum
if #currentfield like 'f1%'
begin
set #field1=replace(#currentfield,'f1:','')
end
if #currentfield like 'f2%' and #rownum<>1
begin
set #field2=replace(#currentfield,'f2:','')
insert into #formattedtable (field1,field2) values (#field1,#field2)
end
end
select * from #formattedtable
end
go
declare #resulttable table (field1 varchar(100),field2 varchar(100))
insert into #resulttable
exec usp_retrieveAPIdata 1
This is the problem with INSERT EXEC I have run into this many times over the years. Here are a few options - none of them are perfect, each has it's pros/cons but should help get you across the finish line nonetheless.
Sample Procs:
USE tempdb
GO
-- Sample Procs
CREATE PROC dbo.proc1 #a INT, #b INT
AS
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);
GO
CREATE PROC dbo.proc2 #a INT, #b INT
AS
DECLARE #x TABLE (a INT, b INT);
INSERT #x(a,b)
EXEC dbo.proc1 5,10;
SELECT x.a, x.b FROM #x AS x;
This will fail due to nesting INSERT EXEC:
DECLARE #a INT = 2, #b INT = 4;
DECLARE #t2 TABLE (a INT, b INT);
INSERT #t2(a,b)
EXEC dbo.proc2 5,10;
Option #1: Extract the stored procedure logic and run it directly
Here I'm simply taking the logic from dbo.proc2 and running it ad-hoc
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2, #b INT = 4;
INSERT #t2 (a,b)
-- Logic Extracted right out of dbo.proc1:
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);
SELECT t2.* FROM #t2 AS t2;
Option #2 - Extract the proc logic and run it as Dynamic SQL
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2,
#b INT = 4;
DECLARE #SQL NVARCHAR(4000) = N'
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);',
#ParmDefinition NVARCHAR(500) = N'#a INT, #b INT';
INSERT #t2
EXEC sys.sp_executesql #SQL, #ParmDefinition, #a=#a, #b=#b;
SELECT t2.* FROM #t2 AS t2; -- validation
Option #3 - option #2 with the proc code directly from metadata
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2,
#b INT = 4;
DECLARE
#SQL NVARCHAR(4000) =
( SELECT SUBSTRING(f.P, CHARINDEX('SELECT',f.P),LEN(f.P))
FROM (VALUES(OBJECT_DEFINITION(OBJECT_ID('proc1')))) AS f(P)),
#ParmDefinition NVARCHAR(500) = N'#a INT, #b INT';
EXEC sys.sp_executesql #SQL, #ParmDefinition, #a=#a, #b=#b;
The downside here is parsing out what I need. I made my example simple with the logic beginning with a SELECT clause, the real world is not as kind. The upside, compared to manually adding the logic, is that your code will be up-to-date. Changes to the proc automatically change your logic (but can also break the code).
Option #4: Global Temp Table
I haven't really tried this but it should work. You could re-write the proc (proc2 in my example) like this:
ALTER PROC dbo.proc2 #a INT, #b INT, #output BIT = 1
AS
IF OBJECT_ID('tempdb..##x','U') IS NOT NULL DROP TABLE ##x;
CREATE TABLE ##x(a INT, b INT);
INSERT ##x(a,b)
EXEC dbo.proc1 5,10;
IF #output = 1
SELECT x.a, x.b FROM ##x AS x;
GO
I am populating a global temp table with the result set then adding an option to display the output or not. When #output = 0 the result-set will live in ##x, which can be referenced like so:
DECLARE #t2 TABLE (a INT, b INT);
EXEC dbo.proc2 5,10,0;
INSERT #t2(a,b)
SELECT * FROM ##x;
SELECT * FROM #t2;
I think I've cracked it. Weird that you spend all afternoon looking at SQL, then the answer comes to you when you are cleaning out a fish tank
I need to split my sproc into two. The first part calls the API, and receives the answer as a JSON array. JSON is basically text, so rather than convert this into a table, I should just return in as an NVARCHAR(MAX) to the calling sproc.
The calling sproc can then call a second sproc to format this JSON into a table format.
As the first sproc isn't returning a table, SQL won't care about the nested Insert...exec, and as the second sproc isn't using a cmdshell, it doesn't need an insert...exec, so it can receive the results into a table
Here is the above example, but with the sproc split into 2...
begin tran
go
create procedure usp_retrieveAPIdata
#inparameter int,
#resultstring varchar(max) output
as
begin
declare #APIcall varchar(200)
--this would normally be an API call, returning a JSON array
set #APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'
declare #resulttable table
(outputfield varchar(100),ID int identity)
insert into #resulttable
exec xp_cmdshell #APICall
set #resultstring=''
select #resultstring=#resultstring + isnull(outputfield,'') + '¶' from #resulttable order by ID --using '¶' as a random row delimiter
end
go
create procedure usp_formatdata (#instring varchar(max))
as
begin
print #instring
declare #resulttable table
(outputfield varchar(100),ID int)
insert into #resulttable (outputfield,ID)
select value,idx+1 from dbo.fn_split(#instring,'¶');
declare #formattedtable table
(field1 varchar(100),field2 varchar(100))
declare #rownum int =0
declare #field1 varchar(100)
declare #field2 varchar(100)
declare #currentfield varchar(100)
while exists (select * from #resulttable where ID>#rownum)
begin
set #rownum=#rownum+1
select #currentfield=outputfield from #resulttable where ID=#rownum
if #currentfield like 'f1%'
begin
set #field1=replace(#currentfield,'f1:','')
end
if #currentfield like 'f2%' and #rownum<>1
begin
set #field2=replace(#currentfield,'f2:','')
insert into #formattedtable (field1,field2) values (#field1,#field2)
end
end
select field1,field2 from #formattedtable
end
go
declare #resulttable table (field1 varchar(100),field2 varchar(100))
declare #outstring varchar(max)
exec usp_retrieveAPIdata 110,#resultstring=#outstring output
insert into #resulttable
exec usp_formatdata #outstring
select * from #resulttable
rollback
Many thanks to everyone who took the time to contribute to this thread

SQL: How to make table name in stored procedure dynamic

I am pretty new to SQL Server and hope someone here can help me with this (I'm using QL Server 2008).
The following is a small procedure that works as intended.
Now I would like to use the same procedure to update multiple tables as all these tables have exactly the same column names and column formatting, the only difference is the 2nd part of the table name for which I added XXX below.
Can someone tell me how this could be made dynamic and also provide me some explanations on this ?
I cannot provide much more here as I wasn't sure about how to approach this - other than probably declaring #sql nvarchar(max) and wrapping the whole query in SET #sql = N'...' before executing it.
My stored procedure:
CREATE PROCEDURE [dbo].[Cal_UpdateTeam]
#team nvarchar(100),
#teamID int,
#notes nvarchar(1000),
#log nvarchar(100),
#admin varchar(50)
AS
BEGIN
SET NOCOUNT ON;
BEGIN
IF NOT EXISTS
(
SELECT *
FROM Cal_XXX
WHERE teamID = #teamID
)
INSERT INTO Cal_XXX
(
team,
teamID,
notes,
log,
admin
)
SELECT #team,
#teamID,
#notes,
#log,
#admin
ELSE
UPDATE Cal_XXX
SET team = #team,
teamID = #teamID,
notes = #notes,
log = #log,
admin = #admin
WHERE teamID = #teamID
END
END
Many thanks for any tips and advise on this, Mike.
you should wrap your sql query in an nvarchar and then execute that query as in the below example :
declare #sql nvarchar(max)
declare #TableName nvarchar(max)
set #TableName = 'mytable'
set #sql = 'Select * from ' + #TableName
Exec sp_executesql #sql
in SP you can use Temporary Tables fro example:
CREATE PROCEDURE SELECT_TABLE
#REQUEST_ID INT
AS
BEGIN
/*************************************
** Temporary table **
*************************************/
CREATE TABLE #SOURCE (
ID INT
, ID_PARENT INT
, NAME VARCHAR(200)
, SORT INT
..
..
)
IF #REQUEST_ID = 'YES' BEGIN
INSERT INTO #SOURCE SELECT * FROM SOURCE_A
END
ELSE BEGIN
INSERT INTO #SOURCE SELECT * FROM SOURCE_B
END
SELECT * FROM #SOURCE
.....
END
GO
in SP you can encapsulate other SPs with different table names like parameter:
CREATE PROCEDURE SELECT_FROM_TABLE_A
AS
BEGIN
SELECT * FROM SOURCE_A
END
GO
CREATE PROCEDURE SELECT_FROM_TABLE_B
AS
BEGIN
SELECT * FROM SOURCE_B
END
GO
CREATE PROCEDURE SELECT_TABLE
#REQUEST_ID INT
AS
BEGIN
/**********************************************
** Subrequest select **
**********************************************/
IF #REQUEST_ID = 'YES' BEGIN
-- Request SP fro Source A
EXEC SELECT_FROM_TABLE_A
END
ELSE
BEGIN
-- Request SP fro Source B
EXEC SELECT_FROM_TABLE_B
END
END
GO

SQL Server: How to achieve re-usability yet flexibility in TSQL

I am using SQL Server 2008 R2. I am having some problems finding an effective coding pattern for SQL which supports code re-usability as well as flexibility. By re-usability, what I mean is keeping SQL queries in Stored Procedures and User Defined Functions.
Now, if I choose Stored Procedures, I will be sacrificing its usability in a query directly. If I choose User Defined Functions, I won't be able to use DML statements.
For example, suppose I created a Stored Procedures which inserts one contact record. Now, if I am having a table which can act as a source of multiple contact records, all I am left with are either WHILE loops or CURSORs, which is clearly not a recommended option, due to its performance drawbacks. And due to the fact that DML statements are not allowed in User Defined Functions, I simply cannot use them for this purpose.
Although, If I am not concerned with code re-usability, then instead of using Stored Procedures I can surely use same set of queries again and again to avoid while loops.
What pattern should I follow?
Here is a similar Stored Procedures:-
ALTER Proc [dbo].[InsertTranslationForCategory]
(
#str nvarchar(max),
#EventId int,
#CategoryName NVarchar(500),
#LanguageId int,
#DBCmdResponseCode Int Output,
#KeyIds nvarchar(max) Output
)as
BEGIN
DECLARE #XmlData XML
DECLARE #SystemCategoryId Int
DECLARE #CategoryId Int
Declare #Counter int=1
Declare #tempCount Int
Declare #IsExists int
Declare #TranslationToUpdate NVarchar(500)
Declare #EventName Varchar(200)
declare #Locale nvarchar(10)
declare #Code nvarchar(50)
declare #KeyName nvarchar(200)
declare #KeyValue nvarchar(500)
select #Locale=locale from languages where languageid = #LanguageId
SET #DBCmdResponseCode = 0
SET #KeyIds = ''
select #EventName = eventName from eventLanguages
where eventID = #EventId
--BEGIN TRY
Select #SystemCategoryId=CategoryId from SystemCategories where Name=rtrim(ltrim(#CategoryName))
Select #CategoryId=CategoryId from Categories where Name=rtrim(ltrim(#CategoryName)) and EventId=#EventId
if (#str='deactivate')
Begin
Delete from Codetranslation where CategoryId=#CategoryId
Update Categories set [Status]=0, Isfilter=0 where CategoryId=#CategoryId and Eventid=#EventId
Set #DBCmdResponseCode=2
return
End
set #XmlData=cast(#str as xml)
DECLARE #temp TABLE
(
Id int IDENTITY(1,1),
Code varchar(100),
Translation varchar(500),
CategoryId int
)
Insert into #temp (Code,Translation,CategoryId)
SELECT
tab.col.value('#Code', 'varchar(200)'),
tab.col.value('#Translation', 'varchar(500)'),#SystemCategoryId
FROM #XmlData.nodes('/Data') AS tab (col)
select #tempCount=Count(*) from #temp
if(IsNull(#CategoryId,0)>0)
Begin
While (#Counter <= #tempCount)
Begin
Select #IsExists= count(sc.categoryid) from #temp t Inner Join SystemCodetranslation sc
On sc.categoryid=t.CategoryId
where ltrim(rtrim(sc.code))=ltrim(rtrim(t.code)) and ltrim(rtrim(sc.ShortTranslation))=ltrim(rtrim(t.Translation))
and t.Id= #Counter
print #IsExists
Select #Code = Code , #KeyValue = Translation from #temp where id=#counter
set #KeyName = ltrim(rtrim(#EventName)) + '_' + ltrim(rtrim(#CategoryName)) + '_' + ltrim(rtrim(#Code)) + '_LT'
exec dbo.AddUpdateKeyValue #EventId,#Locale, #KeyName,#KeyValue,NULL,12
select #KeyIds = #KeyIds + convert(varchar(50),keyvalueId) + ',' from dbo.KeyValues
where eventid = #EventId and keyname = #KeyName and locale = #Locale
set #KeyName = ''
set #KeyValue = ''
Set #Counter= #Counter + 1
Set #IsExists=0
End
End
--- Inser data in Codetranslation table
if(isnull(#CategoryId,0)>0)
Begin
print #CategoryId
Delete from codetranslation where categoryid=#CategoryId
Insert into codetranslation (CategoryId,Code,LanguageId,ShortTranslation,LongTranslation,SortOrder)
SELECT
#CategoryId,
tab.col.value('#Code', 'varchar(200)'), #LanguageId,
tab.col.value('#Translation', 'varchar(500)'),
tab.col.value('#Translation', 'varchar(500)'),0
FROM #XmlData.nodes('/Data') AS tab (col)
Update Categories set [Status]=1 where CategoryId=#CategoryId and Eventid=#EventId
End
Set #DBCmdResponseCode=1
set #KeyIds = left(#KeyIds,len(#KeyIds)-1)
END
You can use table variable parameter for your user defined functions.
following code is an example of using table variable parameter in stored procedure.
CREATE TYPE IdList AS TABLE (Id INT)
CREATE PROCEDURE test
#Ids dbo.IdList READONLY
AS
Select *
From YourTable
Where YourTable.Id in (Select Id From #Ids)
End
GO
In order to execute your stored procedure use following format:
Declare #Ids dbo.IdList
Insert into #Ids(Id) values(1),(2),(3)
Execute dbo.test #Ids
Edit
In order to return Inserted Id, I don't use from Table Variable Parameter. I use following query sample for this purpose.
--CREATE TYPE NameList AS TABLE (Name NVarChar(100))
CREATE PROCEDURE test
#Names dbo.NameList READONLY
AS
Declare #T Table(Id Int)
Insert Into YourTable (Name)
OUTPUT Inserted.Id Into #T
Select Name
From #Names
Select * From #T
End
GO

Using a variable to represent multiple values

I have the following part of a query:
Where id in (1,2,3) And country in('France','Italy','Spain')
I want to declare 2 variables and do it like:
Where id in (idsVaraible) And country in(countriesVriable)
It is more like substituting sql code in sql code to make my long query readable and more useful, is there any way to do this?
I think it's more like eval in java script.
Well if you need to pass these sets in as strings, one way would be dynamic SQL:
DECLARE #ids VARCHAR(32) = '1,2,3';
DECLARE #countries VARCHAR(2000) = 'France,Italy,Spain';
DECLARE #sql NVARCHAR(MAX) = N'SELECT ... FROM ...
WHERE id IN (' + #ids + ') AND country IN ('''
+ REPLACE(#countries, ',',''',''') + ''');';
PRINT #sql;
-- EXEC sp_executesql #sql;
Another way would be table-valued parameters. First create these types in your database:
CREATE TYPE dbo.TVPids AS TABLE(ID INT);
CREATE TYPE dbo.TVPcountries AS TABLE(Country VARCHAR(255));
Now your stored procedure can take these types as input:
CREATE PROCEDURE dbo.whatever
#i dbo.TVPids READONLY,
#c dbo.TVPcountries READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT ... FROM dbo.yourtable AS t
INNER JOIN #i AS i ON i.ID = t.ID
INNER JOIN #c AS c ON c.country = t.country;
END
GO
Now your app can pass these two parameters in as sets (e.g. from a DataTable) instead of building a comma-separated string or handling multiple parameters.
Please try using temp table variables:
DECLARE #tblID as TABLE(ID INT)
DECLARE #tblCountry as TABLE(Country NVARCHAR(50))
INSERT INTO #tblID VALUES (1),(2),(3)
INSERT INTO #tblCountry VALUES ('France'),('Italy'),('Spain')
WHERE id in (select ID from #tblID) And country in(select Country from #tblCountry)

How to select Table and Column Names from passed parameters in SQL Server?

I have two very similar tables in our database, and I need to write a stored procedure for my Visual Studio 2010 Web Application to read the data from one of these tables given a table number.
Currently, we only have two tables to select from, but I can see this growing to more as this project grows.
This is sort of what I am trying to do, but this code is not correct:
PROCEDURE [dbo].[spGetData]
#tableID int
AS
BEGIN
SET NOCOUNT ON;
declare #col1 nvarchar(50), #table nvarchar(50)
set #col1=case when #tableID=1 then 'SMRequestID' else 'WHRequestID' end
set #table=case when #tableID=1 then 'SMRequest' else 'WHRequest' end
select #col1 as 'Request', WorkOrder, PartNumber, Qty, EmployeeID
from #table
END
Basically, the ColumnName and TableName depend on the #tableID parameter that will be passed in.
How would I go about doing that?
Note: My searches are not turning up anything related, but I am a C# developer and not a database developer. I imagine this has been asked before, it is just I am not using the right keywords.
Although I think Mark is quite correct given the small number of tables and simplicity of your queries, here is a dynamic sql example that passes both the table and column names:
CREATE PROCEDURE spGetData
(
#TableName nvarchar(128),
#ColumnName nvarchar(128)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(4000)
SET #SQL = 'SELECT ' + #ColumnName + ', as Request, WorkOrder, PartNumber, Qty, EmployeeID FROM ' + #TableName
EXEC sp_executesql #SQL
END
You can call it as follows:
exec spGetData 'SMRequest', 'SMRequestID'
exec spGetData 'WHRequest', 'WHRequestID'
One option would be to use a conditional based upon the ID and put the code for a specific table in each section for the table.
I prefer this method to get away from the dynamic sql and allow the database server to get a fighting chance to optimize the thing for speed reasons by precompiling.
NOTE: database servers are pretty bad at string manipulation (create dynamic sql) in general.
EDIT1: EXAMPLE
FOR INSTANCE: THIS SQL
declare #mytest varchar(5)
set #mytest = 'PROCS'
IF #mytest = 'PROCS'
BEGIN /* STORED PROCS */
SELECT DISTINCT
o.name AS ObjectName_StoredProcedure
FROM sysobjects as o
WHERE o.xtype = 'P'
END
ELSE
IF #mytest = 'DEFAULT'
BEGIN
SELECT DISTINCT
o.name AS ObjectName_StoredProcedure
FROM sysobjects as o
WHERE o.xtype = 'D'
END
gives you the store procedure names or the default constraints depending on what you pass to the parameter.
EDIT2: Based on OP code:
CREATE PROCEDURE [dbo].[spGetData]
(#tableID int )
AS
BEGIN
SET NOCOUNT ON;
IF #tableID = 1
BEGIN
SELECT SMSRequestId AS 'Request',
WorkOrder, PartNumber, Qty, EmployeeID
FROM SMRequest
END
IF #tableID = 2
BEGIN
SELECT WHRequestID AS 'Request',
WorkOrder, PartNumber, Qty, EmployeeID
FROM WHRequest
END
END
Do it with dynamic SQL:
PROCEDURE [dbo].[spGetData]
#tableID int
AS
BEGIN
SET NOCOUNT ON;
declare #col1 nvarchar(50), #table nvarchar(50), #cmd nvarchar(400)
set #col1=case when #tableID=1 then 'SMRequestID' else 'WHRequestID' end
set #table=case when #tableID=1 then 'SMRequest' else 'WHRequest' end
#cmd = "select " + #col1 + " as 'Request', WorkOrder, PartNumber, Qty, EmployeeID from " + #table
EXEC(#cmd)
END