Slow exists check when adding a conditional test

2019-08-27 15:35发布

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.

2条回答
ら.Afraid
2楼-- · 2019-08-27 16:09

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
查看更多
看我几分像从前
3楼-- · 2019-08-27 16:11

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.

查看更多
登录 后发表回答