{"id":2767,"date":"2015-04-02T05:29:43","date_gmt":"2015-04-02T12:29:43","guid":{"rendered":"http:\/\/3.209.169.194\/blogs\/kimberly\/?p=2767"},"modified":"2015-11-03T10:41:53","modified_gmt":"2015-11-03T18:41:53","slug":"stored-procedure-execution-with-parameters-variables-and-literals","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/stored-procedure-execution-with-parameters-variables-and-literals\/","title":{"rendered":"Stored Procedure Execution with Parameters, Variables, and Literals"},"content":{"rendered":"<p>In Nov 2014, SQLskills made an <a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/calling-user-group-leaders-want-present-2015\/\" target=\"_blank\">offer to user group leaders<\/a> to deliver remote user group sessions in 2015\u00a0and we&#8217;ve been having great fun delivering them. So far the team has delivered 34 sessions and we have 72 signed up! At my last session (for the San Diego SQL Server User Group), I delivered a session on stored procedures. It was great fun and I\u00a0had a few questions at the end that made me come up with some new sample code&#8230; that&#8217;s where this post is coming from!<\/p>\n<p>The question was\u00a0because I said to make note of something being a parameter and not a variable (during the lecture) and so the question was &#8211; what&#8217;s the difference and how does that change the behavior?<\/p>\n<h2><span style=\"text-decoration: underline;\"><strong>Definitions<br \/>\n<\/strong><\/span><\/h2>\n<p><strong>Parameters<\/strong> are values\u00a0that are being passed <em>into<\/em> a stored procedure. Since these are part of the call to execute the procedure; these are &#8220;known&#8221; during compilation \/ optimization (but, that&#8217;s only IF SQL Server has to compile \/ optimize,\u00a0more on that in a minute)<\/p>\n<p><strong>Variables<\/strong> are\u00a0assigned at runtime and therefore are\u00a0&#8220;unknown&#8221; during compilation \/ optimization.<\/p>\n<p><strong>Literals<\/strong> are known at all times as they are exactly that &#8211; a simple \/ straightforward value<\/p>\n<p>Review this heavily commented stored procedure:<\/p>\n<p>CREATE PROCEDURE ParamVarLit<br \/>\n    (@p1    varchar(15)) &#8212; defining the parameter name @p1 AND the data type<br \/>\nAS<br \/>\n&#8212; Declarations \/ assignments<br \/>\nDECLARE @v1 varchar(15); &#8212; defining the variable name @v1 AND the data type<br \/>\nSELECT @v1 = @p1;        &#8212; assigning the variable to the parameter input value<\/p>\n<p>DECLARE @v2 varchar(15); &#8212; defining the variable name @v2 AND the data type<br \/>\nSELECT @v2 = &#8216;Tripp&#8217;;    &#8212; assigning the variable to a literal value<\/p>\n<p>                         &#8212; Note: also acceptible is this format<br \/>\n                         &#8212; DECLARE @v2 varchar(15) = &#8216;Tripp&#8217;<\/p>\n<p>&#8212; Statement 1 (using a parameter)<br \/>\nSELECT [m].*<br \/>\nFROM [dbo].[member] AS [m]<br \/>\nWHERE [m].[lastname] = @p1 <\/p>\n<p>&#8212; Statement 2 (using a variable)<br \/>\nSELECT [m].*<br \/>\nFROM [dbo].[member] AS [m]<br \/>\nWHERE [m].[lastname] = @v1; &#8212; or @v2, these will work EXACTLY the same way!<\/p>\n<p>&#8212; Statement 3 (using a literal)<br \/>\nSELECT [m].*<br \/>\nFROM [dbo].[member] AS [m]<br \/>\nWHERE [m].[lastname] = &#8216;Tripp&#8217;;<br \/>\nGO<\/p>\n<p>In the stored procedure you can see that there&#8217;s one input parameter, we&#8217;ll assign that when we call the stored procedure.<\/p>\n<p>We can assign parameters by name (line two) or we can assign them by position (note, when\u00a0you have a lot of parameters this can be frustrating)<\/p>\n<p>EXEC [dbo].[ParamVarLit] @p1 = &#8216;Tripp&#8217; &#8212; assignment by name<br \/>\nEXEC [dbo].[ParamVarLit] &#8216;Tripp&#8217;       &#8212; assignment by position<\/p>\n<p>Outside of that, everything else is being defined \/\u00a0assigned \u00a0within the stored procedure.<\/p>\n<h2><span style=\"text-decoration: underline;\">Setup<\/span><\/h2>\n<p>Now, we have to see how each of them works from an optimization perspective. To do this, I&#8217;m going to use a sample database called Credit. You can download a copy of it from <a href=\"https:\/\/www.sqlskills.com\/sql-server-resources\/sql-server-demos\/\" target=\"_blank\">here<\/a>. Restore the 2008 database if you&#8217;re working with 2008, 2008R2, 2012, or 2014. Only restore the 2000 database if you&#8217;re working with\u00a02000\u00a0or\u00a02005.<\/p>\n<p>Also, if you&#8217;re working with SQL Server 2014, you&#8217;ll potentially want to change your compatibility mode to allow for the new cardinality estimation model. However, I often recommend staying with the old CE until you&#8217;ve done some testing. To read a bit more on that, check out the section titled:\u00a0<strong>Cardinality Estimator Options for SQL Server 2014<\/strong> in <a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/sqlskills-procs-analyze-data-skew-create-filtered-statistics\/\" target=\"_blank\">this blog post<\/a>.<\/p>\n<p>So, to setup for this demo &#8211; we need to:<\/p>\n<p>(1) Restore the Credit sample\u00a0database<\/p>\n<p>(2) Leave the\u00a0compatibility mode\u00a0at the level restored (this will use the legacy CE). Consider testing with the new CE but for this example, the behavior (and all estimates) don&#8217;t actually change.<\/p>\n<p>(3) Update a row and add an index &#8211; use this\u00a0code:<\/p>\n<p>USE Credit;<br \/>\nGO<\/p>\n<p>UPDATE [dbo].[member]<br \/>\n    SET [lastname] = &#8216;Tripp&#8217;<br \/>\n    WHERE [member_no] = 1234;<br \/>\nGO<\/p>\n<p>CREATE INDEX [MemberLastName] ON [dbo].[member] ([Lastname]);<br \/>\nGO<\/p>\n<h2><span style=\"text-decoration: underline;\"><strong>Execution<\/strong><\/span><\/h2>\n<p>To execute this procedure it&#8217;s simple &#8211; just use one of the execution methods above and make sure that you turn on &#8220;Show Actual Execution Plan&#8221; from the Query, drop-down menu.<\/p>\n<p>EXEC [dbo].[ParamVarLit] @p1 = &#8216;Tripp&#8217; &#8212; assignment by name<br \/>\nGO<\/p>\n<p>This is what you&#8217;ll see when you execute:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2768\" src=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-1024x802.png\" alt=\"ExecutionPlans\" width=\"1024\" height=\"802\" srcset=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-1024x802.png 1024w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-300x235.png 300w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-900x705.png 900w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans.png 1073w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p>Notice that the first statement and the third statement both use an index to look up the row but\u00a0statement two does not. Why? It&#8217;s all tied to whether or not the value was &#8220;known&#8221; at the time of optimization. And, most importantly &#8211; this procedure\u00a0was the executed when there wasn&#8217;t already a plan in cache. Because there wasn&#8217;t already a plan in cache, SQL Server was able to optimize this procedure for the parameters passed in at THIS execution. This does NOT happen for subsequent\u00a0executions.<\/p>\n<p>More specifically, when the statement KNOWS that the value is &#8216;Tripp&#8217; (statement 1 knows because it&#8217;s getting optmized for &#8216;Tripp&#8217; and statement 3 knows because the value is hard-coded for &#8216;Tripp&#8217;)\u00a0then SQL Server can look to the statistics to determine how much data is going to be processed.\u00a0In this case,\u00a0SQL Server estimates that there are very few rows with a last name of &#8216;Tripp&#8217; (from the statistics,\u00a0it thinks there&#8217;s only 1). As a result, an index would be helpful to find this highly selective result so it chose a\u00a0plan to\u00a0use an index.<\/p>\n<p>For statement 2 though, SQL Server doesn&#8217;t seem to know to use an index. Why? Because here the variable (@v1) was unknown at the time of compilation \/ optimization.\u00a0The variable is not assigned until\u00a0actual execution\u00a0but\u00a0execution only occurs <em><strong>after<\/strong> <\/em>a plan has been generated. So, the problem with variables is that SQL Server doesn&#8217;t know their actual values until <em><strong>after<\/strong> <\/em>it&#8217;s chosen a plan. This can be both good and bad. Remember,\u00a0SQL Server has to do <em>something<\/em>&#8230; So, in this case, SQL Server uses an\u00a0<em>average<\/em>\u00a0to estimate the rows and come up with a plan. This\u00a0<em>average<\/em>\u00a0comes from the density_vector component of a statistic rather than this histogram. If your data is reasonably evenly distributed then this can be good. And, it also means that the plan won&#8217;t change change after it&#8217;s kicked out of cache and a different execution occurs with different parameters. Some\u00a0have learned this trick and have used it with success &#8211; but, only because their data is either evenly distributed OR the executions are using values that all resemble the average.<\/p>\n<p style=\"padding-left: 30px;\">NOTE: This is EXACTLY the same behavior as using the OPTION (OPTIMIZE FOR\u00a0UNKNOWN) clause on the statement within the stored procedure.<\/p>\n<p>In this case, however, &#8216;Tripp&#8217; is NOT like the average value and so the plan for statement 2 is not ideal for a variable assigned to &#8216;Tripp&#8217;. The data has a lot of duplicates and the average number of rows for most names is quite high (where an index is no longer userful). However, &#8216;Tripp&#8217; is really not an average\u00a0data value here and so the plan might be good for most other values. But, in this case, it&#8217;s not good for the value Tripp.<\/p>\n<p>Execute the procedure again but supply a different value for @p1:<\/p>\n<p>EXEC [dbo].[ParamVarLit] @p1 = &#8216;Anderson&#8217; &#8212; assignment by name<br \/>\nGO<\/p>\n<p>This is what you&#8217;ll see when you execute with &#8216;Anderson&#8217; AFTER having created a plan for &#8216;Tripp&#8217;:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2768\" src=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-1024x802.png\" alt=\"ExecutionPlans\" width=\"1024\" height=\"802\" srcset=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-1024x802.png 1024w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-300x235.png 300w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans-900x705.png 900w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/ExecutionPlans.png 1073w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p><strong>Wait &#8211; there&#8217;s no difference? <\/strong><\/p>\n<p>Nope, absolutely none! Really,\u00a0review every aspect of your output \/plan [not the actual\u00a0values] and you&#8217;ll see it&#8217;s exactly the same! The plan that you see is always the estimated plan and the estimated plan is chosen when the stored procedure is optimized \/ compiled. Optimization \/ compilation occurs only when there isn&#8217;t already a plan in cache\u00a0for that procedure.<\/p>\n<p><strong>Why are they the same?<\/strong><\/p>\n<p>The parameter &#8211; &#8216;Tripp&#8217; was used on the first execution and this is what was &#8220;sniffed&#8221; and use for optimization. When I say &#8220;sniffed&#8221; all that means is that the value was KNOWN such that the optimizer could look at the statistics (and specifically the histogram) to\u00a0estimate how many rows had a last name of &#8216;Tripp.&#8217; It turns out that the estimate was 1. You can see this by hovering over the Index Seek in the first statement:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Diagrams.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2769\" src=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Diagrams.jpg\" alt=\"Diagrams\" width=\"851\" height=\"678\" srcset=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Diagrams.jpg 851w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Diagrams-300x239.jpg 300w\" sizes=\"auto, (max-width: 851px) 100vw, 851px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p>If you were to kick this plan out of cache and re-execute with &#8216;Anderson&#8217; then something interesting would happen:<\/p>\n<p>EXEC [sp_recompile] &#8216;[dbo].[ParamVarLit]&#8217;;<br \/>\nGO<\/p>\n<p>EXEC [dbo].[ParamVarLit] @p1 = &#8216;Anderson&#8217;;<br \/>\nGO<\/p>\n<p>Results in this plan:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2770\" src=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson-1024x778.png\" alt=\"Statement1Estimate_Anderson\" width=\"1024\" height=\"778\" srcset=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson-1024x778.png 1024w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson-300x228.png 300w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson-900x684.png 900w, https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2015\/04\/Statement1Estimate_Anderson.png 1056w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p><strong>There&#8217;s really one\u00a0incredibly important\u00a0observation here: ONLY the first statement&#8217;s plan changed!<\/strong><\/p>\n<p>The first statement&#8217;s plan changed because on this execution SQL Server was able to &#8220;sniff&#8221; the parameter and optimize \/ compile a plan specific to it.\u00a0More specifically, when the statement KNOWS that the value is &#8216;Anderson&#8217; (again, only statement 1 knows this)\u00a0then SQL Server can look to the statistics to determine how much data is going to be processed.\u00a0In this case,\u00a0SQL Server estimates that there are numerous\u00a0rows with a last name of &#8216;Anderson&#8217; (from the statistics, estimates 385 rows). As a result, the data is not selective enough to warrant using an index so in this case, SQL Server uses a table scan. (shown as a clustered index scan solely because the table has a clustered index)<\/p>\n<h2><span style=\"text-decoration: underline;\">Bringing It All Together<\/span><\/h2>\n<p>Parameters are evaluated and\u00a0sniffed ONLY when a plan is being created. This ONLY happens when a plan is NOT already in the cache. And, sniffing is fantastic for THAT specific execution because &#8220;sniffing&#8221; a parameter lets SQL Server use the histogram component of statistic to determine the estimate. While the histogram is not always perfect, it&#8217;s usually a\u00a0more\u00a0accurate way of estimating rows. But, this can also lead to parameter sniffing problems where subsequent executions don&#8217;t perform well because the plan in cache wasn&#8217;t optimized for their values. There are many solutions to this problem, I covered a few of the options here: <a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/high-performance-procedures\/\" target=\"_blank\">Building High Performance Stored Procedures<\/a>.<\/p>\n<p>Variables are ALWAYS deemed &#8220;unknown&#8221; and they cannot be sniffed. In this case, SQL Server doesn&#8217;t have a value to lookup in a statistic&#8217;s histogram. Instead, SQL Server uses an <em>average<\/em> to estimate the rows and come up with a plan. But. as I mentioned &#8211; this can be sometimes good and sometimes bad.<\/p>\n<p>The literal is the easiest of them all. SQL Server knows this value and there&#8217;s absolutely nothing that will ever change that value. This can be sniffed and it will use the histogram getting the best estimate to use for optimization.<\/p>\n<p>Play around with this sample procedure. Review the plans and the estimates v. actuals. Next week I&#8217;ll dive more into the statistics themselves and where the specific estimates are coming from!<\/p>\n<p><strong>Have fun and thanks for reading!<\/strong><br \/>\nk<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Nov 2014, SQLskills made an offer to user group leaders to deliver remote user group sessions in 2015\u00a0and we&#8217;ve been having great fun delivering them. So far the team has delivered 34 sessions and we have 72 signed up! At my last session (for the San Diego SQL Server User Group), I delivered a [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[50,72],"tags":[],"class_list":["post-2767","post","type-post","status-publish","format-standard","hentry","category-optimizing-procedural-code","category-statistics"],"_links":{"self":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts\/2767","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/comments?post=2767"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts\/2767\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/media?parent=2767"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/categories?post=2767"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/tags?post=2767"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}