Generate dynamic PATINDEX statement - sql-server

I have the following two strings:
DECLARE #Str1 VARCHAR(MAX) = 'John A Mak|Street Road UAE'
DECLARE #Str2 VARCHAR(MAX) = '[First Name],[Last Name],[Middle Name]|[Address1],[Address2]'
Note: Both strings are dynamic may comes with more or less values.
Expected result: Want to if any of the given text present in the given columns using the PATINDEX. The following PATINDEX statement gonna used in the WHERE clause of the SELECT statement.
PATINDEX('John',[First Name]) + PATINDEX('A',[First Name]) + PATINDEX('Mak',[First Name]) +
PATINDEX('John',[Last Name]) + PATINDEX('A',[Last Name]) + PATINDEX('Mak',[Last Name]) +
PATINDEX('John',[Middle Name]) + PATINDEX('A',[Middle Name]) + PATINDEX('Mak',[Middle Name]) +
PATINDEX('Street',[Address1]) + PATINDEX('Road',[Address1]) + PATINDEX('UAE',[Address1]) +
PATINDEX('Street',[Address2]) + PATINDEX('Road',[Address2]) + PATINDEX('UAE',[Address2]) > 0
My try:
DECLARE #Str1 VARCHAR(MAX) = 'John A Mak|Street Road UAE'
DECLARE #Str2 VARCHAR(MAX) = '[First Name],[Last Name],[Middle Name]|[Address1],[Address2]'
DECLARE #Length int = 0
DECLARE #Length1 int = 0
DECLARE #Length2 int = 0
DECLARE #Position int = 0
DECLARE #Position1 int = 0
DECLARE #Position2 int = 0
DECLARE #Value varchar(max)
DECLARE #Value1 varchar(max)
DECLARE #Value2 varchar(max)
DECLARE #P_Str2 VARCHAR(MAX) = ''
DECLARE #P_Str3 VARCHAR(MAX) = ''
DECLARE #P_Str1 VARCHAR(MAX) = ''
DECLARE #FinalString VARCHAR(MAX) = ''
SET #P_Str1 = #Str1+'|';
SET #P_Str3 = #Str2+'|';
IF OBJECT_ID('tempdb..#tempt', 'U') IS NOT NULL DROP TABLE #tempt;
CREATE TABLE #tempt(keywords varchar(max));
WHILE CHARINDEX('|', #P_Str3, #Position2+1)>0
BEGIN
set #Length2 = CHARINDEX('|', #P_Str3, #Position2+1) - #Position2
set #Value2 = SUBSTRING(#P_Str3, #Position2, #Length2)
SET #P_Str2 = #Value2+',';
PRINT('--'+#P_Str2);
--WHILE LOOP for creating string for PAT INDEX
WHILE CHARINDEX(',', #P_Str2, #Position+1)>0
BEGIN
set #Length = CHARINDEX(',', #P_Str2, #Position+1) - #Position
set #Value = SUBSTRING(#P_Str2, #Position, #Length)
WHILE CHARINDEX('|', #P_Str1, #Position1+1)>0
BEGIN
set #Length1 = CHARINDEX('|', #P_Str1, #Position1+1) - #Position1
set #Value1 = SUBSTRING(#P_Str1, #Position1, #Length1)
PRINT('Value1--'+#Value1);
PRINT('Value--'+#Value);
INSERT INTO #tempt
SELECT DISTINCT split.a.value('.', 'VARCHAR(100)') AS Keywords
FROM
(
SELECT CAST ('<S>' + REPLACE(ltrim(rtrim(#Value1)), ' ', '</S><S>') + '</S>' AS XML) AS Element
) AS a
CROSS APPLY Element.nodes ('/S') AS split(a)
WHERE split.a.value('.', 'VARCHAR(100)') <> '';
SET #FinalString += STUFF(( SELECT '(PATINDEX('''+keywords+''','+#Value+'),''''0'''') + '
FROM #tempt FOR XML PATH('')), 1,0, '');
DELETE FROM #tempt;
SET #Position1 = CHARINDEX('|', #P_Str1, #Position1+#Length1) +1
END
SET #Position = CHARINDEX(',', #P_Str2, #Position+#Length) +1
END
SET #Position2 = CHARINDEX('|', #P_Str3, #Position2+#Length2) +1
END
PRINT(#FinalString);
But unable to get the expected result.

This is not pretty dynamic SQL, however...
USE Sandbox;
DECLARE #Str1 VARCHAR(MAX) = 'John A Mak|Street Road UAE';
DECLARE #Str2 VARCHAR(MAX) = '[First Name],[Last Name],[Middle Name]|[Address1],[Address2]';
DECLARE #WHERE nvarchar(MAX);
SET #WHERE = STUFF((SELECT N' + ' + NCHAR(10) +
STUFF((SELECT N' + ' +NCHAR(10)+
N'PATINDEX(' + QUOTENAME(DSn.Item,'''') + N',' + DSc.Item + N')' --This trusts no injection.I don't like this.
FROM dbo.DelimitedSplit8K(DS1.Item,' ') DSn
CROSS APPLY dbo.DelimitedSplit8K(DS2.Item,',') DSc
ORDER BY DSc.ItemNumber, DSn.ItemNumber
FOR XML PATH(N'')),1,4,N'')
FROM dbo.DelimitedSplit8K (#Str1,'|') DS1
CROSS APPLY dbo.DelimitedSplit8K (#Str2,'|') DS2
WHERE DS1.ItemNumber = DS2.ItemNumber
ORDER BY DS1.ItemNumber
FOR XML PATH(N'')),1,4,N'') + N' > 0'
SELECT #WHERE;
This outputs:
PATINDEX('John',[First Name]) +
PATINDEX('A',[First Name]) +
PATINDEX('Mak',[First Name]) +
PATINDEX('John',[Last Name]) +
PATINDEX('A',[Last Name]) +
PATINDEX('Mak',[Last Name]) +
PATINDEX('John',[Middle Name]) +
PATINDEX('A',[Middle Name]) +
PATINDEX('Mak',[Middle Name]) +
PATINDEX('Street',[Address1]) +
PATINDEX('Road',[Address1]) +
PATINDEX('UAE',[Address1]) +
PATINDEX('Street',[Address2]) +
PATINDEX('Road',[Address2]) +
PATINDEX('UAE',[Address2]) > 0
Note the use of DelimitedSplit8k, which you'll need on your instance to get this done.
Edit/Note: This is not injection safe. Specifically because of + DSc.Item +. The OP, in their sample data, provides already quoted strings; it is therefore assumed that the strings are properly quoted; I.e. not "[" & ColumnName & "]" (that is still open to injection, as any ] passed won't be escaped). If the columnnames are not properly quoted elsewhere, I strongly suggest removing the brackets ([]) in the passed value and using + QUOTENAME(DSc.Item) + instead.

Related

SQL Server - string separation with length cutoff

So I have a string that is from search and it can contain multiple words I want to cutoff the words at 10 characters i.e.
DECLARE #SearchString varchar(255) = 'Administration Duplication'
becomes: 'Administra Duplicatio'
From here with few modifications:
Create Function dbo.[getFirstTenCharacters]
(
#String Varchar(Max)
)
RETURNS Varchar(Max)
BEGIN
Declare #Xml Xml
Declare #firsttenletter Varchar(Max)
Declare #delimiter Varchar(5)
SET #delimiter=' '
SET #Xml = cast(('<a>'+replace(#String,#delimiter,'</a><a>')+'</a>') AS XML)
;With CTE AS (SELECT A.value('.', 'varchar(max)') as [Column]
FROM #Xml.nodes('a') AS FN(a) )
SELECT #firsttenletter =Stuff((SELECT ' ' + LEFT([Column],10)
FROM CTE
FOR XML PATH('') ),1,0,'')
RETURN (#firsttenletter)
END
GO
SELECT dbo.[getFirstTenCharacters]('Administration Duplication');
WORKING DEMO
A rather simpler version would look something like.....
DECLARE #SearchString Varchar(255) = 'Administration Duplication'
;WITH X AS (
SELECT Split.a.value('.', 'VARCHAR(10)') Words
FROM
(SELECT Cast ('<X>' +
Replace(#SearchString, ' ', '</X><X>') + '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a)
)
SELECT STUFF((SELECT ' ' + Words
FROM X
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'')
Create a function to split the strings.
should look something like this:
CREATE FUNCTION [dbo].[SPLIT_STRING](
#TEXT varchar(8000)
,#COLUMN int
,#SEPARATOR varchar(1)
)RETURNS varchar(8000)
AS
BEGIN
DECLARE #POS_START int = 1
DECLARE #POS_END int = CHARINDEX(#SEPARATOR, #TEXT, #POS_START)
WHILE (#COLUMN >1 AND #POS_END> 0)
BEGIN
SET #POS_START = #POS_END + 1
SET #POS_END = CHARINDEX(#SEPARATOR, #TEXT, #POS_START)
SET #COLUMN = #COLUMN - 1
END
IF #COLUMN > 1 SET #POS_START = LEN(#TEXT) + 1
IF #POS_END = 0 SET #POS_END = LEN(#TEXT) + 1
RETURN SUBSTRING (#TEXT, #POS_START, #POS_END - #POS_START)
END
THEN DO:
SELECT LEFT([dbo].[SPLIT_STRING](COLNAME,1,' '),10)+LEFT([dbo].[SPLIT_STRING](COLNAME,2,' '),10)

Split multiple string's into multiple columns

I have the following three different strings which needs to split into three different columns.
Example:
String 1:
Declare #str1 varchar(max) = 'A1,A2,A3'
String 2:
Declare #str2 varchar(max) = 'B1,B2,B3'
String 3:
Declare #str2 varchar(max) = 'C1,C2,C3'
NoteI want to store the above three strings into three different columns.
Expected Output:
colA colB colC
------------------
A1 B1 C1
A2 B2 C2
A3 B3 C3
Attempt:
SQL Fiddle: http://sqlfiddle.com/#!3/d41d8/41345
I know its a bit heavy but it will work
Declare #str1 varchar(max) = 'A1,A2,A3'
Declare #str2 varchar(max) = 'B1,B2,B3'
Declare #str3 varchar(max) = 'C1,C2,C3'
DECLARE #RowCount TINYINT
DECLARE #i TINYINT = 0
DECLARE #Table AS TABLE
(
colA varchar(MAX)
,ColB varchar(MAX)
,ColC varchar(MAX)
)
SET #RowCount = len(#str1) - len(replace(#str1, ',', ''))
WHILE(#i<=#RowCount)
BEGIN
INSERT INTO #Table
SELECT LEFT(#str1,CHARINDEX(',',#str1+',',0)-1) AS colA
,LEFT(#str2,CHARINDEX(',',#str2+',',0)-1) AS colB
,LEFT(#str3,CHARINDEX(',',#str3+',',0)-1) AS colC
SET #str1 = STUFF(#str1,1,CHARINDEX(',',#str1,0),'')
SET #str2 = STUFF(#str2,1,CHARINDEX(',',#str2,0),'')
SET #str3 = STUFF(#str3,1,CHARINDEX(',',#str3,0),'')
SET #i = #i + 1
END
SELECT * FROM #Table
If you have atmost three values then try this.
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3'
SELECT Parsename(Replace(#str1, ',', '.'), 3) 'FST_COL',
Parsename(Replace(#str1, ',', '.'), 2) 'SCD_COL',
Parsename(Replace(#str1, ',', '.'), 1) 'TRD_COL' into #temp
Or
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3',
#sql NVARCHAR(max),
#loop INT,
#cnt INT=1
SELECT #loop = Len(#str1) - Len(Replace(#str1, ',', '')) + 1
SET #sql=' WITH Split_cols ( xmlcol)
AS (SELECT CONVERT(XML, ''<cols><col>''
+ Replace('''
+ #str1 + ''', '','', ''</col><col>'') + ''</col></cols>'') as xmlcol)
SELECT '
WHILE #cnt <= #loop
BEGIN
SET #sql+=' xmlcol.value(''/cols[1]/col['
+ CONVERT(VARCHAR(30), #cnt)
+ ']'', ''varchar(100)'') AS col'
+ CONVERT(VARCHAR(30), #cnt) + ','
SET #cnt=#cnt + 1
END
SET #sql=LEFT(#sql, Len(#sql) - 1)
SET #sql +=' FROM Split_cols '
--PRINT #sql
EXEC Sp_executesql #sql
THIS WILL WORK WITH ANY NUMBER OF STRINGS AND VALUES NOT HARDCODED
CREATE FUNCTION dbo.splitstring (#stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([ID] INT IDENTITY(1,1),[Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
-- USE THIS PARAMETER TO PASS VALUE
DECLARE #STRING VARCHAR(MAX)
DECLARE #COUNT INT
DECLARE #I INT
DECLARE #COLUMNNAME VARCHAR(MAX)
DECLARE #CREATETABLE VARCHAR(MAX)
DECLARE #INSERT VARCHAR(MAX)
IF OBJECT_ID('TEMPDB..##TEMPTABLE') IS NOT NULL
DROP TABLE ##TEMPTABLE
IF OBJECT_ID('TEMPDB..##RETURNLIST') IS NOT NULL
DROP TABLE ##RETURNLIST
SELECT * INTO ##RETURNLIST FROM dbo.splitstring(#STRING)
select #COUNT = COUNT(*) from ##RETURNLIST
SET #I=0
SET #CREATETABLE = 'CREATE TABLE ##TEMPTABLE ('
WHILE (#COUNT>0)
BEGIN
SET #COLUMNNAME = 'COLUMN'+ CONVERT(varchar(10), #I) + ' VARCHAR(MAX)'
SET #CREATETABLE = #CREATETABLE + #COLUMNNAME
IF(#COUNT<>1)
SET #CREATETABLE = #CREATETABLE + ', '
SET #I = #I+1
SET #COUNT = #COUNT -1;
END
SET #CREATETABLE = #CREATETABLE + ' )'
EXECUTE(#CREATETABLE)
SET #INSERT = 'INSERT INTO ##TEMPTABLE VALUES( '
WHILE (#I>0)
BEGIN
SET #INSERT = #INSERT +''''+ (SELECT NAME FROM ##RETURNLIST WHERE ID = #COUNT+1) +''''
IF(#I<>1)
SET #INSERT = #INSERT + ', '
SET #I = #I-1
SET #COUNT = #COUNT +1;
ENDenter code here
SET #INSERT = #INSERT + ' )'
EXECUTE(#INSERT)
EXECUTE('SELECT * FROM ##TEMPTABLE')
You haven't given any criteria for joining the values together for each of the rows of columns so I've just joined them in descending order. This solution can handle any number of items in the lists but if the number gets too high you may need to use the MAX RECURSION query hint.
DECLARE #strA NVARCHAR(MAX) = 'A1,A2,A3';
DECLARE #strB NVARCHAR(MAX) = 'B1,B2,B3,B4';
DECLARE #strC NVARCHAR(MAX) = 'C1,C2,C3';
WITH stringData AS (
--each group of comma separate values with a group identifier
SELECT 'a' AS grp, #strA AS strng
UNION ALL
SELECT 'b' AS grp, #strB
UNION ALL
SELECT 'c' AS grp, #strC
),
splitStrings AS (
--a recursive CTE to split the comma separated values
SELECT grp, CAST('' AS NVARCHAR(MAX)) AS item,
strng AS cText
FROM stringData
UNION ALL
SELECT grp,
CASE
WHEN CHARINDEX(N',',cText,0)>0 THEN LEFT(cText,CHARINDEX(N',',cText,0)-1) --SUBSTRING(cText,0,CHARINDEX(N',',cText,0))
ELSE cText
END,
RIGHT(cText,LEN(cText)-CHARINDEX(N',',cText,0))
FROM splitStrings
WHERE cText!=item
)
SELECT grp,
item,
ROW_NUMBER() OVER(PARTITION BY grp ORDER BY item) AS rnum
INTO #stringValues --put the results in a temp table so we don't need to execute the recursive CTE more than once
FROM splitStrings
WHERE len(item)>0;
DECLARE #maxNum INT = (SELECT MAX(rnum) FROM #stringValues);
--join the values together
WITH allNums AS (
SELECT 1 AS num
UNION ALL
SELECT num+1
FROM allNums
WHERE num<#maxNum
)
SELECT sa.item AS colA,
sb.item AS colB,
sc.item AS colC
FROM allNums
LEFT JOIN #stringValues AS sa ON sa.rnum=allNums.num AND sa.grp='A'
LEFT JOIN #stringValues AS sb ON sb.rnum=allNums.num AND sb.grp='B'
LEFT JOIN #stringValues AS sc ON sc.rnum=allNums.num AND sc.grp='C'
DROP TABLE #stringValues;

SQL Server 2012 Dynamic Query calling from Functions return

I have a scalar-valued [return type nvarchar] function that returns a dynamic Query String.
Let my Function is like following …
CREATE FUNCTION ABC
(
)
RETURNS nvarchar(MAX)
AS
BEGIN
return 'Select * from Table1'
END
I want to Execute it like following …
Select * from dbo.ABC()
Or
Select * from EXEC(dbo.ABC())
Is That possible in SQL Server 2012?
I have to do it without using openquery.
-- My Actual Function --
ALTER FUNCTION [dbo].[RowToColumn]
(
-- Add the parameters for the function here
)
RETURNS nvarchar(MAX)
AS
BEGIN
DECLARE #PunchList nvarchar(MAX)
DECLARE #PunchListTOP nvarchar(MAX)
DECLARE #SQL nvarchar(MAX)
DECLARE #Flag INT
SET #PunchList = ''
SET #PunchListTOP = ''
DECLARE #I INT
SET #Flag = (select MAX(X) as MMAX from (select Employee_ID,Date_Of_Working, count(Date_Of_Working) as X from DataInOneRowStep3 group by Date_Of_Working ,Employee_ID ) A)
Set #I =1
WHILE (#I<= #Flag)
BEGIN
if (#PunchList = '' )
BEGIN
Set #PunchList = 'Punch_' + CONVERT(varchar, #I)
Set #PunchListTOP = ' MAX(Punch_' + CONVERT(varchar, #I) +') Punch_' + CONVERT(varchar, #I)
END
else
BEGIN
Set #PunchList = #PunchList + ',' + 'Punch_' + CONVERT(varchar, #I)
Set #PunchListTOP =#PunchListTOP +','+ ' MAX(Punch_' + CONVERT(varchar, #I) +') Punch_' + CONVERT(varchar, #I)
END
SET #I = #I + 1
END
SET #SQL ='SELECT Employee_Id,Date_OF_Working,Shift_Id,'+#PunchListTOP+' FROM (
SELECT * from DataInOneRowStep4
) as s
PIVOT
(
MAX(EntryTime)
FOR Punch IN ('+#PunchList+')
)AS piv group by Employee_Id,Date_OF_Working,Shift_Id'
RETURN #SQL
END

Conversion failed when converting date and/or time from character string?

I use pivot table but when I execute I got this error please help me I am stuck on this thanks in advance sorry if any mistake and please also tell me that how to handle string in string in sql server I use " '' '',''-'' " but not sure it is correct or not please help me as soon as posible here are the query
SELECT #SQL = 'SELECT '
WHile #I <= #Months
BEGIN
SET #YearMonth = REPLACE(LTRIM(SUBSTRING(CONVERT(VARCHAR, DateAdd(Month, #I, #StartDate), 13), 3,9)), ' ', '-')
SET #ColumnText = #YearMonth
SET #SQL = #SQL + 'ISNULL([' + #YearMonth + '], 0.00) as ''' + #ColumnText + ''','
SET #I = #I +1
END
Set #I = 0
SET #SQL = #SQL + 'Product,Supplier,PackPrice,QuantityInPack,Code,SupplierID,Description,RebateAmount
FROM
(
SELECT p.NAME AS product,p.Code AS Code,P.Description as Description,oi.PackPrice,oi.QuantityInPack AS Packsize,S.ID AS SupplierID,s.[Name] AS Supplier,
CASE when oi.RebateType=2 then (oi.PackPrice*oi.RebateAmount)/100 ELSE oi.RebateAmount END AS RebateAmount ,o.Total as TotalSale,
REPLACE(LTRIM(SUBSTRING(CONVERT(VARCHAR, O.CreatedDate, 13), 3,9)), '' '',''-'') as tdate
FROM dbo.SCM_Product p WITH (NOLOCK)
INNER JOIN SCM_OrderItem oi WITH (NOLOCK) ON oi.ProductId = p.ID
LEFT OUTER JOIN dbo.SCM_Supplier s WITH(NoLock) ON oi.SupplierID=s.ID
LEFT OUTER JOIN SCM_Order o WITH (NOLOCK) ON o.Id = oi.ProductId AND O.Status <> 5 -- CANCELLED
AND O.Deleted = 0
WHERE ('''+#SupplierID+''' IS NULL OR s.ID IN (SELECT * FROM GetIDsTableFromIDsList(#SupplierID)))
AND ('''+#ProductID+''' IS NULL OR p.ID IN (SELECT * FROM GetIDsTableFromIDsList(#ProductID)) )
AND (o.CreatedDate >= '''+ dbo.GetDatePart(#StartDate) +''' AND o.CreatedDate <= '''+ dbo.GetDatePart(#Enddate) +''')
GROUP BY REPLACE(LTRIM(SUBSTRING(CONVERT(VARCHAR, o.CreatedDate, 13), 3,9)), '' '',''-''),
s.ID,p.Code,p.NAME,s.NAME,P.Description,oi.packPrice,oi.QuantityInPack,oi.RebateAmount,oi.RebateType,oi.PackPrice,o.Total
) SD
PIVOT
( SUM( SD.TotalSale) FOR SD.tdate IN ('
WHile #I <= #Months
BEGIN
SET #YearMonth = REPLACE(LTRIM(SUBSTRING(CONVERT(VARCHAR, DateAdd(Month, #I, #StartDate), 13), 3,9)), ' ', '-')
IF #I = #Months
BEGIN
SET #SQL = #SQL + ' [' + #YearMonth + ']'
END
ELSE
BEGIN
SET #SQL = #SQL + ' [' + #YearMonth + '],'
END
SET #I = #I +1
END
SET #SQL = #SQL + ')) as pvt
ORDER BY Supplier '
--EXEC(#SQL)
PRINT #SQL
The easiest way of doing it would be to use QUOTENAME() function,because you could easily miss quote in a large query.
Example:
DECLARE #name AS CHAR(10) = 'Revolver'
DECLARE #sql as NVARCHAR(300) = 'SELECT *
FROM Music.Album
WHERE Name ='+ QUOTENAME(#name,'''')
PRINT #sql;
or
DECLARE #name AS CHAR(10) = 'Revolver'
DECLARE #sql as NVARCHAR(300) = 'SELECT *
FROM Music.Album
WHERE Name ='''+ #name + ''''
PRINT #sql;

What are the dangers of dynamic SQL, and can they be avoided?

We've just been given the following code as a solution for a complicated search query in a new application provided by offshore developers. I'm skeptical of the use of dynamic SQL because I could close the SQL statement using '; and then excute a nasty that will be performed on the database!
Any ideas on how to fix the injection attack?
ALTER procedure [dbo].[SearchVenues] --'','',10,1,1,''
#selectedFeature as varchar(MAX),
#searchStr as varchar(100),
#pageCount as int,
#startIndex as int,
#searchId as int,
#venueName as varchar(100),
#range int,
#latitude varchar(100),
#longitude varchar(100),
#showAll int,
#OrderBy varchar(50),
#SearchOrder varchar(10)
AS
DECLARE #sqlRowNum as varchar(max)
DECLARE #sqlRowNumWhere as varchar(max)
DECLARE #withFunction as varchar(max)
DECLARE #withFunction1 as varchar(max)
DECLARE #endIndex as int
SET #endIndex = #startIndex + #pageCount -1
SET #sqlRowNum = ' SELECT Row_Number() OVER (ORDER BY '
IF #OrderBy = 'Distance'
SET #sqlRowNum = #sqlRowNum + 'dbo.GeocodeDistanceMiles(Latitude,Longitude,' + #latitude + ',' + #longitude + ') ' +#SearchOrder
ELSE
SET #sqlRowNum = #sqlRowNum + #OrderBy + ' '+ #SearchOrder
SET #sqlRowNum = #sqlRowNum + ' ) AS RowNumber,ID,RecordId,EliteStatus,Name,Description,
Address,TotalReviews,AverageFacilityRating,AverageServiceRating,Address1,Address2,Address3,Address4,Address5,Address6,PhoneNumber,
visitCount,referalCount,requestCount,imgUrl,Latitude,Longitude,
Convert(decimal(10,2),dbo.GeocodeDistanceMiles(Latitude,Longitude,' + #latitude + ',' + #longitude + ')) as distance
FROM VenueAllData '
SET #sqlRowNumWhere = 'where Enabled=1 and EliteStatus <> 3 '
--PRINT('#sqlRowNum ='+#sqlRowNum)
IF #searchStr <> ''
BEGIN
IF (#searchId = 1) -- county search
BEGIN
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and Address5 like ''' + #searchStr + '%'''
END
ELSE IF(#searchId = 2 ) -- Town search
BEGIN
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and Address4 like ''' + #searchStr + '%'''
END
ELSE IF(#searchId = 3 ) -- postcode search
BEGIN
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and Address6 like ''' + #searchStr + '%'''
END
IF (#searchId = 4) -- Search By Name
BEGIN
IF #venueName <> ''
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and ( Name like ''%' + #venueName + '%'' OR Address like ''%'+ #venueName+'%'' ) '
ELSE
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and ( Name like ''%' + #searchStr + '%'' OR Address like ''%'+ #searchStr+'%'' ) '
END
END
IF #venueName <> '' AND #searchId <> 4
SET #sqlRowNumWhere = #sqlRowNumWhere + ' and ( Name like ''%' + #venueName + '%'' OR Address like ''%'+ #venueName+'%'' ) '
set #sqlRowNum = #sqlRowNum + ' ' + #sqlRowNumWhere
--PRINT(#sqlRowNum)
IF #selectedFeature <> ''
BEGIN
DECLARE #val1 varchar (255)
Declare #SQLAttributes varchar(max)
Set #SQLAttributes = ''
Declare #tempAttribute varchar(max)
Declare #AttrId int
while (#selectedFeature <> '')
BEGIN
SET #AttrId = CAST(SUBSTRING(#selectedFeature,1,CHARINDEX(',',#selectedFeature)-1) AS Int)
Select #tempAttribute = ColumnName from Attribute where id = #AttrId
SET #selectedFeature = SUBSTRING(#selectedFeature,len(#AttrId)+2,len(#selectedFeature))
SET #SQLAttributes = #SQLAttributes + ' ' + #tempAttribute + ' = 1 And '
END
Set #SQLAttributes = SUBSTRING(#SQLAttributes,0,LEN(#SQLAttributes)-3)
set #sqlRowNum = #sqlRowNum + ' and ID in (Select VenueId from '
set #sqlRowNum = #sqlRowNum + ' CachedVenueAttributes WHERE ' + #SQLAttributes + ') '
END
IF #showAll <> 1
set #sqlRowNum = #sqlRowNum + ' and dbo.GeocodeDistanceMiles(Latitude,Longitude,' + #latitude + ',' + #longitude + ') <= ' + convert(varchar,#range )
set #withFunction = 'WITH LogEntries AS (' + #sqlRowNum + ')
SELECT * FROM LogEntries WHERE RowNumber between '+ Convert(varchar,#startIndex) +
' and ' + Convert(varchar,#endIndex) + ' ORDER BY ' + #OrderBy + ' ' + #SearchOrder
print(#withFunction)
exec(#withFunction)
As an aside, I would not use EXEC; rather I would use sp_executesql. See this superb article, The Curse and Blessings of Dynamic SQL, for the reason and other info on using dynamic sql.
See this answer.
Also, these:
Am I immune to SQL injections if I use stored procedures?
Avoiding SQL Injection in SQL query with Like Operator using parameters?
Can I protect against SQL Injection by escaping single-quote and surrounding user input with single-quotes?
Here's an optimized version of the query above that doesn't use dynamic SQL...
Declare #selectedFeature as varchar(MAX),
#searchStr as varchar(100),
#pageCount as int,
#startIndex as int,
#searchId as int,
#venueName as varchar(100),
#range int,
#latitude varchar(100),
#longitude varchar(100),
#showAll int,
#OrderBy varchar(50),
#SearchOrder varchar(10)
Set #startIndex = 1
Set #pageCount = 50
Set #searchStr = 'e'
Set #searchId = 4
Set #OrderBy = 'Address1'
Set #showAll = 1
--Select dbo.GeocodeDistanceMiles(Latitude,Longitude,#latitude,#longitude)
DECLARE #endIndex int
SET #endIndex = #startIndex + #pageCount -1
;
WITH LogEntries as (
SELECT
Row_Number()
OVER (ORDER BY
CASE #OrderBy
WHEN 'Distance' THEN Cast(dbo.GeocodeDistanceMiles(Latitude,Longitude,#latitude,#longitude) as varchar(10))
WHEN 'Name' THEN Name
WHEN 'Address1' THEN Address1
WHEN 'RecordId' THEN Cast(RecordId as varchar(10))
WHEN 'EliteStatus' THEN Cast(EliteStatus as varchar(10))
END) AS RowNumber,
RecordId,EliteStatus,Name,Description,
Address,TotalReviews,AverageFacilityRating,AverageServiceRating,Address1,Address2,Address3,Address4,Address5,Address6,PhoneNumber,
visitCount,referalCount,requestCount,imgUrl,Latitude,Longitude,
Convert(decimal(10,2),dbo.GeocodeDistanceMiles(Latitude,Longitude,#latitude,#longitude)) as distance
FROM VenueAllData
where Enabled=1 and EliteStatus <> 3
And
(
(Address5 like #searchStr + '%' And #searchId = 1) OR
(Address4 like #searchStr + '%' And #searchId = 2) OR
(Address6 like #searchStr + '%' And #searchId = 3) OR
(
(
#searchId = 4 And
(Name like '%' + #venueName + '%' OR Address like '%'+ #searchStr+'%')
)
)
)
And
ID in (
Select VenueID
From CachedVenueAttributes
--Extra Where Clause for the processing of VenueAttributes using #selectedFeature
)
And
(
(#showAll = 1) Or
(#showAll <> 1 and dbo.GeocodeDistanceMiles(Latitude,Longitude,#latitude,#longitude) <= convert(varchar,#range ))
)
)
SELECT * FROM LogEntries
WHERE RowNumber between #startIndex and #endIndex
ORDER BY CASE #OrderBy
WHEN 'Distance' THEN Cast(Distance as varchar(10))
WHEN 'Name' THEN Name
WHEN 'Address1' THEN Address1
WHEN 'RecordId' THEN Cast(RecordId as varchar(10))
WHEN 'EliteStatus' THEN Cast(EliteStatus as varchar(10))
END
The only thing I haven't fixed is the selection from CachedVenueAttributes that seems to build up a where statement in a loop. I think I might put this in a table valued function, and refactor it in isolation to the rest of the procedure.
I like dynamic SQL for search.
Where I have used it in the past I have used .Net prepared statements with any user generated string being passed in as a parameter NOT included as text in the SQL.
To run with the existing solution you can do a number of thing to mitigate risk.
White list input, validate input so that it can only contain a-zA-Z0-9\w (alpha numerics and white space) (bad if you need to support unicode chars)
Execute any dynamic sql as a restricted user. Set owner of stored proc to a user which has only read access to the tables concerned. deny write to all tables ect. Also when calling this stored proc you may need to do it with a user with similar restrictions on what they can do, as it appares MS-SQL executes dynamic sql within a storedproc as the calling user not the owner of the storedproc.
I've realized that this is a really old post, but when doing things like:
AND
(
(#showAll = 1)
OR (#showAll <> 1
AND dbo.GeocodeDistanceMiles(Latitude,Longitude,#latitude,#longitude) <= convert(varchar,#range))
)
... an OPTION(RECOMPILE) will usually help pick a more concise plan, as long as it's not going to be executed a thousand times per second or anything.

Resources