<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Statistics on Rudi Bruchez</title><link>https://www.pachadata.com/en/tags/statistics/</link><description>Recent content in Statistics on Rudi Bruchez</description><generator>Hugo</generator><language>en</language><lastBuildDate>Tue, 20 Oct 2026 08:22:24 +0200</lastBuildDate><atom:link href="https://www.pachadata.com/en/tags/statistics/index.xml" rel="self" type="application/rss+xml"/><item><title>SQL Server string statistics</title><link>https://www.pachadata.com/en/docs/articles/sql-server/performance/string-statistics/</link><pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.pachadata.com/en/docs/articles/sql-server/performance/string-statistics/</guid><description>&lt;p&gt;&lt;em&gt;Co-written with Claude, who ran much of the testing and research.&lt;/em&gt;&lt;/p&gt;&#10;&lt;div class="alert alert-success" role="alert"&gt;&lt;div class="h4 alert-heading" role="heading"&gt;TL;DR&lt;/div&gt;&#10;&lt;p&gt;String statistics have existed in SQL Server since version 2005. They are used to estimate the cardinality of &lt;code&gt;LIKE&lt;/code&gt; searches, such as &lt;code&gt;Email LIKE '%.org'&lt;/code&gt;. When the first column of a statistics object is a character type, that is &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;varchar&lt;/code&gt;, &lt;code&gt;nchar&lt;/code&gt; or &lt;code&gt;nvarchar&lt;/code&gt;, including &lt;code&gt;varchar(max)&lt;/code&gt;, &lt;code&gt;nvarchar(max)&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt; and &lt;code&gt;ntext&lt;/code&gt;, the statistics object holds a string summary in addition to the histogram. You can see it in &lt;code&gt;DBCC SHOW_STATISTICS&lt;/code&gt;, where the &lt;code&gt;String Index&lt;/code&gt; property is then &lt;code&gt;YES&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;This summary is a sample of the column values, taken at a regular interval in sort order. Its size is 5% of the rows read by the statistics, plus 10; with &lt;code&gt;FULLSCAN&lt;/code&gt;, 5% of the table rows: 510 strings for 10,000 rows, 2,510 for 50,000. But from about 56,000 rows read on, it stops growing and stays around 2,800 strings, whatever the size of the table (and even fewer for &lt;code&gt;nvarchar&lt;/code&gt; and for long strings). The strings are stored in a &lt;a href="https://en.wikipedia.org/wiki/Trie"&gt;trie&lt;/a&gt; (prefix tree): each node holds a piece of a string, and strings that start the same way share the nodes of that beginning.&lt;/p&gt;&#10;&lt;p&gt;To estimate a &lt;code&gt;LIKE&lt;/code&gt; with wildcards, the optimizer applies the pattern to each string of the sample, then multiplies the proportion found by the number of rows in the table. When no string in the sample matches the pattern, the proportion would be zero, and so would the estimate. The optimizer then acts as if a tenth of a string matched: on the million rows of &lt;code&gt;Contact.ProspectUS&lt;/code&gt;, whose sample holds 2,825 strings, a pattern not found in the sample is estimated at 1,000,000 × 0.1 / 2,825, or 35.4 rows. That is the floor of the estimate.&lt;/p&gt;&#10;&lt;p&gt;Frequent patterns are estimated correctly, and rare patterns get the floor value, roughly the number of rows divided by 28,000. The summary is not used for cardinality estimation when the column is wrapped in an expression, such as a function call, when the pattern uses &lt;code&gt;ESCAPE&lt;/code&gt;, comes from a local variable or is &lt;code&gt;nvarchar&lt;/code&gt; against a &lt;code&gt;varchar&lt;/code&gt; column (hence an implicit conversion), or when the column is a &lt;code&gt;varchar&lt;/code&gt; with a UTF-8 collation. In those cases, the current estimator guesses 9% of the table rows for a pattern that starts with a wildcard, and estimates a prefix from the histogram. Beyond 80 characters, only the first 40 and the last 40 are kept, joined together.&lt;/p&gt;&#10;&lt;/div&gt;</description></item></channel></rss>