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
Related
We have a stored procedure
CREATE PROCEDURE [dbo].[usp_s_eval_expr]
#formula NVARCHAR(max),
#result SQL_VARIANT OUTPUT
AS
BEGIN
DECLARE #sql NVARCHAR(max)
DECLARE #cal_value FLOAT
SET #sql = N'DECLARE #x xml=''''SELECT #cal_value=CAST(#x.query('''+#formula+''') as nvarchar(max))'
EXEC sp_executesql #sql,N'#cal_value FLOAT OUTPUT',#cal_value OUTPUT
SET #result = #cal_value
SELECT #result
END
GO
It is created to evaluate mathematical expressions, I understand that there are better ways to solve the expression, it is deployed in production and I don't have the right to modify it.
It works with most of the functions but is throwing errors while using it to find the absolute value of a number.
For example,
DECLARE #formula NVARCHAR(max) = ' abs(-1.5) '
DECLARE #result SQL_VARIANT
EXEC [usp_s_eval_expr] #formula, #result OUTPUT
SELECT #result
This throws the error following error whereas ceiling and round functions work well.
Msg 50000, Level 16, State 1, Procedure usp_s_eval_expr, Line 32 [Batch Start Line 0]
XQuery [query()]: There is no function '{http://www.w3.org/2004/07/xpath-functions}:abs()'
Is there any way to achieve the absolute value of a number order than checking less than 0, the reason why I don't want to check less than is the expression that results in -1.5 is very big, and I don't want to use it twice to achieve the abs functionality.
According to the documentation available by download from https://learn.microsoft.com/en-us/openspecs/sql_standards/MS-SQLSTANDLP/89fb00b1-4b9e-4296-92ce-a2b3f7ca01d2 SQL Server does not support the fn:abs() function.
Also: are you sure about the namespace {http://www.w3.org/2004/07/xpath-functions}. That's from an early draft of XPath 2.0, not the final W3C spec. But I don't think SQL Server ever implemented the final spec anyway.
Another way to get the absolute value might be to convert to a string and strip the minus sign.
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
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.
Why did they never let us do this sort of thing:
Create Proc RunParameterisedSelect
#tableName varchar(100),
#columnName varchar(100),
#value varchar(100)
as
select * from #tableName
where #columnName = #value
You can use #value as a parameter, obviously, and you can achieve the whole thing with dynamic SQL, but creating it is invariably a pain.
So why didn't they make it part of the language in some way, rather than forcing you to EXEC(#sql)?
Quite simply because such a thing is impossible. This is a bit like asking, "Why was this JavaScript syntax never implemented":
var operator = "<";
var statement = "if"
var op1 = 4
statement (op1 operator 5) op1++; // whatever
It's never been implemented because this is un-implementable and, frankly, it does not make any sense. JavaScript has eval() for dynamic code:
code = statement+" (op1 "+operator+" 5) op1++;";
eval( code );
And SQL Server has EXECUTE for dynamic SQL:
/* example only, it is not recommendable to actually *do* this */
Create Proc RunParameterisedSelect
#tableName varchar(100),
#columnName varchar(100),
#value varchar(100)
as
begin
declare #code varchar(8000)
set #code = 'select * from ['+#tableName+'] where ['+#columnName+'] = '+#value
exec (#code)
end
The essence is the same - if it is not a fixed, immutable code structure (and a table or column name is code in SQL, not a variable), then you must make a string out out it first and parse that. The interpreter/compiler must build a new syntax tree (which itself will be fixed again, of course).
Because procedures precompile on first execution into query plans, and this would not be possible like that. I agree it is paifull, it is only so, though,f or people which are stored procedure addicts - do not use sp's for queriey, build dynamic query strings and you have no problems.
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 :)