• Behold The Power of Dynamic SQL- Part 1

    This blog post has been adapted from my presentation at Pass Summit 2018. To download the powerpoint and scripts from that session, click here

    I’m using Stack Overflow’s medium sized database 

    It’s a bit painful to get in database form, please see Brent Ozar’s blog for instructions

    See The Setup Objects

    USE [StackOverflow2013]
    GO
    
    /****** Object:  UserDefinedTableType [dbo].[parameters]    Script Date: 10/20/2018 12:45:01 PM ******/
    CREATE TYPE [dbo].[parameters] AS TABLE(
    	[param] [nvarchar](100) NULL,
    	[param_alias] [nvarchar](100) NULL,
    	[datatype] [nvarchar](100) NULL
    )
    GO
    
    CREATE TABLE [dbo].[tempParams](
    	[param] [nvarchar](100) NULL,
    	[fieldname] [nvarchar](100) NULL,
    	[param_alias] [nvarchar](100) NULL,
    	[whereclause] [nvarchar](200) NULL,
    	[datatype] [nvarchar](100) NULL,
    	[rownum] [bigint] NULL
    ) ON [PRIMARY]
    GO
    
    See The Indexes

    USE [StackOverflow2013]
    GO
    
    SET ANSI_PADDING ON
    GO
    
    -------------------------------------------------------------------------------------
    --dbo.Posts
    -------------------------------------------------------------------------------------
    /****** Object:  Index [IX_OwneruserId_Includes]    Script Date: 11/2/2018 7:09:02 PM ******/
    CREATE NONCLUSTERED INDEX [IX_OwneruserId_Includes] ON [dbo].[Posts]
    (
    	[OwnerUserId] ASC
    )
    INCLUDE ( 	[Body],
    	[CreationDate],
    	[Score]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    
    -------------------------------------------------------------------------------------
    --dbo.Users
    -------------------------------------------------------------------------------------
    /****** Object:  Index [IX_Age_Includes]    Script Date: 11/2/2018 7:09:26 PM ******/
    CREATE NONCLUSTERED INDEX [IX_Age_Includes] ON [dbo].[Users]
    (
    	[Age] ASC
    )
    INCLUDE ( 	[DisplayName],
    	[DownVotes],
    	[Location],
    	[Reputation],
    	[UpVotes]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    
    /****** Object:  Index [IX_DisplayName_Includes]    Script Date: 11/2/2018 7:09:41 PM ******/
    CREATE NONCLUSTERED INDEX [IX_DisplayName_Includes] ON [dbo].[Users]
    (
    	[DisplayName] ASC
    )
    INCLUDE ( 	[Age],
    	[DownVotes],
    	[Location],
    	[Reputation],
    	[UpVotes]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    
    /****** Object:  Index [IX_Location_Includes]    Script Date: 11/2/2018 7:09:52 PM ******/
    CREATE NONCLUSTERED INDEX [IX_Location_Includes] ON [dbo].[Users]
    (
    	[Location] ASC
    )
    INCLUDE ( 	[Age],
    	[DisplayName],
    	[DownVotes],
    	[Reputation],
    	[UpVotes]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    
    -------------------------------------------------------------------------------------
    --dbo.Votes
    -------------------------------------------------------------------------------------
    /****** Object:  Index [IX_CreationDate]    Script Date: 11/2/2018 7:10:07 PM ******/
    CREATE NONCLUSTERED INDEX [IX_CreationDate] ON [dbo].[Votes]
    (
    	[CreationDate] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    /****** Object:  Index [IX_PostId_Includes]    Script Date: 11/2/2018 7:10:17 PM ******/
    CREATE NONCLUSTERED INDEX [IX_PostId_Includes] ON [dbo].[Votes]
    (
    	[PostId] ASC
    )
    INCLUDE ( 	[CreationDate],
    	[VoteTypeId]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    
    -------------------------------------------------------------------------------------
    --dbo.VoteTypes
    -------------------------------------------------------------------------------------
    /****** Object:  Index [IX_Name]    Script Date: 11/2/2018 7:10:31 PM ******/
    CREATE NONCLUSTERED INDEX [IX_Name] ON [dbo].[VoteTypes]
    (
    	[Name] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    GO
    

    At a previous job, we were not allowed to use Dynamic SQL.  At one point, a perfect use case for Dynamic SQL presented itself. The search procedure in question had around 15 nullable parameters and was incredibly slow. It looked something like this, but this one only has 10 nullable parameters.

    See The BadSearchProc Code

    USE [StackOverflow2013]
    GO
    
    /****** Object:  StoredProcedure [dbo].[BadSearchProc]    Script Date: 11/20/2018 8:12:03 PM ******/
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    
    CREATE PROCEDURE [dbo].[BadSearchProc] 
    @CreationDate datetime = null,
    @postScore int = null,
    @voteCreationDate datetime = null,
    @votetype varchar(50) = null,
    @askerDisplayName nvarchar(40) = null,
    @askerLocation nvarchar(100) = null,
    @askerUpVotes int = null,
    @askerDownVotes int = null,
    @askerRep int = null,
    @askerAge int = null
    AS
    
    /*BadSearchProc*/
    select 
      p.Id,
      p.Body,
      p.CreationDate,
      p.Score,
      v.CreationDate as voteCreationDate,
      vt.Name as voteType,
      asker.DisplayName as askerDisplayName,
      asker.Location as askerLocation,
      asker.UpVotes,
      asker.DownVotes,
      asker.Reputation,
      asker.Age
    FROM dbo.Posts p
    LEFT JOIN dbo.Votes v 
      ON p.Id = v.PostId
    LEFT JOIN dbo.VoteTypes vt 
      on v.VoteTypeId = vt.Id
    LEFT JOIN dbo.Users asker 
      ON p.OwnerUserId = asker.Id
    where 
      (@CreationDate is null or p.CreationDate = @CreationDate)
    and 
      (@postScore is null or p.Score = @postScore)
    and 
      (@voteCreationDate is null or v.CreationDate = @voteCreationDate)
    and 
      (@votetype is null OR vt.name = @votetype)
    and 
      (@askerDisplayName is null or asker.DisplayName = @askerDisplayName)
    and 
      (@askerLocation is null or asker.Location = @askerLocation)
    and 
      (@askerUpVotes is null OR asker.UpVotes = @askerUpVotes)
    and 
      (@askerDownVotes is null OR asker.DownVotes = @askerDownVotes)
    and 
      (@askerRep is null or asker.Reputation = @askerRep)
    and 
      (@askerAge is null or asker.Age = @askerAge);
    
    
    GO
    

    I wrote the stored procedure using Dynamic SQL, and ran a series of tests. I had three different scenarios that I compared:

    1. Old approach- no change
    2. Old approach with targeted indexing- no code changes
    3. New approach- Dynamic SQL

    I presented my findings, and Dynamic SQL won hands down. Yes, targeted indexing can help, but a bad execution plan is a bad execution plan and indexes will not save you. Despite the strong case that I made, I would not be allowed to use Dynamic SQL to solve the performance problem.

    One of the guys I presented to asked the question: “Is there anything you can do to replicate the results without using Dynamic SQL?”

    Months later after I had turned in my resignation, and I came back to that question. I thought “Yes, there is something that I can do to achieve optimal execution plans without using Dynamic SQL”. I could already think of the downsides of this approach, but I decided to write a stored procedure that generates all of the cases anyways. The CASES (IF blocks) will give you a separate query that is tailored to fit the usage of the parameters at the time they are being used. Yes you could just use OPTION (RECOMPILE) but what’s the fun in that? And besides, I’m trying to avoid burning up the CPU. 

    In this approach, some assumptions are being made. The stored procedure receives a query as one of the parameters.

    1. Everything in the query is necessary
    2. All of the JOINs in the query are necessary
    3. All of the fields in the SELECT clause are necessary
    4. The only thing that really matters here is the WHERE clause

    I decided to name this new stored procedure useDynamicSQLInstead.

    Here are some other potential names I’ve considered

    1. theUnmaintainableCodeGenerator
    2. theCustomerObfuscationTool

    Here’s the code! I only coded it to work for 20 parameters. Doing it for any more than that is insane. In fact, I could only every get the procedure to finish for 17 parameters. 15 had over 450K lines of code!

    See The useDynamicSQLInstead Proc
    USE [StackOverflow2013]
    GO
    
    /****** Object:  StoredProcedure [dbo].[useDynamicSQLinstead]    Script Date: 11/20/2018 8:47:00 PM ******/
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    
    -----------------------------------------------------------------------------------
    -- Written by Cyndi Johnson for use in her 'Behold The Power of Dynamic SQL'
    -- session at Pass Summit 2018 on November 9, 2018. Permission for reuse is granted
    -- in whole or in part, with attribution. However, if you put this code ( or the 
    -- code this procedure generates) on a Production server, you are doing so against 
    -- developer's advice. Do so at your own risk! No warranty is implied.
    -----------------------------------------------------------------------------------
    CREATE PROCEDURE [dbo].[useDynamicSQLinstead]
      @query NVARCHAR(MAX) = '',
      @parameters AS dbo.parameters READONLY
    AS
    BEGIN
    
      SET NOCOUNT ON;
    
      IF OBJECT_ID('dbo.tempParams') IS NOT NULL
        TRUNCATE TABLE dbo.tempParams;
      IF OBJECT_ID('dbo.ParamPivot') IS NOT NULL
        DROP TABLE dbo.ParamPivot;
    
      DECLARE
        @param         NVARCHAR(100),
        @id            INT,
        @fieldname     NVARCHAR(100),
        @whereclause   NVARCHAR(MAX),
        @ifstatement   NVARCHAR(MAX),
        @paramCount    INT,
        @combinations  INT,
        @loopcount     INT,
        @paramcounter  INT,
        @fullstatement NVARCHAR(MAX) = '',
        @setstatement  NVARCHAR(MAX) = '',
        @casecounter   INT           = 0;
    
      DECLARE
        @bit1              BIT = 0,
        @bit2              BIT = 0,
        @bit3              BIT = 0,
        @bit4              BIT = 0,
        @bit5              BIT = 0,
        @bit6              BIT = 0,
        @bit7              BIT = 0,
        @bit8              BIT = 0,
        @bit9              BIT = 0,
        @bit10             BIT = 0,
        @bit11             BIT = 0,
        @bit12             BIT = 0,
        @bit13             BIT = 0,
        @bit14             BIT = 0,
        @bit15             BIT = 0,
        @bit16             BIT = 0,
        @bit17             BIT = 0,
        @bit18             BIT = 0,
        @bit19             BIT = 0,
        @bit20             BIT = 0,
        @ParmDefinition    NVARCHAR(MAX),
        @SetParmDefinition NVARCHAR(MAX);
    
      DECLARE @bitflipper BIT = 0;
    
      DECLARE
        @parameter1  NVARCHAR(100),
        @parameter2  NVARCHAR(100),
        @parameter3  NVARCHAR(100),
        @parameter4  NVARCHAR(100),
        @parameter5  NVARCHAR(100),
        @parameter6  NVARCHAR(100),
        @parameter7  NVARCHAR(100),
        @parameter8  NVARCHAR(100),
        @parameter9  NVARCHAR(100),
        @parameter10 NVARCHAR(100),
        @parameter11 NVARCHAR(100),
        @parameter12 NVARCHAR(100),
        @parameter13 NVARCHAR(100),
        @parameter14 NVARCHAR(100),
        @parameter15 NVARCHAR(100),
        @parameter16 NVARCHAR(100),
        @parameter17 NVARCHAR(100),
        @parameter18 NVARCHAR(100),
        @parameter19 NVARCHAR(100),
        @parameter20 NVARCHAR(100);
    
      SET @ParmDefinition
        = N'@bit1 BIT,
    		@bit2 BIT,
    		@bit3 BIT,
    		@bit4 BIT,
    		@bit5 BIT,
    		@bit6 BIT,
    		@bit7 BIT,
    		@bit8 BIT,
    		@bit9 BIT,
    		@bit10 BIT,
    		@bit11 BIT,
    		@bit12 BIT,
    		@bit13 BIT,
    		@bit14 BIT,
    		@bit15 BIT,
    		@bit16 BIT,
    		@bit17 BIT,
    		@bit18 BIT,
    		@bit19 BIT,
    		@bit20 BIT';
    
      SET @SetParmDefinition
        = N'@param1 NVARCHAR(100) OUT,
    		@param2 NVARCHAR(100) OUT,
    		@param3 NVARCHAR(100) OUT,
    		@param4 NVARCHAR(100) OUT,
    		@param5 NVARCHAR(100) OUT,
    		@param6 NVARCHAR(100) OUT,
    		@param7 NVARCHAR(100) OUT,
    		@param8 NVARCHAR(100) OUT,
    		@param9 NVARCHAR(100) OUT,
    		@param10 NVARCHAR(100) OUT,
    		@param11 NVARCHAR(100) OUT,
    		@param12 NVARCHAR(100) OUT,
    		@param13 NVARCHAR(100) OUT,
    		@param14 NVARCHAR(100) OUT,
    		@param15 NVARCHAR(100) OUT,
    		@param16 NVARCHAR(100) OUT,
    		@param17 NVARCHAR(100) OUT,
    		@param18 NVARCHAR(100) OUT,
    		@param19 NVARCHAR(100) OUT,
    		@param20 NVARCHAR(100) OUT ';
    
    
    INSERT INTO dbo.tempParams
    (
      param,
      fieldname,
      param_alias,
      whereclause,
      datatype,
      rownum
    )
      SELECT
        param,
        fieldname = RIGHT(param, LEN(param) - 1),
        param_alias,
        whereclause = param_alias + '=' + param,
        datatype,
        rownum = ROW_NUMBER() OVER (ORDER BY param)
      FROM
        @parameters;
    
    -- Demo query
    --SELECT 'dbo.tempParams',* FROM dbo.tempParams ORDER BY rownum;
    
      SELECT
        @paramCount = COUNT(1)
      FROM
        dbo.tempParams;
    
      SET @paramcounter = 1;
      WHILE @paramcounter <= @paramCount
      BEGIN
    
        SELECT
          @setstatement
          = N'SELECT @param' + LTRIM(RTRIM(CAST(@paramcounter AS CHAR(100)))) + N' = '
            + N'fieldname FROM dbo.tempParams WHERE rownum=' + LTRIM(RTRIM(CAST(@paramcounter AS CHAR(100))));
    
        EXECUTE sp_executesql
          @setstatement,
          @SetParmDefinition,
          @parameter1 OUT,
          @parameter2 OUT,
          @parameter3 OUT,
          @parameter4 OUT,
          @parameter5 OUT,
          @parameter6 OUT,
          @parameter7 OUT,
          @parameter8 OUT,
          @parameter9 OUT,
          @parameter10 OUT,
          @parameter11 OUT,
          @parameter12 OUT,
          @parameter13 OUT,
          @parameter14 OUT,
          @parameter15 OUT,
          @parameter16 OUT,
          @parameter17 OUT,
          @parameter18 OUT,
          @parameter19 OUT,
          @parameter20 OUT;
    
        SET @paramcounter = @paramcounter + 1;
      END;
    
      -- Figure out how many combinations there are 
      -- Each parameter can be null or non-null, so two different 
      -- values for the purposes of this code
      SET @combinations = POWER(2, @paramCount);
    
      -- Dynamically create table to help iterate through 
      -- all the cases (@combinations of parameter usage)
      DECLARE
        @cols            NVARCHAR(MAX),
        @cols2           NVARCHAR(MAX),
        @createstatement NVARCHAR(MAX),
        @insertstatement NVARCHAR(MAX),
        @insertvalues    NVARCHAR(MAX) = '';
    
      SELECT
        @cols = STUFF(
                (
                  SELECT
                    DISTINCT
                    ', ' + t2.fieldname + ' bit '
                  FROM
                    dbo.tempParams AS t2
                  ORDER BY
                    ', ' + t2.fieldname + ' bit '
                  FOR XML PATH('')
                ),
                1,
                2,
                ' '
                     ) + N' ';
    
      SELECT
        @cols2 = STUFF(
                 (
                   SELECT
                     DISTINCT
                     ', ' + t2.fieldname + ' '
                   FROM
                     dbo.tempParams AS t2
                   ORDER BY
                     ', ' + t2.fieldname + ' '
                   FOR XML PATH('')
                 ),
                 1,
                 2,
                 ' '
                      ) + N' ';
    
      SET @cols = @cols + N', id_num INT IDENTITY(1,1) ';
    
      SET @createstatement = N'CREATE TABLE dbo.ParamPivot ( ' + @cols + N' )';
    
      EXEC (@createstatement);
    
    
      -- Populate #ParamPivot
      SET @loopcount = 1;
      WHILE @loopcount <= @combinations
      BEGIN
    
        SET @insertvalues = N'';
        SET @insertstatement = N'INSERT INTO dbo.ParamPivot ( ' + @cols2 + N' ) VALUES(';
    
        SET @paramcounter = 1;
        WHILE @paramcounter <= @paramCount
        BEGIN
    
          SET @insertvalues = @insertvalues + N'@bit' + LTRIM(RTRIM(CAST(@paramcounter AS CHAR(100)))) + N', ';
          SET @paramcounter = @paramcounter + 1;
    
        END;
    
        --Remove the trailing comma
        SET @insertvalues = LEFT(@insertvalues, LEN(@insertvalues) - 1);
        SET @insertvalues = @insertvalues + N')';
    
        SET @fullstatement = @insertstatement + @insertvalues;
    
        EXECUTE sp_executesql
          @fullstatement,
          @ParmDefinition,
          @bit1,
          @bit2,
          @bit3,
          @bit4,
          @bit5,
          @bit6,
          @bit7,
          @bit8,
          @bit9,
          @bit10,
          @bit11,
          @bit12,
          @bit13,
          @bit14,
          @bit15,
          @bit16,
          @bit17,
          @bit18,
          @bit19,
          @bit20;
    
        SET @loopcount = @loopcount + 1;
      END;
    
      DECLARE
        @updatestatement        NVARCHAR(MAX) = '',
        @updatefieldOutput       NVARCHAR(100) = '',
        @setfieldvarstatement   NVARCHAR(MAX) = '',
        @setfieldParmDefinition NVARCHAR(MAX) = '';
    
      SET @setfieldParmDefinition
        = N'@updatefield NVARCHAR(100) OUT,
    		@parameter1 NVARCHAR(100),
    		@parameter2 NVARCHAR(100),
    		@parameter3 NVARCHAR(100),
    		@parameter4 NVARCHAR(100),
    		@parameter5 NVARCHAR(100),
    		@parameter6 NVARCHAR(100),
    		@parameter7 NVARCHAR(100),
    		@parameter8 NVARCHAR(100),
    		@parameter9 NVARCHAR(100),
    		@parameter10 NVARCHAR(100),
    		@parameter11 NVARCHAR(100),
    		@parameter12 NVARCHAR(100),
    		@parameter13 NVARCHAR(100),
    		@parameter14 NVARCHAR(100),
    		@parameter15 NVARCHAR(100),
    		@parameter16 NVARCHAR(100),
    		@parameter17 NVARCHAR(100),
    		@parameter18 NVARCHAR(100),
    		@parameter19 NVARCHAR(100),
    		@parameter20 NVARCHAR(100) ';
    
      -- Populate bit flags
      DECLARE @divider INT = @combinations;
    
      SET @loopcount = 1;
      WHILE @divider > 1
      BEGIN
    
        SET @divider = @divider / 2;
        SET @bitflipper = 1;
    
        SET @setfieldvarstatement
          = N'SELECT @updatefield = @parameter' + +LTRIM(RTRIM(CAST(@loopcount AS CHAR(100))));
    
        EXECUTE sp_executesql
          @setfieldvarstatement,
          @setfieldParmDefinition,
          @updatefieldOutput OUT,
          @parameter1,
          @parameter2,
          @parameter3,
          @parameter4,
          @parameter5,
          @parameter6,
          @parameter7,
          @parameter8,
          @parameter9,
          @parameter10,
          @parameter11,
          @parameter12,
          @parameter13,
          @parameter14,
          @parameter15,
          @parameter16,
          @parameter17,
          @parameter18,
          @parameter19,
          @parameter20;
    
        -- This section will populate dbo.ParamPivot with 
        -- all the combinations of parameter use
        DECLARE bitcursor CURSOR LOCAL FAST_FORWARD FOR SELECT id_num FROM dbo.ParamPivot;
    
        OPEN bitcursor;
    
        FETCH NEXT FROM bitcursor
        INTO
          @id;
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
    
          IF @id % @divider = 1
             OR @divider = 1
            SET @bitflipper = ~ @bitflipper;
    
          SET @updatestatement
            = N'UPDATE p SET ' + @updatefieldOutput + N'= ' + LTRIM(RTRIM(CAST(@bitflipper AS CHAR(100))))
              + N' FROM dbo.ParamPivot p WHERE id_num = ' + LTRIM(RTRIM(CAST(@id AS CHAR(100))));
    
          EXECUTE sp_executesql 
    		@updatestatement;
    
          FETCH NEXT FROM bitcursor
          INTO
            @id;
    
        END;
        CLOSE bitcursor;
        DEALLOCATE bitcursor;
    
    
        IF @loopcount = 1
        BEGIN
          EXECUTE sp_executesql 
    		@updatestatement;
        END;
    
        SET @loopcount = @loopcount + 1;
    
      END;
    
      -- Demo query
      --SELECT * FROM dbo.ParamPivot;
    
      DECLARE
        @fieldlist  NVARCHAR(MAX),
        @fieldquery NVARCHAR(MAX);
    
      SET @fieldlist = N'';
      SET @fieldquery = N'';
    
      DECLARE
        @paramlist      NVARCHAR(MAX),
        @sprocparamlist NVARCHAR(MAX),
        @datatype       NVARCHAR(100);
    
      SET @paramlist = N'';
      SET @sprocparamlist = N'';
      SET @datatype = N'';
    
      -----------------------------------------------------------------------------------------------------------------------------------------------
      -- Build The Procedure
      -----------------------------------------------------------------------------------------------------------------------------------------------
      PRINT 'CREATE PROCEDURE [dbo].[RenameMePlease]';
    
      SET @loopcount = 1;
      WHILE @loopcount <= @paramCount
      BEGIN
    
        SELECT
          @datatype = datatype
        FROM
          dbo.tempParams
        WHERE
          rownum = @loopcount;
    
        SET @paramlist = @paramlist + N'@param' + LTRIM(RTRIM(CAST(@loopcount AS CHAR(100)))) + N',';
    
        SET @setfieldvarstatement
          = N'SELECT @updatefield = @parameter' + LTRIM(RTRIM(CAST(@loopcount AS CHAR(100))));
        --PRINT @setfieldvarstatement
    
        EXECUTE sp_executesql
          @setfieldvarstatement,
          @setfieldParmDefinition,
          @updatefieldOutput OUT,
          @parameter1,
          @parameter2,
          @parameter3,
          @parameter4,
          @parameter5,
          @parameter6,
          @parameter7,
          @parameter8,
          @parameter9,
          @parameter10,
          @parameter11,
          @parameter12,
          @parameter13,
          @parameter14,
          @parameter15,
          @parameter16,
          @parameter17,
          @parameter18,
          @parameter19,
          @parameter20;
    
        SET @sprocparamlist = @sprocparamlist + CASE WHEN @loopcount =1 THEN '' ELSE + CHAR(10) END + N'@' + @updatefieldOutput + N' ' + @datatype + N' = NULL,';
        SET @fieldlist = @fieldlist + @updatefieldOutput + N',';
        SET @loopcount = @loopcount + 1;
    
      END;
    
      SET @sprocparamlist = LEFT(@sprocparamlist, LEN(@sprocparamlist) - 1);
      PRINT @sprocparamlist;
    
      PRINT 'AS';
      PRINT '';
      PRINT 'BEGIN';
    
      SET @paramlist = LEFT(@paramlist, LEN(@paramlist) - 1);
    
      DECLARE @cursorParmDefinition NVARCHAR(MAX);
    
      SET @cursorParmDefinition
        = N'@param1 NVARCHAR(100) OUT,
    		@param2 NVARCHAR(100) OUT,
    		@param3 NVARCHAR(100) OUT,
    		@param4 NVARCHAR(100) OUT,
    		@param5 NVARCHAR(100) OUT,
    		@param6 NVARCHAR(100) OUT,
    		@param7 NVARCHAR(100) OUT,
    		@param8 NVARCHAR(100) OUT,
    		@param9 NVARCHAR(100) OUT,
    		@param10 NVARCHAR(100) OUT,
    		@param11 NVARCHAR(100) OUT,
    		@param12 NVARCHAR(100) OUT,
    		@param13 NVARCHAR(100) OUT,
    		@param14 NVARCHAR(100) OUT,
    		@param15 NVARCHAR(100) OUT,
    		@param16 NVARCHAR(100) OUT,
    		@param17 NVARCHAR(100) OUT,
    		@param18 NVARCHAR(100) OUT,
    		@param19 NVARCHAR(100) OUT,
    		@param20 NVARCHAR(100) OUT';
    
      SET @fieldlist = LEFT(@fieldlist, LEN(@fieldlist) - 1);
      SET @fieldquery = N'DECLARE ParamPivotCursor CURSOR GLOBAL FAST_FORWARD
    					FOR SELECT ' + @fieldlist + N' FROM dbo.ParamPivot ORDER BY id_num';
    
      EXECUTE sp_executesql 
    	@fieldquery;
    
      DECLARE @opencursor NVARCHAR(MAX);
      SET @opencursor = N'OPEN ParamPivotCursor
    					FETCH NEXT FROM ParamPivotCursor
    					INTO ' + @paramlist;
    				
      EXECUTE sp_executesql
        @opencursor,
        @cursorParmDefinition,
        @parameter1 OUT,
        @parameter2 OUT,
        @parameter3 OUT,
        @parameter4 OUT,
        @parameter5 OUT,
        @parameter6 OUT,
        @parameter7 OUT,
        @parameter8 OUT,
        @parameter9 OUT,
        @parameter10 OUT,
        @parameter11 OUT,
        @parameter12 OUT,
        @parameter13 OUT,
        @parameter14 OUT,
        @parameter15 OUT,
        @parameter16 OUT,
        @parameter17 OUT,
        @parameter18 OUT,
        @parameter19 OUT,
        @parameter20 OUT;
    
    
      WHILE @@FETCH_STATUS = 0
      BEGIN
    
        SET @casecounter = @casecounter + 1;
    
        SET @whereclause = N'WHERE ';
        IF @query LIKE '%WHERE%'
        BEGIN
          SET @whereclause = N'AND ';
        END;
    
        SET @ifstatement = N'IF ';
    
    
        IF @parameter1 = 1
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 1;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter1 = 0
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 1;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter2 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 2
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 2;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter2 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 2
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 2;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter3 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 3
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 3;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
    
        IF @parameter3 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 3
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 3;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
        END;
    
        IF @parameter4 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 4
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 4;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
    
        IF @parameter4 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 4
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 4;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
        END;
    
        IF @parameter5 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 5
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 5;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter5 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 5
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 5;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter6 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 6
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 6;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter6 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 6
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 6;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter7 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 7
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 7;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter7 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 7
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 7;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter8 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 8
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 8;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter8 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 8
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 8;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter9 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 9
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 9;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter9 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 9
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 9;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter10 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 10
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 10;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter10 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 10
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 10;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter11 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 11
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 11;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter11 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 11
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 11;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter12 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 12
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 12;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter12 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 12
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 12;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter13 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 13
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 13;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter13 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 13
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 13;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter14 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 14
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 14;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter14 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 14
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 14;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
        END;
    
        IF @parameter15 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 15
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 15;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter15 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 15
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 15;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter16 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 16
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 16;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter16 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 16
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 16;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter17 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 17
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 17;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter17 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 17
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 17;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter18 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 18
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 18;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter18 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 18
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 18;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter19 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 19
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 19;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter19 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 19
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 19;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
        IF @parameter20 = 1
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 20
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 20;
    
          SET @whereclause = @whereclause + @fieldname + N' = ' + @param + N' AND ';
          SET @ifstatement = @ifstatement + @param + N' IS NOT NULL AND ';
    
        END;
        IF @parameter20 = 0
           AND EXISTS
        (
          SELECT
            *
          FROM
            dbo.tempParams
          WHERE
            rownum = 20
        )
        BEGIN
          SELECT
            @fieldname = param_alias,
            @param = param,
            @datatype = datatype
          FROM
            dbo.tempParams
          WHERE
            rownum = 20;
    
          SET @ifstatement = @ifstatement + @param + N' IS NULL AND ';
    
        END;
    
    
        PRINT '--CASE ' + LTRIM(RTRIM(CAST(@casecounter AS CHAR(100))));
        IF RIGHT(@ifstatement, 4) = 'AND '
          SET @ifstatement = LEFT(@ifstatement, LEN(@ifstatement) - 4);
        IF @ifstatement <> 'IF '
          PRINT @ifstatement;
        PRINT 'BEGIN';
        PRINT @query;
    
        IF @whereclause <> 'AND '
           AND RIGHT(@whereclause, 4) = 'AND '
          SET @whereclause = LEFT(@whereclause, LEN(@whereclause) - 4);
        IF @whereclause <> 'WHERE '
           AND @whereclause <> 'AND '
          PRINT @whereclause;
    	
    
        PRINT 'END';
        PRINT '';
    
    
        DECLARE @fetchnext NVARCHAR(MAX);
    
        SET @fetchnext = N'FETCH NEXT FROM ParamPivotCursor
    						INTO ' + @paramlist;
    
        EXECUTE sp_executesql
          @fetchnext,
          @cursorParmDefinition,
          @parameter1 OUT,
          @parameter2 OUT,
          @parameter3 OUT,
          @parameter4 OUT,
          @parameter5 OUT,
          @parameter6 OUT,
          @parameter7 OUT,
          @parameter8 OUT,
          @parameter9 OUT,
          @parameter10 OUT,
          @parameter11 OUT,
          @parameter12 OUT,
          @parameter13 OUT,
          @parameter14 OUT,
          @parameter15 OUT,
          @parameter16 OUT,
          @parameter17 OUT,
          @parameter18 OUT,
          @parameter19 OUT,
          @parameter20 OUT;
    
      END;
    
    
      CLOSE ParamPivotCursor;
      DEALLOCATE ParamPivotCursor;
    
      PRINT 'END';
      PRINT '';
      PRINT 'GO';
    
    
    END;
    GO
    

    Within this stored procedure, you will find several interesting things

    • Dynamic SQL
      • Traditional use for SELECT and UPDATES
      • Dynamic Pivot
      • Output parameters
    • Acceptable Uses of Cursors (this proc will never live in a production environment!)
      • Local Fast_Forward
      • Global Fast_Forward

    Using this procedure looks like this:

    See the EXEC statement
    USE [StackOverflow2013]
    GO
    
    
    DECLARE @param AS dbo.parameters
    
    --To change the results, use more or less parameters
    INSERT INTO @param
    SELECT '@CreationDate','p.CreationDate','DATETIME'
    UNION ALL
    SELECT '@postScore','p.Score','INT'
    UNION ALL
    SELECT '@voteCreationDate','v.CreationDate','DATETIME'
    UNION ALL
    SELECT '@votetype','vt.name','VARCHAR(50)'
    UNION ALL
    SELECT '@askerDisplayName','asker.DisplayName','NVARCHAR(40)'
    UNION ALL
    SELECT '@askerLocation','asker.Location','NVARCHAR(100)'
    UNION ALL
    SELECT '@askerUpVotes','asker.UpVotes','INT'
    UNION ALL
    SELECT '@askerDownVotes','asker.DownVotes','INT'
    UNION ALL
    SELECT '@askerRep','asker.Reputation','INT'
    UNION ALL
    SELECT '@askerAge','asker.Age','INT'
    
    
    -- This may take awhile if you have a high number of parameters
    EXEC dbo.useDynamicSQLinstead 
    @query = N'select 
      p.Id,
      p.Body,
      p.CreationDate,
      p.Score,
      v.CreationDate as voteCreationDate,
      vt.Name as voteType,
      asker.DisplayName as askerDisplayName,
      asker.Location as askerLocation,
      asker.UpVotes,
      asker.DownVotes,
      asker.Reputation,
      asker.Age
    FROM dbo.Posts p
    LEFT JOIN dbo.Votes v 
      ON p.Id = v.PostId
    LEFT JOIN dbo.VoteTypes vt 
      on v.VoteTypeId = vt.Id
    LEFT JOIN dbo.Users asker 
      ON p.OwnerUserId = asker.Id',
    @parameters = @param
    

    If you want to see the full stored procedure that is generated, please download the files or run the EXEC yourself.  As you can see, there are 1024 cases, and the proc is 26,641 lines long.

     

    Take the output, paste it into another window, remove the (10 rows affected) message at the top, rename the stored proc to “MonsterSearchProc” and click Execute. If you scroll all the way to the bottom, it should look like this:

    Why so many cases? Our refactor for a relatively small search procedure turned into a gigantic mess! An UNMAINTAINABLE collection of very precise queries surrounded with IF statements evaluating whether each of the parameters was being used or if it was NULL. Long story short- ORs are bad! They might seem like something you’re familiar with, but the truth is, they cause an awful lot of complexity to enter the execution plans.

    Each optional parameter has 2 ways it can be used: It can be NULL, or it can be populated.
    For every parameter that is in play in your query, you multiply the possibilities together.
    This gets us to Powers of 2.

    One parameter leads you to 2 different queries (CASES) 2^1
    Two parameters leads you to 4 different queries (CASES) 2^2
    .
    .
    Ten parameters leads you to 1,024 different queries (CASES) 2^10
    .
    .
    Twenty parameters leads you to 1,048,576 different queries (CASES) 2^20

    Without OPTION (RECOMPILE), the execution plan will end up “sniffing” the parameters and it will sniff all NULLS. NULLS mean that you don’t really want to filter, so you need to SCAN the entire table just to be safe. If you did want to filter, you could use a SEEK which is much less expensive in terms of READS and is consequently much faster.

    Our MonsterSearchProc, like the Dynamic SQL approach, will lead us to better execution plans. Indexes will be better utilized, and searching will be much faster.

    In case it isn’t clear, there are several reasons to use Dynamic SQL.

    • Easier to maintain that writing all of the queries, although Dynamic SQL can end up being quite complex
    • Dynamic SQL can do things that normal SQL just can’t do, like PIVOT with dynamic column names
    • You will only enter the proc cache as you need a plan for that particular combination of parameter use. The MonsterSearchProc definitely bloats the cache, and bloat will make your DBAs unhappy!

    Finally, here’s the BadSearchProc refactored using Dynamic SQL

    USE [StackOverflow2013]
    GO
    
    /****** Object:  StoredProcedure [dbo].[GoodSearchProc]    Script Date: 11/20/2018 10:40:49 PM ******/
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    
    
    
    
    
    CREATE PROCEDURE [dbo].[GoodSearchProc] 
    @CreationDate datetime = null,
    @postScore int = null,
    @voteCreationDate datetime = null,
    @votetype varchar(50) = null,
    @askerDisplayName nvarchar(40) = null,
    @askerLocation nvarchar(100) = null,
    @askerUpVotes int = null,
    @askerDownVotes int = null,
    @askerRep int = null,
    @askerAge int = null,
    @debug int = 0
    AS
    
    declare @SQL nvarchar(max) = ''
    declare @ParameterDefinitions nvarchar(max) = ''
    
    -- We are assuming that all of the fields in the fields list 
    -- are necessary, and all of the JOINs are necessary
    set @SQL = '/*Good*/ 
      select 
      p.Id,
      p.Body,
      p.CreationDate,
      p.Score,
      v.CreationDate as voteCreationDate,
      vt.Name as voteType,
      asker.DisplayName as askerDisplayName,
      asker.Location as askerLocation,
      asker.UpVotes,
      asker.DownVotes,
      asker.Reputation,
      asker.Age
      FROM dbo.Posts p
      LEFT JOIN dbo.Votes v 
        ON p.Id = v.PostId
      LEFT JOIN dbo.VoteTypes vt 
        on v.VoteTypeId = vt.Id
      LEFT JOIN dbo.Users asker 
        ON p.OwnerUserId = asker.Id '
    
    -- If at least one filter is used, add the WHERE clause
    IF @CreationDate IS NOT NULL 
      OR @postScore IS NOT NULL
      OR @voteCreationDate IS NOT NULL
      OR @voteType IS NOT NULL 
      OR @askerDisplayName IS NOT NULL
      OR @askerLocation IS NOT NULL
      OR @askerUpVotes IS NOT NULL
      OR @askerDownVotes IS NOT NULL 
      OR @askerRep IS NOT NULL 
      OR @askerAge IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
      WHERE '
    END
    
    
    IF @CreationDate IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        p.CreationDate = @CreationDate AND ';
    END  
    
    IF @postScore IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        p.Score = @postScore AND ';
    END
    
    IF @voteCreationDate IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        v.CreationDate = @voteCreationDate AND ';
    END
    
    IF @votetype IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        vt.name = @votetype AND ';
    END
    
    if @askerDisplayName IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.DisplayName = @askerDisplayName AND ';
    END
    
    IF @askerLocation IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.Location = @askerLocation AND ';
    END
    
    IF @askerUpVotes IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.UpVotes = @askerUpVotes AND ';
    END
    
    IF @askerDownVotes IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.DownVotes = @askerDownVotes AND '
    END
    
    IF @askerRep IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.Reputation = @askerRep AND ';
    END
    
    IF @askerAge IS NOT NULL
    BEGIN
      set @SQL = @SQL + '
        asker.Age = @askerAge AND ';
    END
    
    --Remove the trailing 'AND '
    set @SQL = LEFT(@SQL, LEN(@SQL) - 4)
    
    set @ParameterDefinitions = 
      '@CreationDate datetime,
      @postScore int,
      @voteCreationDate datetime,
      @votetype varchar(50),
      @askerDisplayName nvarchar(40),
      @askerLocation nvarchar(100),
      @askerUpVotes int,
      @askerDownVotes int,
      @askerRep int,
      @askerAge int'
    
    if @debug = 1
    BEGIN
      PRINT @SQL;
    END
    
    --Execute Dynamic SQL
    EXECUTE sp_executesql
      @SQL,                       --Dynamic SQL string
      @ParameterDefinitions,      --Parameter definitions
      @CreationDate,              --Parameter1
      @postScore,                 --Parameter2
      @voteCreationDate,          --Parameter3
      @votetype,                  --Parameter4
      @askerDisplayName,          --Parameter5
      @askerLocation,             --Parameter6
      @askerUpVotes,              --Parameter7
      @askerDownVotes,            --Parameter8
      @askerRep,                  --Parameter9
      @askerAge                   --Parameter10
    
    
    
    GO
    

    In my next post, I will go over what is happening in the execution plans for the BadSearchProc, MonsterSearchProc, and GoodSearchProc.

    Thanks for reading!