Dynamic SQL (passing table name as parameter) - sql-server

I want to write a stored proc which will use a parameter, which will be the table name.
E.g:
#tablename << Parameter
SELECT * FROM #tablename
How is this possible?
I wrote this:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetAllInterviewQuestions]
#Alias varchar = null
AS
BEGIN
Exec('Select * FROM Table as ' #Alias)
END
But it says incorrect syntax near #Alias.

Well, firstly you've omitted the '+' from your string. This way of doing things is far from ideal, but you can do
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'SELECT * FROM ' + QuoteName(#Alias)
Exec(#SQL)
I'd strongly suggest rethinking how you do this, however. Generating Dynamic SQL often leads to SQL Injection vulnerabilities as well as making it harder for SQL Server (and other DBs) to work out the best way to process your query. If you have a stored procedure that can return any table, you're really getting virtually no benefit from it being a stored procedure in the first place as it won't be able to do much in the way of optimizations, and you're largely emasculating the security benefits too.

You'll have to do it like this:
exec('select * from '+#tablename+' where...')
But make sure you fully understand the risks, like SQL injection attacks. In general, you shouldn't ever have to use something like this if the DB is well designed.

Don't you mean
Exec('SELECT * FROM ' + #tableName)
Also, the error you get is because you've forgotten a + before #Alias.

Often, having to parameterize the table name indicates you should re-think your database schema. If you are pulling interview questions from many different tables, it is probably better to create one table with a column distinguishing between the questions in whatever way the different tables would have.

Most implementations of SQL do not allow you to specify structural elements - table names, column names, order by columns, etc. - via parameters; you have to use dynamic SQL to parameterize those aspects of a query.
However, looking at the SQL, you have:
Exec('SELECT * FROM Table AS ' #Alias)
Surely, this would mean that the code will only ever select from a table called 'Table', and you would need to concatenate the #Alias with it -- and in many SQL dialects, concatenation is indicated by '||':
Exec('SELECT * FROM Table AS ' || #Alias)
This still probably doesn't do what you want - but it might not generate a syntax error when the procedure is created (but it would probably generate an error at runtime).

Related

Parameters.Add and Sql Injection

Does sqlCommand.Parameters.Add(sqlParam) checks reserve words such as 'Table', 'Drop' etc.
Basically i want to know by using above how we can avoid Sql Injection what is the mechanism over there.
It all depends on what you plan to do with the parameters in the SQL you are executing. The good thing about using .Parameters.Add() is that the values are passed seperately and not part of 1 big chunk-o-sql. Off course it's up to you to decide what to do with them then.
Assuming you do something like this:
SELECT * FROM myTable WHERE customer_nr = #customer_nr
Then it doesn't really matter if a 'hacker' passed something along the lines of ';DROP TABLE myTable --. The query will simply not return anything because no customer is named `';DROP TABLE myTable --'
However, if you're going to use it like this:
SELECT #sql = 'SELECT * FROM myTable WHERE customer_nr = ''' + #customer_nr + ''''
EXEC (#sql)
then you defeat the purpose of the system and the hacker WILL be able to do some SQL-Injection-ish stuff.
No it doesn't treat parameters as reserve words. Using parametrized stored procedures is best way to avoid sql injection.

SQL Server Dynamic Stored Procedure

I am having the stored procedure. For that i need to pass the Database name as the paramters from another application or another SP. I know the approach of dynamic SQL, something like,
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='select * from '+#dbname+'.dbo.table'
end
exec mysp 'mydb'
But i dont want the SQL statements as a string. Because in my SP, i have many Sql statements are coming (Not like this only SELECT statement). so can i use,
USE DatabaseName
inside the stored procedure, so that i can use the db name in the sql statements directly without making it as string. Or any other approach is there.
My requirements, only for db name, i dont want the entire the sql statement to be dynamic...
please help me out.
Thanks in advance.
You can add the USE instruction to the dynamic query you are creating. Then you can work with that database's tables and other objects without the qualifier (within the dynamic query):
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='use '+#dbname;
set #sql=#sql + ';select ... from dbo.table1';
set #sql=#sql + ';update dbo.table2...';
set #sql=#sql + ';insert into dbo.table3...';
...
exec(#sql);
end
exec mysp 'mydb'
However, while you can do that, it's not something that you should do, unless you really have to. You are probably trying to avoid creating the same procedure in different DBs, but you may be getting you other problems with this approach, or robbing yourself of some advantages you might otherwise have without resorting to dynamic queries in SPs.
No, USE isn't allowed in stored procedures, functions and triggers.
A stored procedure is supposed to be local to the database. To access another database, there is one way (as far as I know), and it's the one you used.

Alternate synonym in SQL Server in one transaction

I am new to Transact SQL programming.
I have created a stored procedure that would drop and create an existing synonym so that it will point to another table. The stored procedure takes in 2 parameters:
synonymName - an existing synonym
nextTable - the table to be point at
This is the code snippet:
...
BEGIN TRAN SwitchTran
SET #SqlCommand='drop synonym ' + #synonymName
EXEC sp_executesql #SqlCommand
SET #SqlCommand='create synonym ' + #synonymName + ' for ' + #nextTable
EXEC sp_executesql #SqlCommand
COMMIT SwitchTran
...
We have an application that would write data using the synonym regularly.
My question is would I run into a race condition where the synonym is dropped, while the application try to write to the synonym?
If the above is a problem, could someone give me suggestion to the solution.
Thanks
Yes, you'd have a race condition.
One way to manage this is to have sp_getapplock after BEGIN TRAN in Transaction mode and trap/handle the return status as required. This will literally serialise (in the execution sense, not isolation) callers so only one SPID executes at any one time.
I'm fairly certain you will indeed get race conditions. Synonym Names are intended to be used for shortening the name of an object and aren't supposed to change any more often than other objects. I'm guessing by your description that you are using it for code reuse. You are probably better off using Dynamic SQL instead, which incidentally you already are.
For more information on Dynamic SQL you might want to consider a look at this article on by Erland Sommarskog that OMG Poinies references in a lot of his answers. Particularly the section on Dealing with Dynamic Table and Column Names which I've quotes below
Dealing with Dynamic Table and Column
Names
Passing table and column names as
parameters to a procedure with dynamic
SQL is rarely a good idea for
application code. (It can make
perfectly sense for admin tasks). As
I've said, you cannot pass a table or
a column name as a parameter to
sp_executesql, but you must
interpolate it into the SQL string.
Still you should protect it against
SQL injection, as a matter of routine.
It could be that bad it comes from
user input.
To this end, you should use the
built-in function quotename() (added
in SQL 7). quotename() takes two
parameters: the first is a string, and
the second is a pair of delimiters to
wrap the string in. The default for
the second parameter is []. Thus,
quotename('Orders') returns [Orders].
quotename() takes care of nested
delimiters, so if you have a really
crazy table name like Left]Bracket,
quotename() will return
[Left]]Bracket].
Note that when you work with names
with several components, each
component should be quoted separately.
quotename('dbo.Orders') returns
[dbo.Orders], but that is a table in
an unknown schema of which the first
four characters are d, b, o and a dot.
As long as you only work with the dbo
schema, best practice is to add dbo in
the dynamic SQL and only pass the
table name. If you work with different
schemas, pass the schema as a separate
parameter. (Although you could use the
built-in function parsename() to split
up a #tblname parameter in parts.)
While general_select still is a poor
idea as a stored procedure, here is
nevertheless a version that summarises
some good coding virtues for dynamic
SQL:
CREATE PROCEDURE general_select #tblname nvarchar(128),
#key varchar(10),
#debug bit = 0 AS DECLARE #sql nvarchar(4000)
SET #sql = 'SELECT col1, col2, col3
FROM dbo.' + quotename(#tblname) + '
WHERE keycol = #key' IF #debug = 1
PRINT #sql EXEC sp_executesql #sql, N'#key varchar(10)', #key = #key
- I'm using sp_executesql rather than EXEC().
- I'm prefixing the table name with dbo.
- I'm wrapping #tblname in quotename().
- There is a #debug parameter.

Can I create a VIEW that has a dynamic name in another DB with MS SQL?

I found strange rules in MS SQL CREATE VIEW syntax.
It must be on the first line of query batch processing and it must be created in the current database.
I should make VIEWs that have dynamic name described by string variables
(type: VARCHAR or NVARCHAR). And those VIEWs should be created in other databases.
Because of the rule, CREATE VIEW statement must be on the first line of query batch processing, it cannot be after USE statement.
So, I tried to change databases with USE & GO statement. But GO statement seemed to make clear all the variables. Therefore they are not available that describe VIEW name after GO statement.
Do you have any opinon for me?
And if you know the reasons of CREATE VIEW syntax rules, please tell me.
Oh~, Sorry. I missed one thing. The names of databases are also dynamic.
And VIEWs, I want to make, not only should access tables of other databases
but also shoule be created in other databases.
Though I don't know OLAP well, I think this situation is involved OLAP.
You are able to dynamically create a sql-string and execute it.
DECLARE #ViewName VARCHAR(100)
SET #ViewName = 'MyView'
USE MyDB;
EXEC ( 'CREATE VIEW dbo.' + #ViewName + ' '
+ 'AS SELECT * FROM dbo.MyTable')
CREATE SYNONYM Resource1 FOR LinkedServer.Database.Schema.Table
GO
CREATE VIEW Resource1View
AS
SELECT *
FROM Resource1
GO
Now you can alter the synonym as much as you like and all your views referencing it will refer to the correct thing. If this doesn't solve the problem, then I would suggest that the way you're designing your system is not best. Please describe more what you are doing and why so we can advise you better.
As for "GO", is it actually not a SQL statement. It is never submitted to the server. The client sees the line with GO on it, and separates the submitted query into separate batches. A trace will prove this, as will EXEC 'SELECT 1' + CHAR(13) + CHAR(10) + 'GO' + CHAR(13) + CHAR(10) + 'SELECT 2'.
If you're using OLAP as in Analysis Services, then I'm not experienced enough with that to help you, but I would think there'd be ways to choose the database to connect to just like in SSRS, and that queries don't have to live in the database but could live in the SSAS application.
I found it. It's the nested EXEC.

Searching on a table whose name is defined in a variable

simple problem, but perhaps no simple solution, at least I can't think of one of the top of my head but then I'm not the best at finding the best solutions.
I have a stored proc, this stored proc does (in a basic form) a select on a table, envision this:
SELECT * FROM myTable
okay, simple enough, except the table name it needs to search on isn't known, so we ended up with something pretty similiar to this:
-- Just to give some context to the variables I'll be using
DECLARE #metaInfoID AS INT
SET #metaInfoID = 1
DECLARE #metaInfoTable AS VARCHAR(200)
SELECT #metaInfoTable = MetaInfoTableName FROM MetaInfos WHERE MetaInfoID = #MetaInfoID
DECLARE #sql AS VARCHAR(200)
SET #sql = 'SELECT * FROM ' + #metaInfoTable
EXEC #sql
So, I, recognize this is ultimately bad, and can see immediately where I can perform a sql injection attack. So, the question is, is there a way to achieve the same results without the construction of the dynamic sql? or am I going to have to be super, super careful in my client code?
You have to use dynamic sql if you don't know the table name up front. But yes, you should validate the value before attempting to use it in an SQL statement.
e.g.
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=#metaInfoTable)
BEGIN
-- Execute the SELECT * FROM #metaInfoTable dynamic sql
END
This will make sure a table with that name exists. There is an overhead to doing this obviously as you're querying INFORMATION_SCHEMA. You could instead validate the #metaInfoTable contains only certain characters:
-- only run dynamic sql if table name value contains 0-9,a-z,A-Z, underscores or spaces (enclose table name in square brackets, in case it does contain spaces)
IF NOT #metaInfoTable LIKE '%^[0-9a-zA-Z_ ]%'
BEGIN
-- Execute the SELECT * FROM #metaInfoTable dynamic sql
END
Given the constraints described, I'd suggest 2 ways, with slight variations in performance an architecture.
Choose At the Client & Re-Architect
I'd suggest that you should consider a small re-architecture as much as possible to force the caller/client to decide which table to get its data from. It's a code smell to hold table names in another table.
I am taking an assumption here that #MetaInfoID is being passed from a webapp, data access block, etc. That's where the logic of which table to perform the SELECT on should be housed. I'd say that the client should know which stored procedure (GetCustomers or GetProducts) to call based on that #MetaInfoID. Create new method in your DAL like GetCustomersMetaInfo() and GetProductsMetaInfo() and GetInvoicesMetaInfo() which call into their appropriate sprocs (with no dynamic SQL needed, and no maintenance of a meta table in the DB).
Perhaps try to re-architect the system a little bit.
In SQL Server
If you absolutely have to do this lookup in the DB, and depending on the number of tables that you have, you could perform a handful of IF statements (as many as needed) like:
IF #MetaInfoID = 1
SELECT * FROM Customers
IF #MetaInfoID =2
SELECT * FROM Products
-- etc
That would probably become to be a nightmare to maintain.
Perhaps you could write a stored procedure for each MetaInfo. In this way, you gain the advantage of pre-compilation, and no SQL injection can occur here. (imagine if someone sabotaged the MetaInfoTableName column)
IF #MetaInfoID = 1
EXEC GetAllCustomers
IF #MetaInfoID = 2
EXEC GetAllProducts

Resources