views:

88

answers:

2

I am using this condition as part of my where clause: exists (select * from PRPB PB where PA.mid = PB.mid and (@inpo is null or PB.inpo = @inpo)) order by price.

While testing non-null @inpo values, I noticed the query runs much faster when I instead use this condition: exists (select * from PRPB PB where PA.mid = PB.mid and (PB.inpo = @inpo)) order by price. That this causes a non-neglible speed difference suggests that I will be forced to use two separate queries with an if statement in order to decide whether to filter out by @inpo or not. This strikes me as a bad thing, since it means a lot of code repetition.

Things I have tried:

  • Creating a bit that stores whether @inpo is non-null and comparing with that.
  • Moving the nullity check to the left of the exists statement and doing an or with the whole thing (this slows things down a lot, which surprises me).
  • Moving the nullity check to the leftmost of the where class and oring it with all the clauses (this also slows things down a lot, which doesn't surprise me at all, since it means the nullity check always happens, regardless of whether or not PA.mid = PB.mid).

My goal is to have it perform this check much faster without having two copies of my query.

Is this possible? If not, why not? If so, how?

Note: See also a 2nd related question here.

+1  A: 

To reuse code without paying performance penalty you can either create a view or create an inline UDF. Both are macros that are expanded by the optimizer. For example instead of the following duplication of code:

CREATE PROCEDURE MyProc
  @i1 INT,
  @inpo INT
AS
BEGIN
IF @inpo IS NULL BEGIN
  SELECT a,b,c 
    FROM dbo.YourTable 
    WHERE i1 = @i1 
    ORDER BY c;
END ELSE BEGIN
  SELECT a,b,c 
    FROM dbo.YourTable 
    WHERE i1 = @i1 
      AND inpo = @inpo
    ORDER BY c;
END
END

wrap the query in an inline udf and reuse it:

CREATE FUNCTION dbo.ReuseMyQuery(@i1 INT)
RETURNS TABLE AS RETURN(
SELECT a,b,c, inpo FROM dbo.YourTable WHERE i1 = @i1
)
GO

ALTER PROCEDURE MyProc
  @i1 INT,
  @inpo INT
AS
BEGIN
IF @inpo IS NULL BEGIN
  SELECT a,b,c 
    FROM dbo.ReuseMyQuery(@i1)
    ORDER BY c;
END ELSE BEGIN
  SELECT a,b,c 
    FROM dbo.ReuseMyQuery(@i1)
    WHERE inpo = @inpo
    ORDER BY c;
END
END
AlexKuznetsov
I'm not clear on what my view or UDF would contain that would help me.
Brian
My intuition is telling me I should be able to use a with statement and avoid the view and function, but I couldn't get it to work. I suspect I could also just use a temporary table instead. I'll look at it tomorrow :/
Brian
This solution works.
Brian
A: 

What if you tried doing this:

exists (select * from PRPB PB where PA.mid = PB.mid and PB.inpo = ISNULL(@inpo, PB.inpo)) order by price

The ISNULL function will cause it to return the second parameter when @inpo is NULL, and by returning PB.inpo then that equality will always evaluate to true, which matches your or condition.

scwagner
TO my best knowledge, this approach is not index friendly. Could run slowly
AlexKuznetsov
This is very slow.
Brian