Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
GenNS
Starting Member
11 Posts |
Posted - 2010-10-26 : 12:41:13
|
Hi,Just to get it out of the way, I'm not very experienced with SQL.I have a web project that has a search function so that people can find a list of stores that sell a particular product that the user searches for.My query works, in that it returns the desired results, but as the database grows it's gotten painfully slow, and the database will ultimately be much bigger than it is even now.So I'm hoping somebody can help me by telling me how I should be doing this vs. how I'm doing it now.The three tables accessed in this query are:ItemsProds (about 100k records currently)GroupsProds (about 25k records)Stores (about 2500 records)Note that the joins are there because I only want to show stores that have at least one product listed. Also, the other elements in the WHERE clause are there because I only want to show stores whose accounts are active. The other parts of the WHERE clause obviously apply to the user's search, the city and the area.Here's the current query, which takes the text the user is searching for (@Phrase), the city that the search applies to (@City) and the suburb or city area (@Area) and returns the various fields related to the stores in the list.SELECT DISTINCT Stores.ID AS storeID, Stores.Name AS storeName, Stores.Logo,Stores.productID, Stores.datSubStart, Stores.Address1,Stores.Address2, Stores.City, Stores.State, Stores.Zip,Stores.Phone, Stores.URL, Stores.bitAccessible, Stores.bitOpenSunday,Stores.bitGreen, Stores.bitOutdoor, Stores.bitDelivery, Stores.ValetType,Stores.numLocations, Stores.PriceGroup, Stores.Rating, Stores.DescriptionFROM (ItemsProds INNER JOIN Stores ON ItemsProds.StoreID = Stores.ID)INNER JOIN GroupsProds ON ItemsProds.groupID = GroupsProds.IDWHERE CONTAINS(Stores.*, @Phrase)AND ItemsProds.Hide=0 AND Stores.bitActive<>0 AND Stores.CityID = @City AND Stores.NeighborhoodID = @AreaORDER BY Stores.productID DESC, Stores.Name In the web app, I'm also doing a COUNT version of this first that just returns the count, because I'm paginating the results. So between the count and the actual query to return the results, it's taking like 5 seconds every time I hit the page.I imagine that, while pretty straightforward, this is not a good way to do this query, so again, any help would be much appreciated.Thanks in advance!G |
|
yosiasz
Master Smack Fu Yak Hacker
1635 Posts |
Posted - 2010-10-26 : 16:18:51
|
do you have indexes on following fieldsItemsProds.StoreID Stores.IDItemsProds.groupID GroupsProds.IDStores.CityIDStores.NeighborhoodIDmake @Phrase nvarchar if it is not alreadyto name a fewIf you don't have the passion to help people, you have no passion |
 |
|
GenNS
Starting Member
11 Posts |
Posted - 2010-10-26 : 16:54:44
|
A couple of those indexes were missing, so thanks for reminding me of that.However, I added them, and it's still taking about 8 seconds for the page (in the web app) to load. @Phrase was already nvarchar.Now, if I execute the SP in Management Studio directly, it shows a time of 00:00:02 to return the 85 rows in the resulting set (is that 2 seconds or 2/10ths of a second?).So I guess I'm not sure if the issue is definitely with the query or if there's some logic in the page that's displaying the results that needs fixing. |
 |
|
yosiasz
Master Smack Fu Yak Hacker
1635 Posts |
Posted - 2010-10-26 : 17:00:03
|
sounds like there might be a bottle neck past the sql server. run a sql profiler and see what is happening under the hood while you are hitting the web page.If you don't have the passion to help people, you have no passion |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-10-27 : 01:18:54
|
I find CONTAINS is the bottleneck amongst clients that use it, and they find it difficult to scale it - so when the site gets busy then the Full Text Search becomes the limiting factor.If you are doing a COUNT(*) first you are doubling the amount of work - at the least get the results into a temporary table and use that for the COUNT and to JOIN to the tables for the result / paging.How many columns from Stores are in the Full Text Index?Try breaking this down into DECLARE @dtStart datetimeSET @dtStart = GetDate()SELECT Stores.ID AS [T_StoreID]INTO #TEMPFROM StoresWHERE CONTAINS(Stores.*, @Phrase)SELECT [Elapsed] = DATEDIFF(Millisecond, @dtStart, GetDate())SELECT [Count]=COUNT(*) FROM #TEMPand review the time that took, then review the time for:DECLARE @dtStart2 datetimeSET @dtStart2 = GetDate()SELECT DISTINCT Stores.ID AS storeID, Stores.Name AS storeName, Stores.Logo,Stores.productID, Stores.datSubStart, Stores.Address1,Stores.Address2, Stores.City, Stores.State, Stores.Zip,Stores.Phone, Stores.URL, Stores.bitAccessible, Stores.bitOpenSunday,Stores.bitGreen, Stores.bitOutdoor, Stores.bitDelivery, Stores.ValetType,Stores.numLocations, Stores.PriceGroup, Stores.Rating, Stores.DescriptionINTO #TEMP2FROM (ItemsProds INNER JOIN Stores ON ItemsProds.StoreID = Stores.ID)INNER JOIN GroupsProds ON ItemsProds.groupID = GroupsProds.IDINNER JOIN #TEMP ON T_StoreID = Stores.IDWHERE CONTAINS(Stores.*, @Phrase)AND ItemsProds.Hide=0 AND Stores.bitActive<>0 AND Stores.CityID = @City AND Stores.NeighborhoodID = @AreaORDER BY Stores.productID DESC, Stores.NameSELECT [Elapsed] = DATEDIFF(Millisecond, @dtStart2, GetDate())SELECT [Count]=COUNT(*) FROM #TEMP2I expect most of the time is in the first one. (Note that the second query is going to a temporary table, we don't want to time the transmission, and display time, at the front end, just the query time)The other problem that you have is using the DISTINCT. You are asking SQL to query a bunch of rows, and then sort them, and then throw away the duplicates. Run the second query again but without the DISTINCT. What's the difference in the COUNT?You are only selecting columns from STORES table, so don't join to any tables that will match multiple rows for a single store. Instead use a WHERE EXISTS on those table(s) if you only want stores that have at least one row in the [ItemsProds] table |
 |
|
GenNS
Starting Member
11 Posts |
Posted - 2010-10-27 : 09:42:15
|
Thanks so much, Kristen!Ok, so I created an SP based on your message and executed it in the Management Console. I also renamed Elapsed and Count to Elapsed1, Elapsed2, Count1 and Count2 just so I was sure that I was reading the results right.Here's what it came back with:For the first part, with the @Phrase searchElapsed1: 16Count1: 232For the second part, with the select of all the Stores fields and the Inner Joins:Elapsed2: 46Count2: 0Also, just FYI, @Phrase is usually something like FORMSOF(INFLECTIONAL, tent) or even FORMSOF(INFLECTIONAL, fishing) AND FORMSOF(INFLECTIONAL, pole) (not sure if that matters, but I thought I should mention it)Hmm. I just ran it again and now I got:Elapsed1: 16Count1: 232Elapsed2: 0Count2: 0I don't know if Elapsed2 is 0 this time because something was cached in SQL Server or what.But I also am not sure why Count2 is 0. I would think Count2 should be 123 (123 is the number of rows returned by the original query if I use the same value for @Phrase)I executed the original, then removed the # from the table names in your suggestion so that I could go in and check the IDs manually and they're correct, so again, I'm not sure why Count2 is 0. I thought maybe it's because in the final INNER JOIN there was no table name before T_StoreID, so I changed it to #TEMP.T_StoreID but that still yields 0.I did realize that the second INNER JOIN (with GroupsProds) was superfluous, though, and I've taken that one out. (but I ran it that way originally with the same results). I also tried reversing the order of the FROM / INNER JOIN elements but still 0 results in #TEMP2. |
 |
|
GenNS
Starting Member
11 Posts |
Posted - 2010-10-27 : 10:23:10
|
Oh, also, I forgot to mention that I looked up WHERE EXISTS (I haven't used that before) but I wasn't sure how to write it so that I'm checking to see which stores have at least one item, but also satisfying the other parts of the WHERE clause (checking to make sure the store is active, the items aren't all hidden, the city and area are correct).I think I got it though, and it's SO much faster, wow!I modified the original to this:FROM StoresWHERE CONTAINS(Stores.*, @Phrase)AND bitActive<>0 AND CityID = @CityAND EXISTS (SELECT ItemsProds.ID FROM ItemsProds WHERE ItemsProds.bitHide=0 AND ItemsProds.StoreID = Stores.ID) Making that change...removing the INNER JOINs and using EXISTS...has made it literally 8 times faster in real usage.Do you think it's still worth pursuing your original suggestion of separating out the CONTAINS (full-text) search and then pulling stores from the resulting IDs? It seems like a good idea, but I can't figure out why that join didn't work.Thanks again! |
 |
|
GilaMonster
Master Smack Fu Yak Hacker
4507 Posts |
Posted - 2010-10-27 : 10:27:57
|
Is the distinct really necessary? If not, remove it. It's an expensive operation.--Gail ShawSQL Server MVP |
 |
|
GenNS
Starting Member
11 Posts |
Posted - 2010-10-27 : 11:46:43
|
Yeah, I didn't think about that...with the JOINs gone, it's not really doing anything anyway. Thanks! |
 |
|
|
|
|
|
|