How to concatenate variables into SQL strings - sql

I need to concatenate a variable table name into my SQL query such as the following...
ALTER FUNCTION fn_myfunction(#KeyValue text)
BEGIN
INSERT INTO #tmpTbl1
SELECT #KeyValue AS fld1
FROM tbl + #KeyValue + KeyValue.fld1
I also attempted the following but it told me I had to declare the table variable?
ALTER FUNCTION fn_myfunction(#KeyValue text, #KeyTable text)
FROM #KeyTable.fld1

You can accomplish this (if I understand what you are trying to do) using dynamic SQL.
The trick is that you need to create a string containing the SQL statement. That's because the tablename has to specified in the actual SQL text, when you execute the statement. The table references and column references can't be supplied as parameters, those have to appear in the SQL text.
So you can use something like this approach:
SET #stmt = 'INSERT INTO #tmpTbl1 SELECT ' + #KeyValue
+ ' AS fld1 FROM tbl' + #KeyValue
EXEC (#stmt)
First, we create a SQL statement as a string. Given a #KeyValue of 'Foo', that would create a string containing:
'INSERT INTO #tmpTbl1 SELECT Foo AS fld1 FROM tblFoo'
At this point, it's just a string. But we can execute the contents of the string, as a dynamic SQL statement, using EXECUTE (or EXEC for short).
The old-school sp_executesql procedure is an alternative to EXEC, another way to execute dymamic SQL, which also allows you to pass parameters, rather than specifying all values as literals in the text of the statement.
FOLLOWUP
EBarr points out (correctly and importantly) that this approach is susceptible to SQL Injection.
Consider what would happen if #KeyValue contained the string:
'1 AS foo; DROP TABLE students; -- '
The string we would produce as a SQL statement would be:
'INSERT INTO #tmpTbl1 SELECT 1 AS foo; DROP TABLE students; -- AS fld1 ...'
When we EXECUTE that string as a SQL statement:
INSERT INTO #tmpTbl1 SELECT 1 AS foo;
DROP TABLE students;
-- AS fld1 FROM tbl1 AS foo; DROP ...
And it's not just a DROP TABLE that could be injected. Any SQL could be injected, and it might be much more subtle and even more nefarious. (The first attacks can be attempts to retreive information about tables and columns, followed by attempts to retrieve data (email addresses, account numbers, etc.)
One way to address this vulnerability is to validate the contents of #KeyValue, say it should contain only alphabetic and numeric characters (e.g. check for any characters not in those ranges using LIKE '%[^A-Za-z0-9]%'. If an illegal character is found, then reject the value, and exit without executing any SQL.

You could make use of Prepared Stements like this.
set #query = concat( "select name from " );
set #query = concat( "table_name"," [where condition] " );
prepare stmt from #like_q;
execute stmt;

Related

why I need a dynamic query for "column value from a query"

For example this returns a value from a query, which I will then use as a column name.
#A=Select top 1 productid from productlist order by timestamp desc
then I would like this "productid" A to be used in the other table
Select #A from customerlist
then the result is #A value instead of field value in customerlist.
When I use dynamic query, I can get right result.
Why?
(I know I can use join but because this productlist table is dynamic, so let's assume it is a sub query)
You need "dynamic SQL" because SQL will NOT allow you to use a parameter as a column name or a table name. You can only use parameters for data values such as in a where clause where column1 = #val
set #A = 'çolumn1'
Select #A from customerlist -- this fails because it is not allowed
Dynamic SQL is a "hack" to get around those restrictions as the SQL statement is placed into a string along with any value held by parameters.
set #A = 'çolumn1'
set #SQL = 'Select ' + #A + ' from customerlist;'
execute #SQL -- this works, the SQL statement is valid with no parameters as column names
The string formed as #SQL is a complete sql statement without needing any parameters as column names.
Note: the syntax I used here is incomplete and is based on MS SQL Server, different databases will use a different, but similar, syntax.

I want to create a stored procedure using T-SQL that dynamically creates a table based on outside data that isn't vulnerable to SQL injection

I have a table which has a column that represents the name of a table we'd like to create. There's a foreign key relationship to another table which has a column representing the name of the columns for the desired table (all data types assumed to be nvarchar). I'm using a stored procedure to create this table. Essentially what I'm doing is getting all of the relevant data from my tables, then building a SQL string up to generate the table, and finally using EXEC sp_executesql #CreateTableSQL.
#CreateTableSQL is generated through string concatenation like this:
SET #CreateTableSQL = 'CREATE TABLE ' + #TableName + ' (' + #ColumnString + ')';
This leaves me vulnerable to SQL injection. If someone were to use a #TableName value of:
C (t int); DROP TABLE MyTable;--
then this would drop MyTable (undesirable).
Can someone help me build this SQL and leave it invulnerable to injection? Help is greatly appreciated. Thanks!
You can make use of QUOTENAME() function which will enforce square brackets [] around the variables(Table and column names) and any value passed to these variables will only be treated as an Object name.
Something like ......
SET #CreateTableSQL = 'CREATE TABLE ' + QUOTENAME(#TableName)
+ ' (' + QUOTENAME(#ColumnString) + ')';
Now even if someone passes a value of C (t int); DROP TABLE MyTable;-- to any of these variables, the whole value C (t int); DROP TABLE MyTable;-- will still be treated as an object name.

Using a string of quoted values in a variable for a SQL WHERE CLAUSE

The answer escapes me...maybe because it is not possible...
Example that works...
SELECT * FROM TABLEA WHERE FIELD1 IN ('aaa','bbb','ccc')
Example that does not work...
Attempt to leverage variable so that I can define the values once in a string of statements
DECLARE #ListValues VARCHAR(50)
SET #ListValues = '''aaa'',''bbb'',''ccc'''
SELECT * FROM TABLEA WHERE FIELD1 IN (#ListValues)
This is is obviously only a small part of the equation and for other reasons...
I cannot leverage a table for the values and change this to a true sub-query
The closest question I could find was this one... but does not cover my requirements obviously...
Storing single quotes in varchar variable SQL Server 2008
Thanks in advance.
You can do this using dynamic SQL:
DECLARE #ListValues VARCHAR(MAX)
,#SQL VARCHAR(MAX)
SELECT #ListValues = '''aaa'',''bbb'',''ccc'''
,#SQL = 'SELECT * FROM TABLEA WHERE FIELD1 IN ('+#ListValues+')'
EXEC (#SQL)
It doesn't work because the IN operator expects a list of items - here strings.
What you're supplying with your #ListValues variable however is a single string - not a list of strings.
What you could do is use a table variable and store your values in it:
DECLARE #ListOfValues TABLE (ItemName VARCHAR(50))
INSERT INTO #ListOfValues(ItemName)
VALUES('aaa'), ('bbb'), ('ccc')
SELECT *
FROM TABLEA
WHERE FIELD1 IN (SELECT ItemName FROM #ListOfValues)
Build your whole SQL query dynamically (say it's stored in a string variable #sql),
and then execute it with EXEC (#sql). Better yet, use the sp_executesql SP
because this approach is more secure (less prone to SQL injection) than EXEC.
See: sp_executesql
The IN operator in SQLServer expect a list of values, your variable is a single string, the query parsed will be different
SELECT * FROM TABLEA WHERE FIELD1 IN ('aaa','bbb','ccc')
SELECT * FROM TABLEA WHERE FIELD1 IN ("'aaa','bbb','ccc'")
Attention: the double quotes are there only for readability, to get the string with single quote in it.
if you know a programming language the first one is like searching in an array, the second is a string.
To store a list in your variable it need to a table
DECLARE #varTable TABLE (field1 varchar())
So that you can use it in your IN
SELECT * FROM TABLEA WHERE FIELD1 IN (SELECT field1 FROM #varTable)
To add values to the table variable use an INSERT statament like usual for tables.

Create INSERT statement using parameter

I need to create a INSERT statement using parameters. Say I have two variable name #DestinationFields, #InsertValues.
Here #DestinationFields contain the column name like: product,price and #InsertValues contains the values for those two columns, like: Book,100.
Now, How i create a insert command to insert those values where each value need to add a quotation mark .I already tried as
I already tried as
EXEC('INSERT into tbl_test('+#DestinationFields+')values('+#InsertValues+')')
But it's returning an error.
The name "book" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some
contexts) variables. Column names are not permitted.
How do I do it? Thanks in advance.
Pretending there is no problem of SQL injection here*, you can quickly fix your code by adding quotation marks around Book. The value of # InsertValues should be
'Book', 100
instead of simply
Book, 100
You need to add quotation marks around each string value; otherwise, strings are interpreted as names, which is not valid.
EDIT : (in response to a comment) If all columns are of varchar type, you can put quotes around the entire string, and replace all commas with the quote-comma-quote pattern, like this:
values('''+REPLACE(#InsertValues,',',''',''')+''')'
* You should not put code like this into production, because it can be manipulated to harm your system rather severely. Here is a good illustration of the problem (link).
Try:
DECLARE #DestinationFields VARCHAR(200);
SET #DestinationFields = 'Col1, Col2, Col3'
DECLARE #InsertValues VARCHAR(200);
SET #InsertValues = '1, 2, 3'
DECLARE #SQLString VARCHAR(1000);
SET #SQLString = 'INSERT INTO tbl_test (' + #DestinationFields + ') VALUES (' + #InsertValues + ')';
EXEC (#SQLString)
However, this is very open to SQL Injection attacks. But, it will do what you require.
The Curse and Blessing of Dynamic SQL

SQL Server - Replacing Single Quotes and Using IN

I am passing a comma-delimited list of values into a stored procedure. I need to execute a query to see if the ID of an entity is in the comma-delimited list. Unfortunately, I think I do not understand something.
When I execute the following stored procedure:
exec dbo.myStoredProcedure #myFilter=N'1, 2, 3, 4'
I receive the following error:
"Conversion failed when converting the varchar value '1, 2, 3, 4' to data type int."
My stored procedure is fairly basic. It looks like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter nvarchar(512) = NULL
AS
SET NOCOUNT ON
BEGIN
-- Remove the quote marks so the filter will work with the "IN" statement
SELECT #myFilter = REPLACE(#myFilter, '''', '')
-- Execute the query
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
t.ID IN (#myFilter)
ORDER BY
t.Name
END
How do I use a parameter in a SQL statement as described above? Thank you!
You could make function that takes your parameter, slipts it and returns table with all the numbers in it.
If your are working with lists or arrays in SQL Server, I recommend that you read Erland Sommarskogs wonderful stuff:
Arrays and Lists in SQL Server 2005
You need to split the string and dump it into a temp table. Then you join against the temp table.
There are many examples of this, here is one at random.
http://blogs.microsoft.co.il/blogs/itai/archive/2009/02/01/t-sql-split-function.aspx
Absent a split function, something like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter varchar(512) = NULL -- don't use NVARCHAR for a list of INTs
AS
SET NOCOUNT ON
BEGIN
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
CHARINDEX(','+CONVERT(VARCHAR,t.ID)+',',#myFilter) > 0
ORDER BY
t.Name
END
Performance will be poor. A table scan every time. Better to use a split function. See: http://www.sommarskog.se/arrays-in-sql.html
I would create a function that takes your comma delimited string and splits it and returns a single column table variable with each value in its own row. Select that column from the returned table in your IN statement.
I found a cute way of doing this - but it smells a bit.
declare #delimitedlist varchar(8000)
set #delimitedlist = '|1|2|33|11|3134|'
select * from mytable where #delimitedlist like '%|' + cast(id as varchar) + '|%'
So... this will return all records with an id equal to 1, 2, 33, 11, or 3134.
EDIT:
I would also add that this is not vulnerable to SQL injection (whereas dynamic SQL relies on your whitelisting/blacklisting techniques to ensure it isn't vulnerable). It might have a performance hit on large sets of data, but it works and it's secure.
I have a couple of blog posts on this as well, with a lot of interesting followup comments and dialog:
More on splitting lists
Processing list of integers