TSQL using a wildcard in a where clause with dynamic sql - sql

It appears that using the LIKE in a condition with wildcards and a variable inside of dynamic sql doesn't work, although it doesn't give an error. Here's an example.
The column called code has values like A0B01C02,A0B02C2D05,A0B02C2D05, etc and I am trying to match on rows containing a subset like 'B1'. When I do this it works and returns results as expected.
set #sql='select * from table where code like ''%B01%'''
exec sp_executesql #sql
If I hardcode the value of the variable
set #code='B01'
and modify the sql statement to concatenate the quotes and wildcards:
set #sql='select * from table where code like ' +''''+ '%'+#code + '%' + ''''
exec sp_executesql #sql
This returns the results as expected, but I had to hard code the variable. However, when I need to make the match using a variable for B01 and that the variable is set with a select statement, I don't get any results returned. I define an nvarchar like this:
set #code=(select top 1 code from anotherTable where USERID=#PersonId)
I confirmed that the select statement above returns the expected code, however. There is no error, but the query is "executed successfully". Am I missing something in the syntax for the where clause?

You can find a discussion of this at http://ask.sqlservercentral.com/questions/275/dynamic-where-clause-how-can-i-use-a-variable-in-an-in-predicate/312#312
My answer was to do the Parse By Comma Function.
/*****************************************************************
**** Parse A Comma Delimited String Into A Table
*****************************************************************/
ALTER FUNCTION [dbo].[ParseByComma] (
#String VARCHAR(600) )
RETURNS #TblSubString TABLE
(
VarSubString VARCHAR(50)
)
AS
BEGIN
DECLARE #intPos INT,
#SubStr VARCHAR(50)
-- Remove All Spaces
SET #String = REPLACE(#String, ' ','')
-- Find The First Comma
SET #IntPos = CHARINDEX(',', #String)
-- Loop Until There Is Nothing Left Of #String
WHILE #IntPos > 0
BEGIN
-- Extract The String
SET #SubStr = SUBSTRING(#String, 0, #IntPos)
-- Insert The String Into The Table
INSERT INTO #TblSubString (VarSubString) VALUES (#SubStr)
-- Remove The String & Comma Separator From The Original
SET #String = SUBSTRING(#String, LEN(#SubStr) + 2, LEN(#String) - LEN(#SubStr) + 1)
-- Get The New Index To The String
SET #IntPos = CHARINDEX(',', #String)
END
-- Return The Last One
INSERT INTO #TblSubString (VarSubString) VALUES (#String)
RETURN
END

I do not have a SQL Server in front of me at the moment but I wonder if this syntax might work for you?
set #sql='SELECT * FROM table WHERE UPPER(code) LIKE ''%''||(UPPER(COALESCE('''||#code||''',code)))||''%'' '
exec sp_executesql #sql
Best regards,
Kevin

Related

Generating filed name with concat

My table has column names m1,m2,m3...,m12.
I'm using iterator to select them and insert them one by one in another table.
In this iterator I'm trying to generate filed names with:
'['+concat('m',cast(#P_MONTH as nvarchar))+']'
where #P_MONTH is incrementing in each loop.
so for #P_MONTH = 1 this suppose to give [m1] which works fine.
But when I run query I get:
Conversion failed when converting the nvarchar value '[m1]' to data
type int.
And if I put simply [m1] in that select it works ok.
How to concat filed name so it can be actually interpreted as filed name from certain table?
EDIT
Here is full query:
DECLARE #SQLString nvarchar(500),
#P_YEAR int,
#P_MONTH int = 1
set #P_YEAR = 2018
WHILE #P_MONTH < 13
BEGIN
SET #SQLString =
'INSERT INTO [dbo].[MASTER_TABLE]
(sector,serial,
date, number, source)'+
'SELECT ' + '[SECTOR],[DEPARTMENT]' +
QUOTENAME(cast(CONVERT(datetime,CONVERT(VARCHAR(4),#P_YEAR)+RIGHT('0'+CONVERT(VARCHAR(2),#P_MONTH),2)+'01',5) as nvarchar))+
QUOTENAME ('M',cast(#P_MONTH as nvarchar)) +
'EMPLOYED' +
'FROM [dbo].[STATS]'+
'where YEAR= #P_YEAR'
EXECUTE sp_executesql #SQLString
SET #P_MONTH = #P_MONTH + 1
END
It's still not working. It executes successfully but it does nothing.
Good day,
Let's create a simple table for the sake of the explanation
DROP TABLE IF EXISTS T
GO
CREATE TABLE T(a1 INT)
GO
INSERT T(a1) VALUES (1),(2)
GO
SELECT a1 FROM T
GO
When we are using a query like bellow, the server parse the text as a value and not as a column name
DECLARE #String NVARCHAR(10)
SELECT #String = '1'
--
SELECT '['+concat('a',cast(#String as nvarchar))+']'
FROM T
GO
This mean that the result will be 2 rows with no name for the column and the value will be "[a1]"
Moreover, the above query uses the brackets as part of the string.
One simple solution is to use the function QUOTENAME in order to add brackets around a name.
Another issue in this approach is the optional risk of SQL Injection. QUOTENAME might not be perfect solution but can help in this as well.
If we need to use entities name dynamically like in this case the column name then for most cases using dynamic query is the best solution. This mean to use the Stored Procedure sp_executesql as bellow
DECLARE #String INT
SELECT #String = 1
DECLARE #SQLString nvarchar(500);
SET #SQLString =
'SELECT ' + QUOTENAME(concat('a',cast(#String as nvarchar))) + ' FROM T'
EXECUTE sp_executesql #SQLString
GO

Apply filter on sql query conditionally

Lets say I have a parameter #Name in a Stored Procedure. I want to filter by this parameter only if it is not empty / null. In any other case I want to ignore the filter.
I came up with the following two solutions. For the sake of example let us consider only the case that parameter is empty.
select *
from MyTable
where (len(rtrim(ltrim(#Name))) > 0 and Name = #Name) or (len(rtrim(ltrim(#Name))) = 0)
and the second one
#query = 'select * from MyTable'
if (len(rtrim(ltrim(#Name))) > 0)
#query = #query + ' Name = #Name '
Both of the approaches are working as expected.
Which do you think is the most clean (in terms of code) and easily maintainable
Are there any other (better) alternatives.
Note: This question may also suit in Code Review, please comment if you think so, in order to migrate there
It can be simplified like this
select *
from MyTable
where Name = #Name or #Name = '' or #Name is null
or as mentioned in comments, use NULLIF to check for empty string then replace it with NULL then validate it against IS NULL
where (Name = #Name or nullif(#Name, '') is null)
You don't need to check for length, by default, sql server is trailing-spaces-sensitive (The only exception to this rule is when the right side of the LIKE predicate's expression contains trailing spaces, then the pad is not removed).
Take the code below.
DECLARE #Name=' '
IF(#Name='') SELECT 1 ELSE SELECT 0
If you run the above code above you will get a result of 1. In your case, you can drop the LTRIM and RTRIM and simply test for equality against an empty string literal.
select *
from MyTable
where ((#Name='' OR #Name IS NULL)OR(Name = #Name))
OR
IF(#Name='') SET #Name=NULL
select *
from MyTable
where (#Name IS NULL OR Name = #Name)
if you are working with dynamic sql in stored procedure try something like this . It is better to use different variables for main select query and dynamic where query which can be extended easily . using this approach it will be easy to maintain when you proc becomes lengthy.
declare #finalquery varchar(max)
declare #mainSelectquery nvarchar(500);
declare #whereCondtions varchar (1000);
declare #DateParam datetime
set #mainSelectquery=''
set #whereCondtions =''
set #finalquery =''
set #DateParam=getdate()
set #mainSelectquery = 'select * from tblOrders where 1=1 '
set #whereCondtions = ' and Order_site =''TSN'''
set #whereCondtions = #whereCondtions + ' AND CAST(ORDER_APPROVED_DATE_LT AS DATE)=CAST(GETDATE() AS DATE)'
set #finalquery =( #mainSelectquery + #whereCondtions)
print #finalquery
---- You can further extend this by adding more where condition based on the parameter pass in stored proc
if (#OrderID !=0)
begin
set #whereCondtions = ' OrderID='+str ( #stateRefID )
end

declaring T-Sql parameter for comma delimited list of integers

I have a table that contains a list of performances. These performances are grouped by production number. What I am trying to do is create a stored procedure that will return the last performance for each production entered. I would like to be able to input the production ids as a list of ids. Below is my procedure so far. Difficulty is I'm not sure how best to declare the #prod_no parameter to be used in the IN statement.
CREATE PROCEDURE IP_MAX_PERF_DATE
-- Add the parameters for the stored procedure here
#prod_no
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT [prod_season_no], MAX([perf_dt]) As max_dt FROM [T_PERF] WHERE [prod_season_no] IN (#prod)
GROUP By [prod_season_no];
END
GO
Any ideas
Try the sp_executesql
CREATE PROCEDURE IP_MAX_PERF_DATE
#prod_no nvarchar(500)
AS
BEGIN
SET NOCOUNT ON;
declare #statement nvarchar(1000)
set #statement = N'SELECT [prod_season_no], MAX([perf_dt]) As max_dt FROM [T_PERF] WHERE [prod_season_no] IN (' + #prod_no + ') GROUP By [prod_season_no]'
EXEC sp_executesql
#stmt = #statement
END
GO
generally there are three ways to pass in a list of Ids:
Option 1: use comma separated list and split it in the stored procedure. this requires you to have a split function, or use dynamic sql (not preferred most of the time due to performance problem - at least hard to see the execution plan and you lose the point of using stored procedure to optimize your query)
Option 2: use xml, and again, you need to query the xml to find out the Ids
Option 3: use table valued parameter, this requires you to have a user defined table type
a detailed comparison could be found here:
http://www.adathedev.co.uk/2010/02/sql-server-2008-table-valued-parameters.html
This is what I've always done for passing in comma sepearted Integer IDs.
ALTER FUNCTION [dbo].[SplitArray]
(
#List varchar(500)
)
RETURNS
#ArrayValues table
(
ListID int
)
AS
BEGIN
DECLARE #ListID varchar(10), #Pos int
SET #List = LTRIM(RTRIM(#List))+ ','
SET #Pos = CHARINDEX(',', #List, 1)
IF REPLACE(#List, ',', '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #ListID = LTRIM(RTRIM(LEFT(#List, #Pos - 1)))
IF #ListID <> ''
BEGIN
INSERT INTO #ArrayValues (ListID)
VALUES (CAST(#ListID AS int)) --Use Appropriate conversion
END
SET #List = RIGHT(#List, LEN(#List) - #Pos)
SET #Pos = CHARINDEX(',', #List, 1)
END
END
RETURN
END
To use it, simply join it on your query like so:
Select a.* From Apples a Inner Join dbo.SplitArray(#IDList) array on a.AppleID = array.ListID

Passing SQL stored procedure entirety of WHERE clause

I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'

Dynamic SQL Comma-Delimited Value Query

[Update: Using SQL Server 2005]
Hi, what I want to do is query my stored procedure with a comma-delimited list of values (ids) to retrieve rows of data.
The problem I am receiving is a conversion error:
Conversion failed when converting the varchar value ' +
#PassedInIDs + ' to data type int.
The statement in my where-clause and error is:
...
AND (database.ID IN (' + #PassedInIDs + '))
Note: database.ID is of int type.
I was following the article at:
http://www.sql-server-helper.com/functions/comma-delimited-to-table.aspx
but did not complete because of the error.
In my execution script I have:
...
#PassedInIDs= '1,5'
Am I doing something wrong here?
Thank you for your help.
I would strongly suggest that you use the second method from that link. Create a user-defined function that turns your comma-delimited string into a table, which you can then select from easily.
If you do a Google on Erland and "Dynamic SQL" he has a good writeup of the pitfalls that it entails.
For one, you are passing a string to the IN function in SQL. If you look back at the original article, you'll see that instead of issuing a direct SQL statement, it instead is building a string which is the SQL statement to execute.
There is no string evaluation in SQL. This:
database.ID IN (' + #PassedInIDs + ')
will not be turned to:
database.ID IN (1,2,3)
just because the #PassedInIDs parameter happens to contain '1,2,3'. The parameter is not even looked at, because all you have is a string containing " + #PassedInIDs + ". Syntactically, this is equivalent to:
database.ID IN ('Bob')
To make it short, you can't do what you attempt here in SQL. But there are four other possibilities:
you construct the SQL string in the calling language and abandon the stored procedure altogether
you use a dynamic prepared statement with as many parameters in the IN clause as you pan to use
you use a fixed prepared statement with, say, 10 parameters: IN (?,?,?,?,?,?,?,?,?,?), filling only as many as you need, setting the others to NULL
you create a stored procedure with, say, 10 parameters and pass in as many as you need, setting the others to NULL: IN (#p1, #p2, ..., #p10).
I would create a CLR table-valued function:
http://msdn.microsoft.com/en-us/library/ms131103.aspx
In it, you would parse the string apart and perform a conversion to a set of rows. You can then join on the results of that table, or use IN to see if an id is in the list.
You need to treat ufn_CSVToTable like it's a table. So you can join the function:
JOIN ufn_CSVToTable(#PassedInIDs) uf ON database.ID = uf.[String]
I suggest using XML for this in SQL 2005. Somewhat bulkier, but it can be easier. It allows you to select the XML into a table which can then be joined or inserted etc.
Look at Sql Server's OPENXML() if you haven't already.
For example, you could pass in something like:
'12...'
and then use:
exec sp_xml_preparedocument #doc OUTPUT, #xmlParam
SELECT element
FROM OPENXML (#doc, 'Array/Value', 2) WITH (element varchar(max) 'text()')
That should be a start
this may be solved by 6 ways as mentioned in Narayana's article Passing a list/array to an SQL Server stored procedure
And my most strait forward implementation is
declare #statement nvarchar(256)
set #statement = 'select * from Persons where Persons.id in ('+ #PassedInIDs +')'
exec sp_executesql #statement
-
Here is what I have found and tested:
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE FUNCTION [dbo].[SplitStrings] ( #IDsList VARCHAR(MAX) )
RETURNS #IDsTable TABLE ( [ID] VARCHAR(MAX) )
AS
BEGIN
DECLARE #ID VARCHAR(MAX)
DECLARE #Pos VARCHAR(MAX)
SET #IDsList = LTRIM(RTRIM(#IDsList)) + ','
SET #Pos = CHARINDEX(',', #IDsList, 1)
IF REPLACE(#IDsList, ',', '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #ID = LTRIM(RTRIM(LEFT(#IDsList, #Pos - 1)))
IF #ID <> ''
BEGIN
INSERT INTO #IDsTable
( [ID] )
VALUES ( CAST(#ID AS VARCHAR) )
END
SET #IDsList = RIGHT(#IDsList, LEN(#IDsList) - #Pos)
SET #Pos = CHARINDEX(',', #IDsList, 1)
END
END
RETURN
END
GO
Here is how function Call:
SELECT * FROM dbo.SplitStrings('123,548,198,547,965')
Try this:
DECLARE #Ids varchar(50);
SET #Ids = '1,2,3,5,4,6,7,98,234';
SELECT *
FROM sometable
WHERE ','+#Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';