Quantcast
Channel: Erik Darling – Brent Ozar Unlimited®
Viewing all articles
Browse latest Browse all 370

A Simple Stored Procedure Pattern To Avoid

$
0
0

Get Yourself Together

This is one of the most common patterns that I see in stored procedures. I’m going to simplify things a bit, but hopefully you’ll get enough to identify it when you’re looking at your own code.

Here’s the stored procedure:

CREATE OR ALTER PROCEDURE dbo.you_are_not_clever ( @cd DATETIME NULL)
AS
    BEGIN

        DECLARE @id INT;

        SELECT   TOP 1
                 @id = u.Id 
        FROM     dbo.Users AS u
        WHERE    ( u.CreationDate >= @cd OR @cd IS NULL )
        ORDER BY u.Reputation DESC;

        SELECT   p.OwnerUserId, pt.Type, SUM(p.Score) AS ScoreSum
        FROM     dbo.Posts AS p
        JOIN     dbo.PostTypes AS pt
            ON p.PostTypeId = pt.Id
        WHERE    p.OwnerUserId = @id
        GROUP BY p.OwnerUserId, pt.Type;

    END;

There’s a lot of perceived cleverness in here.

The problem is that SQL Server doesn’t think you’re very clever.

Pattern 1: Assigning The Variable

Leaving aside that optional parameters aren’t SARGable, something even stranger happens here.

And I know what you’re thinking (because you didn’t read the post I just linked to), that you can get the “right” plan by adding a recompile hint.

Let’s test that out.

DECLARE @id INT;
		DECLARE @cd DATETIME = '2017-01-01';

        SELECT   TOP 1
                 @id = u.Id --Assigning the id to a variable
        FROM     dbo.Users AS u
        WHERE    ( u.CreationDate >= @cd OR @cd IS NULL )
        ORDER BY u.Reputation DESC;

        SELECT   TOP 1
                 u.Id -- Not sssigning the id to a variable
        FROM     dbo.Users AS u
        WHERE    ( u.CreationDate >= @cd OR @cd IS NULL )
        ORDER BY u.Reputation DESC
        OPTION ( RECOMPILE );

Here are the plans, for all you nice people NOT WORKING AT WORK.

Just like in the post I linked up there (that you still haven’t read yet — for SHAME), the first plan refuses to recognize that an index might make things better.

The second plan does, but as usual, the missing index request is rather underwhelming.

But what’s more interesting, is that even with a recompile hint, The Optimizer doesn’t ‘sniff’ the variable value in the first query that assigns to @id.

you’re crazy

This only happens in queries that test if a variable is null, like the pattern above, and it becomes more obvious with a better index in place.

CREATE INDEX ix_helper ON dbo.Users(CreationDate, Reputation DESC)

Parades!

We’ve got it all! A seek vs a scan, a bad estimate! Probably some other stuff!

Pattern 2: Using That Assigned Variable

The Great And Powerful Kendra blogged about a related item a while back. But it seems like you crazy kids either didn’t get the message, or think it doesn’t apply in stored procedures, but it does.

If we look at the plan generated, the cardinality estimate gets the density vector treatment just like Kendra described.

Commotion

In my histogram, the values plug in below, and give us 79.181327288022

SELECT (38485046 * 2.057457E-06) AS lousy

This gets worse when a user with a lot of data, where other parts of the plan start to go downhill.

Everyday I’m Suffering.

How To Avoid These Problems

Both of the problems in these patterns can be avoided with dynamic SQL, or sub stored procedures.

Unfortunately, IF branching doesn’t work the way you’d hope.

If you’re ever confused by bad query plans that crop up in a stored procedure, sneaky stuff like this can totally be to blame.

Thanks for reading!

Applications are open now for our 2019 scholarship program.


Viewing all articles
Browse latest Browse all 370

Trending Articles