create or alter proc printThisAsTableHeader
#n as int
as
begin
select 'values' as '#n is good'
end
go
exec printThisAsTableHeader 2
the above code's output is:
#n is good
values
I want #n should be replaced with its value.
I try many ways like format, concat, convert, cast, and concatenation operator as well, but unable to do that.
objective:
I just want to make a procedure that tacks an int parameter #n
and print nth highest salary.
n=7
7th highest salary
salary value
As I mention in my comment, this has a strong smell of an XY Problem. The above doesn't appear to actually achieve anything on it's own, so you're clearly trying to solve a different problem, that very likely doesn't even need dynamic SQL.
If you "must" (which I doubt) do this, then you you can achieve this like below:
CREATE OR ALTER PROC dbo.printThisAsTableHeader #n int AS
BEGIN
DECLARE #SQL nvarchar(MAX) = N'SELECT ''values'' AS ' + QUOTENAME(CONCAT(#n,N' is good')) + N';'
EXEC sys.sp_executesql #SQL;
END;
DB<>Fiddle
Related
I am attempting to create a stored procedure that allows values to be passed to it, however, when I try to use Dynamic T-SQL I raise an error MSG 207
I know that the error message is supposed to indicate that the column name was misspelled in my statement thanks to the following site:
https://www.tsql.info/error/msg-207-level-16-invalid-column-name.php
This, however, does not make sense as the following hard coded statement works just fine.
INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES ('shockc', '11653798', GETDATE(), 'GAD4859');
ALTER PROCEDURE [dbo].[spAddDriversLicense]
-- Add the parameters for the stored procedure here
#DriversLicensePrefix NCHAR(8),
#DriversLicenseSuffix NCHAR(10)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #sqlInsertDriversLicense NVARCHAR(MAX)
SET #sqlInsertDriversLicense = 'INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES (' + #DriversLicensePrefix + ', ' + #DriversLicenseSuffix + ', GETDATE(), GPC4859);'
EXECUTE sp_executesql #sqlInsertDriversLicense
END
I've been spilling over this code for hours and I'm thoroughly confused as to why there is any difference between the two INSERT statements. The values being passed are the same but an error is raised when I attempt to pass them dynamically. How is this raising an error??
I've used some code from this video to attempt to learn how pass Scalar Values dynamically and have had luck in other stored procedures.
https://www.youtube.com/watch?v=RHHKG65WEoU
When working with dynamic strings, select them first, so you can check that the resulting syntax is workable.
You need single quotes in the result, so you need more than single, single quotes, in the code:
declare #DriversLicensePrefix nchar(8) = 'A2345678'
declare #DriversLicenseSuffix nchar(10) = 'abcdefghij'
DECLARE #sqlInsertDriversLicense nvarchar(max)
SET #sqlInsertDriversLicense = 'INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber)
VALUES (N''' + #DriversLicensePrefix + ''', N''' + #DriversLicenseSuffix + ''', ''' + convert(char(8),GETDATE(),112) + ''', ''GPC4859'');'
select #sqlInsertDriversLicense
+-------------------------------------------------------------------------------------------------------------------+
| result: |
+-------------------------------------------------------------------------------------------------------------------+
| INSERT INTO [dbo].[tDriversLicense] (DriversLicensePrefix, DriversLicenseSuffix, DateAdded, DriversLicenseNumber) |
| VALUES (N'A2345678', N'abcdefghij', '20181109', 'GPC4859'); |
+-------------------------------------------------------------------------------------------------------------------+
NB You should use convert(char(8),getdate(),112) SQL Server will recognize the YYYYMMDD format as a date value regardless of server default settings.
The result see above demonstrates what the insert statement MUST be, note that it it contains several single quotes.
When you are concatenating the SQL statement, you are also dealing with strings, and every part of that has to be contained within single quotes.
So there are multiple needs for single quotes.
And; So you need multiple single quotes throughout out the concatenation, some to help form the SQL statement, and others to be INSIDE that statement.
/* get this into #sql */
select 'Y' as col1;
declare #SQL as nvarchar(max)
set #SQL = N'select ''Y'' as col1;'
select #SQL;
+---------------------+
| #SQL |
+---------------------+
| select 'Y' as col1; |
+---------------------+
In the larger query 2 variables are defined a NCHAR(8) or (10) as you have defined them a Nchar then when inserting data into those you should prefix that input by N
As posted in a comment already there is no need for this dynamic approach at all.
The following code will show a straight and a dynamic approach. Try it out:
USE master;
GO
CREATE DATABASE testDB;
GO
USE testDB;
GO
CREATE TABLE TestTable(SomeContent VARCHAR(100),SomeDate DATETIME,SomeFixString VARCHAR(100));
GO
--This procedure will use the parameters directly. No need for any dynamic SQL:
CREATE PROCEDURE TestStraight(#content VARCHAR(100))
AS
BEGIN
INSERT INTO TestTable(SomeContent,SomeDate,SomeFixString)
VALUES(#content,GETDATE(),'Proc Straight');
END
GO
--this procedure will use real parameters. You should never use parameters in string concatenation. One day you might meet bobby tables...
CREATE PROCEDURE TestDynamic(#content VARCHAR(100))
AS
BEGIN
DECLARE #cmd NVARCHAR(MAX)=
N'INSERT INTO TestTable(SomeContent,SomeDate,SomeFixString)
VALUES(#DynamicContent,GETDATE(),''Proc Dynamic'');'
EXEC sp_executesql #cmd,N'#DynamicContent VARCHAR(100)',#DynamicContent=#content;
END
GO
--Test it
EXEC TestStraight 'yeah!';
EXEC TestDynamic 'oh yeah!';
SELECT * FROM TestTable;
GO
--Clean up
USE master;
GO
--careful with real data!
--DROP DATABASE testDB;
I have a very basic question about declaring some variables in TSQL.
When i declare a numeric variable in TSQL like this, everthing is ok:
DECLARE #Value AS NUMERIC(18,2) = 1.23
But what if i want the decimals to be set with a parameter?
DECLARE #NrOfDecimals AS INTEGER = 2
DECLARE #Value AS NUMERIC(18,#NrOfDecimals) = 1.23
--This second line throws a compile error "Expecting INTEGER"
So the second line throws a compile error "Expecting INTEGER".
But isn't #NrOfDecimals an Integer? So why is the compiler complaining??
Am i missing something??
You could dynamically create your queries and execute them.
This will work for the situation in your question:
DECLARE #NrOfDecimals AS INTEGER = 2
DECLARE #myCommand as NVARCHAR(1000)
SET #myCommand = 'DECLARE #value as numeric(18,' + CAST(#NrOfDecimals as VARCHAR) + ') = 1.23'
execute sp_executesql #myCommand
In order to do this for every situation, you should create a table with all queries you will have to run.
In order to create all of these queries you will have to use a cursor.
Documentation for cursors you can find here:
http://msdn.microsoft.com/en-us/library/ms180169.aspx
http://www.mssqltips.com/sqlservertip/1599/sql-server-cursor-example/
Also, I have answered another question and gave an example with a cursor:
SQL Query to find a column name throughout the Database
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.
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.
I've got a stored procedure in my database, that looks like this
ALTER PROCEDURE [dbo].[GetCountingAnalysisResults]
#RespondentFilters varchar
AS
BEGIN
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a, 114c61f2-8935-4755-b4e9-4a598a51cc7f'''
DECLARE #SQL nvarchar(600)
SET #SQL =
'SELECT *
FROM Answer
WHERE Answer.RespondentId IN ('+#RespondentFilters+'''))
GROUP BY ChosenOptionId'
exec sp_executesql #SQL
END
It compiles and executes, but somehow it doesn't give me good results, just like the IN statement wasn't working. Please, if anybody know the solution to this problem, help me.
You should definitely look at splitting the list of GUIDs into a table and joining against that table. You should be able to find plenty of examples online for a table-valued function that splits an input string into a table.
Otherwise, your stored procedure is vulnerable to SQL injection. Consider the following value for #RespondentFilters:
#RespondentFilters = '''''); SELECT * FROM User; /*'
Your query would be more secure parsing (i.e. validating) the parameter values and joining:
SELECT *
FROM Answer
WHERE Answer.RespondentId IN (SELECT [Item] FROM dbo.ParseList(#RespondentFilters))
GROUP BY ChosenOptionId
or
SELECT *
FROM Answer
INNER JOIN dbo.ParseList(#RespondentFilters) Filter ON Filter.Item = Answer.RespondentId
GROUP BY ChosenOptionId
It's slightly more efficient as well, since you aren't dealing with dynamic SQL (sp_executesql will cache query plans, but I'm not sure if it will accurately identify your query as a parameterized query since it has a variable list of items in the IN clause).
You need single quotes around each GUID in the list
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a'', ''114c61f2-8935-4755-b4e9-4a598a51cc7f'''
It looks like you don't have closing quotes around your #RespondentFilters '8ec94bed-fed6-4627-8d45-21619331d82a, 114c61f2-8935-4755-b4e9-4a598a51cc7f'
Since GUIDs do a string compare, that's not going to work.
Your best bet is to use some code to split the list out into multiple values.
Something like this:
-- This would be the input parameter of the stored procedure, if you want to do it that way, or a UDF
declare #string varchar(500)
set #string = 'ABC,DEF,GHIJK,LMNOPQRS,T,UV,WXY,Z'
declare #pos int
declare #piece varchar(500)
-- Need to tack a delimiter onto the end of the input string if one doesn't exist
if right(rtrim(#string),1) ','
set #string = #string + ','
set #pos = patindex('%,%' , #string)
while #pos 0
begin
set #piece = left(#string, #pos - 1)
-- You have a piece of data, so insert it, print it, do whatever you want to with it.
print cast(#piece as varchar(500))
set #string = stuff(#string, 1, #pos, '')
set #pos = patindex('%,%' , #string)
end
Code stolen from Raymond Lewallen
I think you need quotes inside the string too. Try:
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a'',''114c61f2-8935-4755-b4e9-4a598a51cc7f'''
You could also consider parsing the #RespondentFilters into a temporary table.
Tank you all for your ansewers. They all helped a lot. I've dealt with the problem by writing a split function, and it works fine. It's a litte bit overhead from what I could have done, but you know, the deadline is hiding around the corner :)