Background
I have the following function, which ranks results by how closely they match the value that was searched for:
CREATE FUNCTION [dbo].[fnGetRelevance]
(
@fieldName nvarchar(50),
@searchTerm nvarchar(50)
)
RETURNS int
AS
BEGIN
if (@fieldName like @searchTerm + '%') -- starts with
begin
return 0
end
else if ((@fieldName like '%' + @searchTerm + '%') and (@fieldName not like @searchTerm + '%')) -- contains, but doesn't start with
begin
return 1
end
return 1
END
So in the context of the following query (an NHibernate query), which is searching for share classes based on the search string Allianz RCM BRIC Stars A it finds 39 results and ranks them so that the one exactly matching that string is on top, and the rest are sorted alphabetically below it.
select top 50 *
from ShareManager.ShareClass sc
-- and a few other tables with an inner join and a left join
where (sc.ShareClass_Id in
(
/* filter by some business criteria which is a single
select statement that does 2 more inner joins */
)
and 1 = 1
and (sc.ShareClass_Name like '%Allianz%' /* @p11 */)
and (sc.ShareClass_Name like '%RCM%' /* @p12 */)
and (sc.ShareClass_Name like '%BRIC%' /* @p13 */)
and (sc.ShareClass_Name like '%Stars%' /* @p14 */)
and (sc.ShareClass_Name like '%A%' /* @p15 */)
order by dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc
Question
The problem I’m having is that dbo.fngetrelevance is causing my NHibernate queries to time out. I’ve tried extending the timeout but that’s not working, and I don’t think it’s really the issue anyway. When I remove the function it works as expected.
Is there a way on SQL Server 2008 to make this faster, or to implement the ranking with NHibernate in such a way that it will not timeout?
Supplementary Information
I expect someone might suggest that I reduce the number of joins. We’ve already been through a lot of optimisation in order to speed up these queries as much as possible. It would be a huge effort for us to figure out how to optimise any further, on the scale of modifying the overall schema. Unfortunately we’re not going to get the go-ahead for this at this stage of the game, (and for only 1 fund, as far as I can see at the moment)
For the record, this is how I’m using the function with NHibernate:
string querystring =
"select sc, sctr" +
" from ShareClass as sc" +
// joins to 2 other tables
" and (" + expressionTokenizer.ToResult("sc.Name") + ") "
+ this.AddShareClassOrder(order, "sc", "sctr", searchExpression);
var result = _session.CreateQuery(querystring)
.AddNameSearchCriteria(expressionTokenizer)
.AddDataUniverseParameters(dataUniverseHelper)
.SetFirstResult((pageSize * (pageNum - 1)))
.SetMaxResults(pageSize)
.List();
with AddShareClassOrder effectively returning
fieldName = string.Format("dbo.fngetrelevance({1}.{2}, '{0}'), {1}.{2}", textToSearchFor, shareClassPrefix, "Name");
return String.Format(" order by {0} {1}", fieldName, direction);
or, the following as it’s represented in the SQL:
dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc
I must disagree with the other answers posted here; the
%%use here is a red herring, as you’re not doing any filtering on this expression (in which case they’d most certainly be right). Your problem is your UDF; as it stands now, your UDF will not be inlined into the query. Instead, the query engine will take the entire result set, invoke the function for every single row, then capture those results to sort. You need to define and use your function in such a way that it will be inlined into the query.For more information, see this article, but the short version is to change your function to this:
(I eliminated your second condition as it seemed unnecessary, since it returned the same value as the fallback value. If that was a mistake, it should be fairly obvious how to modify the expression above to get what you want.)
Then use it like this:
I can’t speak to how to change your NHibernate query to do this, but that’s not your issue.