<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>performance &#8211; SQLpowered.com</title>
	<atom:link href="https://sqlpowered.com/tag/performance/feed/" rel="self" type="application/rss+xml" />
	<link>https://sqlpowered.com</link>
	<description>SQL Server + BI</description>
	<lastBuildDate>Mon, 10 Jan 2022 20:52:56 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://sqlpowered.com/wp-content/uploads/2020/07/FavIcon-e1594067873682-99x100.png</url>
	<title>performance &#8211; SQLpowered.com</title>
	<link>https://sqlpowered.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Practical Tally Table to WHILE Comparison (Generating Calendar)</title>
		<link>https://sqlpowered.com/practical-tally-table-to-while-comparison-generating-calendar/</link>
					<comments>https://sqlpowered.com/practical-tally-table-to-while-comparison-generating-calendar/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 09 Jan 2022 10:39:28 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5314</guid>

					<description><![CDATA[Practical Tally Table to WHILE Comparison (Generating Calendar) The &#8220;Tally table&#8221; is a well-known concept not only in the SQL Server world but in all set-based (relational) database engines. I don&#8217;t want to repeat some basic or go much deep into it. You can do it on your own starting...]]></description>
										<content:encoded><![CDATA[<p>Practical Tally Table to WHILE Comparison (Generating Calendar)</p>
<p>The &#8220;Tally table&#8221; is a well-known concept not only in the SQL Server world but in all set-based (relational) database engines. I don&#8217;t want to repeat some basic or go much deep into it. You can do it on your own starting i.g. on <a href="https://www.sqlservercentral.com/blogs/tally-tables-in-t-sql" target="_blank" rel="noopener">sqlservercentral.com</a>. My post is just another reminder of why it&#8217;s really good to go with Tally table instead of row-by-row direction. And yes, it&#8217;s about the performance as usual. Let&#8217;s start the game.</p>
<p>Our mission is simple: We would like to generate a simple calendar table with one date (day) per row for all dates between @StartDate and @EndDate. We will use the WHILE loop and add one day to the @StartDate on each cycle. We will measure the total duration of the WHILE loop execution.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-highlight="16-23">SET NOCOUNT ON

DECLARE @StartDate DATE
DECLARE @EndDate DATE
DECLARE @ST DATETIME 

DROP TABLE IF EXISTS [#Calendar]

CREATE TABLE [#Calendar] ( [CalendarDate] [DATE] NOT NULL PRIMARY KEY )

SET @StartDate = '1500-01-01'
SET @EndDate = '2100-12-31'

SET @ST = GETDATE()

   WHILE @StartDate &lt;= @EndDate
   BEGIN
		
	INSERT INTO [#Calendar] VALUES (@StartDate)

	SET @StartDate = DATEADD(DAY, 1, @StartDate)

   END

PRINT DATEDIFF(ms, @ST, GETDATE())

SELECT * FROM [#Calendar] ORDER BY 1
GO
</pre>
<p><img decoding="async" class="alignnone wp-image-5319" src="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1.png" alt="" width="167" height="175" srcset="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1.png 306w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1-286x300.png 286w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1-95x100.png 95w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1-153x160.png 153w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_1-234x245.png 234w" sizes="(max-width: 167px) 100vw, 167px" /></p>
<p>There are totally more than 200k rows for the 6 hundred years range inserted into the calendar table. The execution time is around <strong>2.900 ms</strong> on my test machine with really small differences for multiple executions. Not bad, right?:)</p>
<p>But, can it be better? Let&#8217;s try. Instead of firing more than 200k atomic inserts, we can try to hit the line in one step using the Tally table horsepower.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-highlight="16-26">SET NOCOUNT ON

DECLARE @StartDate DATE
DECLARE @EndDate DATE
DECLARE @ST DATETIME 

DROP TABLE IF EXISTS [#Calendar]

CREATE TABLE [#Calendar] ( [CalendarDate] [DATE] NOT NULL PRIMARY KEY )

SET @StartDate = '1500-01-01'
SET @EndDate = '2100-12-31'

SET @ST = GETDATE()

	;WITH Tally (n) AS
	(
		SELECT 
			ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
		FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a (n)
		   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b (n)
		   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c (n)
		   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n)
		   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n)
		   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n)
	)
	INSERT INTO [#Calendar]
	   ( [CalendarDate] )
	   SELECT DATEADD(DAY, n-1, @StartDate) 
	   FROM Tally
	   WHERE DATEADD(DAY, n-1, @StartDate) &lt;=  @EndDate
           --WHERE n &lt;= DateDiff(Day, @StartDate, @EndDate) + 1
	   ORDER BY 1

PRINT DATEDIFF(ms, @ST, GETDATE())

SELECT * FROM [#Calendar] ORDER BY 1
GO</pre>
<p>The same amount of rows was inserted into the calendar table. But instead of 3 seconds, it took about <strong>360 ms</strong> now! Amazing.</p>
<p>There are two (or even more) possible WHERE clauses to limit the final number of rows we would like to get because the Tally table needs to be generated most of the time bigger than the final result set. In your case, there are 1.000.000 rows ready by the Tally table (the CROSS JOINs count * VALUEs count) but we need only 219.511 rows to be used for the required dates range. This is why we are using the WHERE condition to limit it.</p>
<p>It&#8217;s interesting to compare these two conditions via performance and their execution plans.</p>
<p><code class="EnlighterJSRAW" data-enlighter-language="generic">WHERE DATEADD(DAY, n-1, @StartDate) &lt;= @EndDate</code> =&gt; duration <strong>360 ms</strong></p>
<p><img fetchpriority="high" decoding="async" class="alignnone wp-image-5326" src="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3.png" alt="" width="1235" height="163" srcset="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3.png 2776w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-300x40.png 300w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-1024x135.png 1024w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-150x20.png 150w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-768x102.png 768w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-1536x203.png 1536w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-2048x271.png 2048w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-360x48.png 360w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-160x21.png 160w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-320x42.png 320w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-520x69.png 520w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-720x95.png 720w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-980x130.png 980w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_3-1320x175.png 1320w" sizes="(max-width: 1235px) 100vw, 1235px" /></p>
<p>And the second one:</p>
<p><code class="EnlighterJSRAW" data-enlighter-language="sql">WHERE n &lt;= DateDiff(Day, @StartDate, @EndDate) + 1</code> =&gt; duration<strong> 550 ms</strong></p>
<p><img decoding="async" class="alignnone wp-image-5327" src="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2.png" alt="" width="1019" height="180" srcset="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2.png 2060w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-300x53.png 300w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-1024x181.png 1024w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-150x27.png 150w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-768x136.png 768w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-1536x271.png 1536w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-2048x362.png 2048w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-360x64.png 360w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-160x28.png 160w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-320x57.png 320w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-520x92.png 520w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-720x127.png 720w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-980x173.png 980w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_2-1320x233.png 1320w" sizes="(max-width: 1019px) 100vw, 1019px" /></p>
<p>This is quite an interesting difference in execution plans and performance. Looks like it depends on how data are finally sorted for the clustered index insert: if in Sort operator before or in Clustered Index Insert operator itself. If we will remove PRIMARY KEY constraint from the #Calendar table definition, both execution plans will align to be the same (except parallelism used or not in favor of the first WHERE condition). Btw, removing the clustered key will speed it up from about 360 ms to 90 ms. This is a great gain but the price is we can&#8217;t be sure all dates in the #Calendar table are unique and we will need to index the column soon or later to improve its usage in other queries.</p>
<h5>The Tally table size</h5>
<p>Our Tally table has a total size of 1.000.000 available numbers as mentioned above. Does it somehow affect the performance in this scenario? Let&#8217;s test it. We will remove some unnecessary (0) to limit available numbers to 300k only because we know this will be enough for our requested data range.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-highlight="7">SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n)
   CROSS JOIN (VALUES(0),(0),(0)) f (n)</pre>
<p>With every (0) removed performance gets better by about 5-10%, in our case from about <strong>360 ms</strong> to <strong>220 ms</strong>. This is matching to linear scaling (one (0) is 100k rows removed). So it&#8217;s really important to size the Tally table as much as possible to what is needed.</p>
<p>We can optimize it further and remove this dependency if the Tally table will be prepared in advance, like in this sample:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-highlight="1-15, 33-34">DROP TABLE IF EXISTS [#Tally]

CREATE TABLE [#Tally] ( [n] [INT] NOT NULL PRIMARY KEY )

INSERT INTO [#Tally]
	( [n] )
SELECT 	ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) n
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n)
ORDER BY n
GO

DECLARE @StartDate DATE
DECLARE @EndDate DATE
DECLARE @ST DATETIME 

DROP TABLE IF EXISTS [#Calendar]

CREATE TABLE [#Calendar] ( [CalendarDate] [DATE] NOT NULL PRIMARY KEY )

SET @StartDate = '1500-01-01'
SET @EndDate = '2100-12-31'

SET @ST = GETDATE()

	INSERT INTO [#Calendar]
	   ( [CalendarDate] )
	   SELECT DATEADD(DAY, n-1, @StartDate) 
	   FROM #Tally
	   WHERE n &lt;= DATEDIFF(day, @StartDate, @EndDate) + 1

PRINT DATEDIFF(ms, @ST, GETDATE())

SELECT * FROM [#Calendar] ORDER BY 1
GO</pre>
<p>We have inserted 1.000.000 rows into the #Tally temporary table and indexed it using the PRIMARY KEY which means that clustered index was created. We have also modified the WHERE condition to limit the total number of n values used from the Tally table. The measured duration is now <strong>80 ms</strong> with this execution plan:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5336" src="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4.png" alt="" width="790" height="85" srcset="https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4.png 1637w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-300x32.png 300w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-1024x110.png 1024w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-150x16.png 150w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-768x83.png 768w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-1536x165.png 1536w, https://sqlpowered.com/wp-content/uploads/2022/01/Tally_Table_vs_WHILE_Loop_Execution_Plan_4-360x39.png 360w" sizes="auto, (max-width: 790px) 100vw, 790px" /></p>
<p>The main reason why we should persist the Tally table and reuse it is not only because of the performance but the fact, that it nearly doesn&#8217;t matter how many rows we have in it. The set is ordered and Index Seek (or Scan) operator will need the same IO operations to stop at the desired number of rows required by the WHERE condition. If we will modify the above case and create #Tally table with 100 billion rows the duration and IOs will be the same (there will be a little bit different memory grants or other estimates).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/practical-tally-table-to-while-comparison-generating-calendar/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>sp_get_query_template</title>
		<link>https://sqlpowered.com/sp_get_query_template/</link>
					<comments>https://sqlpowered.com/sp_get_query_template/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 24 May 2015 06:19:23 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=669</guid>

					<description><![CDATA[Systémová procedura sp_get_query_template je nenápadný, ale velmi užitečný pomocník. Primárně je sice určena pro vytváření šablon dotazu pro plan guidy, ale stejně tak dobře ji můžeme využít pro scénář, kdy máme rozsáhlé query s řadou parametrů a z nějakého důvodu potřebujeme dotaz volat z dynamického sql, např. pro různé databáze...]]></description>
										<content:encoded><![CDATA[<p>Systémová procedura <a href="https://msdn.microsoft.com/en-us/library/ms186908.aspx">sp_get_query_template</a> je nenápadný, ale velmi užitečný pomocník. Primárně je sice určena pro vytváření šablon dotazu pro plan guidy, ale stejně tak dobře ji můžeme využít pro scénář, kdy máme rozsáhlé query s řadou parametrů a z nějakého důvodu potřebujeme dotaz volat z dynamického sql, např. pro různé databáze nebo linkované servery. Procedura pracuje podle pravidel forced parametrizace, tzn. umožňuje parametrizovat i dotazy s triviálními exekučními plány.<span id="more-669"></span></p>
<p>Proceduru voláme podle následující šablony:</p>
<pre class="lang:tsql highlight:0 decode:true">sp_get_query_template
   [ @querytext = ] N'query_text'
   , @templatetext OUTPUT 
   , @parameters OUTPUT</pre>
<ul>
<li>@query text &#8211; dotaz, pro který chceme vygenerovat šablonu. Musí být uzavřen do jednoduchých uvozovek a označen jako unicode (N&#8221;).</li>
<li>@templatetext &#8211; vrací text parametrizovaného dotaz</li>
<li>@parameters &#8211; vrací seznam parametrů s datovými typy</li>
</ul>
<p>Jak to vše funguje v praxi si ukážeme v tomto jednoduchém příkladu:</p>
<pre class="lang:tsql decode:true">DECLARE @TemplateText NVARCHAR(MAX)
DECLARE @Parameters NVARCHAR(MAX)
 
EXEC sp_get_query_template
 N'SELECT *
 FROM [dbo].[TestTable]
 WHERE Id IN (5, 6) OR 
 Date BETWEEN GetDATE() AND ''2010-01-01'' OR
 Date = DATEADD(day, 1, GETDATE()) OR
 Date = GETDATE()',
 @templatetext OUTPUT,
 @parameters OUTPUT;

SELECT @TemplateText AS TemplateText;
SELECT @Parameters AS Parameters
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1845" src="https://sqlpowered.com/wp-content/uploads/2015/05/sp_get_query_template-Sample.png" alt="sp_get_query_template-Sample" width="840" height="85" /></p>
<p>Bohužel i zde narazíme na drobná omezení, stačí se podívat na vygenerovanou šablonu v Management Studiu:</p>
<pre class="lang:tsql decode:true ">select * 
from [dbo] . [TestTable] 
where Id in ( @0 , @1 ) or 
	  Date between GetDATE ( ) and @2 or 
	  Date = DATEADD ( day , @3 , GETDATE ( ) ) or 
	  Date = GETDATE ( )</pre>
<p>Vidíme, že v textu se vyskytují mezery mezi znaky, což představuje problém pro dynamické sql a bez dalšího formátování se neobejdeme. Pozornost je třeba věnovat i datovým typům parametrů, protože procedura sice správně pozná INT datové typy, ale pokud bude sloupec [Date] v tabulce dbo.TestTable datového typu DATE nebo DATETIME, dostaneme stejně parametr @2 jako varchar(8000) řetězec.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/sp_get_query_template/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Jednoduché testování výkonu dotazu</title>
		<link>https://sqlpowered.com/jednoduche-testovani-vykonu-dotazu/</link>
					<comments>https://sqlpowered.com/jednoduche-testovani-vykonu-dotazu/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Thu, 07 May 2015 05:56:51 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=644</guid>

					<description><![CDATA[Při ladění dotazu v SQL Serveru máme mnoho pokročilých možností, nicméně v jednoduchosti je někdy síla. Pomocí jednoduchého triku si můžeme zobrazit základní informace a námi laděném dotazu a pravidelně tak ověřovat, zda jsme při jeho ladění úspěšní či nikoliv. Ukážeme si jak na to s několika základními sloupci pro...]]></description>
										<content:encoded><![CDATA[<p>Při ladění dotazu v SQL Serveru máme mnoho pokročilých možností, nicméně v jednoduchosti je někdy síla. Pomocí jednoduchého triku si můžeme zobrazit základní informace a námi laděném dotazu a pravidelně tak ověřovat, zda jsme při jeho ladění úspěšní či nikoliv.<span id="more-644"></span></p>
<p>Ukážeme si jak na to s několika základními sloupci pro základní analýzu. Nebude zkoumat exekuční plány ani další detaily, zajímá nás opravdu jen to, jak si dotaz vede.</p>
<p>Základní kód vypadá takto:</p>
<pre class="lang:tsql decode:true">SET NOCOUNT ON 

DECLARE @StartTime DATETIME 
DECLARE @CPU INT 
DECLARE @TotalElapsedTime INT 
DECLARE @QueryMemmory INT 
DECLARE @Reads INT 
DECLARE @Writes INT 
DECLARE @LogicalReads INT 
DECLARE @RowCount INT 
DECLARE @LastWaitType NVARCHAR(60) 
SELECT  @StartTime = start_time, @CPU = cpu_time, @TotalElapsedTime = total_elapsed_time, 
        @QueryMemmory = granted_query_memory, @Reads = reads, @Writes = writes, 
        @LogicalReads = logical_reads , @LastWaitType = last_wait_type, @RowCount = row_count 
FROM sys.dm_exec_requests 
WHERE session_id = @@SPID

-- QUERY TO TEST-- 

SELECT COUNT(*) NumOfCols FROM sys.columns 

--/QUERY -- 

SELECT CONVERT(NVARCHAR(20), @StartTime, 114) StartTime, 
       cpu_time - @CPU CPU, total_elapsed_time - @TotalElapsedTime TotalElapsedTime, 
       @QueryMemmory QueryMemmory, reads - @Reads Reads, 
       writes - @Writes Writes, logical_reads - @LogicalReads LogicalReads, 
       last_wait_type LastWaitType, row_count - @RowCount RowCnt 
FROM sys.dm_exec_requests 
WHERE session_id = @@SPID
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3014" src="https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern.png" alt="" width="716" height="97" srcset="https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern.png 716w, https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern-150x20.png 150w, https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern-300x41.png 300w, https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern-160x22.png 160w, https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern-320x43.png 320w, https://sqlpowered.com/wp-content/uploads/2015/05/Test_Query_Performance_Pattern-520x70.png 520w" sizes="auto, (max-width: 716px) 100vw, 716px" /></p>
<p>Vše je opravdu velmi jednoduché:</p>
<ol>
<li>Nejprve si před vykonáním samotného dotazu uložíme do proměnných stav klíčových sloupců s dynamického systémovou pohledu <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql?view=sql-server-2017" target="_blank" rel="noopener noreferrer">sys.dm_exec_requests</a>. Pomocí podmínky WHERE session_id = @@SPID zajistíme, že si uložíme hodnoty pouze pro naše aktivní připojení.</li>
<li>Vykonáme dotaz, který sledujeme.</li>
<li>Znovu si načteme ze sys.dm_exec_requests hodnoty sloupců tak, jak vypadají po vykonání dotazu a odečteme od nich hodnoty před spuštěním dotazu uložené v proměnných.</li>
</ol>
<p>Které sloupce ze systémového pohledu si přidáme, případně zda využijeme i další systémové pohledy a získáme komplexní přehled, je již jen na nás.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/jednoduche-testovani-vykonu-dotazu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Why it matters if COLLATE is used in WHERE</title>
		<link>https://sqlpowered.com/why-it-matters-if-collate-is-used-in-where/</link>
					<comments>https://sqlpowered.com/why-it-matters-if-collate-is-used-in-where/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sat, 29 Nov 2014 21:33:37 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=414</guid>

					<description><![CDATA[There is a lot of things we shouldn&#8217;t do or use in WHERE clause, i.e scalar functions or data types conversion. They can lead to a suboptimal execution plan and cause serious performance degradation. A similar situation exists for the COLLATE statement. If it is used in WHERE it prevents...]]></description>
										<content:encoded><![CDATA[<p>There is a lot of things we shouldn&#8217;t do or use in WHERE clause, i.e scalar functions or data types conversion. They can lead to a suboptimal execution plan and cause serious performance degradation. A similar situation exists for the COLLATE statement. If it is used in WHERE it prevents query optimizer to use Index Seek operator and it replaces it with Index Scan which is significantly slower on large tables and a small subset of rows to be selected.</p>
<p>Let&#8217;s demonstrate it.</p>
<p>We will prepare a table with sample values first. The table has [String] columns with Slovak_CI_AS collation.</p>
<pre class="lang:tsql decode:true">-- create sample table
CREATE TABLE [dbo].[SampleTable]
(
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [String] VARCHAR(200) COLLATE Slovak_CI_AS NULL
)
GO

-- populate test data
INSERT [dbo].[SampleTable]
    SELECT TOP 2000
        [o1].[name] + LEFT(CAST(NEWID() AS VARCHAR(36)), 6)
    FROM [sys].[objects] [o1]
        CROSS JOIN [sys].[objects] [o2]
GO

-- create simple index on search argument column
CREATE NONCLUSTERED INDEX [IX_String] ON [dbo].[SampleTable] ( [String] )
GO</pre>
<p>We can run simple query and check its execution plan now:</p>
<pre class="lang:tsql decode:true">SELECT *
FROM [dbo].[SampleTable]
WHERE [String] LIKE 'sysrow%'
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-415" src="https://sqlpowered.com/wp-content/uploads/2015/06/CollateAndExecutionPlan1.jpg" alt="CollateAndExecutionPlan1" width="405" height="98" /></p>
<p>It has returned about 4 rows from a total of 2 thousand rows and Index Seek operator was selected by the optimizer.</p>
<p>Let&#8217;s modify the query and use COLLATE statement to convert it Czech_CI_AS collation first. This can be useful if we need to search for some language-specific letters like ř, š, or č.</p>
<pre class="lang:tsql decode:true">-- execute query 2 and check execution plan
SELECT *
FROM dbo.SampleTable
WHERE String LIKE 'sysrow%' COLLATE Czech_CI_AS
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-416" src="https://sqlpowered.com/wp-content/uploads/2015/06/CollateAndExecutionPlan2.jpg" alt="CollateAndExecutionPlan2" width="449" height="79" /></p>
<p>Index Seek operator was replaced by Index Scan operator which is much less effective for this particular query. You can see that it takes 75% from the total batch, 3x more than the one with Index Seek.</p>
<p>In the latest versions of SQL Server, there is an improvement in query plans explorer which will warn us about such a situation. There is and warning sign displayed for SELECT operator:</p>
<p><img loading="lazy" decoding="async" class="alignnone  wp-image-4472" src="https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1.png" alt="" width="540" height="109" srcset="https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1.png 1217w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-300x61.png 300w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-1024x207.png 1024w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-150x30.png 150w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-768x155.png 768w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-360x73.png 360w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-160x32.png 160w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-320x65.png 320w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-520x105.png 520w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-720x146.png 720w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_1-980x198.png 980w" sizes="auto, (max-width: 540px) 100vw, 540px" /></p>
<p>If we will move the mouse over it more detailed explanation pops up:</p>
<p><img loading="lazy" decoding="async" class="alignnone  wp-image-4473" src="https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2.png" alt="" width="346" height="291" srcset="https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2.png 564w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-300x252.png 300w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-119x100.png 119w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-360x303.png 360w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-160x134.png 160w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-320x269.png 320w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-292x245.png 292w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-405x340.png 405w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-535x450.png 535w, https://sqlpowered.com/wp-content/uploads/2014/11/COLLATE_in_WHERE_Index_Seek_Scan_2-520x437.png 520w" sizes="auto, (max-width: 346px) 100vw, 346px" /></p>
<p>The warning is the same as for standard type conversion using CAST() or CONVERT() functions. When COLLATE is used, then SQL Server converts the data in columns to different collations internally.  That means that COLLATE should be avoided in WHERE clause and if this isn&#8217;t possible then its use should be rethought twice and performance impact considered.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/why-it-matters-if-collate-is-used-in-where/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Why to avoid functions in WHERE?</title>
		<link>https://sqlpowered.com/avoid-functions-in-where/</link>
					<comments>https://sqlpowered.com/avoid-functions-in-where/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sat, 20 Sep 2014 15:19:34 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=383</guid>

					<description><![CDATA[It&#8217;s well known that we should use functions in the WHERE clause because they can have a negative impact or query execution because of the suboptimal execution plan is generated. I have prepared a really simple example of how to demonstrate this. It also demonstrates one rule that we should...]]></description>
										<content:encoded><![CDATA[<p>It&#8217;s well known that we should use functions in the WHERE clause because they can have a negative impact or query execution because of the suboptimal execution plan is generated. I have prepared a really simple example of how to demonstrate this. It also demonstrates one rule that we should follow as much as possible when writing queries: never try to cast column value in comparison if there is any other option on how to evaluate it against scalar expression.</p>
<p>Let&#8217;s see our example. We will create table [dbo].[SampleTable] with [Date] column and populate it with some sample data. Then we will create an index over the [Date] column which one we will expect to be used later by sample query.</p>
<pre class="lang:tsql decode:true">-- Create sample table
CREATE TABLE [dbo].[SampleTable]
(
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [Date] DATETIME
)
GO

-- Populate table with sample data
INSERT [dbo].[SampleTable]
    SELECT TOP 2000 DATEADD(dd, ROW_NUMBER() OVER (ORDER BY [o1].[object_Id], [o2].[object_Id]), 
                              dateadd(d, 0, datediff(d, 0, GETDATE())))
    FROM [sys].[objects] [o1]
        CROSS JOIN [sys].[objects] [o2]
GO

-- Create index on [Date] column
CREATE NONCLUSTERED INDEX [IX_Date] ON [dbo].[SampleTable] 
(
    [Date] ASC
)
GO</pre>
<p>We will execute the following query to get all data for the specified year and check the execution plan:</p>
<pre class="lang:tsql decode:true">SELECT * FROM [dbo].[SampleTable] WHERE YEAR([Date]) = '2011'</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-384" src="https://sqlpowered.com/wp-content/uploads/2015/06/FunctionInWhereIndexScanSeek1.jpg" alt="FunctionInWhereIndexScanSeek1" width="276" height="77" /></p>
<p>Index Scan was used to get requested data. This is not exactly what we would like to see because we have created an index and based od data selectivity we know that only a small subset of rows from the table should be returned. It is caused by the fact that we have used YEAR() function over the [Date] column which prevents the use of index seek because the YEAR() value needs to be calculated for every row first and then filtered further.</p>
<p>How to modify the query to replace Index Scan with Index Seek? It&#8217;s easy. Just think a little bit in dates and years:</p>
<pre class="lang:tsql decode:true">SELECT * FROM dbo.SampleTable WHERE Date &gt;= '2011-01-01' AND Date &lt;= '2011-12-31'
</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-385" src="https://sqlpowered.com/wp-content/uploads/2015/06/FunctionInWhereIndexScanSeek2.jpg" alt="FunctionInWhereIndexScanSeek2" width="279" height="79" /></p>
<p>We are getting exactly the same result set but this time Index Seek plan operator was selected by the query optimizer because YEAR() function was removed and our query is now simple seek for a range of values.</p>
<p>If we will execute both queries in a single batch, we can compare the performance gain we have achieved:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-4400" src="https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where.png" alt="" width="418" height="224" srcset="https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where.png 962w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-300x161.png 300w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-150x80.png 150w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-768x412.png 768w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-360x193.png 360w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-160x86.png 160w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-320x172.png 320w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-457x245.png 457w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-634x340.png 634w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-839x450.png 839w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-932x500.png 932w, https://sqlpowered.com/wp-content/uploads/2014/09/Why-not-use-function-in-where-520x279.png 520w" sizes="auto, (max-width: 418px) 100vw, 418px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/avoid-functions-in-where/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Change in Query Plan Can Affect Query Result Set (Table Spool)</title>
		<link>https://sqlpowered.com/change-in-query-plan-can-affect-query-result-set-table-spool/</link>
					<comments>https://sqlpowered.com/change-in-query-plan-can-affect-query-result-set-table-spool/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sat, 10 May 2014 11:11:38 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[performance]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=331</guid>

					<description><![CDATA[It´s legitimate expectation that a SELECT statement will return consistent results independently on number of rows returned or an internal implementation of query processing. But it is not always true. I will demonstrate it on a simple query using NEWID() function. This function should return new value on every call...]]></description>
										<content:encoded><![CDATA[<p>It´s legitimate expectation that a SELECT statement will return consistent results independently on number of rows returned or an internal implementation of query processing. But it is not always true. I will demonstrate it on a simple query using NEWID() function. This function should return new value on every call (row based) but it depends on which query plan operators are used during query processing and the final result can be quite different.<span id="more-331"></span>Create dbo.Sample table with a column ID and fill 256 rows into it using the GO xxx functionality of SSMS:</p>
<pre class="lang:tsql decode:true">CREATE TABLE dbo.SampleTable 
(
    Id INT IDENTITY(1,1) PRIMARY KEY
)
GO

INSERT dbo.SampleTable
    DEFAULT VALUES
GO 256</pre>
<p>Execute following queries and compare results and execution plans. The only one difference between statements is in the numer of rows to be requested by TOP operator (higlighted line).</p>
<p><strong>Query 1:</strong></p>
<pre class="lang:tsql mark:2 decode:true">SELECT 
    TOP 175
    (SELECT Id FROM dbo.SampleTable WHERE Id = CAST(CAST(NEWID() AS VARBINARY) AS TINYINT))
FROM dbo.SampleTable
GO
</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-332" src="https://sqlpowered.com/wp-content/uploads/2015/06/UnexpectedNewIdBehavior1.jpg" alt="UnexpectedNewIdBehavior1" width="138" height="193" /></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-333" src="https://sqlpowered.com/wp-content/uploads/2015/06/UnexpectedNewIdBehavior2.jpg" alt="UnexpectedNewIdBehavior2" width="600" height="124" /></p>
<p><strong>Query 2:</strong></p>
<pre class="lang:tsql mark:2 decode:true">SELECT 
    TOP 176
    (SELECT Id FROM dbo.SampleTable WHERE Id = CAST(CAST(NEWID() AS VARBINARY) AS TINYINT))
FROM dbo.SampleTable
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-334" src="https://sqlpowered.com/wp-content/uploads/2015/06/UnexpectedNewIdBehavior3.jpg" alt="UnexpectedNewIdBehavior3" width="138" height="194" /></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-335" src="https://sqlpowered.com/wp-content/uploads/2015/06/UnexpectedNewIdBehavior4.jpg" alt="UnexpectedNewIdBehavior4" width="650" height="105" /></p>
<p>This small change in the query (in fact requesting one more row) caused new execution plan to be generated. This is common situation in the SQL Server where the optimizer is cost-based. But what is for sure not expected and predictable is that a change in execution plan means a different result set. Our sample query returns after the execution plan change the same value for all rows instead of the random value as before.</p>
<p>Why that happened? The difference between execution plans can explain that: There is one new operator in the second plan: Table Spool. This operator is used by the query optimizer to persist temporal set of values to the tempdb for later use and it is blocking operator by nature. This process is used whenever the optimizer knows that the density of the column is high and the intermediate result is very complex to calculate. If this is the case the optimizer makes the computation once and stores the result in the temporary space so it can search it later.</p>
<p>My assumption is that when the SQL Server is fetching rows from and Clustered Index Seek into Table Spool operator, it&#8217;s changing the processing mode of an index seek from a row-by-row to batch mode internally that the full set of rows received by seeking the index can be delivered to Table Spool operator. The NEWID() function is then executed only once.</p>
<p>The difference between the number of executions for the Clustered Index Seek can be viewed on the operator properties dialog:</p>
<p><strong>Query 1</strong>: 175 executions                                                <strong>Query 2</strong>: 1 execution</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1743 size-full" src="https://sqlpowered.com/wp-content/uploads/2014/05/Cluster-Index-Seek-Number-of-Executions-1.png" alt="Cluster-Index-Seek-Number-of-Executions-1" width="341" height="555" /> <img loading="lazy" decoding="async" class="alignnone size-full wp-image-1744" src="https://sqlpowered.com/wp-content/uploads/2014/05/Cluster-Index-Seek-Number-of-Executions-2.png" alt="Cluster-Index-Seek-Number-of-Executions-2" width="341" height="555" /></p>
<p>One question still remains: where the boundary value 176 is coming from? I&#8217;m not sure for now. It can be an internal optimizer threshold but this is hardly documented.</p>
<p><strong>Further reading</strong>:</p>
<ul>
<li><a href="https://www.simple-talk.com/sql/learn-sql-server/operator-of-the-week---spools,-eager-spool/">Operator of the Week &#8211; Spools, Eager Spool</a></li>
<li><a href="http://dba.stackexchange.com/questions/27719/create-a-plan-guide-to-cache-lazy-spool-cte-result">Create a plan guide to cache (lazy spool) CTE result</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/change-in-query-plan-can-affect-query-result-set-table-spool/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
