开发者

How can I make this SQL function faster?

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 l开发者_如何学JAVAike '%' + @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:

CREATE FUNCTION [dbo].[fnGetRelevance] 
(   
    @fieldName nvarchar(50),
    @searchTerm nvarchar(50)
)

RETURNS  table
AS
select (case when @fieldName like @searchTerm + '%' then 0 else 1 end) as Value

(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:

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 (select Value from dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A')), sc.ShareClass_Name asc

I can't speak to how to change your NHibernate query to do this, but that's not your issue.


I guess your function can be equally rewritten like this:

CREATE FUNCTION [dbo].[fnGetRelevance] 
(   
    @fieldName nvarchar(50),
    @searchTerm nvarchar(50)
)

RETURNS  int
AS
BEGIN
    if (@fieldName like @searchTerm + '%') -- starts with
    begin       
        return 0
    end
    return 1
END

because you return 0 only when @fieldName starts with @searchTerm and 1 in all other cases.

And instead of calling function

order by dbo.fngetrelevance(sc.ShareClass_Name, 'Allianz RCM BRIC Stars A'), sc.ShareClass_Name asc

you might use the following:

order by 
    case when sc.ShareClass_Name like 'Allianz RCM BRIC Stars A%'
    then 0 else 1 end, 
    sc.ShareClass_Name asc


I am not sure how well you will be able to optimise this function.

The major problem here is the like %somethings% everywhere.

an ex-colleague described it best to me as you can find all the names beginning with E in the phone book easily but finding everyname with an E in it is a very long running process.

You are effectively negating your indexing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜