Declaring output parameters for sp_executesql - sql-server

I'm trying to make a bit of SQL script that will output all the table names in a large database, along with the numbers of fields and records in each, and a list of the field names. This will allow us to focus on the tables with data, and look for field names that match from different tables, which might be appropriate places for joins.
To do this, I'm trying to write dynamic SQL that can cycle through all the tables. But I haven't been able to get sp_executesql to yield outputs that I can insert into my table variable. Here's the code I've written so far:
USE MITAS_TEST;
DECLARE #TablesAbstract TABLE(
TableName VARCHAR(50),
NumberOfFields INT,
NumberOfRecords INT
);
DECLARE #NumberOfRowsCounted INTEGER;
SET #NumberOfRowsCounted = 0;
DECLARE #RecSql NVARCHAR(500);
SET #RecSql = 'EXECUTE(''SELECT #NumberOfRows = COUNT(*) FROM ''+#TableName)';
DECLARE #ParmDefinition NVARCHAR(100);
SET #ParmDefinition = '#TableName NVARCHAR(100), #NumberOfRows INTEGER OUTPUT';
DECLARE #TableN NVARCHAR(100);
SET #TableN = 'MITAS_TEST.dbo.AP500';
EXECUTE sp_executesql #RecSql,
#ParmDefinition,
#TableName = #TableN,
#NumberOfRows = #NumberOfRowsCounted OUTPUT;
I get the following error:
Msg 137, Level 15, State 1, Line 1
Must declare the scalar variable "#NumberOfRows"
I would have thought it sufficed to declare #NumberOfRows in the #ParmDefinition field (based on this source: https://technet.microsoft.com/en-us/library/ms188001(v=sql.90).aspx). What am I doing wrong? Is there a better way?

The inner execute looks like:
EXECUTE('SELECT #NumberOfRows = COUNT(*) FROM MITA_TEST.dbo.AP500')
In that context, #NumberOfRows does not exist. You could change that to another call to sp_executesql, passing the output parameter #NumberOfRows down another level.
Assuming that this represents learning code, and not production. Depending on the source of #TableN, this could be susciptable to SQL injection attacks. See quotename in books online.

Related

How to systematically get stored procedure (SP) parameter names and their values INSIDE the SP execution

As described in title, I am trying to systematically get stored procedure pararameter names and their corresponding values inside the execution of the proper stored procedure.
First point, which is taking stored procedure parameter names, is easy using table [sys].[all_parameters] and the stored procedure name. However, getting the actual values of these parameters is the difficult part, specially when you are not allowed to use table [sys].[dm_exec_input_buffer] (as a developer, I am not allowed to read this table, since it is a system administrator table).
Here is the code I have so far, which I am sure can serve you as a template:
CREATE PROCEDURE [dbo].[get_proc_params_demo]
(
#number1 int,
#string1 varchar(50),
#calendar datetime,
#number2 int,
#string2 nvarchar(max)
)
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
DECLARE #ParameterNames NVARCHAR(MAX) = ( SELECT STRING_AGG([Name], ',') FROM [sys].[all_parameters] WHERE OBJECT_ID = OBJECT_ID('[dbo].[get_proc_params_demo]') )
SET #sql = N'SELECT ' + #ParameterNames;
DECLARE GetParameterValues CURSOR FOR
SELECT DISTINCT [Name] FROM [sys].[all_parameters] WHERE OBJECT_ID = OBJECT_ID('[dbo].[get_proc_params_demo]');
OPEN GetParameterValues;
DECLARE #param_values NVARCHAR(MAX) = NULL
DECLARE #StoredProcedureParameter NVARCHAR(MAX)
FETCH NEXT FROM GetParameterValues INTO #StoredProcedureParameter;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #param_values = 'ISNULL('+#param_values+','')'+#StoredProcedureParameter+','
EXEC(#param_values)
FETCH NEXT FROM GetParameterValues INTO #StoredProcedureParameter;
END;
CLOSE GetParameterValues;
DEALLOCATE GetParameterValues;
SET #param_values = LEFT(#param_values, LEN(#param_values) - 1)
EXEC sp_executesql #sql,#ParameterNames,#param_values;
END
EXEC [dbo].[get_proc_params_demo]
#number1=42,
#string1='is the answer',
#calendar='2019-06-19',
#number2=123456789,
#string2='another string'
This is my approach trying to dynamically get parameter actual values inside a cursor, but it does not work, and I am clueless so far. I know it is quite rudimentary, and I am happy to hear other approaches. To be fair, I don't know if this problem is even possible to solve without system tables, but it would be great.
EDIT: This is an attempt to get a generic code that works on any stored procedure. You do not want to hardcode any parameter name. The only input you have is the stored procedure name via OBJECT_NAME(##PROCID)

Table name as parameter for Microsoft SQL Server procedure

I am trying to use a SQL Server procedure to create two tables with different names depending on whether a variable is yes (1) or no (0). Everything I've seen refers to temporary tables. I would like to store the table permanently in the database.
CREATE PROCEDURE DUALTYPE
#TableName nvarchar(18), #YesNo int
AS
BEGIN
SELECT *
INTO dbo.[#TableName]
FROM
(SELECT * FROM mytable
WHERE dual = #YesNo) dualtab
END
EXEC DUALTYPE #TableName = 'dualtable' #YesNo = 0
The procedure executes, but it thinks my table name is #TableName. How do I tell it to use 'dualtable' instead?
The code below, using T-SQL, allows for different variable inputs using the same query. The output is then stored in different tables on the database. It is good for making unique samples based on different variable criteria.
The declare statements establish the variable type inputs. Those inputs are then defined using the set statements. 'Set #query' is a string which contains the relevant query (in this case, creating a table based on whether 'dual' is yes vs. no). Because the alterable parameters are contained in a string, they are defined using set #parm N'#...', and then connected to the actual variables in 'EXEC...#parms...'. The query below will produce a table named 'Yes' or 'No' if the YesNo variable is set to 1 or 0, respectively.
declare #TableName sysname;
declare #YesNo int;
declare #parms nvarchar(max);
declare #query nvarchar(max);
set #TableName = 'Yes'
set #YesNo = 1
set #query = '
Select * into [mydb].dbo.' + QUOTENAME(#TableName) + '
from
mytab
where dual = #lvl'
set #parms = N'#lvl int'
EXEC sp_executesql #query, #parms, #lvl = #YesNo

Use variable to create new table in ms sql

create proc City_Info
#StateRef nvarchar(20)
as
begin
declare #StateCod nvarchar(3);
declare #Check int;
select #StateCod = StateCod from State_Cod where State_Nam = #StateRef
create table C0NCAT(#StateCod' ,'City')(Sno int identity(1,1))
end
Can Anyone tell how can i fetch a Particular Name from Column and Make table using Procedure in mssql?
First of all it looks like classic example of SELECT * FROM sales + #yymm
This is a variation of the previous case, where there is a suite of tables that actually do describe the same entity. All tables have the same columns, and the name includes some partitioning component, typically year and sometimes also month. New tables are created as a new year/month begins.
In this case, writing one stored procedure per table is not really feasible. Not the least, because the user may want to specify a date range for a search, so even with one procedure per table you would still need a dynamic dispatcher.
If you still want to go this way you could use Dynamic-SQL.
create proc City_Info
#StateRef nvarchar(20)
as
begin
declare #StateCod nvarchar(3);
declare #Check int;
select #StateCod = StateCod from State_Cod where State_Nam = #StateRef;
DECLARE #sql NVARCHAR(MAX) =
'create table '
+ QUOTENAME(C0NCAT(#StateCod ,'City'))
+ '(Sno int identity(1,1))';
EXEC sp_executesql #sql
end

Stored procedure to insert values into dynamic table

I was wondering if I can make a stored procedure that insert values into dynamic table.
I tried
create procedure asd
(#table varchar(10), #id int)
as
begin
insert into #table values (#id)
end
also defining #table as table var
Thanks for your help!
This might work for you.
CREATE PROCEDURE asd
(#table nvarchar(10), #id int)
AS
BEGIN
DECLARE #sql nvarchar(max)
SET #sql = 'INSERT INTO ' + #table + ' (id) VALUES (' + CAST(#id AS nvarchar(max)) + ')'
EXEC sp_executesql #sql
END
See more here: http://msdn.microsoft.com/de-de/library/ms188001.aspx
Yes, to implement this directly, you need dynamic SQL, as others have suggested. However, I would also agree with the comment by #Tomalak that attempts at universality of this kind might result in less secure or less efficient (or both) code.
If you feel that you must have this level of dynamicity, you could try the following approach, which, although requiring more effort than plain dynamic SQL, is almost the same as the latter but without the just mentioned drawbacks.
The idea is first to create all the necessary insert procedures, one for every table in which you want to insert this many values of this kind (i.e., as per your example, exactly one int value). It is crucial to name those procedures uniformly, for instance using this template: TablenameInsert where Tablename is the target table's name.
Next, create this universal insert procedure of yours as follows:
CREATE PROCEDURE InsertIntValue (
#TableName sysname,
#Value int
)
AS
BEGIN
DECLARE #SPName sysname;
SET #SPName = #TableName + 'Insert';
EXECUTE #SPName #Value;
END;
As can be seen from the manual, when invoking a module with the EXECUTE command, you can specify a variable instead of the actual module name. The variable in this case should be of a string type and is supposed to contain the name of the module to execute. This is not dynamic SQL, because the syntax is not the same. (For this to be dynamic SQL, the variable would need to be enclosed in brackets.) Instead, this is essentially parametrising of the module name, probably the only kind of natively supported name parametrisation in (Transact-)SQL.
Like I said, this requires more effort than dynamic SQL, because you still have to create all the many stored procedures that this universal SP should be able to invoke. Nevertheless, as a result, you get code that is both secure (the #SPName variable is viewed by the server only as a name, not as an arbitrary snippet of SQL) and efficient (the actual stored procedure being invoked already exists, i.e. it is already compiled and has a query plan).
You'll need to use Dynamic SQL.
To create Dynamic SQL, you need to build up the query as a string. Using IF statements and other logic to add your variables, etc.
Declare a text variable and use this to concatenate together your desired SQL.
You can then execute this code using the EXEC command
Example:
DECLARE #SQL VARCHAR(100)
DECLARE #TableOne VARCHAR(20) = 'TableOne'
DECLARE #TableTwo VARCHAR(20) = 'TableTwo'
DECLARE #SomeInt INT
SET #SQL = 'INSERT INTO '
IF (#SomeInt = 1)
SET #SQL = #SQL + #TableOne
IF (#SomeInt = 2)
SET #SQL = #SQL + #TableTwo
SET #SQL = #SQL + ' VALUES....etc'
EXEC (#SQL)
However, something you should really watch out for when using this method is a security problem called SQL Injection.
You can read up on that here.
One way to guard against SQL injection is to validate against it in your code before passing the variables to SQL-Server.
An alternative way (or probably best used in conjecture) is instead of using the EXEC command, use a built-in stored procedure called sp_executesql.
Details can be found here and usage description is here.
You'll have to build your SQL slightly differently and pass your parameters to the stored procedure as arguments as well as the #SQL.

Using sp_executesql with params complains of the need to declare a variable

I am attempting to make a stored procedure that uses sp_executesql. I have looked long and hard here, but I cannot see what I am doing incorrectly in my code. I'm new to stored procedures/sql server functions in general so I'm guessing I'm missing something simple. The stored procedure alter happens fine, but when I try run it I'm getting an error.
The error says.
Msg 1087, Level 15, State 2, Line 3
Must declare the table variable "#atableName"
The procedure looks like this.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_TEST]
#tableName varchar(50),
#tableIDField varchar(50),
#tableValueField varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLString nvarchar(500);
SET #SQLString = N'SELECT DISTINCT #aTableIDField FROM #atableName';
EXEC sp_executesql #SQLString,
N'#atableName varchar(50),
#atableIDField varchar(50),
#atableValueField varchar(50)',
#atableName = #tableName,
#atableIDField = #tableIDField,
#atableValueField = #tableValueField;
END
And I'm trying to call it with something like this.
EXECUTE sp_TEST 'PERSON', 'PERSON.ID', 'PERSON.VALUE'
This example isn't adding anything special, but I have a large number of views that have similar code. If I could get this stored procedure working I could get a lot of repeated code shrunk down considerably.
Thanks for your help.
Edit: I am attempting to do this for easier maintainability purposes. I have multiple views that basically have the same exact sql except the table name is different. Data is brought to the SQL server instance for reporting purposes. When I have a table containing multiple rows per person id, each containing a value, I often need them in a single cell for the users.
You can not parameterise a table name, so it will fail with #atableName
You need to concatenate the first bit with atableName, which kind defeats the purpose fo using sp_executesql
This would work but is not advisable unless you are just trying to learn and experiment.
ALTER PROCEDURE [dbo].[sp_TEST]
#tableName varchar(50),
#tableIDField varchar(50),
#tableValueField varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLString nvarchar(500);
SET #SQLString = N'SELECT DISTINCT ' + quotename(#TableIDField) + ' FROM ' + quotename(#tableName);
EXEC sp_executesql #SQLString;
END
Read The Curse and Blessings of Dynamic SQL
You cannot use variables to pass table names and column names to a dynamic query as parameters. Had that been possible, we wouldn't actually have used dynamic queries for that!
Instead you should use the variables to construct the dynamic query. Like this:
SET #SQLString = N'SELECT DISTINCT ' + QUOTENAME(#TableIDField) +
' FROM ' + QUOTENAME(#TableName);
Parameters are used to pass values, typically for use in filter conditions.

Resources