XMLpitstop.com   |  VBnetexpert.com   |  Community Credit  
 
 
Pitstop Search:  
in
 
Sign in | Join | Help
 
 
  Blog
    Home  
 
  Entries By Date
 
<November 2007>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678
 
 
  Blog Categories
   
 
  Archives
    December 2008 (1)  
    November 2008 (3)  
    September 2008 (3)  
    August 2008 (3)  
    June 2008 (4)  
    May 2008 (2)  
    April 2008 (3)  
    March 2008 (3)  
    February 2008 (5)  
    December 2007 (4)  
    November 2007 (1)  
    October 2007 (3)  
 
  Syndication
    RSS  
    Atom  
    Comments RSS  

Thursday, November 15, 2007 - Posts

  .NET Flea Market  
 

No, I won't use EXEC and you can't make me.

One time I was writing a dynamic search where the user could choose to search multiple fields. Typically you build a sql statement and add the search criteria in the where clause based on whatever fields the user is searching in. Well, that was my first attempt. Reality whacked me in the face when I dicovered that the users don't have direct table access permissions; everything has to be done through a stored procedure.

Now, I perceive a sort of religious war between people who use dynamic sql with EXEC statements in stored procs and those who do not believe in it. I fall into the latter camp. So out of stubborness, I set out to create a dynamic query that didn't use SQL EXEC. This is what I came up with.

First, the SP takes a @keywords parameter that contains comma-separated search terms (this is all AND searches). Then I have a bunch of flag parameters that indicate which fields to search in. The SP uses a function that I wrote and love called CSVToTable. It splits CSV values into a table that you can join to.

ALTER FUNCTION [dbo].[fn_CSVToTable] (@CSVList varchar(8000),@Delimiter char(1))  
RETURNS @csvtable table (val varchar(1000))
AS  
BEGIN 
-- variables for position marking
declare @separatorposition int, @arrayvalue varchar(1000)
-- Pad the list if needed
if substring(rtrim(@csvlist),len(rtrim(@csvlist)),1)<>@Delimiter
	set @csvlist = @csvlist + @Delimiter
-- Loop through string
while patindex('%'+@Delimiter+'%', @csvlist) <> 0
begin
	select @separatorPosition = patindex('%'+@Delimiter+'%', @csvlist) 
	select @arrayValue = left(@csvlist, @separatorPosition - 1)
	INSERT into @csvtable(val) values (rtrim(ltrim(@arrayValue)))
	
	select @csvlist = stuff(@csvlist,1,@separatorPosition, '')
end 
-- return table
return 
END

The magic happens when you join to the table of keywords. You do a join using a substring match while making sure the flag is set. Here's the query that can easily be converted to a stored proc. This is using Northwind, BTW.

declare @keywords varchar(2000),
@SearchFirstName bit,
@SearchLastName bit,
@SearchTitle bit,
@SearchAddress bit,
@SearchCity bit,
@SearchRegion bit,
@SearchCountry bit,
@SearchNotes bit

declare @t table(keyword varchar(50))

set @keywords='sales,full'
set @searchtitle=1
set @searchlastname=1

-- put the keywords into a table
insert @t 
select * from fn_CSVToTable(@keywords,',')

-- query the items
select employeeid,title,lastname,firstname,address,city,region,postalcode,country
from (
	select t.keyword,employeeid,title,lastname,firstname,address,city,region,postalcode,country
	from Employees e 
	join @t t on (@searchFirstName<>0 and patindex('%'+keyword+'%',e.FirstName)>0)
			or (@searchLastName<>0 and patindex('%'+keyword+'%',e.LastName)>0)
			or (@searchTitle<>0 and patindex('%'+keyword+'%',e.Title)>0)
			or (@searchAddress<>0  and patindex('%'+keyword+'%',e.Address)>0)
			or (@searchCity<>0  and patindex('%'+keyword+'%',e.city)>0)
			or (@searchRegion<>0  and patindex('%'+keyword+'%',e.region)>0)
			or (@searchCountry<>0  and patindex('%'+keyword+'%',e.Country)>0)
			or (@searchNotes<>0  and patindex('%'+keyword+'%',e.Notes)>0)
	) r
group by employeeid,title,lastname,firstname,address,city,region,postalcode,country
having count(keyword)>=(select count(*) from @t)-- we must match all the keywords
order by employeeid

So what this does is joins a row from the keyword temporary table for each searched row that matches. You may match one, two, or more rows, so your temporary result set will be at most: # of rows in searched query * # of keywords searched. File that under memory considerations if you want to implement this technique. Once that is built, it is filtered using the HAVING to make sure that all the keywords were matched somewhere.

What else can you do with this query? How about ranked results? Try changing the query to:

select count(keyword)/cast((select count(*) from @t) as float),employeeid,title,lastname,firstname,
address,city,region,postalcode,country
from (
	select t.keyword,employeeid,title,lastname,firstname,address,city,region,postalcode,country
	from Employees e 
	join @t t on (@searchFirstName<>0 and patindex('%'+keyword+'%',e.FirstName)>0)
			or (@searchLastName<>0 and patindex('%'+keyword+'%',e.LastName)>0)
			or (@searchTitle<>0 and patindex('%'+keyword+'%',e.Title)>0)
			or (@searchAddress<>0  and patindex('%'+keyword+'%',e.Address)>0)
			or (@searchCity<>0  and patindex('%'+keyword+'%',e.city)>0)
			or (@searchRegion<>0  and patindex('%'+keyword+'%',e.region)>0)
			or (@searchCountry<>0  and patindex('%'+keyword+'%',e.Country)>0)
			or (@searchNotes<>0  and patindex('%'+keyword+'%',e.Notes)>0)
	) r
group by employeeid,title,lastname,firstname,address,city,region,postalcode,country
order by count(keyword) desc

Now you have ranked matches with the percent matched.

That's really all I wanted to put out there - just a little expression of stubborness and probably a disregard of performance to illustrate a point

 
 
 

 
Copyright © . All Rights Reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems