Why was this T-SQL Syntax never implemented? - sql-server

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.

Related

Declare VARCHAR(x) with x as a parameter?

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

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.

SQL Injection in Code/Static SQL (T-SQL)

Are parametrized static/code SQL statements subject to SQL injection attacks?
For example, let's say I have the following simplified stored procedure:
Does the fact that I am passing the input #PSeries_desc mean I am subject to injection attacks if it is parameterized?
Previously, this was a dynamic SQL statement and the code was executed using exec as opposed to sp_executesql So, it definitely was open to attacks.
CREATE procedure get_product_by_title
#PSearchType int = NULL
, #Pseries_desc varchar(40) = NULL
as
begin
declare
#whereLikeBeg varchar(1)
, #whereLikeEnd varchar(1)
set #whereLikeBeg = ''
set #whereLikeEnd = ''
if #search_code = 'contains'
begin
set #whereLikeBeg = '%'
set #whereLikeEnd = '%'
end
if #search_code = 'starts_with'
begin
set #whereLikeEnd = '%'
end
select
distinct B.parent_product_id
, B.parent_product_id
from
tableA
where
parent_product_id = child_product_id
and product_title like #whereLikeBeg + #Pseries_desc + #whereLikeEnd
end
This code look safe to me...
Parametrized query is not the only way to protect yourself from SQL-injection attacks but it's probably the simplest and safest way to do it.
And even if you forget about the sql-injection attacks, building query dynamically is not a good practice, especially when you are working with strings because they might contains SQL reserved words / characters that will have an impact on your query.
If you are using parameterized queries in the access code, you don't need to worry. Checking for it inside the stored procedure is improper.

T-SQL: How Do I Create A "Private" Function Inside A Stored Procedure

Okay so I'm writing a SQL Server 2008 Stored Procedure (maintenance script).
In doing so, being a good boy I've done plenty of error handling, checking rowcounts, printing output messages, etc
But in doing this, I've found myself writing over and over again something like this:
SELECT #RowsAffected = ##ROWCOUNT
IF #RowsAffected > 0
BEGIN
PRINT CAST(#RowsAffected, NVARCHAR(2)) + 'rows updated.'
END
Or debug messages like this:
PRINT 'User ' + CAST(#UserId AS NVARCHAR(5)) + ' modified successfully'
Is there a way I can create a kind of 'subroutine' inside the stored procedure (like a private method) that can accept something as a parameter (doesn't have to though) and do some logic?
I want to be able to do something like this:
CheckRowCounts
Or this:
PrintUserUpatedMessage(#UserId)
Which would then perform the above logic (check rowcount, print message, etc)
And yes obviously I can create a UDF, but then i would need to create/drop it etc as this logic is only required for the life of the execution of this stored procedure.
Getting sick and tired of writing the same code over and over again, and changing all the different areas I've used it when I get an error =)
Can anyone help?
EDIT
Ok so I ended up creating a scalar UDF function (seems only way).
However, I have awarded the correct answer to Fredrik as although I don't plan to implement this, it is both a correct answer and a creative one at that.
Thanks for all the advice/help.
I first tried to create another, temporary SP, from within an existing SP - which didn't work, but after experimenting a bit I think you could go with something like this (if you don't mind dynamic SQL):
CREATE PROCEDURE sp_myTest_v1_0(#firstName NVARCHAR(255)) AS
BEGIN
-- declare private method
DECLARE #privateMethod NVARCHAR(255), #privateMethodSig NVARCHAR(255)
SELECT #privateMethod =
'DECLARE #x INT' + CHAR(10) +
'WHILE ISNULL(#x,0) < 10 BEGIN' + CHAR(10) +
'PRINT #param1 + CAST(#x AS VARCHAR)' + CHAR(10) +
'SET #x = ISNULL(#x,0)+1' + CHAR(10) +
'END', #privateMethodSig = '#param1 NVARCHAR(255)'
-- call privateMethod
EXEC sp_executesql #privateMethod, #privateMethodSig, #param1 = #firstName
END
GO
Not really anything like that in T-SQL. The closest thing, as you stated, is a scalar udf, and you don't seem to be a fan of that idea. I don't see the issue with creating some helper functions like that and leaving them in the database. Surely you have other procedures that could benefit from good messages.

Inserting a string of form "GUID1, GUID2, GUID3 ..." into an IN statement in TSQL

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 :)

Resources