chris c
chris c

Reputation: 331

Parameterized stored procedure bypass left join if certain parameters are not passed to the procedure

I have added a left join to my stored procedure, but my query now takes 4 to 10 times longer to run even if I don't pass the parameters required for the join.

Here is my stored procedure:

USE [Test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[OrderLoadAllPaged]
    @OrderId int = 0,
    @WarehouseId int = 0,
    @PaymentMethodSystemName nvarchar(max) = null,
    @OrderStatusId int = 0,
    @PaymentStatusId int = 0,
    @ShippingStatusId int = 0,
    @BillingEmail nvarchar(max) = null,
    @BillingFirstName nvarchar(max) = null,
    @BillingLastName nvarchar(max) = null,
    @ShippingMethod nvarchar(max) = null,
    @CreatedFromUtc datetime = null,
    @CreatedToUtc datetime = null
AS
BEGIN
    DECLARE
        @sql nvarchar(max)

    SET NOCOUNT ON;

    SELECT TOP 100 *
    FROM [Test].[dbo].[Order] o with (NOLOCK)

    LEFT join [Test].[dbo].[Address] a on (a.Id LIKE o.BillingAddressId)
    WHERE (@BillingEmail IS null OR a.[Email] = @BillingEmail)
    AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
    AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)

    AND
        o.[Deleted] = 0

    AND (o.[Id] = @OrderId OR @OrderId = 0)
    AND (o.[WarehouseId] = @WarehouseId OR @WarehouseId = 0)

    AND (@PaymentMethodSystemName IS null OR o.[PaymentMethodSystemName] = @PaymentMethodSystemName)

    AND (o.[OrderStatusId] = @OrderStatusId OR @OrderStatusId = 0)
    AND (o.[PaymentStatusId] = @PaymentStatusId OR @PaymentStatusId = 0)
    AND (o.[ShippingStatusId] = @ShippingStatusId OR @ShippingStatusId = 0)

    AND (@ShippingMethod IS null OR o.[ShippingMethod] = @ShippingMethod)
    AND o.[CreatedOnUtc] >= ISNULL(@CreatedFromUtc, '1/1/1900')
    AND o.[CreatedOnUtc] < ISNULL(@CreatedToUtc, '1/1/2999')

    ORDER BY o.[CreatedOnUtc] DESC
END

So if I don't pass in @BillingEmail, @BillingFirstName or @BillingLastName it's like the left join is still considered:

If I comment out the left join, my query is really quick again:

/*LEFT join [Test].[dbo].[Address] a on (a.Id LIKE o.BillingAddressId)
    WHERE (@BillingEmail IS null OR a.[Email] = @BillingEmail)
    AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
    AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)*/
    WHERE --need to change
        o.[Deleted] = 0

How can I bypass that query all together, if @BillingEmail, @BillingFirstName or @BillingLastName are all null.

I thought maybe something like this, but it's not right:

case
    when (ISNULL(@BillingEmail) OR ISNULL(@BillingFirstName) OR ISNULL(@BillingLastName))
    then 
        WHERE
            o.[Deleted] = 0
    ELSE
        LEFT join [Test].[dbo].[Address] a on (a.Id LIKE o.BillingAddressId)
        WHERE (@BillingEmail IS null OR a.[Email] = @BillingEmail) --LIKE '%' + @BillingEmail + '%'
        AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
        AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)
        AND
            o.[Deleted] = 0
end

I looked at indexes but I don't think I can create indexes for those 3 parameters. Any ideas how I can bypass that left join?

Upvotes: 0

Views: 147

Answers (2)

Dale K
Dale K

Reputation: 27290

As an alternative you could keep it as a single query as follows:

SELECT TOP 100 *
FROM [Test].[dbo].[Order] o with (NOLOCK)

LEFT join [Test].[dbo].[Address] a on a.Id = o.BillingAddressId and (
    coalesce(@BillingEmail,'') <> ''
    or coalesce(@BillingFirstName,'') <> ''
    or coalesce(@BillingLastName,'') <> ''
  )

WHERE (@BillingEmail IS null OR a.[Email] = @BillingEmail)
AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)

Note the join is only attempted if one of the Billing variables has a value.

Upvotes: 1

Ed Bangga
Ed Bangga

Reputation: 13006

Lets try to move your left join table in a filtered subquery.

left join 
    (select * from [Test].[dbo].[Address] a 
        where (@BillingEmail IS null OR a.[Email] = @BillingEmail)
        AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
        AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)) t1
        ON t1.Id = o.BillingAddressId

to bypass left join based on your criteria, you can use below code.

if (ISNULL(@BillingEmail, '') = '' and ISNULL(@BillingFirstName, '') = '' and ISNULL(@BillingLastName, '') = '')
    begin
        SELECT TOP 100 *
        FROM [Test].[dbo].[Order] o with (NOLOCK)
        WHERE o.[Deleted] = 0
        AND (o.[Id] = @OrderId OR @OrderId = 0)
        AND (o.[WarehouseId] = @WarehouseId OR @WarehouseId = 0)
        AND (@PaymentMethodSystemName IS null OR o.[PaymentMethodSystemName] = @PaymentMethodSystemName)
        AND (o.[OrderStatusId] = @OrderStatusId OR @OrderStatusId = 0)
        AND (o.[PaymentStatusId] = @PaymentStatusId OR @PaymentStatusId = 0)
        AND (o.[ShippingStatusId] = @ShippingStatusId OR @ShippingStatusId = 0)
        AND (@ShippingMethod IS null OR o.[ShippingMethod] = @ShippingMethod)
        AND o.[CreatedOnUtc] >= ISNULL(@CreatedFromUtc, '1/1/1900')
        AND o.[CreatedOnUtc] < ISNULL(@CreatedToUtc, '1/1/2999')
        ORDER BY o.[CreatedOnUtc] DESC
    end
else
    begin   
        SELECT TOP 100 *
        FROM [Test].[dbo].[Order] o with (NOLOCK)
         left join 
            (select * from [Test].[dbo].[Address] a 
                where (@BillingEmail IS null OR a.[Email] = @BillingEmail)
                AND (@BillingFirstName IS null OR a.[FirstName] = @BillingFirstName)
                AND (@BillingLastName IS null OR a.[LastName] = @BillingLastName)) t1
                ON t1.Id = o.BillingAddressId
        WHERE o.[Deleted] = 0
        AND (o.[Id] = @OrderId OR @OrderId = 0)
        AND (o.[WarehouseId] = @WarehouseId OR @WarehouseId = 0)
        AND (@PaymentMethodSystemName IS null OR o.[PaymentMethodSystemName] = @PaymentMethodSystemName)
        AND (o.[OrderStatusId] = @OrderStatusId OR @OrderStatusId = 0)
        AND (o.[PaymentStatusId] = @PaymentStatusId OR @PaymentStatusId = 0)
        AND (o.[ShippingStatusId] = @ShippingStatusId OR @ShippingStatusId = 0)

        AND (@ShippingMethod IS null OR o.[ShippingMethod] = @ShippingMethod)
        AND o.[CreatedOnUtc] >= ISNULL(@CreatedFromUtc, '1/1/1900')
        AND o.[CreatedOnUtc] < ISNULL(@CreatedToUtc, '1/1/2999')

        ORDER BY o.[CreatedOnUtc] DESC
    end

Upvotes: 1

Related Questions