SQL Server Full-Text search fails when searching more than one word - stored-procedures

Symptoms:
Searching for a single word (i.e. "Snap") works
Searching for another word contained in the same field (i.e. "On") also works
Searching for "Snap On" at the same time returns 0 results, even though it shouldn't.
The setup:
SQL Server 2008 R2 with Advanced Features
nopCommerce 3.0
Things I have done:
I added the Product.MetaKeywords column to the full text search catalog
I added a bit into the Stored Procedure that performs the search to search through the MetaKeywords
Now the nopCommerce boards are fairly slow, but I'm positive the problem is within the SQL Stored Procedure anyway, so I figured I would ask for some SQL Server help here, even if you aren't familiar with the nopCommerce web app, you may have some information you can help me with.
The stored procedure in question is too large to post entirely here, but basically it dynamically adds "OR" or "AND" in between the keyword searches to generate the phrase used in a Contains clause. It selects through several unions various searchable fields by using Contains.
Here is the bit I added into the stored procedure
SET #sql = #sql + '
UNION
SELECT p.Id
FROM Product p with (NOLOCK)
WHERE '
IF #UseFullTextSearch = 1
SET #sql = #sql + 'CONTAINS(p.[MetaKeywords], #Keywords) '
ELSE
SET #sql = #sql + 'PATINDEX(#Keywords, p.[MetaKeywords]) > 0 '
#Keywords, at this point, if I am reading the procedure correctly, has a value of: "Snap* AND On*"
I don't understand why my query of "Snap On" returns 0 results, but "Snap" and "On" individually work fine.
The minimum search length is set to 1, so it's not that.
I should add that searching for "Snap* OR On*" works, but I cannot use OR because then searching for "Snap On" will also return "Snap Dragon" and other unrelated things.
--EDIT--
The problem wasn't any of that. I got some advice elsewhere and the problem was actually the stoplist. I managed to fix my issue simply by changing the stoplist on the product table from <system> to <off>.
To do this, follow these steps.
browse to your table in SQL Server management studio
Right click on the table and select "Full-Text Index"
Select "Properties" under "Full-Text Index"
In the "General" Tab, change "Full-Text Index Stoplist" to <off>
I had to do it this way because I was unable to get the transact SQL to work. It kept telling me there was no such object as the table I was attempting to modify. If anyone can provide any insight on how the Alter fulltext index statement works, I'm interested, because I was following the example on the MSDN page to the T and it just kept telling me there was no such object named Product.

The asterisk is not a plain old wildcard. If you are using it anywhere other than at the end of a search term, you're probably not using it correctly. See answers to a similar question
SQL Contains Question
In your case, each search term must be quoted separately. See this example from the docs http://technet.microsoft.com/en-us/library/ms187787(v=sql.90).aspx
SELECT Name
FROM Production.Product
WHERE CONTAINS(Name, '"chain*" OR "full*"');

Related

Having trouble with my NVL function

So I followed the directions that I was given to me in my live lecture word for word and also researched this in my textbook and I've done everything (So I think) properly but I keep getting a error Which I have looked up to see what it means and it didn't help me at all and I've been working on it for a whole day so I'm reaching out to you guys to see if you can see my mistake anywheres in my code
This is the question that I am trying to complete:
Using the BOOK_CUSTOMER table and the NVL function, create a query that will return a list containing the customer number, first name, last name, and the characters ‘NOT REFERRED’ if the customer was not referred by another customer. Give the derived column an alias of REFERRED BY. Do not list any customers that were referred by another customer.
MY CODE =
SELECT CutomerID, FirstName, LastName,
NVL(TO_CHAR(Referred), 'Not Referred'))
FROM Book_Customer;
I also realize that I haven't completed the whole question. I'm just trying to get my NVL to work first and then go onto the Alias and the last part of the question because I do not know how to do either of those yet, any tips on that would be greatly appreciated as well
There's an extra (unmatched) closing paren in your SQL text, and that's going to throw a syntax error.
NVL(TO_CHAR(Referred), 'Not Referred'))
^
There's no matching opening paren for that last paren.
To assign an alias to an expression in the SELECT list, follow the expression with the keyword AS and an alias.
SELECT t.foo AS bar
The resultset will contain a column named bar.
SELECT b.CustomerID
, b.FirstName
, b.LastName
, NVL(TO_CHAR(b.Referred),'Not Referred') AS ReferredBy
FROM Book_Customer b
WHERE b.Referred IS NULL
I finally figured it out! The proper code was:
SELECT CustomerID, FirstName, LastName,
NVL(TO_CHAR(Referred), 'Not Referred') AS "Referred By"
FROM Book_Customer
WHERE Referred IS NULL;
Thank you everyone for all your helpful comments that helped me figure out this problem.

Ascending sort order Index versus descending sort order index when performing OrderBy

I am working on an asp.net mvc web application, and I am using Sql server 2008 R2 + Entity framework.
Now on the sql server I have added a unique index on any column that might be ordered by . for example I have created a unique index on the Sql server on the Tag colum and I have defined that the sort order for the index to be Ascending. Now I have some queries inside my application that order the tag ascending while other queries order the Tag descending, as follow:-
LatestTechnology = tms.Technologies.Where(a=> !a.IsDeleted && a.IsCompleted).OrderByDescending(a => a.Tag).Take(pagesize).ToList(),;
TechnologyList = tms.Technologies.Where(a=> !a.IsDeleted && a.IsCompleted).OrderBy (a => a.Tag).Take(pagesize).ToList();
So my question is whether the two OrderByDescending(a => a.Tag). & OrderBy(a => a.Tag), can benefit from the asending unique index on the sql server on the Tag colum ? or I should define two unique indexes on the sql server one with ascending sort order while the other index with decedning sort order ?
THanks
EDIT
the following query :-
LatestTechnology = tms.Technologies.Where(a=> !a.IsDeleted && a.IsCompleted).OrderByDescending(a => a.Tag).Take(pagesize).ToList();
will generate the following sql statement as mentioned by the sql server profiler :-
SELECT TOP (15)
[Extent1].[TechnologyID] AS [TechnologyID],
[Extent1].[Tag] AS [Tag],
[Extent1].[IsDeleted] AS [IsDeleted],
[Extent1].[timestamp] AS [timestamp],
[Extent1].[TypeID] AS [TypeID],
[Extent1].[StartDate] AS [StartDate],
[Extent1].[IT360ID] AS [IT360ID],
[Extent1].[IsCompleted] AS [IsCompleted]
FROM [dbo].[Technology] AS [Extent1]
WHERE ([Extent1].[IsDeleted] <> cast(1 as bit)) AND ([Extent1].[IsCompleted] = 1)
ORDER BY [Extent1].[Tag] DESC
To answer your question:
So my question is whether the two OrderByDescending(a => a.Tag). &
OrderBy(a => a.Tag), can benefit from the asending unique index on the
sql server on the Tag colum ?
Yes, SQL Server can read an index in both directions: as in index definition or in the exact opposite direction.
However, from your intro I suspect that you still have a wrong impression how indexing works for order by. If you have both, a where clause and an order by clause, you must make sure to have a single index that covers both clauses! It does not help to have on index for the where clause (like on isDeleted and isCompleted — whatever that is in your example) and another index on tag. You need to have a single index that first has the columns of the where clause followed by the columns of the order by clause (multi-column index).
It can be tricky to make it work correctly, but it's worth the effort especially if your are only fetching the first few rows (like in your example).
If it doesn't work out right away, please have a look at this:
http://use-the-index-luke.com/sql/sorting-grouping/indexed-order-by
It is generally best to show the actual SQL query—not the .NET source code—when asking for performance advice. Then I could tell you which index to create exactly. At the moment I'm unsure about isDeleted and isCompleted — are these table columns or expressions that evaluate upon other columns?
EDIT (after you added the SQL query)
There are two ways to make your query work as indexed top-n query:
http://sqlfiddle.com/#!6/260fb/4
The first option is a regular index on the columns from the where clause followed by those from the order by clause. However, as you query uses this filter IsDeleted <> cast(1 as bit) it cannot use the index in a order-preserving way. If, however, you re-phrase the query so that it reads like this IsDeleted = cast(0 as bit) then it works. Please look at the fiddle, I've prepared everything there. Yes, SQL Server could be smart enough to know that, but it seems like it isn't.
I don't know how to tweak EF to produce the query in the above described way, sorry.
However, there is a second option using a so called filtered index — that is an index that only contains a sub-set of the table rows. It's also in the SQL Fiddle. Here it is important that you add the where clause to the index definition in the very same way as it appears in your query.
In both ways it still works if you change DESC to ASC.
The important part is that the execution plan doesn't show a sort operation. You can also verify this in SQL Fiddle (click on 'View execution plan').

Returning a semi-unique set of most recent records

In my application a User has Highlights.
Each Highlight has a HighlightType. So if I run user.highlights I might see an output like this:
Notice that there are many highlights of type_id 47. This marks milestones of the number of times the user has gone running.
What I would like to do is return this full list of records, but only include one highlight for each highlight_type, and I want that one record to be the most recent record (in this case the "50th run" highlight). So in the example above I would get the same results but with IDs 195-199 removed.
Is there an efficient way to accomplish this?
I don't think there is an easy or clean way to achieve that, nor a "Rails way". Look at e.g. this link
According to one suggestion in that link you would do this SQL request:
SELECT h1.*
FROM highlights h1
LEFT JOIN highlights h2
ON (h1.user_id = h2.user_id
AND h1.highlight_type_id = h2.highlight_type_id
AND h1.created_at < h2.created_at)
WHERE h2.id IS NULL AND h1.user_id = <the user id you are interested in>
group by h1.highlight_type_id
I think it will be some performance problem if you have big tables maybe, an it not so very clean I think.
Otherwise, if there isn't so much highlights for a user I would have done something like this:
rows = {}
user.highlights.order('highlight_type_id, created_at DESC').each do |hi|
rows[hi.highlight_type_id] ||= hi
end
# then use rows which will have one object for each highlight_type_id
The DESC on created_at is important
EDIT:
I also saw some suggestions based on this
user.highlights.group('highlight_type_id').order('created_at DESC')
And that was also how I first thought it should be solved, but I tested it and it doesn't seems to get a correct result - at least on my test data.

How do I use TADOQuery.Parameters with integer parameter types that have to be put in two or more places in a query?

I have a complex query that contains more than one place where the same primary key value must be substituted. It looks like this:
select Foo.Id,
Foo.BearBaitId,
Foo.LinkType,
Foo.BugId,
Foo.GooNum,
Foo.WorkOrderId,
(case when Goo.ZenID is null or Goo.ZenID=0 then
IsNull(dbo.EmptyToNull(Bar.FanName),dbo.EmptyToNull(Bar.BazName))+' '+Bar.Strength else
'#'+BarZen.Description end) as Description,
Foo.Init,
Foo.DateCreated,
Foo.DateChanged,
Bug.LastName,
Bug.FirstName,
Goo.BarID,
(case when Goo.ZenID is null or Goo.ZenID=0 then
IsNull(dbo.EmptyToNull(Bar.BazName),dbo.EmptyToNull(Bar.FanName))+' '+Bar.Strength else
'#'+BarZen.Description end) as BazName,
GooTracking.Status as GooTrackingStatus
from
Foo
inner join Bug on (Foo.BugId=Bug.Id)
inner join Goo on (Foo.GooNum=Goo.GooNum)
left join Bar on (Bar.Id=Goo.BarID)
left join BarZen on (Goo.ZenID=BarZen.ID)
inner join GooTracking on(Goo.GooNum=GooTracking.GooNum )
where (BearBaitId = :aBaitid)
UNION
select Foo.Id,
Foo.BearBaitId,
Foo.LinkType,
Foo.BugId,
Foo.GooNum,
Foo.WorkOrderId,
Foo.Description,
Foo.Init,
Foo.DateCreated,
Foo.DateChanged,
Bug.LastName,
Bug.FirstName,
0,
NULL,
0
from Foo
inner join Bug on (Foo.BugId=Bug.Id)
where (LinkType=0) and (BearBaitId= :aBaitid )
order by BearBaitId,LinkType desc, GooNum
When I try to use an integer parameter on this non-trivial query, it seems impossible to me. I get this error:
Error
Incorrect syntax near ':'.
The query works fine if I take out the :aBaitid and substitute a literal 1.
Is there something else I can do to this query above? When I test with simple tests like this:
select * from foo where id = :anid
These simple cases work fine. The component is TADOQuery, and it works fine until you add any :parameters to the SQL string.
Update: when I use the following code at runtime, the parameter substitutions are actually done (some glitch in the ADO components is worked around) and a different error surfaces:
adoFooContentQuery.Parameters.FindParam('aBaitId').Value := 1;
adoFooContentQuery.Active := true;
Now the error changes to:
Incorrect syntax near the keyword 'inner''.
Note again, that this error goes away if I simply stop using the parameter substitution feature.
Update2: The accepted answer suggests I have to find two different copies of the parameter with the same name, which bothered me so I reworked the query like this:
DECLARE #aVar int;
SET #aVar = :aBaitid;
SELECT ....(long query here)
Then I used #aVar throughout the script where needed, to avoid the repeated use of :aBaitId. (If the number of times the parameter value is used changes, I don't want to have to find all parameters matching a name, and replace them).
I suppose a helper-function like this would be fine too: SetAllParamsNamed(aQuery:TAdoQuery; aName:String;aValue:Variant)
FindParam only finds one parameter, while you have two with the same name. Delphi dataset adds each parameter as a separate one to its collection of parameters.
It should work if you loop through all parameters, check if the name matches, and set the value of each one that matches, although I normally choose to give each same parameter a follow-up number to distingish between them.

dbExpress design question

Does anybody know (or care to make a suppostion as to) why TSqlDataset has a commandtext property (string) whereas TSqlQuery has a sql property (tstrings)?
Consider the sql statement
select id, name from
table
order by name
If I use a TSqlQuery, then I can change the table name in the query dynamically by accessing sql[1], but if I am using a TSqlDataset (as I have to if I need a bidrectional dataset, the dataset is connected to a provider and thence to a tclientdataset), I have to set the commandtext string literally. Whilst the above example is trivial, it can be a problem when the sql statement is much more involved.
Update:
Judging by the comments and answers so far, it seems that I was misunderstood. I don't care very much for improving the runtime performance of the components (what does one millisecond matter when the query takes one second) but I do care about the programmer (ie me) and the ability to maintain the program. In real life, I have the following query which is stored in a TSqlQuery:
select dockets.id, dockets.opendate, customers.name, statuses.statname,
dockets.totalcost, dockets.whopays, dockets.expected, dockets.urgent,
(dockets.totalcost - dockets.billed) as openbill,
(dockets.totalcost - dockets.paid) as opencost,
location.name as locname, dockets.attention,
statuses.colour, statuses.disporder, statuses.future, dockets.urgcomment
from location, statuses, dockets left join customers
on dockets.customer = customers.id
where dockets.location = location.id
and dockets.status = statuses.id
I haven't counted the number of characters in the string, but I'm sure that there are more than 255, thus precluding storing the query in a simple string. In certain circumstances, I want to filter the amount of data being displayed by adding the line 'and statuses.id = 3' or 'and customers.id = 249'. If the query were stored as TStrings, then I could add to the basic query the dummy line 'and 1 = 1', and then update this line as needed. But the query is one long string and I can't easily access the end of it.
What I am currently doing (in lieu of a better solution) is creating another TSqlDataSet, and setting its commandtext to the default TSqlDataSet's commandtext whilst appending the extra condition.
1) TSQLQuery is rather for compatibility with BDE TQuery. And BDE TQuery has SQL: TStrings property. TSQLDataSet is what supposed to be used for new applications.
2) Although SQL: TStrings is usefull for some tasks, it is also error prone. Often programmers forget to clear SQL property before filling again. Also if your query is a big one, the filling of SQL may lead to performance degradation. Because on each SQL.Add(...) call dbExpress code parses query when ParamCheck is True. That may be solved by using BeginUpdate / EndUpdate or setting ParamCheck to False. But note, setting ParamCheck to False stops automatic parameters creation.
SQLQuery1.SQL.BeginUpdate;
try
SQLQuery1.SQL.Clear;
SQLQuery1.SQL.Add('SELECT * FROM');
SQLQuery1.SQL.Add('Orders');
finally
SQLQuery1.SQL.EndUpdate;
end;
CommandText does not have such issues.
3) You can use Format function for building a dynamic SQL string:
var
sTableName: String;
...
sTableName := 'Orders';
SQLDataSet1.CommandText := Format('select * from %s', [sTableName]);
4) Other data access libraries, like AnyDAC, have macro variables, simplifying dynamic query text building. For example:
ADQuery1.SQL.Text := 'SELECT * FROM &TabName';
ADQuery1.Macros[0].AsRaw := 'Orders';
ADQuery1.Open;
I would have to say that the TSqlQuery uses TStrings (TWideStrings in Delphi 2010) because it is much more flexible.
Suppose your query was:
Select
Item1,
Item2,
Item3,
Item4
FROM MyTable
It's a lot easier to read
You can copy and paste into an external query tool and it stays formatted
It's easy to comment out sections
Select
Item1,
/*
Item2,
Item3,
*/
Item4
FROM MyTable
You can easily add items
Select
Item1,
Item2,
Item2a,
Item2b,
Item3,
Item3a,
Item3b,
Item4
FROM MyTable
Try doing that to a contiguous set of characters that goes on forever in one long line with no line breaks inside an edit window that is always to small for viewing that doesn't allow for wrapped text etc. etc. etc.
Just $0.02.

Resources