How to use isnull() in a varchar query sql server - sql-server

I build a query within a variable of type varchar I want to make test with isnull()
example:
declare #sql varchar(max)
set #sql = '
Select top (100) id
FROM RIGHT R inner join RIGHT_TYPE RT on
R.RIGHT_TYPE_CODE = RT.CODE
WHERE R.RIGHT_TYPE_CODE = isnull('+#rightType+', R.RIGHT_TYPE_CODE)
'
exec (#sql)
go
#rightType is a parameter of my stored procedure.
The problem that when I have #rightType equals to null nothings works
Thank you.

To have optional parameters in dynamic SQL, this is usually the best option:
DECLARE #rightType nvarchar(50)
DECLARE #sql nvarchar(max);
set #sql = '
Select top (100) id
FROM RIGHT R inner join RIGHT_TYPE RT on
R.RIGHT_TYPE_CODE = RT.CODE
WHERE 1=1'
if (#rightType is not NULL) set #sql = #sql + ' and R.RIGHT_TYPE_CODE = #rightType'
EXEC sp_executesql #sql, N'#rightType nvarchar(50)', #rightType
This way you're not concatenating the input string with the SQL, so there's no chance of SQL injection happening, and the optimizer likes this a lot more, because the parameter is in the SQL only when it's actually given.
The 1=1 is in the SQL so that you can append any number (or none) criteria to the SQL without having to worry to have correct amount of ands.

Related

How to pass list of table names to a stored procedure in SQL Server?

I'm trying to fetch the data in a specific table name by passing tableName as a parameter to the stored procedure.
CREATE PROCEDURE schemaName.spDynamicTableName
#tableName NVARCHAR(100)
AS
BEGIN
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT * FROM ' + #tableName
EXECUTE sp_executesql #sql
END;
--> EXEC schemaName.spDynamicTableName 'Employee';
Now, how can I pass list of table names to a procedure so that procedure will iterate over the list of table names and fetch the data from all the tables?
Ok, let's start off with the problems you have in your current set up. Firstly it sounds like you have a design flaw here. Most likely you are using a table's name to infer information that should be in a column. For example perhaps you have different tables for each client. In such a scenario the client's name should be a column in a singular table. This makes querying your data significantly easier and allows for good use for key constraints as well.
Next, your procedure. This is a huge security hole. The value of your dynamic object is not sanitised nor validated meaning that someone (malicious) has almost 100 characters to mess with your instance and inject SQL into it. There are many articles out there that explain how to inject securely (including by myself), and I'm going to cover a couple of processes here.
Note that, as per my original paragraph, you likely really have a design flaw, and so that is the real solution here. We can't address that in the answers here though, as we have no details of the data you are dealing with.
Fixing the injection
Injecting Securely
The basic's of injecting a dynamic object name is to make it secure. You do that by using QUOTENAME; it both delimit identifies the object name and escapes any needed characters. For example QUOTENAME(N'MyTable') would return an nvarchar with the value [MyTable] and QUOTENAME(N'My Alias"; SELECT * FROM sys.tables','"') would return the nvarchar value "My Alias""; SELECT U FROM sys.tables".
Validating the value
You can easily validate a value by checking that the object actually exists. I prefer to do this with the sys objects, so something like this would work:
SELECT #SchemaName = s.[name],
#TableName = t.[name]
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.[name] = #Schema --This is a parameter
AND t.[name] = #Table; --This is a parameter
As a result, if the FROM returns no values, then the 2 variables in the SELECT won't have a value assigned and no SQL will be run (as {String} + NULL = NULL).
The Solution
Table Type Parameter
So, to allow for multiple tables, we need a table type parameter. I would create one with both the schema and table name in the columns, but we can default the schema name.
CREATE TYPE dbo.Objects AS table (SchemaName sysname DEFAULT N'dbo',
TableName sysname); --sysname is a sysnonym for nvarchar(128) NOT NULL
And you can DECLARE and INSERT into the TYPE as follows:
DECLARE #Objects dbo.Objects;
INSERT INTO #Objects (TableName)
VALUES(N'test');
Creating the dynamic statement
Assuming you are using a supported version of SQL Server, you'll have access to STRING_AGG; this removes any kind of looping from the procedure, which is great for performance. If you're using a version only in extended support, then use the "old" FOR XML PATH method.
This means you can take the values and create a dynamic statement along the lines of the below:
SET #SQL = (SELECT STRING_AGG(N'SELECT * FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';',' ')
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN #Objects O ON s.name = O.SchemaName
AND t.name = O.TableName);
The Stored Proecure
Putting all this together, this will give you a procedure that would look like this:
CREATE PROC schemaName.spDynamicTableName #Objects dbo.Objects AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = (SELECT STRING_AGG(N'SELECT N' + QUOTENAME(t.[name],'''') + N',* FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';',#CRLF) --I also inject the table's name as a column
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN #Objects O ON s.name = O.SchemaName
AND t.name = O.TableName);
EXEC sys.sp_executesql #SQL;
END;
And then you would execute it along the lines of:
DECLARE #Objects dbo.Objects;
INSERT INTO #Objects (SchemaName,TableName)
VALUES(N'dbo',N'MyTable'),
(N'dbo',N'AnotherTable');
EXEC schemaName.spDynamicTableName #Objects;
This one accepts a comma delimited list of tables and guards against SQL injection with a simple QUOTENAME escape (not sure if this is quite enough though):
IF OBJECT_ID('dbo.spDynamicTableName') IS NOT NULL DROP PROC dbo.spDynamicTableName
GO
/*
EXEC dbo.spDynamicTableName 'Students,Robert--
DROP TABLE Students'
*/
CREATE PROC dbo.spDynamicTableName
#tableName NVARCHAR(100)
AS
BEGIN
DECLARE #sql nvarchar(max)
SELECT #sql = STRING_AGG('SELECT * FROM ' + QUOTENAME(value), ';')
FROM STRING_SPLIT(#tableName, ',')
--PRINT #sql
EXEC dbo.sp_executesql #sql
END;
GO
There are two ways you can do this: use a string that contains the names you want and are separated by a special character as:
Table1, Table2, Table3
and split it in the stored procedure (check this)
The second method: make a typo as follows:
CREATE TYPE [dbo].[StringList] AS TABLE
(
[TableName] [NVARCHAR(50)] NULL
)
Add a parameter for your stored procedure as StringList:
CREATE PROCEDURE schemaName.spDynamicTableName
#TableNames [dbo].[StringList] READONLY,
AS
BEGIN
END;
Then measure its length using the following code and make a repeat loop::
DECLARE #Counter INT
DECLARE #TableCount INT
SELECT #TableCount = Count(*), #Counter = 0 FROM #TableNames
WHILE #Counter < #TableCount
BEGIN
SELECT #TableName = Name
FROM #TableNames
ORDER BY Name
OFFSET #Counter ROWS FETCH NEXT 1 ROWS ONLY
SET #sql = 'SELECT * FROM ' + #TableName
EXECUTE sp_executesql #sql
SET #Counter = #Counter + 1
END

sp_executesql with always true condition

i have been using dynamic queries in sql-server like:
declare #sql nvarchar (1000),#condition nvarchar(100)='';
set #sql=N'select * from tablename where (0=0)'+#condition+'';
exec(#sql)
by this i was able to get my result whether the #condition has any value or not.
But i got to know that sp_executesql is better then exec as it promote query plan reuse.
So, i tried my query with `sp_executesql,
set #sql =N'select * from dbo.testclient where (0=0) #condition'
exec sp_executesql #sql,N'#condition nvarchar(100)',#condition
but it failed with an error as
Incorrect syntax near '#condition'.
My problem is how can i make the above query to work with sp_executesql where the parameter #condition can be a condition or blank (' ') and what am i doing wrong.
When you use variables such as #condition in sp_executesql, it does not simply replace your variable in the sql string with the content of the variable.
What happens is that the variable is bound to the supplied value and the sql statement is untouched.
This all means that you need to create a full sql statement which uses variables if you want to leverage query plan reuse.
For example:
SET #byIDsql = 'select * from tableName where (0=0) and Id = #Id'
SET #byNameSQL = 'select * from tableName where (0=0) and FirstName = #FirstName'
Then you can use sp_executesql to supply the value of #id and #firstName and get query plan reuse.
exec sp_executesql #byIDsql, N'#ID INT', 15
Tested code,
DECLARE #Sql nvarchar(MAX)='',
#paramlist nvarchar(4000),
#id int=3
set #paramlist = N' #id1 int'
set #Sql='select * from test where id=#id1'
exec sp_executesql #Sql,#paramlist,#id
select #Sql

Variable table name in select statement

I have some tables for storing different file information, like thumbs, images, datasheets, ...
I'm writing a stored procedure to retrieve filename of a specific ID. something like:
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
if #table='images'
select [filename] from images
where id = #id
if #table='icons'
select [filename] from icons
where id = #id
....
How can I rewrite this procedure using case when statement or should I just use table name as variable?
You can't use case .. when to switch between a table in the FROM clause (like you can in a conditional ORDER BY). i.e. so the following:
select * from
case when 1=1
then t1
else t2
end;
won't work.
So you'll need to use dynamic SQL. It's best to parameterize the query as far as possible, for example the #id value can be parameterized:
-- Validate #table is E ['images', 'icons', ... other valid names here]
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'select [filename] from **TABLE** where id = #id';
SET #sql = REPLACE(#sql, '**TABLE**', #table);
sp_executesql #sql, N'#id INT', #id = #id;
As with all dynamic Sql, note that unparameterized values which are substituted into the query (like #table), make the query vulnerable to Sql Injection attacks. As a result, I would suggest that you ensure that #table comes from a trusted source, or better still, the value of #table is compared to a white list of permissable tables prior to execution of the query.
Just build SQL string in another variable and EXECUTE it
DECLARE #sql AS NCHAR(500)
SET #sql=
'SELECT [filename] '+
' FROM '+#table+
' WHERE id = #id'
EXECUTE(#sql)
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
DECLARE #SQL nvarchar(max);
SET #SQL = 'select [filename] from ' + #table + ' where id = ' + #id
EXECUTE (#SQL)

Using temp table with exec #sql in stored procedure

I have a stored procedure and part of them as below:
#DRange is a incoming varchar value
declare #sql varchar(max)
set #sql = 'select * into #tmpA from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
exec (#sql)
select * from #tmpA
The problem is when I execute the stored procedure, an error message occurs:
Cannot find the object "#tmpA" because it does not exist or you do not have permissions.
Is it not possible to use temp table and execute it or did I do something wrong?
#tmpA is created in a different scope, so is not visible outside of the dynamic SQL. You can just make the ultimate SELECT a part of the dynamic SQL. Also a couple of other things:
Always use the schema prefix when creating/referencing objects
Always use sp_executesql for dynamic SQL; in this case it allows you to parameterize the #DRange value and avoid SQL injection risks.
Always prefix Unicode strings with N - Unicode is required for sp_executesql but if you get lazy about this in other areas of your code it can also lead to painful implicit conversions.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'select * into #tmpA from dbo.TableA
where create_date >= DATEADD(DAY, -#DRange, GETDATE())
AND is_enabled = 1; SELECT * FROM #tmpA';
EXEC sp_executesql #sql, N'#DRange INT', #DRange;
Of course if all you're doing is selecting, I have a hard time understanding why this is dynamic SQL in the first place. I assume your query (or what you later do with the temp table) is more complicated than this - if so, don't dumb it down for us. Telling us your whole problem will prevent a lot of back and forth, as the additional details could change the answer.
Here's what I'd do.
declare #sql varchar(max)
set #sql = 'select * from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
Select * Into #tmpA from TableA where create_date = '01/01/1000' -- to create a blank table
insert into #tmpA
exec (#sql)
select * from #tmpA

Variable for Column name in Scalar-Valued Function

How do I use a variable for Column name in Scalar-Valued Function?
CREATE FUNCTION [dbo].[GetValue]
(
#ID varchar(36),
#PreprtyName varchar(150)
)
RETURNS varchar(250)
AS
BEGIN
DECLARE #RetVal varchar(MAX)
DECLARE #SQL varchar(MAX)
SET #RetVal =''
SET #SQL = 'SET #RetVal = (Select '+ #PreprtyName + ' FROM TableMedia WHERE ID = '''+ #ID +')'''
exec #SQL
SET #RetVal = #RetVal
RETURN #RetVal
END
Getting error Could not find "Could not find stored procedure"
Here is what I'm trying to avoid.
SELECT pr.ProductID, tManufacturerImage.Image, tMediaCenter.ManualFileName,tMediaCenter.BrochureFileName, tMediaCenter.AssemblyFileName
FROM tMediaCenter RIGHT OUTER JOIN
tProduct AS pr INNER JOIN
tmp_dmi AS dmi ON REPLACE(REPLACE(pr.SKU, 'ACS', ''), 'DMI', '') = RTRIM(LTRIM(dmi.pri_SKU)) ON tMediaCenter.ProductID = pr.ProductID LEFT OUTER JOIN
tManufacturer INNER JOIN
tManufacturerImage ON tManufacturer.ManufacturerID = tManufacturerImage.ManufacturerID ON pr.ManufacturerID = tManufacturer.ManufacturerID
WHERE (pr.ManufacturerID = 'f35fc01680-4938-4070-a367-38c31efb01f') AND (dmi.MAP IS NULL) AND (pr.ParentID <> '')
this does not work for me.
You can't. A user-defined function does not support dynamic SQL. You will need to do this with a stored procedure instead, or better define your ultimate goal instead of telling us you need to solve it with dynamic SQL in a function.
CREATE PROCEDURE [dbo].[GetValues]
#PreprtyName VARCHAR(150)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT ' + #PreprtyName + ' FROM dbo.Table;';
EXEC sp_executesql #sql;
END
GO
You won't have an easy time doing this inline in your query, sorry. The issue is that there is no way to do this from a function, and there is no way to call a stored procedure for each row in a single query. You could construct a loop and everything else but I think the above procedure fits your requirement without all that extra work and overhead.
If you need to limit it to a certain set of ID values, you'll need to describe how you're trying to do that now.

Resources