• You’re NOT the one

    We had been dating for close to two years when he gave me a very special gift for Christmas of 2022. A Bath and Body Works shower scrub scented with roses, my favorite scent, tucked away inside my stocking. As a mother of four, my stocking was usually the neglected one since I was the one doing the stockings, and I’d never think to buy special small presents for myself. I appreciated that he thought of me enough to fill my stocking with small gifts, and the message on this gift was precious. “You’re the one”.

    When I saw it, it warmed my heart and worried me. I didn’t want to draw attention to the message on the shower scrub. I was afraid to read too much into the words on the bottle, but I secretly hoped that he meant it. I was terrified that he was idealizing me, and I knew if that was the case, it would not last forever. Maybe he was still in the honeymoon phase of the relationship. Time would tell.

    “You’re the one” sat in my shower mostly unused until he broke up with me. I wanted to hold onto that message for as long as I could, so I used it sparingly. Words have always been so important to me, and he wasn’t much for words. He’d talk all day about the most mundane of things, but using words to talk about feelings was damn near impossible for him. I hoped in time that would change, but it never did. I thought he just needed time to warm up to me.

    When he ended our relationship less than a year later, I was devastated. I quickly started using the shower scrub so I could throw away the daily reminder of him. But the daily reminders were all over my house, my neighborhood, my city, and my mind. He had become a major part of my life, and now he was gone. There would be no escaping the onslaught of triggers that would cut deep into my heart and remind me of what I had lost.

    Four months later I still wonder what went wrong for him, but I also wonder why I have to keep repeating this pattern: being in relationships with men who are emotionally constipated and unable to talk about their feelings, but who are also deeply incapable of acknowledging their own needs even to themselves.

    I have wasted nearly three decades of my life on men like this. because guess what? My dad was like this. Thanks dad. The main difference between my dad and the important men in my life is that my dad was not conflict avoidant. He was aggressive, not passive or passive-aggressive.

    I was aware of this pattern when we began dating, but the awareness did not prevent me from repeating it. It did not prevent me from hoping that he would be different. There were ways that he showed up that were very different from my past relationships. Ways that were healing. He was consistent, and warm, and liked to spend time with me. He tried so hard, up until the moment he didn’t want to try any longer. He never expressed any signs of an internal struggle within himself, or things that I could do differently that would make him feel more connected. I am left yet again wondering what I did wrong or how I was not enough, when I know consciously that things were never going to work out the way that I needed them to, because he was incapable of communicating his emotional state.

  • Unattended Grief and The Dark Night of The Soul

    I’m in a very sorrowful place, crying almost daily and doing everything I can to process the emotions that are long overdue.

    I know intuitively that it is time to feel the feelings I have been pushing down for as long as I can remember.

    How did I get here?

    I’m sure if I dug deep enough I could trace everything back to my childhood, but it isn’t the time to do that. For now, I’ll start with adulthood and my history with anti-depressants.

    I was married at 21. My husband was only 6 months older than me, and we were both attending college and working as food servers during the early years of our marriage. At some point I caught a flu that lasted over a month and drained me of my energy for several more. I went to the university health clinic and discovered I was infected with mononucleosis. Looking back I don’t know how I managed to get my studies done but somehow I did.

    The months following the acute illness were a struggle. My appetite was non-existent and I had no energy. When the doctor suggested Prozac, I decided to give them a try. I’ve always struggled with side-effects to medications, and this marked the beginning of attempting anti-depressant after anti-depressant and contemplating how much the benefits were worth the costs. Are the nausea and headaches worth having the crippling sadness taken away? With Prozac, the side effects were horrible. I still remember experiencing my first “brain-zap” while standing in the Chili’s that I worked at in Reno. I didn’t stay on that drug much longer.

    Within a few years I would have my first child and experience bouts of post-partum depression. Sometime after the rush of childbirth wore off the exhaustion and depression began. I was simultaneously happy and fulfilled while also exhausted and miserable. I turned into a giant clam, and I told no one that I was suffering. I’d have to repeat this struggle 2 more times before I would finally tell my doctor as I approached delivery that I was most likely going to get post-partum depression.

    It was between my third and fourth child that a doctor would find an anti-depressant that I found tolerable. The doctor was an endocrinologist I went to be checked for thyroid problems. Within days of being put on thyroid medication, the depression I felt lifted. For a few years, that’s all I needed, but then my job in tech started becoming stressful in ways that no woman should ever experience. My appetite disappeared, I lost weight, and I wasn’t sleeping. When I went in for a regular checkup with that doctor, he suggested I try an atypical anti-depressant called Mirtazapine. He suggested it because two of the side effects could work in my favor: weight gain and sleepiness.

    Initially that drug was a miracle that I was grateful for. It wasn’t until over a decade later I began to understand what a nightmare it really was.

    Sometimes I wonder what person I would be and where my career would’ve ended up be if I had not taken that drug. I slept well and I looked healthy, but I was emotionally blunted and only felt a few of the major emotions: mainly anxiety. Depression that had taken away my appetite and my will to live morphed into a very functional, career-driven anxiety while on Mirtazapine. I regret the disconnected mother I was during those years. I regret how I let the stress of taking care of my family financially overwhelm me to the point where that drug seemed like a great way out.

    Fast forward to the end of 2019. Mirtazapine had stopped working, and I wasn’t sleeping well anymore. My job at that time was quite stressful, but not in the ways that my first job in tech was. I quit taking Mirtazapine and did a sleep study shortly thereafter. What I learned was interesting. When I laid in bed for hours thinking that I was not asleep, I was in a light stage of sleep. I also learned that for whatever reason, I really wasn’t getting much REM sleep. Looks like I probably wasn’t processing emotions well either: https://neurosciencenews.com/sleep-emotion-processing-20578/

    It took me years to get off of Mirtazapine. I had been gradually tapering down for close to 3 years prior, thinking that many of my other health problems might be tied to that drug. At the end of 2019, I was to the point where I could go off of it and not experience a psychotic break because of it. Luckily I had a doctor who would work with me and my own plan for how to taper down. Most doctors would taper you off much faster than I found feasible, so eventually, I would come up with my own plan.

    I ended up quitting the stressful job in April of 2020, after the pandemic had started. The stress of the societal upheavals combined with my marriage ending was intense, but looking back, I still was not emotionally present in the way that I am now. I believe it took four years for the emotional blunting effects of that drug to wear off.

    At the end of 2023, my heart was broken by a rather sudden and cold withdraw by a person whom I thought would never do such a thing to me. I was going through some other major stressors when he told me it was over. I was worried about possibly losing my job in consulting and dealing with a possible ulcer and gastritis infection. I was in pain and losing weight, and now the person I felt closest to in the world was gone.

    I knew instinctively that the sadness that surfaced after the breakup could not entirely be pinned on the breakup. I knew that there were other emotions tucked away that I had neglected tending to: the guilt and sadness for the end of my marriage, the death of the person I used to think that I was, the guilt for not being a better mother and for being career-obsessed when my children were young. The guilt of being a reactive coworker who’d get triggered at the slightest thing, and the employee who gave too much to her job while neglecting her marriage and her children. I regret all the present moments that I wasted by being trapped somewhere else, worried about the future or obsessing about the past.

    I find myself somewhere in The Dark Night of The Soul. Perhaps I’m still experiencing the existential depression. I feel that my ego is dying and none of the things that used to drive me matter much anymore. What does matter to me? In many ways, I have lived my life up to this point for others, deriving no real happiness from it and running on the fumes of anxiety, trying to be what everyone else expected me to me. I am done with that style of living, if it can be called that. I think what I’ve been doing can more accurately be called “coping”.

    A few things are emerging, and I don’t find it coincidental that they are all creative. Writing, music, dance, and painting all hold a sacred space for me. I love to cook and travel and spend time in nature. I feel called to finish a book I’ve been working on for years. I’m questioning whether or not I will always work in tech. Perhaps after my children are grown I will reevaluate and see if I want to teach like I originally planned to when I was in college.

    I don’t know what the future holds. I’m scared these dark emotions will continue to linger and no rays of sunshine will ever come through again. I’m scared that I’ll break from the pressure of having to support my family when I really just want to rest. I’m finding ways to take care of myself that I have never bothered with before, like meditation and learning how to be still and in solitude. I have hope that the darkness will pass, because it has always passed before. Is this time different? Yes, very much so, but I know that it is different in ways that will significantly change me the rest of my life. I’m in the in-between stage between being in a cocoon and being a butterfly. If you look too closely, you would see a gooey disorganized mess, but if you wait long enough, you should see the butterfly.

  • Paralyzed with Fear

    I must’ve been about 7 or 8 years old, but it’s unclear. Most likely it was somewhere between 5 and 9.

    I woke up suddenly with a sharp pain under my left ribs, and a man on top of me. He had his other hand on my throat and a sharp object in my side. The pain under my ribs was so intense that I wasn’t aware of the feeling of being raped.

    The man was familiar to me. Family. He was probably in his late teens or early twenties at the time.

    He said things to me, but the details are unclear. I laid there as still as I could, confused about what was happening and in shock from the pain.

    This sequence of events was from a dream that woke me a few years ago. It was at a point in my life when I was in therapy during my divorce and going through EMDR treatments for a handful of other traumatic experiences I’ve had. The dream was so real that the pain under my ribs woke me. As soon as I woke up, I knew who the man was: my mom’s youngest brother, a schizophrenic who’d struggled with drug abuse for many years and who also served time in prison for being caught in a child-porn sting.

    I knew some details about this man’s childhood because through the years my mom and other family members revealed the horrors that came from growing up in that household. Both brothers would end up becoming schizophrenic. It was clear that they suffered the brunt of the abuse. I don’t know much about what the sisters went through, but I’m sure it was deeply traumatizing as well.

    Growing up in Las Vegas, my mother kept us away from her family in Ogden, Utah. I remember traveling from Las Vegas to Ogden only one time before traveling for my grandfather’s funeral at some point during my teenage years. It didn’t occur to me until years later that she may have been strategically keeping her distance to protect her own children from the monsters in that house. Sadly the next generation did not escape unscathed- I know that the same man in my dream, my mother’s youngest brother, abused some of my cousins.

    I do not know if he abused me or my brother. My mother died in 2006. I asked my dad if he was aware of anything, and he was not. My dad had his own traumas he suffered from while I was growing up, and it wouldn’t surprise me if he was too caught up in them to notice anything suspicious happening with his children while we were in that house visiting my mother’s family.

    I do not know if the dream that I had was a memory. Perhaps my brain concocted it from the things I learned over the years about the horrific childhood my mother and her siblings had.

    If it was real, it would explain many things about why I am the way that I am. The fears that I have that do not make sense.

    Perhaps I will never know if that dream was real, and perhaps that is for the best. Is it possible to heal things that your consciousness is repressing?

  • Heartbreak and Art

    Recently I started drawing and painting with watercolors with my daughters, as a way to give myself a break from the sadness of heartbreak. Most people I’m close to know that I do not consider myself artistic, beyond dance. What they don’t know is that my mother was an avid painter, and she was talented. I remember growing up with an oil painting of hers in our living room of a forest scene with a river and a deer. It was beautiful.

    Looking back on my childhood, I knew I was not talented with drawing or painting very early on. I have clear memories around age 7 or 8 of trying to draw, becoming frustrated, and giving up. I didn’t try much after that point, and only did enough to get by when it was required for a class assignment.

    Perhaps the heartbreak sadness put me in a different neural place than I am normally. I have spent the majority of my life being anxious and emotionally shut down. I can count the number of times I’ve been sad as an adult on one hand. It’s not a place I’m comfortable.

    When I began to draw and later paint, I discovered that I am not as terrible as I remember being. This fascinated me, as it was like a piece of my mother I didn’t know existed was alive in me. She died suddenly on my birthday in 2006, my first year working as a software developer in tech. The small piece of her emerging made me wonder what else from her was inside of me, possibly suppressed all these years.

    I’ve been reflecting on these thoughts the last few weeks, and what I’ve found was startling. I buried all the soft things in myself away from the world, myself and even most of the people in my life, to be safe.

    Why though? What would possess me to do that?

    My father was abusive. He verbally abused my mom almost every night, escalating to physical violence more times than I’d care to admit. My brother and I occasionally became his targets, but my mother was his favorite victim. She was so kind and patient, even while he was in an alcoholic stupor. She did not deserve his violence. I think I must’ve blamed her somehow, and on some deep level decided I’d rather be like my dad than be on the receiving end of the violence. Childhood psyche’s do weird things to protect us. Perhaps I gravitated towards math and away from English and the arts in an attempt to be like him. As young as 5, I was subconsciously shaping my identity in a way to embrace him and shun her.

    My identity formation would not be complete for quite some time. In high school, I was quiet and barely spoke, and people often demanded that I stop mumbling. I felt invisible. Nothing I had done through the years to gain the approval of my father had worked. He was an alcoholic workaholic who missed school performances and award celebrations often. It wasn’t until college that I stopped mumbling, and it wasn’t until I was working in tech that I started speaking in a way that would discourage men from ignoring me and talking over me. I was firm and assertive, quick with the words, often sharp-tongued. I see that now as an over-correction to the mumbling and to the verbal abuse, but it would be one that would persist for a very long time.

    Kindness and patience were traits in me that only my children would see. I’ve always been grateful knowing that the good in me came from her. The traits that made me good in math and tech came from him, or so I thought. It’s possible psychology pushed me that direction more than genetics did.

    With these new revelations about my mother’s traits in me, I’m excited to find out how I will change as I attempt to dig into the past and reconcile the parts of me that have been long exiled. I’ve missed her so much over the years, but I’m only beginning to properly grieve the loss. Now that I know there’s more of her inside of me than I thought, in some small way I find myself comforted.

  • Reflections on Dating, at 48

    I do not enjoy dating, and also do not have much experience with it. I only dated a small handful of men (boys?) by the time I got married at 21.

    Now that I’m older, I have things going for me that I did not back then. I’m a bit wiser, educated, and not in a rush to have children since I have four, but I find myself missing the naïveté of youth. I miss the excitement of dating and getting to know someone without the weight of the world on my shoulders. I miss the days when it felt like time would never run out, and we had everything to look forward to and nothing to regret. I miss the simplicity of days long gone.

    Lately it seems that everyone dating at my age is in a weird rush. So eager to find the next person but too busy to get to know them, willing to make quick judgments based on looks and initial chemistry, and not interested in assessing for compatibility and depth. It makes me wonder if our culture has irrevocably broken relationships with its collective short attention span and fantastical definition of love.

    Not all hope is lost though! While dating apps have trashed the modern dating scene, there is a focus online with emotional intelligence and healing. Many terrible people who have no real intent on “doing the work” use the psychological lingo as an attempt to signal that they are healed while continuing in their toxic behaviors, but there are also many good people undertaking the hard work of looking inward and correcting their own bad patterns. I’m sure my own bad patterns will show up on this blog in many of my posts as I have painfully become aware of many of them over the last several years.

    I’m grateful for therapists and psychologists putting free content online, and in abundance. I am thankful for the wisdom of people like Matthew Hussey and Jay Shetty. I find a great deal of hope knowing that so many people are listening to them.

  • T-SQL Merge Statements- Just Don’t

    There are plenty of reasons not to use a MERGE statement. Many of those reasons have been covered in depth before. Instead of covering them myself, I will point you to another useful post on the subject.

    Bugs Microsoft refuses to fix: https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

    The bugs alone are reasons enough to avoid using it, and also to refactor any existing MERGE statements you have in your code base. Those bugs include ones that have performance implications as well as ones that could lead to incorrect results. Long story short, Microsoft refuses to fix several of them, and some of the other major issues are “by design”!

    If you still feel the desire to use them, please keep reading, and I hope by the end of this blog you will be convinced that

    1. Less statements does not always lead to better code or better performance.
    2. MERGE is like a VAMPIRE and once it is in your codebase, it’s just a matter of time before it drains your servers of their lifeblood.

    Less Statements are Not Always Better!

    Most application developers understand that with code what you see isn’t what you get. Sometimes writing a piece of code may be short and concise, but what the compiler does underneath the covers may be quite complex. This is often what separates inexperienced developers from the expert ones. This basic concept can also be applied to the database, by considering what the SQL Server engine does with the TSQL statement. A statement that looks simple may end up becoming quite complex and difficult for the engine to process, and how the processing is done will be influenced by the size of the data and the indexes on those tables, as well as how the query itself is written.

    It always makes me cringe when I hear an application or database developer talk about the “ease” of writing a MERGE statement compared to the difficultly of writing separate INSERT/UPDATE/DELETE statements and possibly additional logic for any logging code that they want, wrapped inside a transaction. What I hear is that they buy into the myth that “less code is better” and that they don’t understand they are prioritizing their own ease of writing code OVER how the code performs for the users. <sigh>

    Even if that developer can convince me that the only type of MERGE statement in their database is the OVERKILL CASE (cases defined below), I will still push back against it’s use based purely on the fact that having MERGE around at all brings serious risk along with it.

    WAYS TO USE THE MERGE STATEMENT

    1. Uses 1 of 3 underlying statement (OVERKILL CASE)- You only need one type of underlying statement (INSERT/UPDATE/OR DELETE) but instead of just writing that statement, you insist on MERGE most likely because you really love the OUTPUT functionality. For some odd reason Microsoft gave the Merge Output more powers than it gave INSERT/UPDATE/DELETE. Vote for my suggestion here: https://feedback.azure.com/forums/908035-sql-server/suggestions/39249022-tsql-expand-output-clause-functionality. Developers, please stop prioritizing the ease of writing the logging code over performance. Not to mention MERGE is clunky and difficult to write and maintain.
    2. Uses 2 of 3 underlying statements– At least on the surface I can understand why you bothered with the clunky syntax of MERGE. You may think on some level that you would get separate execution plan operators for each logical part of the MERGE, but you’d be wrong. The engine wants to perform your request all at once, which may mean loading a very big table into memory, locking it up for other users, and taking way too long to do what it really needs to do.
    3. Uses 3 of 3 underlying statements– Same flaws as #2, but with even more syntax complexity. You’re not gaining anything by writing one seriously complex statement, please stop. Well, you may be gaining the ire of your database administrators who watch these suckers go off the rails.

    MERGE is a VAMPIRE!

    Let’s assume for a second that you only use the OVERKILL case outlined above and you never see the bugs manifest in your system. I’d personally doubt the veracity of such statements, but let’s go ahead and assume they are true for the sake of argument.

    You may have Standards outlined somewhere that explicitly mention MERGE is only allowed for certain scenarios. Unless your processes are TIGHT, someone is bound to come along, see a MERGE statement and take it as permission that MERGE statements in general are OK. They will not see the nuance of it’s use. Guess what? Neither will your pull request reviewers. Some DBA’s may even miss it. BAM! Next thing you know you have a server down in production. Most likely it won’t happen right away because the table it’s written against is relatively small. But just wait. Data has a tendency to grow.

    Your code is your standards. Repeat it with me: YOUR CODE IS YOUR STANDARDS.

    Merge is like a vampire. Once you invite it in, it’s only a matter of time until it bites you!

  • The Database is a Bridge and a Magnet

    In 2012, I took a huge leap. I left a job where I was doing exceptionally well because I recognized the need to get away from Visual Foxpro and develop skills in other areas. I knew that besides getting my feet wet with SQL Server, I would probably not learn any more worthwhile technical skills at that company.

    I planned on using SQL Server as the bridge to the next thing, but ended up spending a great deal of time there and consequently honed that skill so much that people think I’m a DBA and often refer to me as one. Becoming a DBA was never my plan. I couldn’t care less about what drives your data is on, or whether you use replication or Always on Availability Groups. Being on a newer version of SQL Server Enterprise version is pretty cool though. Doing database administrator types of things is not fulfilling to me but reading and writing code is. Emphasis on writing. And I don’t mean Powershell.

    The job I took right after I left was purely a TSQL developer role. I went from a very small privately owned company to a big publicly traded Fortune 500 one. I loved the coding work I did there, but the time I spent in meetings bored me to tears, so when a contact from the first job reached out and asked me if I was interested in moving to Salt Lake City, I said YES.

    I knew that taking that job as their IT Manager would mean doing more than being a SQL Server and ERP developer. At the time, I thought being a network administrator interested me. My dad owned his own company doing just that, so I knew enough to be intrigued. In reality, I really don’t like reading event logs, and I prefer to read code to figure things out.

    I enjoyed my time with that company, and accomplished many great things. I spent a great deal of time on reporting, automating reporting, and coding EDI integrations. I also greatly enhanced a warehouse management system and fully implemented it while there. I was quite proud of the work I did, even though a good deal of it was in Foxpro. I was helping this company to become data driven. The work mattered, and I was valued and respected. I ended up leaving that job after 3 years, because I knew that I had grown as much as I could there. I needed to work for a tech company again, one that had their own enterprise level application and a technical team that I could learn from.

    The next job was really close to my home, and even though the interview was underwhelming, I accepted the job anyways. They had what I needed in their environment- they used SQL Server and had their own enterprise level application even though it was in another dying language. I did mostly database work while there, working on complex ETL projects using SSIS and performance tuning slow stored procedures. The culture wasn’t right for me though. Before this job, I really didn’t care much about culture, but now I know better.

    That brings me to my current job. I’ve been there over a year and half now, and was hired for my database developer skills. I quickly proved myself to them by rewriting a complex stored procedure that other developers must’ve been too afraid to change in any meaningful way. After that project, I was assigned to several other database projects, and ended up becoming a performance tuning guru that other teams come to for advice when their code doesn’t perform.

    Even though I knew I was appreciated and I had skills that others lacked, it wasn’t fulfilling to me. Perhaps the thing in my personality (or neural makeup) that drives me to hyper-focus on a thing to the point of absurdity was blinding me to the fact that someone moved my cheese! I was quite content with the work I had done at my first job. I recognized that being in a huge enterprise level application was the best environment for me to learn in. I learned fast and retained the information better than many around me. This helped me to become a developer that others would turn to for help. Thank you John Carpenter for showing me my potential!

    So for several months I hit the job search again, but I am quite picky and my list of requirements has grown over the years. I needed to be hired for my database skills, specifically SQL Server, but the environment needed to be one where I could do more than just write T-SQL. I’m interested in several things but being an application developer has been the thing I’ve been searching for all these years. I found a job that was pretty close to what I’ve been looking for, although I would’ve been more of a database/API developer than an application developer. I gave my notice, but to my surprise, my current job counter offered and gave me a chance to do more inside their enterprise application. FINALLY!

    Have I really found what I’ve been looking for all these years? I don’t know. I hope so. I can’t imagine getting far away from the database though, because of the utter lack of developers who possess these skills. Apparently SQL is easy to learn but incredibly hard to master, and many developers never bother to really study it. I think that’s tragic, because it is a very useful skill. In a way, I wish that making SQL fast was more fulfilling to me. I just don’t find it challenging in the same way that I found application development challenging.

    My advice to others: recognize that the database can be a bridge to the next thing. There is value in studying it, but once you set yourself apart, it may be hard to get away from. This might be considered a good thing, depending on what fulfills you.

    My advice to recruiters: not all database developers want to be DBAs. Just because a person gains expert level knowledge in one thing doesn’t mean they can’t do the same in something else. You should be assessing ability to learn more than skills previously attained, but perhaps it’s the hiring managers that need that lesson more than the recruiters do.

  • 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!

  • I’m Speaking At Pass Summit 2018!

    I am thrilled, nervous, overwhelmed, humbled, grateful, but mostly LUCKY.

    I’ve been involved with the Pass community since 2014. In 2017, I began presenting at local user groups and SQL Saturdays. So far I think I’ve only done three SQL Saturdays and two local user groups.

    I blog, but only barely.

    I write stuff on Medium occasionally, but that’s more about life than tech.

    I want to do more, but life (mainly my health) gets in the way. It’s a struggle to find the energy to get out of bed every day, manage to be engaged and productive at work, and raise my four children (18,16,13, and 8 years old) with my husband who is battling his own health issues. The last year and a half has been terribly rough. I went from training to compete in a fitness competition to getting several scans of my brain seemingly overnight.

    Things are getting better though! I’ve been feeling much better lately and my job is going very well. I found out earlier this week that I’d be presenting at Summit. Yes, it’s last minute. I was tempted to turn it down, but I know that one month is plenty of time to prepare without OVER preparing.

    How did I get so lucky? I am sure there are speakers out there who have done much more than I have in the Pass community that are scratching their heads about my selection, even if it was clearly last minute and due to spaces needing to be filled. So I am going to tell you!

    I started attending Summit in 2014. 2014 through 2016 I learned a great deal. I took several sessions on T-SQL topics because I’m mainly a database developer. I was briefly a DBA, so I’ve also taken some sessions on random DBA topics just so I could be more knowledgeable. Reporting, BI, and ETL have all come up through my career, but they were never the constant. T-SQL was. For the first few years, the T-SQL coverage at Summit was FANTASTIC.

    Somewhere around 2016 or 2017, the T-SQL coverage at Summit dropped significantly. Last year (2017) I submitted a session called SHARPen Your BILMScript. I had recently begun working with BIML and I’d been trying to work with C# for years. I really enjoyed the subject so I submitted my session. Of course it wasn’t selected. BIML is a niche topic, I don’t work for Varigence, and I don’t have my name on any BIML technical books. I didn’t submit any T-SQL sessions that year because usually there is an abundance of T-SQL sessions at Summit, and they’re done by well known speakers like Aaron Bertrand. I can’t compete with that!

    At Pass Summit 2017 there were barely any sessions on T-SQL. While I admit there was probably more than I found when I searched for them, from my perspective there was not enough. For 2018, I decided to submit two T-SQL sessions. The call for speakers came and the selections were made, but I was not chosen.

    At some point between then and now I decided that I would start making SQL videos and blogging more (inspired by Bert Wagner I suppose). I’ve made one video so far. Shortly after I posted that video on YouTube, I was contacted by Pass and asked if I could present my Behold The Power of Dynamic SQL session. Whoa.

    YES!!! Of course I will present!

    I know I must’ve been chosen because Pass had a spot open and realized that their T-SQL content was lacking, and I happened to have two sessions covering that area. I saw the hole from prior years and I tried to fill it. I was in the right place at the right time.

    Like I said before, I was LUCKY.

    See you at Summit

    PS. I have a degree to teach. I was going to teach math to high school students, but life is funny and I ended up becoming a software engineer instead. It’s time I combine my experience with my education and share what I’ve learned the last 12 years.

  • Dynamic SQL- When To Use?

    The most important question to answer when dealing with Dynamic SQL is:

    When should it be used? 

    Answering that question correctly requires a good understanding of how SQL Server’s Optimizer works.

    Dynamic SQL gives you the ability to be precise, often in situations where the application code is asking too much from the database. This is often referred to as:

    • One-Size-Fits-All queries
    • Catch-All queries

    Why does being precise matter?

    I like to refer to this as the What’s For Dinner? problem. A wife asks her husband what’s for dinner, so the husband decides to respond with questions to see what’s she in the mood for. However, they are already in the car, and she only gives him vague responses such as “I don’t want Mexican food”, or “I really want a margarita, and maybe a steak”. He ends up driving all over the town, circling back and forth several times, wasting time and gas. If she had been precise in her request, he could’ve driven straight there, and minimized the amount of time and gas spent trying to procure dinner.

    The husband is the Optimizer. The wife is the query. The husband wants to answer the question the best possible way, but the wife is making it incredibly difficult. If you needed any further proof that I think like a dude, there it is. SQL Server wants to answer your question (query) quickly, but if you don’t know what you want, you really should solve that problem first.

    Now, that we know we need to be precise, how do we go about being precise? I will delve into the answer in follow up blog posts.
    Thanks for reading!