<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Bart Duncan's SQL Weblog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/bartd/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/bartd/feed.xml</link>
    <description />
    <lastBuildDate>Wed, 14 Feb 2018 17:36:36 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Filtered Indexes and Forced Parameterization: Can't we all just get along?</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/filtered-indexes-and-forced-parameterization</link>
      <pubDate>Thu, 08 Feb 2018 16:31:38 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/?p=485</guid>
      <description><![CDATA[We have a database here that stores information about Azure SQLDB health issues; it was chugging...]]></description>
      <content:encoded><![CDATA[We have a database here that stores information about Azure SQLDB health issues; it was chugging along fine for months, then earlier this week it started to struggle. You can see the large increase in steady-state CPU consumption:

<a href="https://msdnshared.blob.core.windows.net/media/2018/02/filtered_index_cpu_increase.png"><img width="505" height="298" class="alignnone wp-image-495" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/02/filtered_index_cpu_increase-300x177.png" /></a>

After asking around, we learned that <a href="https://technet.microsoft.com/en-us/library/ms175037(v=sql.105).aspx">forced parameterization</a> had been enabled on this database around the time that the performance problem appeared.
<pre>    ALTER DATABASE HealthDB SET PARAMETERIZATION FORCED</pre>
We could have disabled the feature, but it had been enabled for a good reason. We wanted to understand what was going on so we could look for other solutions. QDS and <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-query-performance">Query Performance Insight</a> made it easy to identify the problem query.

<a href="https://msdnshared.blob.core.windows.net/media/2018/02/filtered_index_top_queries.png"><img width="561" height="260" class="alignnone wp-image-505" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/02/filtered_index_top_queries-300x139.png" /></a>

The query was trivial, barely more complicated than this:
<pre>    SELECT * FROM CurrentHealthReports
    WHERE ProjectToCluster = 1</pre>
QDS told us this query was scanning the whole table on each execution, just to return a row or two. The table is about 125 million rows and 600GB. That's hardly one for the record books, but it's still large enough that a full table scan is painful.
<pre>    |--Parallelism(Gather Streams)
        |--Clustered Index Scan(OBJECT:([CurrentHealthReports].[PKCurrentHealthReports]), 
            WHERE:([CurrentHealthReports].[ProjectToCluster]=[@0]))</pre>
This plan choice took me by surprise, because there are only a tiny number of rows with ProjectToCluster = 1, and there's a nonclustered index that has ProjectToCluster as its leading column. A seek on this index would have been much more efficient than the current scan-based plan. And, in fact, QPI also showed us that up until Tuesday this query had been using a much more efficient plan that did a seek on this index. (I have to say: I have a giant crush on QDS and the analysis features built on top of it. The job of identifying problem queries and good vs. bad plan choices used to be so much more difficult!)
<pre>    |--Nested Loops(Inner Join, OUTER REFERENCES:([CurrentHealthReports].[PKCol]))
        |--Index Seek(OBJECT:([CurrentHealthReports].[idx_NotYetProjected]), 
            SEEK:([CurrentHealthReports].[ProjectToCluster]=(1)) ORDERED FORWARD)
        |--Clustered Index Seek(OBJECT:([CurrentHealthReports].[PKCurrentHealthReports]), 
            SEEK:([CurrentHealthReports].[PKCol]=[CurrentHealthReports].[PKCol]) LOOKUP ORDERED FORWARD)</pre>
My first thought was that the optimizer must be over-costing the index seek plan for some reason. I tried to force the old seek plan, so I could explore the reason for the costing error. I was expecting a bad cardinality estimate; instead, I was surprised to see it fail:
<pre>    SELECT * FROM CurrentHealthReports WITH (INDEX = idx_NotYetProjected)
    WHERE ProjectToCluster = 1

    Msg 8622, Level 16, State 1, Line 48
    Query processor could not produce a query plan because of the hints defined in this query.
    Resubmit the query without specifying any hints and without using SET FORCEPLAN.</pre>
Now we knew the superficial reason that the optimizer wasn't choosing this plan: it thought that the plan was illegal. But the reason why the plan was illegal stumped us for a while. Eventually, I looked more closely at the index definition. The fact that it was a <a href="https://docs.microsoft.com/en-us/sql/relational-databases/indexes/create-filtered-indexes">filtered index</a> stood out. A filtered index has a WHERE clause, and its B-tree only indexes rows that satisfy the filter condition. Filtering is a smart choice for this index. But I was suspicious because we were seeing unusual behavior when forcing the index, and most indexes aren't filtered.
<pre>    CREATE INDEX idx_NotYetProjected ON CurrentHealthReports (ProjectToCluster)
    WHERE (ProjectToCluster = 1)</pre>
After reviewing the plans again, we noticed that the query was parameterized. The parameter name "@0" is typical of an auto-parameterized query:
<pre>    select * from CurrentHealthReports where ProjectToCluster = @0</pre>
Now the 8622 error made sense: after forced parameterization was enabled, the predicate "WHERE ProjectToCluster = 1" gets turned into "WHERE ProjectToCluster = @0". The query plan that is generated and cached for this query needs to be able to work for any possible value of @0 -- after all, that's the whole point of forced parameterization. But the index we expected SQL to use is filtered, and it can only be used to find rows where ProjectToCluster = 1. Any plan that relies on this index could never be used for arbitrary values of the parameter. When we tried to force the use of the index, the 8622 was SQL's way of telling us that the plan we were asking for wasn't legal. And, of course, this also explained why the optimizer was no longer choosing the index seek query plan on its own.

Here's the lesson I'm taking away from this: <u><i>The presence of filtered indexes should make you cautious about enabling forced parameterization at the database level</i></u>. The two features don't always play nicely, and the combination can cause query performance problems and/or 8622 errors. To fix the query perf problem, we considered disabling forced parameterization:
<pre>    ALTER DATABASE HealthDB SET PARAMETERIZATION SIMPLE</pre>
In the end, we decided to leave forced parameterization enabled. Instead, we disabled the feature for this particular query with an OPTION (PARAMETERIZATION SIMPLE) hint.
<pre>    DECLARE @stmt nvarchar(max);
    DECLARE @params nvarchar(max);
    EXEC sp_get_query_template 
        N'SELECT * FROM CurrentHealthReports WHERE ProjectToCluster = 1',
        @stmt OUTPUT,
        @params OUTPUT;
    EXEC sp_create_plan_guide
        N'PlanGuide_HealthNotYetProjectedQuery',
        @stmt,
        N'TEMPLATE',
        NULL,
        @params,
        N'OPTION(PARAMETERIZATION SIMPLE)'</pre>
The PARAMETERIZATION SIMPLE hint can only be applied via a plan guide like the one shown above. A slightly less surgical alternative would be an OPTION(RECOMPILE) query hint, which happens to have the side effect of disabling forced parameterization for the query. This one can be applied by a change to the query text.
<pre>    SELECT * FROM CurrentHealthReports
    WHERE ProjectToCluster = 1
    OPTION (RECOMPILE)</pre>
If you want to play with this scenario yourself, I've uploaded a simplified version of it <a href="https://msdnshared.blob.core.windows.net/media/2018/02/filtered_index_forced_parameterization.zip">here</a>.

(Cross-posted to personal blog at <a href="https://bartduncansql.wordpress.com/2018/02/08/filtered-indexes-and-forced-parameterization">https://bartduncansql.wordpress.com/2018/02/08/filtered-indexes-and-forced-parameterization</a>)]]></content:encoded>
    </item>
    <item>
      <title>Row Goals Gone Rogue</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/row-goals-gone-rogue</link>
      <pubDate>Wed, 14 Mar 2012 17:14:49 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2012/03/14/row-goals-gone-rogue/</guid>
      <description><![CDATA[&nbsp;

This post discusses “row goals“, but with a twist. The point is to illustrate how row...]]></description>
      <content:encoded><![CDATA[&nbsp;

This post discusses “<strong>row goals</strong>“, but with a twist. The point is to illustrate how row goals can cause unnecessarily slow queries. First, run this script:
<pre>    USE tempdb
    GO
    IF OBJECT_ID ('even') IS NOT NULL DROP TABLE even;
    IF OBJECT_ID ('odd') IS NOT NULL DROP TABLE odd;
    GO
    CREATE TABLE even (c1 int, c2 CHAR(30));
    CREATE TABLE odd (c1 int, c2 CHAR(30));
    GO
    SET NOCOUNT ON;
    DECLARE @x int;
    SET @x = 1;
    BEGIN TRAN;
    WHILE (@x &lt;= 10000)
    BEGIN
        INSERT INTO even (c1, c2) VALUES (@x * 2, '');
        INSERT INTO odd (c1, c2) VALUES (@x * 2 - 1, '');
        IF @x % 1000 = 0
        BEGIN
            RAISERROR ('Inserted %d rows...', 0, 1, @x) WITH NOWAIT;
            COMMIT TRAN;
            BEGIN TRAN;
        END;
        SET @x = @x + 1;
    END;
    WHILE @@TRANCOUNT &gt; 0 COMMIT TRAN;
    GO</pre>
This will insert 10,000 even integers into a table named [even], and the same number of odd values into a table named [odd]. Then consider this simple query:
<pre>    -- QUERY #1
    SELECT *
    FROM even t1
    INNER JOIN even t2 ON t1.c1 = t2.c1</pre>
This joins the [even] table to itself, and will return all 10,000 rows from the table. If you run with “<strong>SET STATISTICS TIME ON; SET STATISTICS PROFILE ON;</strong>” to see the query plan that SQL selects for this query, you should find that it chooses a hash match:
<pre>StmtText
------------------------------------------------------------------------------------------
  |--Hash Match(Inner Join, HASH:([t1].[c1])=([t2].[c1]), RESIDUAL:([t2].[c1]=[t1].[c1]))
      |--Table Scan(OBJECT:([tempdb].[dbo].[even] AS [t1]))
      |--Table Scan(OBJECT:([tempdb].[dbo].[even] AS [t2]))</pre>
While a hash join is often the fastest join option, a hash has the highest initial <strong>build cost</strong> of any join strategy. Before the hash join can evaluate even a single row for the join, it must first hash all of the rows in one of the join inputs and store these in a hash table (“build cost” refers to this up-front work). Only then can it begin to join the rows in the other input with the rows in the under-the-covers hash table. In contrast to a hash join, a nested loop join strategy has an extremely low build cost. There are no expensive up-front delays before a nested join can produce its first row, but by default SQL optimizes with the goal of minimizing total query execution time. In this case, the nature of a hash join allows it to complete the query in a small fraction of the time it would take for a loop join (on my machine the hash join plan runs in about 100ms), despite the high initial setup cost. So the optimizer is doing the right thing by selecting a hash join for this query. However, watch what happens if you add a “<strong>TOP 1</strong>” to the query:
<pre>    -- QUERY #2
    SELECT <strong><span style="color: #ff0000">TOP 1</span></strong> *
    FROM even t1
    INNER JOIN even t2 ON t1.c1 = t2.c1</pre>
The plan changes to use a loop join, and the query completes in a few milliseconds:
<pre>   Rows  EstRows  Executes  EstExecs StmtText
   ----- -------- --------- --------- -------------------------------------------------------------
   1     1        1         1         |--Top(TOP EXPRESSION:((1)))
   1     1        1         1             |--Nested Loops(Inner Join, WHERE:([t2].[c1]=[t1].[c1]))
   1     1        1         1                 |--Table Scan(OBJECT:([tempdb].[dbo].[even] AS [t1]))
   1     10000    1         1                 |--Table Scan(OBJECT:([tempdb].[dbo].[even] AS [t2]))</pre>
The addition of a <strong>TOP 1</strong> tells the optimizer that it doesn’t have to execute the whole query; it only has to produce enough rows out of each child operator in the query to get a single row out the top. The optimizer takes this into account and correctly estimates that for this join it won’t have to scan many rows at all before finding a match and taking an early exit. As a result, it decides to use a nested loop join, because a loop join should be able to produce that single row and exit long before the hash join could build its hash table. This is also the right thing to do. If you want to prove it to yourself, run query #2 with <strong>SET STATISTICS TIME ON</strong>, then again with an <strong>OPTION(HASH JOIN)</strong> hint to force a hash join despite the higher cost. You should find that the hash join is faster when returning all rows, but the loop join is faster when only one row must be returned. This query optimizer intelligence is possible because of the “row goal” feature.

The <strong>TOP</strong> operator isn’t the only thing that can cause the optimizer to set a row goal. An <strong>OPTION (FAST N)</strong> query hint sets a row goal of N — the optimizer will cost plans in about the same way that it would with a <strong>TOP N</strong>, but without artificially restricting the number of rows the query will return. The chosen plan should return the first N rows as quickly as possible, but the end-to-end query execution time might be much slower than you’d get without the hint. Also, semi-joins or anti-semi-joins, like you’d get with an <strong>EXISTS</strong> or <strong>NOT EXISTS</strong> clause, can also implicitly set a row goal of 1.

If row goals are a new concept for you, I recommend reading through Paul White’s <a target="_parent" href="https://sqlblog.com/blogs/paul_white/archive/2010/08/18/inside-the-optimiser-row-goals-in-depth.aspx" title="Row Goals In Depth">Row Goals In Depth</a> article before continuing.

Now, let’s run the same <strong>TOP 1</strong> query, but instead of joining [even] to itself, we’ll join [even] to the [odd] table:
<pre>     -- QUERY #3
     SELECT TOP 1 *
     FROM even t1
     INNER JOIN <strong><span style="color: #ff0000">odd</span> </strong>t2 ON t1.c1 = t2.c1</pre>
You’ll note that this query is extremely slow relative to the others. On my machine it takes about 9 seconds, which is several thousand times slower than the similar query that does a self-join on the [even] table. Now you and I know that the [even] table contains even integers, and the [odd] table contains odd integers. Based on that understanding, we can guess that this query should return no rows. But the query optimizer has no concept of “odd” and “even”. Remember that <a target="_parent" href="https://blogs.msdn.com/b/bartd/archive/2006/07/25/limited-statistics-granularity.aspx">statistics only provide a high-level summary of the data in a column</a>. In this case, the statistics on the [even] and [odd] tables seem to indicate that the [c1] columns in these two tables have similar data distributions, so from the query optimizer’s perspective this is the same problem it was faced with in query #2. It guesses that it will very quickly find a matching row, so it chooses a loop join-based plan to minimize operator startup costs. If you look at the plan, you can see that it selects the same plan that was used for query #2. Unfortunately, in this case the results are disastrous. The query plan slogged through 100 million rows (10,000 * 10,000) looking for a match for the join, and it never found one. The <strong>Top </strong>operator in this plan reflects the row goal of 1.
<pre>   Rows      EstRows  Executes  EstExecs StmtText
   --------- -------- --------- --------- -------------------------------------------------------------
   0         1        1         1         |--Top(TOP EXPRESSION:((1)))
   0         1        1         1             |--Nested Loops(Inner Join, WHERE:([t2].[c1]=[t1].[c1]))
   10000     1        1         1                 |--Table Scan(OBJECT:([tempdb].[dbo].[even] AS [t1]))
   100000000 10000    1         2                 |--Table Scan(OBJECT:([tempdb].[dbo].[odd] AS [t2]))</pre>
What happened here? This mess is caused by a combination of things:
<ul>
 	<li>The “<strong>TOP 1</strong>” told the query optimizer that it should choose a plan that is likely to return a single row as fast as possible, rather than optimizing for return of all rows that could satisfy the join.</li>
 	<li>The data in the [even] and [odd] tables exhibit negative correlation. That is, the values in [even] are never going to match a value in [odd], even though the data distribution is similar. (If you can make a prediction about the data in one column based on the data in another column, you can say that those columns are <strong>correlated</strong>.)</li>
 	<li>There are a few exceptions, but in general you can say that <em>the query optimizer is ignorant of data correlation</em>. In this case, the implication is that the QO can’t know at compile time that it will never find a value in [even] that matches a value in [odd].</li>
 	<li>Where the data distributions described by SQL’s statistics indicate overlapping data ranges, the optimizer is generally optimistic, and assumes that it will find a match if it joins those two sets.</li>
</ul>
The last item is key. It means that there is a fundamental assumption of positive correlation built into join cardinality estimation. Usually when you join two sets this is the right assumption. For example, consider:
<pre>    SELECT * FROM Employee
    INNER JOIN Manager ON Employee.ManagerId = Manager.Id</pre>
Every employee’s [ManagerId] field will generally refer to a row that exists in the [Manager] table. But occasionally you join two sets together expecting to get no or few matches even though the high-level data distribution looks similar. The example in this post is artificial, but there are plenty of real world examples. Consider a data cleansing routine that is looking for illegal values in some data imported from an untrusted source.
<pre>    SELECT * FROM ImportedData
    WHERE RegionCode IN (SELECT RegionCode FROM DisallowedRegions)</pre>
That query by itself won’t hit this problem because there’s no row goal. But combine it with an <strong>EXISTS</strong> so that the QO sets an implied row goal of 1, and you may find yourself waiting for a while:
<pre>    <strong><span style="color: #ff0000">IF EXISTS</span> </strong>(
        SELECT * FROM ImportedData
        WHERE RegionCode IN (SELECT RegionCode FROM DisallowedRegions))
    BEGIN
        RAISERROR ('Found some bad data', 16, 1)
    END;</pre>
This is by-design behavior. While the optimizer is pretty smart, the hypothetical world it models to come up with cost estimates doesn’t always capture all of the nuances of real world data. Usually these simplifying assumptions are good, because they allow SQL to produce good plans without taking too long to compile. But sometimes the simplified model leads to suboptimal plans. It pays to know the optimizer’s limits, so that you can quickly recognize these cases during query tuning.

&nbsp;

(cross-posted <a href="https://bartduncansql.wordpress.com/2012/03/14/row-goals-gone-rogue">here</a>)

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>Don’t depend on expression short circuiting in T-SQL (not even with CASE)</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/dont-depend-on-expression-short-circuiting-in-t-sql-not-even-with-case</link>
      <pubDate>Thu, 03 Mar 2011 10:41:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2011/03/03/dont-depend-on-expression-short-circuiting-in-t-sql-not-even-with-case/</guid>
      <description><![CDATA[There are a fair number of blog posts and forum discussions regarding expression short circuiting in...]]></description>
      <content:encoded><![CDATA[<p>There are a fair number of blog posts and forum discussions regarding expression short circuiting in T-SQL. Some of the most authoritative posts, like <a href="https://www.sqlmag.com/article/tsql3/short-circuit.aspx"><span style="color: #2970a6;">this one</span></a>, come to the following conclusions: (a) You cannot depend on expression evaluation order for things like &ldquo;<strong>WHERE &lt;expr1&gt; OR &lt;expr2&gt;</strong>&ldquo;, since the optimizer might choose a plan that evaluates the second predicate before the first one. But, (b) order of evaluation of the expressions in a CASE statement is fixed, so you can depend on deterministic short circuit evaluation of a CASE statement. For example, this wouldn&rsquo;t protect you from a divide-by-zero error:</p>
<pre><span style="font-family: courier new,courier;">    WHERE (@value = 0) OR ((col1 / @value) = 2)</span></pre>
<p>But the idea is that this variation is functionally-equivalent, and should protect you from the error:</p>
<pre><span style="font-family: courier new,courier;">    WHERE
        CASE
            WHEN (@value = 0) THEN 2
            ELSE (col1 / @value)
        END = 2</span></pre>
<p>Before now that&rsquo;s the advice I would have offered, too. But I just ran into a situation where a CASE statement does not provide predictable short circuiting. Here&rsquo;s a simplified repro:</p>
<pre><span style="font-family: courier new,courier;">    ALTER FUNCTION dbo.test_case_short_circuit (@input INT)
    RETURNS TABLE
    AS
    RETURN (
        SELECT calculated_value =
            CASE
                WHEN @input &lt;= 0 THEN 0
                ELSE LOG10 (@input)
            END
    );
    GO

    SELECT * FROM dbo.test_case_short_circuit (0);
    GO</span></pre>
<p>This fails with this error:</p>
<p><code><br /><span style="color: #ff0000;">&nbsp;&nbsp;&nbsp;&nbsp; Msg 3623, Level 16, State 1, Line 2</span><br /><span style="color: #ff0000;">&nbsp;&nbsp;&nbsp;&nbsp; An invalid floating point operation occurred.</span><br /></code></p>
<p>The LOG10 function raises this error when its input is 0 or a negative value. In some cases it appears that the plan may still evaluate the expression in the second CASE branch even when it won&rsquo;t be using the value. This is a case where CASE doesn&rsquo;t provide deterministic short circuiting.</p>
<p>I want to make sure no one takes away the conclusion that SQL Server doesn&rsquo;t support expression short circuiting. It definitely does. It&rsquo;s just that you don&rsquo;t have explicit control over the order of expression evaluation &mdash; even with CASE, apparently. And if you&rsquo;re going to depend on short circuiting, you need a deterministic order of expression evaluation.</p>
<p>What can you do about it? One option would be to always scrub things so that an error isn&rsquo;t possible even when the CASE branch&rsquo;s output won&rsquo;t be used. For example, using &ldquo;ELSE LOG10 (CASE WHEN @input &lt;= 0 THEN 1 ELSE @input END)&rdquo; in the repro script doesn&rsquo;t change the behavior of the function, but avoids the error. Unfortunately, that&rsquo;s not so pretty.</p>
<p>UPDATE (4 Mar 2011): To be clear, I&rsquo;ve used CASE before for its short-circuiting properties, and I don&rsquo;t intend to go back and revisit all of that old code in light of this example. This seems like an edge case to me. But it&rsquo;s worth being aware that such edge cases exist if you&rsquo;re thinking about relying on CASE for short circuiting. The most defensive programming approach would be to write the expression in such a way that it doesn&rsquo;t require particular short circuiting behavior. </p>
<p>UPDATE (10 Jun 2011): The owners of this code have marked this bug as fixed. From their comments, it sounds like you are supposed to be able to rely on deterministic order of expression evaluation for CASE statements. But any SQL release in your hands right now will still be vulnerable to this problem -- keep an eye out for the issue as you use CASE for short circuiting.</p>
<p>(Cross-posted to <a href="https://bartduncansql.wordpress.com/2011/03/03/dont-depend-on-expression-short-circuiting-in-t-sql-not-even-with-case/">here</a>.)</p>]]></content:encoded>
    </item>
    <item>
      <title>Query Tuning Fundamentals: Density, Predicates, Selectivity, and Cardinality</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/query-tuning-fundamentals-density-predicates-selectivity-and-cardinality</link>
      <pubDate>Tue, 25 Jan 2011 11:35:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2011/01/25/query-tuning-fundamentals-density-predicates-selectivity-and-cardinality/</guid>
      <description><![CDATA[A few days ago I was in the middle of writing up a quick post about a query performance problem I...]]></description>
      <content:encoded><![CDATA[<p>A few days ago I was in the middle of writing up a quick post about a query performance problem I recently ran into. The writeup referenced <em>predicate selectivity</em>, and I found myself wondering whether everyone who came across the post would have a good intuitive understanding of what that referred to. Just in case, I thought I'd do a quick overview of some terms that you'll definitely run across if you're doing much query tuning.&nbsp;You can find this information in other forms elsewhere on the Internet, but these are practical descriptions that I've found to be useful when introducing people to query tuning concepts.&nbsp;</p>
<hr />
<p><strong><span style="font-size: medium;"><span style="font-size: large;">Density </span><br /></span></strong>Density, when used to describe the data in a column, is a measure of how often duplicate values occur in that column. Another way to think of density is as a measure of the uniqueness of the data in a column: high density --&gt; less unique data. Density values range from 0 to 1.0.&nbsp; There are different (but equivalent) ways to think of density.</p>
<p style="padding-left: 30px;">Density = 1/[# of distinct values in a column] <br />Density = Avg. number of duplicates for a given value / Total row count</p>
<p>Consider a Customers table with a CountryID column and a RegionID column. Suppose that 100 different CountryID values are present in the table, and 1000 different RegionIDs are present. The density of the CountryID column would be higher than the density of the RegionID column because a typical country is less unique than a typical Region. Note that this is an overall characterization of a range of values. There may be some regions&nbsp;that occur more frequently than some countries -- a U.S.-based company might have more customers in California than in Estonia, for example.&nbsp; But CountryID still has higher density than RegionID simply because the <em>average </em>country is represented in more rows than the <em>average </em>region.</p>
<p>Density of the CountryID column:</p>
<p style="padding-left: 30px;">Density = 1/[# of distinct values in&nbsp;the column] <br />Density = 1/100 = 0.01</p>
<p>Note that the density of 0.01, or 1%, corresponds to the percentage of the total rows that would be returned by a query for a single value.&nbsp; In other words, an average country makes up 1% of the table.&nbsp; if there were 10,000 rows in the table, there were be 100 rows (1% of 10,000) per country, on average.&nbsp;&nbsp;</p>
<p>Density of the RegionID column:</p>
<p style="padding-left: 30px;">Density = 1/[# of distinct values in a column] <br />Density = 1/1000 = 0.001</p>
<p>The density of the CountryID column (0.01) is higher than the density of the RegionID column (0.001) because the CountryID column is less unique.</p>
<p>&nbsp;</p>
<p><em>Q: What is the density of a column that contains only a single value repeated in every row? <br /></em>A: 1.0 -- the highest possible density.</p>
<p><em>Q: What is the density of a unique column, such as an IDENTITY or primary key column? </em><br />A: 1/[row count]</p>
<p></p>
<p><strong>Hands-On Example <br /></strong>Here is a script to create the scenario described above.&nbsp;</p>
<p style="padding-left: 30px;">use tempdb<br />go<br />create table customers (customerid int identity, country varchar(10), region varchar(10))<br />go<br />set nocount on<br />begin tran<br />declare @x int<br />set @x = 1<br />while (@x &lt;=10000)<br />begin<br />&nbsp;&nbsp;&nbsp; insert into customers (country, region) values ('Country' + convert (varchar, @x % 100), 'Region' + convert (varchar, @x % 1000))<br />&nbsp;&nbsp;&nbsp; set @x = @x + 1<br />end<br />while @@trancount &gt; 0 commit tran<br />go<br />create statistics stats_country on customers (country) with fullscan<br />create statistics stats_region on customers (region) with fullscan<br />create statistics stats_country_region on customers (country, region) with fullscan<br />go<br />dbcc show_statistics (customers, stats_country)<br />dbcc show_statistics (customers, stats_region)<br />go</p>
<p>Run this and check out the DBCC SHOW_STATISTICS output for the stats on the [country] and [region] columns.&nbsp; In the SHOW_STATISTICS output you'll see a value called "<strong>All density</strong>".&nbsp; This should match the values predicted in the calculations described above: Country and Region densities of 0.01 and 0.001, respectively.&nbsp; (TIP: you should generally use "<strong>All density</strong>" and disregard the "<strong>Density</strong>" value in SHOW_STATISTICS's first resultset -- the value in the "Density" column is generally not used by the query optimizer.)&nbsp; If the statistics are multi-column as in the [stats_country_region] statistics created in this example, an&nbsp;<strong>All density</strong>&nbsp;value is also maintained for combinations of columns; this can help the optimizer make more accurate predictions when a query includes a filter on more than one column.</p>
<table border="0">
<tbody>
<tr>
<td><strong>Name</strong></td>
<td><strong>Updated</strong></td>
<td><strong>Rows</strong></td>
<td><strong>Rows Sampled</strong></td>
<td><strong>Steps</strong></td>
<td><strong>Density</strong></td>
<td><strong>Average Key Length</strong></td>
<td><strong>String Index</strong></td>
<td><strong>Filter Expression</strong></td>
<td><strong>Unfiltered Rows</strong></td>
</tr>
<tr>
<td>stats_country</td>
<td>Jan 25 2011 10:14AM</td>
<td>10000</td>
<td>10000</td>
<td>55</td>
<td>0.01</td>
<td>8.9</td>
<td>YES</td>
<td>NULL</td>
<td>10000</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<table border="0">
<tbody>
<tr>
<td><strong>All density</strong></td>
<td><strong>Average Length</strong></td>
<td><strong>Columns</strong></td>
</tr>
<tr>
<td><span style="background-color: #ffff00;">0.01</span></td>
<td>8.9</td>
<td>country</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<hr />
<p><strong><span style="font-size: medium;"><span style="font-size: large;">Predicate </span><br /></span></strong>A predicate is an expression that evaluates to True or False. In SQL you encounter predicates in joins and in WHERE or HAVING clauses, where they are used to either filter out or qualify rows. Here's a query with two filter predicates:</p>
<p style="padding-left: 30px;">SELECT * <br />FROM table1 <br />WHERE column1 &gt; 20 AND column2 IS NULL</p>
<p>And a query with one join predicate:</p>
<p style="padding-left: 30px;">SELECT * <br />FROM table1 <br />INNER JOIN table2 ON table1.column1 = table2.column1</p>
<p>&nbsp;</p>
<hr />
<p><strong><span style="font-size: large;">Selectivity </span></strong><br />Selectivity is also a measure of uniqueness, of sorts. High selectivity implies high uniqueness, or a low number of matching values. Low selectivity implies a low uniqueness, or a high percent of matches.&nbsp;</p>
<p>Selectivity is most commonly used to describe a predicate. Returning to the example used to describe density, consider a Customers table with a Region column. Suppose that most of the company's customers are in RegionA, and only a few are in RegionB. The predicate "Region='RegionB'" would be described as <em>more selective </em>because it returns a smaller percentage of the rows in the table. (As an aside, this kind of uneven distribution of data within a column is an example of <em>data skew</em>.)&nbsp; The estimated selectivity of a predicate is essential when the optimizer is evaluating possible plans.&nbsp;It will affect things like join order (all other things equal, the most selective join operation is generally performed first) and whether SQL chooses to do a table scan or an index seek followed by a bookmark lookup.</p>
<p>Selectivity for a filter predicate against a base table can be calculated as "[# rows that pass the predicate]/[# rows in the table]".&nbsp; If the predicate passes all rows in the table, its&nbsp;selectivity is 1.0.&nbsp; If it disqualifies all rows, its selectivity is 0. (This can be confusing.&nbsp; Note that 0.000001 reflects a <em>high selectivity </em>even though the number is small, while 1.0 is <em>low selectivity </em>even though the number is higher.)</p>
<p>The optimizer often uses the histogram steps in column statistics to estimate the selectivity of a predicate.&nbsp; Re-run the sample script above, and note the third and final resultset in the "DBCC SHOW_STATISTICS(customers, stats_country)" output.&nbsp; A single row in this resultset represents a step in a histogram.&nbsp; Each histogram step summarizes the density of&nbsp;all rows in the table that have a [country] value that is equal to or less than that row's RANGE_HI_KEY value, but greater than the RANGE_HI_KEY of the preceding histogram step. The snippet below shows the first 4 rows.</p>
<p>&nbsp;</p>
<table border="0">
<tbody>
<tr>
<td>RANGE_HI_KEY</td>
<td>RANGE_ROWS</td>
<td>EQ_ROWS</td>
<td>DISTINCT_RANGE_ROWS</td>
<td>AVG_RANGE_ROWS</td>
</tr>
<tr>
<td>Country0</td>
<td>0</td>
<td>100</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>Country1</td>
<td>0</td>
<td>100</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>Country11</td>
<td>100</td>
<td>100</td>
<td>1</td>
<td>100</td>
</tr>
<tr>
<td>Country13</td>
<td>100</td>
<td>100</td>
<td>1</td>
<td>100</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>Take the third row in the histogram as an example. This step summarizes all rows with&nbsp;a country value&nbsp;greater than 'Country1' (the RANGE_HI_KEY for the preceding step),&nbsp;but less than or equal to 'Country11' (this step's RANGE_HI_KEY).&nbsp; There are 100 rows with exactly the value 'Country11' (EQ_ROWS), and&nbsp;100 rows with some other value in this range (RANGE_ROWS).&nbsp;&nbsp; The DISTINCT_RANGE_ROWS value of 1 tells us that those 100 other RANGE_ROWS are made up of exactly one other distinct [country] value.&nbsp; (These happen to be the rows with a country value of 'Country10', although that information isn't present in the statistics.)&nbsp; I've often thought that the DISTINCT_RANGE_ROWS column was poorly-named; a better name might be "DISTINCT_RANGE_<strong>VALUES</strong>" because it reports a count of distinct values, not rows.&nbsp; The AVG_RANGE_ROWS column reports the average number of rows (100) for each of the (1) distinct values in the range.&nbsp; Note that by dividing RANGE_ROWS by&nbsp;DISTINCT_RANGE_ROWS, you calculate <em>step density</em>, a measure of the uniqueness of an average value within the range of values described by that histogram step.</p>
<p>Some simple examples might help&nbsp;illustrate how the optimizer uses this data to estimate selectivity.&nbsp; For the query below, the optimizer's task is to guess the selectivity of a predicate like "country='Country11'".&nbsp; It will look for&nbsp;the "Country11" value in the&nbsp;histogram on the country column, and will find that this value happens to be one of the RANGE_HI_KEY values (histogram step endpoints).&nbsp;&nbsp;&nbsp;As a result, it can use EQ_ROWS -- the number of rows observed with this exact value. The estimated selectivity of the predicate [country]='Country11' is 100/10,000 (EQ_ROWS / [table rowcount]), or 0.01.&nbsp; The actual selectivity is also 0.01, meaning that the optimizer's&nbsp;estimated selectivity turned out to be accurate.&nbsp;</p>
<table border="0">
<tbody>
<tr>
<td></td>
<td>
<p>Actual Selectivity</p>
</td>
<td>Est. Selectivity</td>
</tr>
<tr>
<td>... WHERE [country]=<span style="background-color: #ffff00;">'Country11'</span></td>
<td>0.1</td>
<td>0.1</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>For the query below the search value 'Country10' is not a RANGE_HI_KEY -- instead, the value falls somewhere in the middle of a range of values that is summarized by a histogram step.&nbsp; This means that the optimizer will need to use step density, which implies an assumtion that the search value is typical of the values in its histogram step. The&nbsp;estimated&nbsp;number of rows with a given value in this historgram step is AVG_RANGE_ROWS, which is 100 for this histogram step.&nbsp; That means that the estimated and actual selectivity are both 0.01 (100 / 10,000).&nbsp;</p>
<table border="0">
<tbody>
<tr>
<td></td>
<td>Actual Selectivity</td>
<td>Est. Selectivity</td>
</tr>
<tr>
<td>... WHERE [country]=<span style="background-color: #ffff00;">'Country10'</span></td>
<td>0.1</td>
<td>0.1</td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>Here's a more interesting example. Note that the optimizer's guessed selectivity is wrong: it thought that 100 rows would be returned (selectivity of 0.1 * 10000 rows in the table), but in reality no rows passed the filter predicate.&nbsp;The search value 'Country10xyz' falls within the same histogram step used in the prior query, so the optimizer guessed the number of rows that would be returned using the exact same calculation as in the prior example. This isn't a bug; it's just a reflection of the fact that statistics can only provide an imperfect summary of&nbsp;the data in a table. You can read more about this <a href="https://blogs.msdn.com/b/bartd/archive/2006/07/25/limited-statistics-granularity.aspx">here</a>.</p>
<table border="0">
<tbody>
<tr>
<td></td>
<td>Actual Selectivity</td>
<td>Est. Selectivity</td>
</tr>
<tr>
<td>..&nbsp;WHERE<span style="background-color: #ffffff;"> [country]=<span style="background-color: #ffff00;">'Country10xyz'</span></span></td>
<td>0</td>
<td>0.1</td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>The information exposed by DBCC SHOW_STATISTICS can give you a better understanding of why the optimizer thinks that a particular plan will be cheaper than other plans. This is just a quick overview; for a much more detailed description SHOW_STATISTICS output and SQL statistics in general, check out the whitepaper "Statistics Used by the Query Optimizer in Microsoft SQL Server 2008" @ <a href="https://msdn.microsoft.com/en-us/library/dd535534(v=sql.100).aspx">https://msdn.microsoft.com/en-us/library/dd535534(v=sql.100).aspx</a>.&nbsp; (If you haven't ever read this paper, it's a very good read.)</p>
<p>&nbsp;</p>
<hr />
<p><strong><span style="font-size: medium;"><span style="font-size: large;">Cardinality </span><br /></span></strong>All of this brings us finally to cardinality, which is kind of "where the rubber hits the road" when you are&nbsp;trying to understand why a query plan is slow.</p>
<p>For our purposes, cardinality can be thought of as the number of rows returned by a query operator.&nbsp; (A few examples of physical query operators that you will probably recognize: Index Seek, Nested Loop Join, Filter)&nbsp; The cardinality of an operator like a Filter is determined by multiplying the selectivity of that operator -- that is, the % of its input rows that it will pass --&nbsp;by the cardinality (rowcount) of the operator's child.&nbsp; If an operator receives 500 rows from its child operator and has a selectivity of 0.3 (30%), it has a cardinality of 500*0.3, or 150 rows.</p>
<p>Each operator in a query plan has an <em>estimated cardinality </em>(the number of rows the optimizer guessed that the operator would return) and an <em>actual cardinality</em> (the number of rows that the operator returned in the real world).&nbsp; You can see both by running a query with "SET STATISTICS PROFILE ON".</p>
<table border="0">
<tbody>
<tr>
<td><strong>Rows</strong></td>
<td><strong>Executes</strong></td>
<td><strong></strong></td>
<td><strong>EstimateRows</strong></td>
<td><strong>EstimateExecutions</strong></td>
</tr>
<tr>
<td></td>
<td></td>
<td>SELECT c1 FROM t1 WHERE c1 = 'xyz'</td>
<td></td>
<td></td>
</tr>
<tr>
<td>2210</td>
<td>1</td>
<td>&nbsp; |--Filter([t1].[c1] = 'xyz')&nbsp;</td>
<td>21.0</td>
<td>1.0</td>
</tr>
<tr>
<td>2742</td>
<td>1</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |--Index Scan(OBJECT:([t1].[idx1]))</td>
<td>2971</td>
<td>1.0</td>
</tr>
<tr>
<td></td>
<td></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>Sometimes the query optimizer cannot accurately predict the number of rows that a given operator will return. This can prevent SQL from estimating the cost of a query plan correctly, which can lead to the selection of a suboptimal plan. Cardinality estimation errors&nbsp;are one of the most common causes of slow query plans in SQL Server, so it is very important to know how to identify cardinality estimation problems in a query plan. (That's a little beyond the point of this post, though -- here I'm&nbsp;just defining&nbsp;some terms.) In the plan above, you can see that SQL made a cardinality estimation error: it guessed that the filter would be very selective and would only pass 21 of 2971 rows, when&nbsp;in reality almost all of the rows passed the filter.</p>
<p>The optimizer has a number of ways&nbsp;to estimate cardinality, none of which are completely foolproof.&nbsp;</p>
<ol>
<li>If the predicate is&nbsp;simple like "column=123" and if the search value happens to be a histogram endpoint (RANGE_HI_KEY), then EQ_ROWS can be used for a very accurate estimate.</li>
<li>If the search value happens to fall between two step endpoints, then the average density of values in that particular histogram step is used to estimate predicate selectivity and operator cardinality.</li>
<li>If the specific search value is not known at compile time, the next best option is to use average column density ("All density"), which can be used to calculate the number of rows that will match an average value in the column.&nbsp;</li>
<li>In some cases none of the above are possible and optimizer has to resort to a "<em>magic number"</em>-based estimate.&nbsp;&nbsp;For example, it might&nbsp;make a totally&nbsp;blind guess that 10% of the rows will be returned, where the "10%" value would be hardcoded in the optimizer's code rather than being derived from statistics.&nbsp;</li>
</ol>
<p>In most scenarios option #1 is the ideal, but because most values aren't histogram endpoints, option #2 is the most frequently-used method for simple filter and join predicates.&nbsp;Option #4 usually provides the worst estimates, and is only used when there is no other alternative. Thankfully, most of the time when a magic number is used, this is a side effect of poor query design; by rewriting the query you can enable a more accurate cardinality estimate based on either average column density or histogram lookup.</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Living with SQL's 900 Byte Index Key Length Limit</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/living-with-sqls-900-byte-index-key-length-limit</link>
      <pubDate>Thu, 06 Jan 2011 13:02:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2011/01/06/living-with-sqls-900-byte-index-key-length-limit/</guid>
      <description><![CDATA[We recently had a situation where we needed to interface with an external non-relational data source...]]></description>
      <content:encoded><![CDATA[<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">We recently had a situation where we needed to interface with an external non-relational data source that happened to use really long strings to identify entity instances. These identity strings had theoretically unbounded length, but in practice they were never more than 1000-2000 characters. The database layer needed to accept an identifier of this sort and use it to retrieve some information from the relational store that was related to that object. I&rsquo;ll call the entity type &ldquo;widget&rdquo;, and the long string identifier from the external system the &ldquo;external key&rdquo;. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">We could just store the external key in the database table and use it to locate the correct widget, but we couldn&rsquo;t afford to scan the widget table for every lookup. The number of rows in the widget table was expected to be large, so we needed the lookup to be indexed. Unfortunately, SQL has a limit of 900 bytes for the keys in each index, and the external key values can be longer than this. If you create an index on combination of columns that could contain more than 900 bytes, you get this warning: </span></p>
<p class="MsoNormal"><span style="color: red;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></span></p>
<p class="MsoNormal"><span style="color: red;"><span style="font-size: small;"><span style="font-family: Calibri;">Warning! The maximum key length is 900 bytes. The index 'xyz' has maximum length of 4000 bytes. For some combination of large values, the insert/update operation will fail.<o:p></o:p></span></span></span></p>
<p class="MsoNormal"><span style="color: red;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></span></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">Then if you try to insert a row into the table that has more than 900 bytes in the indexed columns, it will fail with this error: </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="color: red;"><span style="font-size: small;"><span style="font-family: Calibri;">Msg 1946, Level 16, State 3, Line 11<o:p></o:p></span></span></span></p>
<p class="MsoNormal"><span style="color: red;"><span style="font-size: small;"><span style="font-family: Calibri;">Operation failed. The index entry of length 1501 bytes for the index 'xyz' exceeds the maximum length of 900 bytes.<o:p></o:p></span></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">Even if this restriction within SQL didn&rsquo;t exist, I wouldn&rsquo;t ever think of creating a table with a &gt;1KB primary key. A surrogate primary key like an IDENTITY column or a GUID/UNIQUEIDENTIFIER column should be used, instead. You'd use the surrogate as your primary key, and all lookups and foreign keys within the data tier should use the surrogate.&nbsp;But the external system doesn&rsquo;t know anything about the surrogate keys used in the data tier; the only identifier that the external system can pass to the database is that &gt;900 byte external key value. So while a surrogate for primary key is a good idea here, it doesn&rsquo;t solve the immediate problem &ndash; we still need to be able to do an efficient (indexed) lookup of an external key value in the database, even though a column that could store the external key values would be too large to index. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">If you find yourself in a similar situation, here are some alternatives to consider: </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;">A. Change the external system to use an identifier that is always less than 900 bytes long.</b> Examine the entity&rsquo;s attributes: is there some combination of attributes that together make up a candidate key (in other words, is there some combination of other attributes that is guaranteed to be unique for each entity instance)?<span style="mso-spacerun: yes;">&nbsp; </span>Even if the external system doesn&rsquo;t internally use those columns as an identifier, it could still pass them to the data tier when it wanted to look up a widget. <span style="mso-spacerun: yes;">&nbsp;</span>In the database, you&rsquo;d create a UNIQUE index on this column or combination of columns. <span style="mso-spacerun: yes;">&nbsp;</span></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;">B. Require non-key columns as additional criteria for each lookup</b>. For example, suppose a widget has a &ldquo;Name&rdquo; attribute that is always short enough to be indexed. Name is not guaranteed to be globally unique, so neither the external system nor the relational model can use this as a key. But if the column is fairly selective it may be sufficient to give you lookups that are efficient enough that it&rsquo;s almost like doing an index seek on the external key. In this example you would index the [name] column in the database, and use a routine for lookups like this. </span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">PROC</span> usp_get_widet @external_key <span style="color: blue;">VARCHAR</span><span style="color: gray;">(</span><span style="color: fuchsia;">MAX</span><span style="color: gray;">),</span> @name <span style="color: blue;">NVARCHAR</span><span style="color: gray;">(</span>60<span style="color: gray;">)</span> <span style="color: blue;">AS</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>SELECT</span> <span style="color: gray;">*</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>FROM</span> widgets <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>WHERE</span> external_key <span style="color: gray;">=</span> @external_key <span style="background: yellow; color: gray; mso-highlight: yellow;">AND</span><span style="background: yellow; mso-highlight: yellow;"> name <span style="color: gray;">=</span> @name</span><span style="color: gray;">;</span><span style="color: blue;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">SQL will use a seek on the non-clustered [name] index to narrow the results down to a tiny percentage of the table (hopefully no more than a few rows), then it will do a residual filter to get rid of all but the single row that matches the external key value. Note that you can add more than one supplemental search column if necessary to increase the selectivity of the non-key index seek. Also be sure to note that you still need to do the filter on the [external_key] column. That predicate won&rsquo;t be satisfied by an index seek, but it&rsquo;s still necessary for correctness. Consider making the full external key INCLUDEd in the nonclustered index, to minimize the number of bookmark lookups the engine needs to do to find the single matching row. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">One obvious downside to this approach is that the additional required search criteria complicate the interface between the database and the external component. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;">C. Truncate the external key to 900 bytes and index the truncated portion of the key</b>. For example, your widget table might look like this: </span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">TABLE</span> widgets<span style="color: blue;"> </span><span style="color: gray;">(<br /></span><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span>surrogate_key <span style="color: blue;">INT</span> <span style="color: blue;">PRIMARY KEY IDENTITY</span><span style="color: gray;">,</span> <br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span>external_key <span style="color: blue;">VARCHAR</span><span style="color: gray;">(</span><span style="color: fuchsia;">MAX</span><span style="color: gray;">),</span> <br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="background: yellow; mso-highlight: yellow;">external_key_fragment <span style="color: blue;">AS</span> <span style="color: fuchsia;">LEFT </span><span style="color: gray;">(</span>external_key<span style="color: gray;">,</span> 900<span style="color: gray;">),</span></span> <br /><span style="mso-spacerun: yes;">&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;</span><span style="color: gray;">...</span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">You&rsquo;d create a (nonunique) index on the [external_key_fragment] column, and your lookup procedure might look something like this: </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">PROC</span> usp_get_widet @external_key <span style="color: blue;">VARCHAR</span><span style="color: gray;">(</span><span style="color: fuchsia;">MAX</span><span style="color: gray;">) </span><span style="color: blue;">AS</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>SELECT</span> <span style="color: gray;">*</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>FROM</span> widgets <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>WHERE</span> external_key <span style="color: gray;">=</span> @external_key <br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="background: yellow; color: gray; mso-highlight: yellow;">AND</span><span style="background: yellow; mso-highlight: yellow;"> external_key_fragment <span style="color: gray;">=</span> <span style="color: fuchsia;">LEFT </span><span style="color: gray;">(</span>@external_key<span style="color: gray;">,</span> 900<span style="color: gray;">)</span></span><span style="color: gray;">;</span><o:p></o:p></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">Of course, this assumes that some predictable 900-byte portion of the external key is reasonably selective. If it&rsquo;s possible for a large portion of the rows to have the same initial 900 bytes (assuming the fragment is generated in the way shown above), then this won&rsquo;t help any &ndash; the optimizer will correctly estimate that a scan would be more efficient than a seek, and you&rsquo;ll still end up scanning the entire table for each lookup. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><br /><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;">D. Break up or parse the external key into its component parts, and index a selective subset of the parts.<span style="mso-spacerun: yes;">&nbsp; </span></b>This assumes that the long external key is really a serialized version of a composite key. It must have some underlying structure, and it must be practical to break the key into its child parts within the data tier. It also assumes that some portion of this structured key is guaranteed to be unique enough to support an efficient index seek that only returned a few rows. </span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">For example, suppose that the external key was string that actually encoded a hierarchy like &ldquo;RootIdentifierX\ChildIdentifierY\GrandChildIdentifierZ\...&rdquo;<span style="mso-spacerun: yes;">&nbsp; </span>If you&rsquo;re willing to push knowledge of the structure of this serialized key down into the data tier, the database could parse this apart and use the identifiers for the first few levels of the hierarchy to do a secondary lookup that could be satisfied via an index seek. You&rsquo;d need to store those parts of the external key in separate columns (so they could be index) in addition to the full external key. <span style="mso-spacerun: yes;">&nbsp;</span>Once you&rsquo;ve indexed the portions that you can, consider adding the remainder as an INCLUDEd column in the index; that would allow the index seek to locate the single matching row without resorting to a bookmark lookup. (It would also make it less likely that the optimizer would choose to scan the table because it guessed that a seek on the component parts wouldn&rsquo;t be selective enough.) </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;">E. Hash the external key and index the hash.</b> This is essentially building a hash table and persisting it in the database. With this approach, your widgets table might look something like this: </span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;</span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">TABLE</span> widgets<span style="color: blue;"> </span><span style="color: gray;">(<br /></span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;</span>surrogate_key <span style="color: blue;">INT PRIMARY KEY IDENTITY</span><span style="color: gray;">,</span>&nbsp;<br /><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;</span>external_key <span style="color: blue;">VARCHAR</span><span style="color: gray;">(</span><span style="color: fuchsia;">MAX</span><span style="color: gray;">),</span>&nbsp;<br /><br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;</span><span style="color: green;">-- A computed column that hashes the external key (MD5 returns a&nbsp;<br /><span style="color: #000000;"><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;</span></span><span style="color: green;">-- </span>128-bit hash).&nbsp;<br /></span></span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;</span><span style="background: yellow; mso-highlight: yellow;">external_key_hash <span style="color: blue;">AS</span> <span style="color: fuchsia;">CAST</span><span style="color: blue;"> </span><span style="color: gray;">(</span><span style="color: fuchsia;">HASHBYTES</span><span style="color: gray;">(</span><span style="color: red;">'MD5'</span><span style="color: gray;">,</span> external_key<span style="color: gray;">)</span> <span style="color: blue;">AS</span> <span style="color: blue;">VARBINARY</span><span style="color: gray;">(</span>16<span style="color: gray;">)),</span></span>&nbsp;<br /><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;</span>...<o:p></o:p></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">The [external_key_hash] column would be indexed, and the lookup proc would do this: </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;</span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">PROC</span> usp_get_widet @external_key <span style="color: blue;">VARCHAR</span><span style="color: gray;">(</span><span style="color: fuchsia;">MAX</span><span style="color: gray;">) </span><span style="color: blue;">AS</span>&nbsp;<br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;</span>SELECT</span> <span style="color: gray;">*</span>&nbsp;<br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;</span>FROM</span> widgets&nbsp;<br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;</span>WHERE</span> external_key <span style="color: gray;">=</span> @external_key&nbsp;<br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;</span><span style="background: yellow; color: gray; mso-highlight: yellow;">AND</span><span style="background: yellow; mso-highlight: yellow;"> external_key_hash&nbsp;<br /><span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;<span style="mso-spacerun: yes;">&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="mso-spacerun: yes;">&nbsp;</span></span><span style="color: gray;">=</span> <span style="color: fuchsia;">CAST</span><span style="color: blue;"> </span><span style="color: gray;">(</span><span style="color: fuchsia;">HASHBYTES</span><span style="color: gray;">(</span><span style="color: red;">'MD5'</span><span style="color: gray;">,</span> @external_key<span style="color: gray;">)</span> <span style="color: blue;">AS</span> <span style="color: blue;">VARBINARY</span><span style="color: gray;">(</span>16<span style="color: gray;">));</span></span><o:p></o:p></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">It could be argued that the CAST(HASHBYTES(&hellip;)) stuff should be hidden within a UDF (ditto for alternatives (C) and (D)): </span></p>
<p class="MsoNormal"><br /><span style="font-family: 'Courier New'; color: blue; font-size: 9pt; mso-no-proof: yes;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>CREATE</span><span style="font-family: 'Courier New'; font-size: 9pt; mso-no-proof: yes;"> <span style="color: blue;">FUNCTION</span> dbo<span style="color: gray;">.</span>fn_hash_external_key<span style="color: blue;"> </span><span style="color: gray;">(</span>@urn <span style="color: blue;">nvarchar</span><span style="color: gray;">(</span><span style="color: fuchsia;">max</span><span style="color: gray;">))</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>RETURNS</span> <span style="color: blue;">VARBINARY</span><span style="color: gray;">(</span>16<span style="color: gray;">)<br /></span><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>WITH</span> <span style="color: blue;">SCHEMABINDING</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>AS</span> <br /><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>BEGIN<br /></span><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span><span style="color: blue;">RETURN</span> <span style="color: fuchsia;">HASHBYTES</span><span style="color: blue;"> </span><span style="color: gray;">(</span><span style="color: red;">'MD5'</span><span style="color: gray;">,</span> @urn<span style="color: gray;">);<br /></span><span style="color: blue;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span>END</span><span style="color: gray;">;</span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">The computed column and the lookup proc would both reference this function so that the details of the hash function lived in a single place. That would indeed be cleaner, but before taking that route be sure you&rsquo;re aware of the potential problems with scalar UDF performance that are described at </span><a href="https://www.developerfusion.com/community/blog-entry/8389063/tsql-scalar-functions-are-evil/"><span style="font-family: Calibri; color: #0000ff; font-size: small;">https://www.developerfusion.com/community/blog-entry/8389063/tsql-scalar-functions-are-evil/</span></a><span style="font-family: Calibri; font-size: small;"> and </span><a href="https://sqlblog.com/blogs/adam_machanic/archive/2006/08/04/scalar-functions-inlining-and-performance-an-entertaining-title-for-a-boring-post.aspx"><span style="font-family: Calibri; color: #0000ff; font-size: small;">https://sqlblog.com/blogs/adam_machanic/archive/2006/08/04/scalar-functions-inlining-and-performance-an-entertaining-title-for-a-boring-post.aspx</span></a><span style="font-size: small;"><span style="font-family: Calibri;">.<span style="mso-spacerun: yes;">&nbsp; </span></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">Of all of these, option (A) is the only one that&nbsp;I find&nbsp;very aesthetically pleasing.<span style="mso-spacerun: yes;">&nbsp; </span>The others could work in a pinch, though.<span style="mso-spacerun: yes;">&nbsp; </span>Please drop me a comment if I&rsquo;m overlooking some other alternative here. </span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">(Cross-posted <a href="https://bartduncansql.wordpress.com/2011/02/26/living-with-sqls-900-byte-index-key-length-limit/">here</a>)</span></o:p></p>]]></content:encoded>
    </item>
    <item>
      <title>Query Hash Statistics, a query cost analysis tool, now available for download</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/query-hash-statistics-a-query-cost-analysis-tool-now-available-for-download</link>
      <pubDate>Wed, 03 Nov 2010 11:06:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2010/11/03/query-hash-statistics-a-query-cost-analysis-tool-now-available-for-download/</guid>
      <description><![CDATA[Some time ago I described the query fingerprint and query plan fingerprint (a.k.a. query hash /...]]></description>
      <content:encoded><![CDATA[<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;">Some time ago I described the </span><a href="https://blogs.msdn.com/b/bartd/archive/2008/09/03/query-fingerprints-and-plan-fingerprints_3a00_-the-best-new-sql-2008-feature-you_2700_ve-never-heard-of.aspx"><span style="font-size: small;">query fingerprint</span></a><span style="font-size: small;"> and query plan fingerprint (a.k.a. query hash / query plan hash) features that were added in SQL Server 2008. <span style="mso-spacerun: yes;">&nbsp;</span>A fingerprint identifies all queries or all plans that have the same &ldquo;shape&rdquo;. <span style="mso-spacerun: yes;">&nbsp;</span>With query fingerprints you can get the cumulative cost of all executions of a query <i style="mso-bidi-font-style: normal">even if the query is non-parameterized</i> and has different inline literal values for each execution. <span style="mso-spacerun: yes;">&nbsp;</span>Previously, the only way to get this type of query performance data was to capture a Profiler trace and run the trace through a post-processing tool like <a href="https://support.microsoft.com/kb/944837">ReadTrace</a> or <a href="https://www.scalesql.com/cleartrace">ClearTrace</a>. <span style="mso-spacerun: yes;">&nbsp;</span>Now you can do it with a few DMV queries.</span></span></p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;"><br /></span></span><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;">Query Hash Statistics is a free download that collects historical query and query plan fingerprint statistics and allows you easily&nbsp;to view the data in SSMS and see the true cumulative cost of the queries in each of your databases. <span style="mso-spacerun: yes;">&nbsp;</span>You can use the project as-is, or &ndash; if you&rsquo;re the tinkering type &ndash; disassemble it and build your own custom query hash stats solution. (Source code is included.) <span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><o:p><span style="font-size: small;"><br /></span></o:p></span></p>
<p class="MsoNormal"><span style="font-size: small;"><em><b style="mso-bidi-font-weight: normal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;">Download Query Hash Statistics @</span></b></em><b style="mso-bidi-font-weight: normal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"> <a href="https://code.msdn.microsoft.com/QueryHashStatistics">https://code.msdn.microsoft.com/QueryHashStatistics</a>&nbsp;</span></b><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><span style="color: black;"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></span>&nbsp;</p>
<p class="MsoNormal">&nbsp;</p>
<p class="MsoNormal"><span style="font-size: small;"><span style="text-decoration: underline;"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><strong>Project Description: </strong></span></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="text-decoration: underline;"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"></span></span></span><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;">Query Hash Statistics collects Microsoft SQL Server query execution statistics and uploads the data to a central warehouse. The solution also includes a set of reports to analyze and visualize this data within SQL Server Management Studio (SSMS). Query Hash Statistics differs from most other query cost analysis tools in that it builds on the "query fingerprint" and "query plan fingerprint" features that were added in SQL Server 2008. These features make it possible to calculate the cumulative cost of all executions of a query, even when the application doesn't parameterize its queries. A solution like Query Hash Statistics that builds on these features can do low-overhead query cost monitoring with a degree of accuracy that was impossible in prior releases of SQL Server. </span></span></span></p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;"></span></span><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;</span></span></span></p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">Because the solution relies on new SQL engine features that were added in SQL Server 2008, it only works with SQL Server 2008 and later. </span></span></span></span></p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;"></span></span><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;</span></span></span></p>
<p class="MsoNormal"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><span style="color: black;">Source code is included, to demonstrate how to build similar solutions. In addition to highlighting the engine's query and query plan fingerprint features, the project shows how to use extensibility features in Data Collector and SSMS. By following the patterns demonstrated in this project, you can learn how to:&nbsp;</span>&nbsp;</span></span></span></p>
<ul>
<li>
<div class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><strong>Identify cumulative query cost without a profiler trace </strong>using the SQL Server "plan fingerprint" and "query fingerprint" features. </span></span></span></span></div>
</li>
<li>
<div class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><strong>Use Data Collector to upload data from SQL</strong> <strong>DMVs</strong> to a central management database. </span></span></span></span></div>
</li>
<li>
<div class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><strong>Build reports that can be hosted within SQL Server Management Studio</strong> (SSMS) to analyze the data in your central management database.<o:p></o:p></span></span></span></span></div>
</li>
</ul>
<p class="MsoNormal"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><o:p></o:p><o:p></o:p></span></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/08/55/99/rpt_query_hash_thumbnails.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/CommunityServer-Components-PostAttachments/00-10-08-55-99/rpt_5F00_query_5F00_hash_5F00_thumbnails.png" /></span></span></span></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;</span></span></span></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"></span></span></span></span></o:p></p>
<p><span style="font-size: small;"><span style="font-family: Calibri;">
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;">Check it out, and let us know about your experiences in the Discussions tab on the MSDN Code Gallery project. If you have a suggestion or think you've found a bug, please open a new issue under the Issue Tracker tab. Thanks! </span></span></span></span></span></span></span></span></p>
<p>
<p class="MsoNormal"><span style="font-family: 'Segoe UI','sans-serif'; color: black;"><span style="font-size: small;"></span></span><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;</span></span></span></p>
</p>
</span></span></p>
<p>&nbsp;</p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;">&nbsp;</span></span></span></p>
<p class="MsoNormal"><span style="font-size: x-small;"><span style="font-family: verdana,geneva;"><span style="font-size: x-small;"><span style="color: #ffffff;">Additional query keywords: QueryHashStats Query Hash Stats query performance query troubleshooting cumulative query cost analysis Microsoft SQL Server MSSQL MS SQL T-SQL TSQL Transact-SQL query plan cost cumulative plan cost find expensive queries find slow queries DMV dm_exec_query_stats plan fingerprints query fingerprints plan hash query hash query_plan_hash query_hash dm_exec_requests top queries most expensive queries<br /></span></span></span></span></p>
<p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/08/55/99/rpt_query_hash_thumbnails.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-10-08-55-99/rpt_5F00_query_5F00_hash_5F00_thumbnails.png">rpt_query_hash_thumbnails.png</a></p>]]></content:encoded>
    </item>
    <item>
      <title>T-SQL UDTs.  (Huh!)  What are they good for?</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/t-sql-udts-huh-what-are-they-good-for</link>
      <pubDate>Wed, 25 Aug 2010 00:43:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2010/08/25/t-sql-udts-huh-what-are-they-good-for/</guid>
      <description><![CDATA[(The title of this post might seem a little inflammatory, but it&rsquo;s not just a troll -- I...]]></description>
      <content:encoded><![CDATA[<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;">(The title of this post might seem a little inflammatory, but it&rsquo;s not just a troll -- I really do think that people should seriously question whether it&rsquo;s wise to use T-SQL User Defined Data Types.<span style="mso-spacerun: yes;">&nbsp; </span>Mostly, though, I just like that Edwin Starr song.)<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;">Let me start this post off by saying that I understand why user-defined data types (UDTs) in T-SQL seem alluring.<span style="mso-spacerun: yes;">&nbsp; </span>At first glance, you might expect UDTs to give you some very nice benefits, such as: <o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Expected Benefit #1</span>:</b> Suppose you have a field that stores percentages values (0-100).<span style="mso-spacerun: yes;">&nbsp; </span>In T-SQL you might choose the <b style="mso-bidi-font-weight: normal;">tinyint </b>data type for such a field, but this isn&rsquo;t the only data type that you could have chosen to store percentage values.<span style="mso-spacerun: yes;">&nbsp; </span>If there is more than one table that contains a percentage-type field, you could end up with the same sort of value being stored using different data types.<span style="mso-spacerun: yes;">&nbsp; </span>By creating a UDT called <b style="mso-bidi-font-weight: normal;">PERCENT</b>, you can avoid the need for database developers to memorize &ldquo;<i style="mso-bidi-font-style: normal;">Percentages should be stored using <b style="mso-bidi-font-weight: normal;">tinyint</b></i>.&rdquo; <o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Reality</span>: </b>This is based on the idea that it&rsquo;s easier for a developer to memorize &ldquo;<i style="mso-bidi-font-style: normal;">Percentages are stored using a UDT named <b style="mso-bidi-font-weight: normal;">PERCENT</b></i>&rdquo; than it is to memorize &ldquo;<i style="mso-bidi-font-style: normal;">Percentages are stored using system type <b style="mso-bidi-font-weight: normal;">tinyint</b></i>.&rdquo;<span style="mso-spacerun: yes;">&nbsp; </span>The problem is that you have to know the underlying data type in order to deal with the column correctly.<span style="mso-spacerun: yes;">&nbsp; </span>For example, a database developer that was retrieving a <b style="mso-bidi-font-weight: normal;">PERCENT</b> (UDT) field in a VB.NET or C# app would need to know what .Net data type should be used to retrieve values from the field.<span style="mso-spacerun: yes;">&nbsp; </span>A thoughtful dev would wonder: &ldquo;<i style="mso-bidi-font-style: normal;">Maybe I should store this <b style="mso-bidi-font-weight: normal;">PERCENT </b>value in a <b style="mso-bidi-font-weight: normal;">byte</b> variable, which can hold values from 0 to 255.<span style="mso-spacerun: yes;">&nbsp; </span>But wait: Can a <b style="mso-bidi-font-weight: normal;">PERCENT </b>column hold a negative percentage?<span style="mso-spacerun: yes;">&nbsp; </span>If so, I&rsquo;ll need to use the <b style="mso-bidi-font-weight: normal;">short</b> data type, which can represent negative integers.<span style="mso-spacerun: yes;">&nbsp; </span>Hold on a second! <span style="mso-spacerun: yes;">&nbsp;</span>I wonder if this <b style="mso-bidi-font-weight: normal;">PERCENT </b>data<b style="mso-bidi-font-weight: normal;"> </b>type can store fractional percentages?<span style="mso-spacerun: yes;">&nbsp; </span>If so, I&rsquo;d need to retrieve the column value into a <b style="mso-bidi-font-weight: normal;">decimal </b>variable...</i>&rdquo;<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;">In other words, if you don&rsquo;t use a UDT, the application developer has to memorize &ldquo;<i style="mso-bidi-font-style: normal;">Percentages are stored using system type <b style="mso-bidi-font-weight: normal;">tinyint</b></i>.&rdquo;<span style="mso-spacerun: yes;">&nbsp; </span>But if you use a T-SQL UDT the application developer has to know both &ldquo;<i style="mso-bidi-font-style: normal;">Percentages are stored using a UDT named <b style="mso-bidi-font-weight: normal;">PERCENT</b></i>&rdquo; <span style="text-decoration: underline;">and</span> &ldquo;<i style="mso-bidi-font-style: normal;">Percentages are stored using system type <b style="mso-bidi-font-weight: normal;">tinyint</b></i>.&rdquo;<span style="mso-spacerun: yes;">&nbsp; </span>The attempt to make the app developer&rsquo;s life easier can backfire and actually made it a little harder.<span style="mso-spacerun: yes;">&nbsp; </span>(It&rsquo;s worth mentioning that SQLCLR UDTs don&rsquo;t have this same limitation.<span style="mso-spacerun: yes;">&nbsp; </span>App developers can reference an assembly that gives them a client-side data type that corresponds to the server-side SQLCLR UDT, so the developer doesn&rsquo;t have to know the details of the UDT&rsquo;s implementation in order to use it.) <o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Expected Benefit #2</span>: </b>A <b style="mso-bidi-font-weight: normal;">tinyint</b> can store 0-255, but your percentage fields should only allow 0-100.<span style="mso-spacerun: yes;">&nbsp; </span>You can restrict a column&rsquo;s domain by defining a CHECK constraint on the column.<span style="mso-spacerun: yes;">&nbsp; </span>But if there are several percentage type columns in several different tables, you&rsquo;d need to define identical CHECK constraints on each column.<span style="mso-spacerun: yes;">&nbsp; </span>Wouldn&rsquo;t it be nice if you could attach the constraint to the <b style="mso-bidi-font-weight: normal;">PERCENT</b> user-defined data type, so that it would apply automatically to each column that used this UDT?<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Reality</span>:</b> This would be a real benefit of T-SQL UDTs.<span style="mso-spacerun: yes;">&nbsp; </span>Unfortunately, the ability to attach a rule to a UDT has been officially deprecated, so you shouldn&rsquo;t use this functionality.<span style="mso-spacerun: yes;">&nbsp; </span>Since you can no longer attach custom domain constraints or other business rules to the data type, a T-SQL UDT is really nothing more than an alias for a system data type.<span style="mso-spacerun: yes;">&nbsp; </span>Sorry! <o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Expected Benefit #3</span>: </b>Suppose that your requirements have changed, and now you need to be able to store fractional percentages.<span style="mso-spacerun: yes;">&nbsp; </span>You decide to store percentages as <b style="mso-bidi-font-weight: normal;">decimal(3,2)</b> instead of <b style="mso-bidi-font-weight: normal;">tinyint</b>.<span style="mso-spacerun: yes;">&nbsp; </span>If all percentage columns were defined as <b style="mso-bidi-font-weight: normal;">tinyint</b>, you&rsquo;d have to go around to all of the columns and modify each column&rsquo;s data type separately.<span style="mso-spacerun: yes;">&nbsp; </span>And to change a column&rsquo;s data type you must first drop any indexes or constraints that reference the column.<span style="mso-spacerun: yes;">&nbsp; </span>What a pain.<span style="mso-spacerun: yes;">&nbsp; </span>Wouldn&rsquo;t it be easier if you could just change the <b style="mso-bidi-font-weight: normal;">PERCENT</b> UDT definition in one place? <o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">Reality</span>: </b>T-SQL doesn&rsquo;t have an ALTER TYPE command for UDTs. <span style="mso-spacerun: yes;">&nbsp;</span>To change a UDT definition you must do a DROP TYPE followed by CREATE TYPE.<span style="mso-spacerun: yes;">&nbsp; </span>But of course you can&rsquo;t drop a type that is being used in a column, so you have to do an awkward little dance to switch all of the columns that use the UDT to use the underlying system data type, instead (so they no longer reference the UDT), then drop and redefine the UDT, then ALTER the columns a second time to re-reference the new UDT.<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl1 ALTER COLUMN foo_pct TINYINT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl2 ALTER COLUMN foo_pct TINYINT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl3 ALTER COLUMN foo_pct TINYINT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl4 ALTER COLUMN foo_pct TINYINT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DROP TYPE PERCENT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CREATE TYPE PERCENT FROM DECIMAL(3,2);<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl1 ALTER COLUMN foo_pct PERCENT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;</span>ALTER TABLE tbl2 ALTER COLUMN foo_pct PERCENT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl3 ALTER COLUMN foo_pct PERCENT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl4 ALTER COLUMN foo_pct PERCENT;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;">And if any indexes or constraints reference any of these columns, you&rsquo;d still have to drop those before the first ALTER COLUMN, and recreate them afterward.<span style="mso-spacerun: yes;">&nbsp; </span>If you have to write DDL that redefines the columns using the base data type that the UDT is supposed to hide, where&rsquo;s the abstraction benefit?<span style="mso-spacerun: yes;">&nbsp; </span>More importantly, if you didn&rsquo;t use UDTs this same change can be done with less than half the code: <span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;</span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl1 ALTER COLUMN foo_pct DECIMAL(3,2);<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp; </span><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>ALTER TABLE tbl2 ALTER COLUMN foo_pct DECIMAL(3,2);<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl3 ALTER COLUMN foo_pct DECIMAL(3,2);<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="mso-spacerun: yes;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ALTER TABLE tbl4 ALTER COLUMN foo_pct DECIMAL(3,2);<o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;">So there you have the reasons why I avoid using T-SQL user-defined types in my own code.<span style="mso-spacerun: yes;">&nbsp; </span>It&rsquo;s not that UDTs are all that bad; it&rsquo;s just that in my experience they fall short of their promise, and they can make some things even more cumbersome than the same task would be without the use of UDTs.<span style="mso-spacerun: yes;">&nbsp; </span>If you think I&rsquo;m overlooking some concrete benefit of UDTs, I&rsquo;d love to hear from you in the comments section.<span style="mso-spacerun: yes;">&nbsp; </span>If you do choose to use UDTs, just be sure you aren&rsquo;t basing the decision to use them on inaccurate assumptions about the benefits they&rsquo;ll provide.<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><span style="font-size: small;"><span style="font-family: Calibri;"><b style="mso-bidi-font-weight: normal;"><span style="text-decoration: underline;">An important caveat</span></b>: The above applies to simple T-SQL UDTs.<span style="mso-spacerun: yes;">&nbsp; </span>You can also create a user-defined type in SQL Server using SQLCLR and .Net.<span style="mso-spacerun: yes;">&nbsp; </span>While a T-SQL UDT is never anything more than a simple alias for one of the system data types, a SQLCLR UDT can be a truly custom data type.<span style="mso-spacerun: yes;">&nbsp; </span>With SQLCLR you can implement complex types, embed custom business logic like domain constraints or validation rules into the type, and so on.<span style="mso-spacerun: yes;">&nbsp; </span>In other words, with SQLCLR UDTs you can actually realize the benefit that I called &ldquo;<i style="mso-bidi-font-style: normal;">Expected Benefit #2</i>&rdquo;, above.<span style="mso-spacerun: yes;">&nbsp; </span>As mentioned earlier, you also get much of <i style="mso-bidi-font-style: normal;">Expected Benefit #1</i>.<span style="mso-spacerun: yes;">&nbsp; </span>SQLCLR UDTs still have the limitations described in #3, but the ability to extend SQL Server in the way that SQLCLR allows is very powerful.<span style="mso-spacerun: yes;">&nbsp; </span>That benefit could easily compensate for the issue discussed in #3, if the task you are trying to accomplish would be much harder to implement using one of the system data types.<span style="mso-spacerun: yes;">&nbsp; </span><o:p></o:p></span></span></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt;"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>]]></content:encoded>
    </item>
    <item>
      <title>SQL Server Sizing Resources</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/sql-server-sizing-resources</link>
      <pubDate>Wed, 16 Jun 2010 20:33:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2010/06/16/sql-server-sizing-resources/</guid>
      <description><![CDATA[Recently, I was asked whether Microsoft had any SQL Server hardware sizing tools.&nbsp; The asker...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: small;"></span><span style="font-size: small;">Recently, I was asked whether Microsoft had any SQL Server hardware sizing tools.&nbsp; The asker referred me to <a href="https://www.sizinglounge.com">https://www.sizinglounge.com</a>&nbsp;as an example of what he was looking for.&nbsp; (Sizing Lounge apparently allows you to select an app like SAP or Exchange, answer a couple of questions about things like concurrent user counts and desired CPU utilization levels, and receive a list of possible server configurations that are supposed to be able to handle the load.)&nbsp; This isn't the first time I've fielded this question, so I decided to put my answer in a blog post.&nbsp; </span></p>
<p><span style="font-size: small;">When you're trying to size a server that will be running a well-known application -- say, SAP, or Exchange -- you have a chance of getting a recommendation that is reasonably precise.&nbsp; This is because the person or tool that is&nbsp;making the sizing recommendation can make well-educated guesses based on knowledge of the way that users generally use that app, and the app's typical transaction costs.&nbsp; But if you want non-app-specific guidance for an unknown or arbitrary database app, the problem gets much harder.&nbsp; This is because a single business transaction from one application (say, &ldquo;<em>log in</em>&rdquo;, or &ldquo;<em>submit purchase order</em>&rdquo;) could be orders of magnitude cheaper or more expensive than the exact same type of business transaction in a different app.&nbsp; In other words, there are limits to how precise application-independent sizing guidance can be for an unknown app when the&nbsp;inputs are nothing more than simple metrics like # of concurrent users, queries/second, or transactions/sec.&nbsp; </span></p>
<p><span style="font-size: small;"></span>&nbsp;</p>
<p><span style="font-size: large;"><strong>Application-Specific Server Sizing</strong></span></p>
<p><span style="font-size: small;">My first recommendation is to go to the application developer, not the RDBMS&nbsp;developer, for the most meaningful sizing guidance.&nbsp; I don't think that even Microsoft has explicit sizing guidance for every MS app that runs on SQL, but there are sizing tools or whitepapers that make hardware recommendations for many or most of our SQL apps.&nbsp; A few examples: </span></p>
<ul>
<li><span style="font-size: small;"><a href="https://technet.microsoft.com/en-us/library/cc298801.aspx">Sharepoint</a></span></li>
<li><span style="font-size: small;"><a href="https://msdn.microsoft.com/en-us/library/cc966418.aspx">Reporting Services</a></span></li>
<li><span style="font-size: small;"><a href="https://technet.microsoft.com/en-us/library/bb418778.aspx">Forefront</a></span></li>
<li><span style="font-size: small;"><a href="https://msdn.microsoft.com/en-us/library/bb668966.aspx">Team Foundation Server</a></span></li>
</ul>
<p><span style="font-size: small;"><span style="font-size: small;">If you can't find explicit sizing guidelines for the application you'll be running on SQL, I'd try these alternatives,&nbsp;listed in no particular order: </span></span></p>
<ul>
<li><span style="font-size: small;"><span style="font-size: small;">Case studies from the ISV (case studies often include server hardware details and information about concurrent user counts, data volumes, etc)</span></span></li>
<li><span style="font-size: small;"><span style="font-size: small;">Post a query to online forums dedicated to the app (for&nbsp;Microsoft apps you might try the <a href="https://social.technet.microsoft.com/Forums/en-US/categories">TechNet forums</a>)</span></span></li>
<li><span style="font-size: small;"><span style="font-size: small;">Contact the app ISV directly</span></span></li>
</ul>
<p><span style="font-size: small;"></span>&nbsp;</p>
<p><span style="font-size: large;"><strong>Application-Independent Server Sizing</strong></span></p>
<p><span style="font-size: small;">You may come up empty-handed in your search for application-specific sizing guidance.&nbsp; If the application was developed in-house or if you're sizing a server for an app that doesn't even exist yet, you'll definitely be in this situation.&nbsp; <span style="font-size: small;">Application-independent hardware sizing guides do exist.&nbsp; The first type of resource in this category may not be&nbsp;as simple to use as you might like, but the conclusions you'll come to will be more trustworthy.&nbsp; <span style="font-size: small;">The first two whitepapers below take the approach of giving you the tools you need to identify the key characteristics of&nbsp;an existing application workload, then&nbsp;providing you with a process to translate that into a set of hardware requirements.&nbsp; The database sizing&nbsp;tool will give you a fairly&nbsp;accurate estimated database size, but to get it you must provide a lot of&nbsp;schema details.&nbsp; These&nbsp;are not simple processes, so don't expect to have a set of server specs in 30 seconds.&nbsp; But the complexity is inherent to the problem and can't be eliminated without sacrificing precision, so you can consider it time well spent.&nbsp; </span></span></span></p>
<ul>
<li><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;">Disk subsystem sizing guide from the SQL CAT team: <a href="https://msdn.microsoft.com/en-us/library/ee410782(SQL.100).aspx"><span style="font-size: small;">https://msdn.microsoft.com/en-us/library/ee410782(SQL.100).aspx</span></a></span></span></span></span></span></li>
<li><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;">Fast Track Data Warehouse 2.0 Architecture: <a href="https://msdn.microsoft.com/en-us/library/dd459178.aspx">https://msdn.microsoft.com/en-us/library/dd459178.aspx</a>&nbsp;(not app-specific, but tailored to a particular class of application) </span></span></span></span></span></span></span></li>
<li><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;">DataSizer tool (helps estimate database sizes based on your table schema):&nbsp; </span><a href="https://www.microsoft.com/downloads/details.aspx?FamilyID=564c5704-d4f5-4ee8-9f3c-cb429499d075&amp;displaylang=en"><span style="font-size: small;">https://www.microsoft.com/downloads/details.aspx?FamilyID=564c5704-d4f5-4ee8-9f3c-cb429499d075&amp;displaylang=en</span></a><span style="font-size: small;"> &ndash; This was written for SQL 7.&nbsp; The estimates won&rsquo;t be quite as accurate for more recent SQL versions, but in most cases they should be pretty close unless you&rsquo;re using Enterprise Edition SQL features like compression.&nbsp; (For details about performing this calculation by hand in SQL 2008 R2, see the "<a href="https://msdn.microsoft.com/en-us/library/ms187445.aspx">Estimating the size of&nbsp;a database</a>" topic in Books Online.)&nbsp;</span></span></span></span></span></span></span></span></span></span></span></span></span></li>
</ul>
<p><span style="font-size: small;">Finally, there are SQL sizing tools that will give you hardware recommendations for an unknown app with little to&nbsp;no time investment on your part.&nbsp; </span><span style="font-size: small;">These might provide a nice starting point, but I&rsquo;d take their recommendations with a <a href="https://www.sqlmag.com/article/sql-server/database-sizing-tools-for-sql-server-.aspx">grain of salt</a>.&nbsp; </span><span style="font-size: small;">As&nbsp;mentioned earlier, you can't estimate required hardware with any real precision when the app and the nature of its SQL workload are unknown variables, so be cautious about any recommendation&nbsp;that is made on the basis of a few vague metrics.&nbsp; </span></p>
<ul>
<li><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;">Dell SQL Advisor: </span><a href="https://www.dell.com/content/topics/global.aspx/tools/advisors/sql_advisor?c=us&amp;cs=555&amp;l=en&amp;s=biz"><span style="font-size: small;">https://www.dell.com/content/topics/global.aspx/tools/advisors/sql_advisor?c=us&amp;cs=555&amp;l=en&amp;s=biz</span></a>&nbsp;</span></span></span></li>
<li><span style="font-size: small;"><span style="font-size: small;"><span style="font-size: small;"></span><span style="font-size: small;">HP SQL Sizers (note: separate links to sizing tools for BI workloads vs. OLTP workloads): </span><a href="https://h71028.www7.hp.com/enterprise/cache/3887-0-0-0-121.html"><span style="font-size: small;">https://h71028.www7.hp.com/enterprise/cache/3887-0-0-0-121.html</span></a><span style="font-size: small;"> </span></span></span></li>
</ul>
<p><span style="font-size: small;"></span>&nbsp;</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Purging Data</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/purging-data</link>
      <pubDate>Tue, 01 Jun 2010 15:26:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2010/06/01/purging-data/</guid>
      <description><![CDATA[This post is about deleting a lot of rows, as you might do in a data archiving or purging...]]></description>
      <content:encoded><![CDATA[<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">This post is about deleting a lot of rows, as you might do in a data archiving or purging task.&nbsp; By &ldquo;a lot of rows&rdquo;, I mean anything from a few thousand rows up to billions.&nbsp; This may seem elementary, but there are some surprising problems that can appear if you use a simple DELETE to delete a large volume of data.&nbsp; Below are some best practices to consider. &nbsp;(Note that some, but not all, of these suggestions also apply to large UPDATE or INSERT operations.)&nbsp; <br /><o:p></o:p></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<h2 style="margin: 10pt 0in 0pt;"><span style="mso-fareast-font-family: 'Times New Roman';"><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">TRUNCATE When You Can<o:p></o:p></span></span></span></span></h2>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">The first, and simplest, best practice is to use TRUNCATE instead of DELETE when possible.&nbsp; TRUNCATE will only log extent or page deallocation operations, while a DELETE is a row-by-row operation and must log the pre-delete image of every deleted row.&nbsp; TRUNCATE runs nearly instantaneously, even on very large tables that might take days or hours to clear out with a DELETE statement.&nbsp; But of course TRUNCATE can only be used when you need to delete all of the data in a table.&nbsp; </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><br />&nbsp;</span></span> </p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<h2 style="margin: 10pt 0in 0pt;"><span style="mso-fareast-font-family: 'Times New Roman';"><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Consider Partitioning<o:p></o:p></span></span></span></span></h2>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">If you have Enterprise or Datacenter Edition and are running SQL Server 2005 or later, you may be able to partition your data.&nbsp; With an appropriately-selected partitioning scheme, you can simply switch out the old data.&nbsp; The cost of this is generally similar to a TRUNCATE or DROP TABLE &ndash; in other words, nearly instantaneous.&nbsp; You can learn more about SQL Server partitioning in </span><a href="https://msdn.microsoft.com/en-us/library/ms345146(SQL.90).aspx#sql2k5parti_topic24"><span style="font-family: Calibri; color: #0000ff; font-size: small;">this whitepaper</span></a><span style="font-size: small;"><span style="font-family: Calibri;"> by Kim Tripp.&nbsp; </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><br />&nbsp;</span></span> </p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<h2 style="margin: 10pt 0in 0pt;"><span style="mso-fareast-font-family: 'Times New Roman';"><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Delete in Chunks <o:p></o:p></span></span></span></span></h2>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">Most of the time you only want to delete a subset of the rows in a table.&nbsp; If you can&rsquo;t use partitioning, you must use DELETE.&nbsp; Suppose that you needed to delete a lot of data with a straightforward DELETE statement like this one:&nbsp;</span></span><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p>&nbsp;</p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; DELETE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> <span style="color: blue;">FROM</span> big_table&nbsp;<br /></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; WHERE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> ready_to_archive <span style="color: gray;">=</span> 1<span style="color: gray;">;</span> <o:p></o:p></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p>&nbsp;</p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">This could cause two problems.&nbsp; The first is that the DELETE will acquire exclusive locks on all deleted rows, and those locks will be held until the DELETE completes.&nbsp; If the DELETE runs for hours, other users trying to query or modify the table may be blocked for hours.&nbsp; If more than a few thousand locks are acquired, SQL may escalate to an X table lock that will prevent anyone else from modifying <i>any</i> row in the table, even rows that will not be deleted.&nbsp;&nbsp;</span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span>&nbsp;</p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">The second issue that you might see is transaction log growth.&nbsp; If your database is in simple recovery mode, under normal circumstances a relatively small .LDF file will be sufficient because the space in the log file is frequently reused.&nbsp; But SQL can only reuse space in a transaction log prior to the region that records the oldest uncommitted transaction.&nbsp; The long-running DELETE may cause your .LDF file(s) to grow.&nbsp; If the file grows too large or if you have disabled autogrow you will eventually run out of log space, bringing all activity in the database to a halt.&nbsp; </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal">&nbsp;<o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">You can avoid both of these problems by deleting the rows in chunks, like this: </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span>&nbsp;</p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; DECLARE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> @rows_affected <span style="color: blue;">BIGINT</span><span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; DECLARE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> @delete_batch_size <span style="color: blue;">INT</span><span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; SET</span><span style="font-family: 'Courier New'; font-size: 10pt;"> @delete_batch_size <span style="color: gray;">=</span> 1000<span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; SET</span><span style="font-family: 'Courier New'; font-size: 10pt;"> @rows_affected <span style="color: gray;">=</span> @delete_batch_size<span style="color: gray;">;<br /><br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; WHILE </span><span style="font-family: 'Courier New'; color: gray; font-size: 10pt;">(</span><span style="font-family: 'Courier New'; font-size: 10pt;">@rows_affected <span style="color: gray;">=</span> @delete_batch_size<span style="color: gray;">)<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; BEGIN<br /></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">DELETE</span> <span style="color: blue;">TOP </span><span style="color: gray;">(</span>@delete_batch_size<span style="color: gray;">)</span> <span style="color: blue;">FROM</span> big_table&nbsp;<br /></span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">&nbsp;&nbsp;&nbsp;&nbsp;WHERE</span> ready_to_archive <span style="color: gray;">=</span> 1<span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">SET</span> @rows_affected <span style="color: gray;">=</span> <span style="color: fuchsia;">@@ROWCOUNT</span><span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; END</span><span style="font-family: 'Courier New'; color: gray; font-size: 10pt;">;</span><o:p></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p>&nbsp;</p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">This will delete 1000 rows at a time, continuing until there are no more rows to delete.&nbsp; Each batch of 1000 rows is deleted in a separate transaction, so the batch can run for a long time without holding locks for a long time or causing transaction log growth. &nbsp;You can experiment with different batch sizes, but in my experience a batch size above 1000 rows doesn&rsquo;t improve performance that much.&nbsp; </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">If you want to avoid transaction log growth but don&rsquo;t care about reduced concurrency (for example, if the DELETE is running during a maintenance window when no one else will need to query the table), you may see a small performance boost by deleting in chunks but forcing a table lock on each DELETE.&nbsp; The cost of lock management and other housekeeping tasks is reduced when SQL doesn&rsquo;t have to worry about concurrency.&nbsp; A TABLOCK hint also allows SQL to reclaim empty pages that would otherwise remain allocated to the table.&nbsp; </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;...<br /></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; WHILE </span><span style="font-family: 'Courier New'; color: gray; font-size: 10pt;">(</span><span style="font-family: 'Courier New'; font-size: 10pt;">@rows_affected <span style="color: gray;">=</span> @delete_batch_size<span style="color: gray;">)<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; BEGIN<br /></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">DELETE</span> <span style="color: blue;">TOP </span><span style="color: gray;">(</span>@delete_batch_size<span style="color: gray;">)</span> <span style="color: blue;">FROM</span> big_table <b><span style="text-decoration: underline;"><span style="background: yellow; color: blue; mso-highlight: yellow;">WITH</span><span style="background: yellow; mso-highlight: yellow;"> <span style="color: gray;">(</span><span style="color: blue;">TABLOCK</span><span style="color: gray;">)<br /></span></span></span></b></span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">&nbsp;&nbsp;&nbsp;&nbsp;WHERE</span> ready_to_archive <span style="color: gray;">=</span> 1<span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 'Courier New'; font-size: 10pt;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue;">SET</span> @rows_affected <span style="color: gray;">=</span> <span style="color: fuchsia;">@@ROWCOUNT</span><span style="color: gray;">;<br /></span></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; END</span><span style="font-family: 'Courier New'; color: gray; font-size: 10pt;">;</span><o:p></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;"></span></o:p></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">On the other hand, if you don&rsquo;t care about transaction log growth but you do want to prevent an unnecessary table lock escalation, you can use trace flag 1224:&nbsp;</span></span><span style="font-size: small;"><span style="font-family: Calibri;">&nbsp;</span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="font-family: courier new,courier;"><span style="font-size: medium;"><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; <span style="background-color: #ffff00;">DBCC</span></span></span><span style="background-color: #ffff00;"><span style="font-size: x-small;"> TRACEON</span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">1224</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">);</span></span></span></span></span><span style="font-size: medium;"><span style="font-size: x-small;"><span style="font-family: courier new,courier;">&nbsp;</span></span></span></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; DELETE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> <span style="color: blue;">FROM</span> big_table&nbsp;<br /></span><span style="font-family: 'Courier New'; color: blue; font-size: 10pt;">&nbsp;&nbsp;&nbsp; WHERE</span><span style="font-family: 'Courier New'; font-size: 10pt;"> ready_to_archive <span style="color: gray;">=</span> 1<span style="color: gray;">;</span></span></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><span style="font-family: 'Courier New'; font-size: 10pt;"><span style="color: gray;"></span>&nbsp;<o:p></o:p></span></span></span><span style="font-family: courier new,courier;"><span style="font-size: medium;"><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; <span style="background-color: #ffff00;">DBCC</span></span></span><span style="background-color: #ffff00;"><span style="font-size: x-small;"> TRACEOFF</span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">1224</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">);</span></span></span></span></span><span style="font-size: medium;"><span style="font-size: x-small;"><span style="font-family: courier new,courier;"> </span></span></span></p>
<p>
<p class="MsoNormal">&nbsp;</p>
</p>
<p><span style="font-size: medium;"><span style="font-family: courier new,courier;"><span style="FONT-FAMILY: ; COLOR: #0000ff; FONT-SIZE: x-small"></span></span></span></p>
<p>&nbsp;<span style="font-size: small;"><span style="font-family: Calibri;">Trace flag 1224 is documented and supported.&nbsp; It prevents SQL from escalating to table locks except when it must because the server is running low on memory.&nbsp; This may also be a good compromise solution if you want to maximize concurrency but you require that the entire delete operation be transactional.&nbsp; <br /></span></span></p>
<p>&nbsp;</p>
<p class="MsoNormal"><span style="mso-fareast-font-family: 'Times New Roman';"><span style="font-size: medium;"><span style="color: #4f81bd;"><span style="font-family: Cambria;">Consider Indexes and Foreign Keys<o:p></o:p></span></span></span></span></p>
<p class="MsoNormal"><span style="font-family: Calibri; font-size: small;">If your DELETE statement is running slower than you expect, it might make sense to drop indexes on the table and/or any foreign keys referring to the table, do the DELETE, then recreate the objects you dropped.&nbsp; Indexes on a table can dramatically slow down a large DELETE, even to the point where it is cheaper to rebuild the index from scratch after the DELETE than it would be to maintain the index in place.&nbsp; (Note that sometimes you clearly will see the extra index maintenance operators in the query plan, and other times the query plan won&rsquo;t show this work.&nbsp; But just because you don&rsquo;t see any Index Delete operators in the plan doesn&rsquo;t mean that the DELETE&rsquo;s performance isn&rsquo;t affected by index maintenance overhead.&nbsp; For details, see my earlier post on </span><a href="https://blogs.msdn.com/b/bartd/archive/2006/07/27/wide-vs-narrow-plans.aspx"><span style="font-family: Calibri; font-size: small;">Wide vs. Narrow Plans</span></a><span style="font-size: small;"><span style="font-family: Calibri;">.)&nbsp; Foreign keys on other tables that refer to the table that you&rsquo;re deleting from can also slow down the DELETE.&nbsp; The FK overhead comes from the fact that SQL must verify that you&rsquo;re not deleting any rows that are referenced by rows in the child table.&nbsp;&nbsp;</span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"><o:p></o:p></span></span></p>
<p class="MsoNormal"><o:p><span style="font-family: Calibri; font-size: small;">&nbsp;</span></o:p><span style="font-size: small;"><span style="font-family: Calibri;">It&rsquo;s difficult to generalize about when it might be cost effective to drop indexes or FKs.&nbsp; The effect on the overall runtime of the delete job depends on how many indexes are on the table, the size of the indexes, whether any FK columns in other tables are indexed, the portion of the table you&rsquo;re deleting, whether there is correlation between nonclustered and clustered index keys values, etc.&nbsp; Personally, I&rsquo;d consider testing out the approach and quantifying the benefits when all of the following are true: </span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<p class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;"></span></span></p>
<ol>
<li>
<div class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">Your DELETE query runs slower than you would like </span></span></div>
</li>
<li>
<div class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">Temporarily dropping indexes or FKs won&rsquo;t hurt anything (for example, if the DELETE runs in a maintenance window when no one else is using the database)</span></span></div>
</li>
<li>
<div class="MsoNormal"><span style="font-size: small;"><span style="font-family: Calibri;">You are deleting a fairly large % of the rows in the table (rule of thumb: &gt; 10% of the rows)</span></span>&nbsp;</div>
</li>
</ol>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Finding procedure cache bloat</title>
      <link>https://docs.microsoft.com/archive/blogs/bartd/finding-procedure-cache-bloat</link>
      <pubDate>Wed, 26 May 2010 13:28:00 GMT</pubDate>
      <dc:creator><![CDATA[bartduncan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/bartd/2010/05/26/finding-procedure-cache-bloat/</guid>
      <description><![CDATA[Explicitly parameterizing queries is&nbsp;a well-known best-practice for database app...]]></description>
      <content:encoded><![CDATA[<p>Explicitly parameterizing queries is&nbsp;a well-known best-practice for database app developers.&nbsp; There are cases where it can make sense to execute a query unparameterized, but, in general, you should default to explicit parameterization.&nbsp; It can make your queries faster by avoiding unnecessary compilation when the "same" query&nbsp;is run repeatedly with different parameters, and it can reduce competition for limited&nbsp;memory in SQL Server's visible buffer pool.&nbsp; If you don't already know how to parameterize your queries, or if you want more information about the benefits of parameterization, check out: </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="https://weblogs.sqlteam.com/dang/archive/2008/02/21/Dont-Bloat-Proc-Cache-with-Parameters.aspx">https://weblogs.sqlteam.com/dang/archive/2008/02/21/Dont-Bloat-Proc-Cache-with-Parameters.aspx</a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="https://weblogs.sqlteam.com/dang/archive/2008/02/18/Why-Parameters-are-a-Best-Practice.aspx">https://weblogs.sqlteam.com/dang/archive/2008/02/18/Why-Parameters-are-a-Best-Practice.aspx</a></p>
<p>Below is a handy DMV query that can quickly identify which queries&nbsp;have many different plans cached for the same query.&nbsp; In most cases, this indicates a lack of explicit parameterization of the query.&nbsp; This uses the query plan hash/query plan fingerprint feature that I wrote about <a href="https://blogs.msdn.com/b/bartd/archive/2008/09/03/query-fingerprints-and-plan-fingerprints_3a00_-the-best-new-sql-2008-feature-you_2700_ve-never-heard-of.aspx" title="query plan fingerprints">in an earlier post</a>.&nbsp; It relies on the fact that two queries that have different inline literal values (e.g. "SELECT...WHERE col1 = 123", vs. "SELECT...WHERE col1 = 456") will get two different query plans, but the plans will have the same query_hash value.&nbsp; You must be running SQL Server 2008 or later to use this.&nbsp;&nbsp;&nbsp;</p>
<hr width="5" size="3" style="width: 5px; height: 3px;" />
<p>&nbsp;<span style="font-size: x-small;"><span style="color: #0000ff;">WITH </span>duplicated_plans </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; SELECT</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">TOP</span></span><span style="font-size: x-small;"> 20&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; query_hash</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">SELECT</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">TOP</span></span><span style="font-size: x-small;"> 1 [sql_handle] </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">FROM</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_query_stats</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> s2 </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">WHERE</span></span><span style="font-size: x-small;"> s2</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">=</span></span><span style="font-size: x-small;"> s1</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">ORDER</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">BY</span></span><span style="font-size: x-small;"> [sql_handle]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> sample_sql_handle</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">SELECT</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">TOP</span></span><span style="font-size: x-small;"> 1 statement_start_offset </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">FROM</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_query_stats</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> s2 </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">WHERE</span></span><span style="font-size: x-small;"> s2</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">=</span></span><span style="font-size: x-small;"> s1</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">ORDER</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">BY</span></span><span style="font-size: x-small;"> [sql_handle]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> sample_statement_start_offset</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">SELECT</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">TOP</span></span><span style="font-size: x-small;"> 1 statement_end_offset </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">FROM</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_query_stats</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> s2 </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">WHERE</span></span><span style="font-size: x-small;"> s2</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">=</span></span><span style="font-size: x-small;"> s1</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">query_hash </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">ORDER</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">BY</span></span><span style="font-size: x-small;"> [sql_handle]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> sample_statement_end_offset</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CAST</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">pa</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">value </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">INT</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">dbid</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>COUNT</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(*)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> plan_count&nbsp;<br /></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; FROM</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_query_stats</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> s1<br /></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; </span>OUTER</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">APPLY</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_plan_attributes</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">s1</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">plan_handle</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> pa&nbsp;<br /></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; WHERE</span></span><span style="font-size: x-small;"> pa</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">attribute </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">=</span></span><span style="font-size: x-small;"> </span><span style="color: #ff0000; font-size: x-small;"><span style="color: #ff0000; font-size: x-small;">'dbid'<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; GROUP</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">BY</span></span><span style="font-size: x-small;"> query_hash</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,</span></span><span style="font-size: x-small;"> pa</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">value<br /></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp; ORDER</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">BY</span></span><span style="font-size: x-small;"> </span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;">COUNT</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(*)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">DESC<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)<br /></span></span><span style="font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">SELECT<br /></span></span><span style="font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; </span>query_hash</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; </span>plan_count</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; </span>CONVERT</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">NVARCHAR</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">80</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">),</span></span><span style="font-size: x-small;"> </span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;">REPLACE</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;">REPLACE</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>LTRIM</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SUBSTRING</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sql</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">[text]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>(</span></span><span style="font-size: x-small;">sample_statement_start_offset </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">/</span></span><span style="font-size: x-small;"> 2</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">+</span></span><span style="font-size: x-small;"> 1</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CASE<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN</span></span><span style="font-size: x-small;"> sample_statement_end_offset </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">=</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">-</span></span><span style="font-size: x-small;">1 </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">THEN</span></span><span style="font-size: x-small;"> </span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;">DATALENGTH</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">sql</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">[text]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ELSE</span></span><span style="font-size: x-small;"> sample_statement_end_offset&nbsp;<br /></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; END</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">-</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">sample_statement_start_offset </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">/</span></span><span style="font-size: x-small;"> 2</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>)<br /></span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>),<br /></span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHAR</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">10</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">),</span></span><span style="font-size: x-small;"> </span><span style="color: #ff0000; font-size: x-small;"><span style="color: #ff0000; font-size: x-small;">''</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">),</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">CHAR</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">13</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">),</span></span><span style="font-size: x-small;"> </span><span style="color: #ff0000; font-size: x-small;"><span style="color: #ff0000; font-size: x-small;">''</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">))</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> qry</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;</span>OBJECT_NAME</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">sql</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">objectid</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">sql</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">[dbid]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> [object_name]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,<br /></span></span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; </span>DB_NAME</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">duplicated_plans</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">[dbid]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> [database_name]<br /></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">FROM</span></span><span style="font-size: x-small;"> duplicated_plans <br /></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">CROSS</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">APPLY</span></span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">sys</span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">dm_exec_sql_text</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">duplicated_plans</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">sample_sql_handle</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">AS</span></span><span style="font-size: x-small;"> </span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;">sql<br />WHERE</span></span><span style="font-size: x-small;"> </span><span style="color: #ff00ff; font-size: x-small;"><span style="color: #ff00ff; font-size: x-small;">ISNULL</span></span><span style="color: #0000ff; font-size: x-small;"><span style="color: #0000ff; font-size: x-small;"> </span></span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">(</span></span><span style="font-size: x-small;">duplicated_plans</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">.</span></span><span style="font-size: x-small;">[dbid]</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">,</span></span><span style="font-size: x-small;"> 0</span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">)</span></span><span style="font-size: x-small;"> </span><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">!=</span></span><span style="font-size: x-small;"> 32767</span><span style="font-size: x-small;"> </span><span style="color: #008000; font-size: x-small;"><span style="color: #008000; font-size: x-small;">-- ignore queries from Resource DB <br /><span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">AND&nbsp;<span style="color: #000000;">plan_count <span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">&gt; <span style="color: #000000;">1<span style="color: #808080; font-size: x-small;"><span style="color: #808080; font-size: x-small;">;</span></span></span></span></span></span></span></span></span></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<hr width="1" style="width: 1px;" />
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<hr width="1" style="width: 1px;" />
<p>&nbsp;</p>]]></content:encoded>
    </item>
  </channel>
</rss>