The Cost of Function Use In A Where Clause

  • Yeaup, I'll be one of the first to agree and I don't think anyone will argue with that basic rule... my point is that Jeremy produced some code that's completely sargeable and it defies that basic rule... that's worth an experiment or two 🙂

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Ok, I've looked through all the posts here, but I don't see anyone bringing up the SOUNDEX / DIFFERENCE functions. We're currently using this to help "enhance" fuzzy searches in the database. Granted this will slow the performance but that's the trade off and it's not a heavily used procedure (only issued when the user specifically executes it via a button click).

    So, is there any way to enhance this?

    ALTER PROCEDURE [Company].[usp_get_Companies]

    (

    @Name VarChar(255) = Null

    )

    AS

    Set NoCount On

    Set Transaction Isolation Level Read Uncommitted

    Declare @SoundExName VarChar(255)

    Select

    @Name = Replace(Replace(IsNull(@Name, ''), ' ', '%') + '%', '%%', '%'),

    @SoundExName = Replace(Replace(Replace(Replace(Replace(Replace(@Name, ' ', ''), '.', ''), '[', ''), ']', ''), '/', ''), '\', '')

    /*

    NOTE:

    At this point the @Name should either be just '%' or '%My%Company%'.

    The latter probably not the most efficient but used to pick up more matches.

    @SoundExName is trying to optimize the name for better soundex matching

    TODO:

    Determine if there is a way to split the @Name into "words" and seach on the

    individual words.

    */

    Select

    C.ID,

    C.Name

    From [Company].[Companies] C

    Where

    (C.[Name] Like @Name)

    Or (

    (Difference(

    Replace(Replace(Replace(C.[Name], ' ', ''), '.', ''), '[', ''),

    @SoundExName

    ) = 4

    )

    )

    Set Transaction Isolation Level Read Committed

    Set NoCount Off

  • This is NOT what I would do, but depending on the size of your company table...its something different

    Declare @SoundExName varchar(50)

    Set @SoundExName = 'Smith, John'

    Select a.EmpId, Difference(a.AssociateNm, @SoundExName) Diff

    Into #Temp

    From Associate a

    Select EmpId from #Temp where diff = 4

  • If you want to go with Jeremy's suggestion, you can also throw an index on the Temp table to speed your searching. Though, it might not be worth it.

    Regardless, check your execution plans carefully every time you make a change so that you optimize for your configuration.

    Brandie Tarvin, MCITP Database AdministratorLiveJournal Blog: http://brandietarvin.livejournal.com/[/url]On LinkedIn!, Google+, and Twitter.Freelance Writer: ShadowrunLatchkeys: Nevermore, Latchkeys: The Bootleg War, and Latchkeys: Roscoes in the Night are now available on Nook and Kindle.

  • Hi,

    First of all thank you, it's a good point to be considered. In fact, I use functions on the WHERE clause very often, specially when filtering dates ... how do you optimize a query like this:

    SELECT SUM(Amount) FROM myEntries WHERE YEAR(myEntries.myDate) = @Year AND MONTH(myEntries.myDate) <= @Month

    Any clue? thank you

    regards

  • That's what I'm wondering, Ruben. I asked that in an earlier post and haven't seen a response yet.

    I can't think of a good way to optimize that one. I might be missing something obvious, though.

    Brandie Tarvin, MCITP Database AdministratorLiveJournal Blog: http://brandietarvin.livejournal.com/[/url]On LinkedIn!, Google+, and Twitter.Freelance Writer: ShadowrunLatchkeys: Nevermore, Latchkeys: The Bootleg War, and Latchkeys: Roscoes in the Night are now available on Nook and Kindle.

  • Maybe I don't understand completely but why wouldn't you just create a computed column in the table and then just index that? Seems pretty straight forward.

  • tymberwyld (2/29/2008)


    Ok, I've looked through all the posts here, but I don't see anyone bringing up the SOUNDEX / DIFFERENCE functions. We're currently using this to help "enhance" fuzzy searches in the database. Granted this will slow the performance but that's the trade off and it's not a heavily used procedure (only issued when the user specifically executes it via a button click).

    So, is there any way to enhance this?

    ALTER PROCEDURE [Company].[usp_get_Companies]

    (

    @Name VarChar(255) = Null

    )

    AS

    Set NoCount On

    Set Transaction Isolation Level Read Uncommitted

    Declare @SoundExName VarChar(255)

    Select

    @Name = Replace(Replace(IsNull(@Name, ''), ' ', '%') + '%', '%%', '%'),

    @SoundExName = Replace(Replace(Replace(Replace(Replace(Replace(@Name, ' ', ''), '.', ''), '[', ''), ']', ''), '/', ''), '\', '')

    /*

    NOTE:

    At this point the @Name should either be just '%' or '%My%Company%'.

    The latter probably not the most efficient but used to pick up more matches.

    @SoundExName is trying to optimize the name for better soundex matching

    TODO:

    Determine if there is a way to split the @Name into "words" and seach on the

    individual words.

    */

    Select

    C.ID,

    C.Name

    From [Company].[Companies] C

    Where

    (C.[Name] Like @Name)

    Or (

    (Difference(

    Replace(Replace(Replace(C.[Name], ' ', ''), '.', ''), '[', ''),

    @SoundExName

    ) = 4

    )

    )

    Set Transaction Isolation Level Read Committed

    Set NoCount Off

    After more consideration, I would actually create an indexed persisted computed column on your customer table...

    --Would be a computed column on a real table or such

    Create Table Customer(

    EmpId int not null,

    CustomerNm varchar(50) not null,

    SoundExCustomerNm as SoundEx(CustomerNm) Persisted

    )

    Create Clustered Index IX_EmpId on #Customer(EmpId)

    Create Nonclustered Index IX_CustomerNm on #Customer(CustomerNm)

    Create Nonclustered Index IX_SoundExCustomerNm on #Customer(SoundExCustomerNm)

    --Then all you have to do is the following (assuming that my hardcoded values are your replaced values...thats a different topic, but i would use a lookup table for that)

    Select EmpId

    From #Customer

    Where SoundEx('Smith,') = SoundExCustomerNm

    UNION

    --Run Query

    Select EmpId

    From #Customer

    Where CustomerNm Like 'Smith,%'

  • Ruben,

    I'd build a @StartDate and an @EndDate Variable

    Build them using your two parameters of Month and Year

    Start date = 1st of Month

    EndDate = last day of month

    then use the Between Operator in the WHERE Clause (Or a variation of = as discussed in other posts)

    GAJ

    Gregory A Jackson MBA, CSM

  • ruben ruvalcaba (2/29/2008)


    Hi,

    First of all thank you, it's a good point to be considered. In fact, I use functions on the WHERE clause very often, specially when filtering dates ... how do you optimize a query like this:

    SELECT SUM(Amount) FROM myEntries WHERE YEAR(myEntries.myDate) = @Year AND MONTH(myEntries.myDate) <= @Month

    Any clue? thank you

    regards

    If I were doing this It's look something like this I would imagine, although I'd put it in a stored procedure and just pass in year and month.

    DECLARE @startDate DATETIME,

    @endDate DATETIME,

    @Year CHAR(4),

    @Month INT

    --Setting your requested values This would be passed into a sproc etc.

    SELECT @Year = '2000', @Month = 4

    SELECT @startDate = @year --'2000-01-01 00:00:00.000'

    SELECT @endDate = DATEADD(MONTH,@Month ,DATEADD(ms,-3,@startDate))

    SELECT SUM(Amount)

    FROM myEntries

    WHERE myEntries.myDate between @startDate AND @endDate

    To help us help you read this[/url]For better help with performance problems please read this[/url]

  • Okay, but we're still running under the assumption that @StartDate and @EndDate are constants set by the coder/end user. What if they're dynamic?

    I have a situation where I actually use variables to compare later table dates with, but the variables are based on the current date & time verses values in a table.

    tblAccountingPeriods has an ID field (int identity), a StartDate and an EndDate, both datetime. We use this table for all our month end stuff, since our month end doesn't necessarily coincide with calendar month end.

    Declare @LastMonthEnd datetime;

    If Day(GetDate()) > 26 and Day(GetDate()) <= 31

    Set @LastMonthEnd = (Select Max(EndDate) from tblAccountingPeriods

    where DateAdd(mm,-1,GetDate()) > BeginDate

    and Month(DateAdd(mm,-1,GetDate())) = Month(EndDate))

    Else

    Set @LastMonthEnd = (Select Max(EndDate) from tblAccountingPeriods

    where DateAdd(mm,-2,GetDate()) > BeginDate

    and Month(DateAdd(mm,-2,GetDate())) = Month(EndDate));

    I use this code to set the variables so that in the SELECT later on, I can say "WHERE FieldX = @LastMonthEnd".

    The point is, we're talking about ways to improve functionality on the main SELECT statement's WHERE clause, but if you have a situation where your dates are dynamic and you have to get them from a source to create your variables to begin with, you're still going to eventually have to use a function in a WHERE clause at some point. Even if it's not in the actual query, but in the setting of the variable as above.

    So, unless I'm wrong, there are just some situations where you have to determine the best place to put the function and then bite the bullet and accept the processing cost.

    Thoughts?

    Brandie Tarvin, MCITP Database AdministratorLiveJournal Blog: http://brandietarvin.livejournal.com/[/url]On LinkedIn!, Google+, and Twitter.Freelance Writer: ShadowrunLatchkeys: Nevermore, Latchkeys: The Bootleg War, and Latchkeys: Roscoes in the Night are now available on Nook and Kindle.

  • FYI, I know I need to reverse the BeginDate in the above code with the DateAdd. But it was when I got to the AND part of the WHERE that I realized I would still have a function on both sides.

    Brandie Tarvin, MCITP Database AdministratorLiveJournal Blog: http://brandietarvin.livejournal.com/[/url]On LinkedIn!, Google+, and Twitter.Freelance Writer: ShadowrunLatchkeys: Nevermore, Latchkeys: The Bootleg War, and Latchkeys: Roscoes in the Night are now available on Nook and Kindle.

  • I agree with you Brandie,

    The Point is, I guess, is to try hard to reduce or limit the use of functions in a WHERE clause when and where you can.

    If you cant, you cant.

    same concept as using Temp Tables and cursors.

    Reduce them as much as possible but sometimes you gotta do what you gotta do.

    As DBAs, Architects, Engineers, we just need to have a good understanding of the ramifications of our decisions. BUT,.....at some point the rubber hits the road and we gotta make a choice. Sometimes the choices arent ideal.

    Great discussions here.

    GAJ

    Gregory A Jackson MBA, CSM

  • Brandie,

    Any chance something like this would work for you?

    If Day(GetDate()) > 26 and Day(GetDate()) <= 31

    Set @LastMonthEnd =

    (Select Max(EndDate)

    from tblAccountingPeriods

    where BeginDate < DateAdd(mm,-1,GetDate())

    and endDate BETWEEN DATEADD(mm, DATEDIFF(mm,0,getdate())-1, 0)

    AND DATEADD(ms, -3, DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))

    Else

    Set @LastMonthEnd = (Select Max(EndDate) from tblAccountingPeriods

    where BeginDate < DateAdd(mm,-2,GetDate())

    and EndDate BETWEEN DATEADD(mm, DATEDIFF(mm,0,getdate())-2, 0)

    AND DATEADD(ms, -3, DATEADD(mm, DATEDIFF(mm,0,getdate())-1, 0))

    To help us help you read this[/url]For better help with performance problems please read this[/url]

  • Thanks to all,

    As some of you suggested, I been using a computed column for month and year and doing the queries over this columns, but I was looking for an alternative, because I don't know if this approach could be faster than another alternative. I'll try with the solution proposed by SSChasing Mays and SSC Rookie and compare the performance.

    Regards

Viewing 15 posts - 61 through 75 (of 98 total)

You must be logged in to reply to this topic. Login to reply