<?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>development &#8211; SQLpowered.com</title>
	<atom:link href="https://sqlpowered.com/tag/development/feed/" rel="self" type="application/rss+xml" />
	<link>https://sqlpowered.com</link>
	<description>SQL Server + BI</description>
	<lastBuildDate>Fri, 31 Dec 2021 20:36:00 +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>development &#8211; SQLpowered.com</title>
	<link>https://sqlpowered.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Comparing strings with trailing spaces</title>
		<link>https://sqlpowered.com/comparing-strings-with-trailing-spaces/</link>
					<comments>https://sqlpowered.com/comparing-strings-with-trailing-spaces/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Wed, 20 Feb 2019 21:41:22 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=3173</guid>

					<description><![CDATA[There is all the time confusion in the developer&#8217;s daily job how exactly is SQL Server handling padding spaces before and after strings in comparison and data persistence. Time to make it more clear now. The few most important things to remember are: SQL Server follows the ANSI/ISO SQL-92 specification...]]></description>
										<content:encoded><![CDATA[<p>There is all the time confusion in the developer&#8217;s daily job how exactly is SQL Server handling padding spaces before and after strings in comparison and data persistence. Time to make it more clear now.</p>
<p>The few most important things to remember are:</p>
<ol>
<li>SQL Server follows the ANSI/ISO SQL-92 specification on how to compare strings with spaces. The ANSI standard requires<strong> padding for the character strings used in comparisons so that their lengths match before comparing them</strong>.  The padding directly affects WHERE and HAVING clause predicates and other Transact-SQL string comparisons.</li>
<li>The only exception to this rule is the <strong>LIKE</strong> predicate. When the right side of a LIKE predicate expression features a value with a trailing space, SQL Server does not pad the two values to the same length before the comparison occurs.</li>
<li>The <strong><a href="https://docs.microsoft.com/en-us/sql/t-sql/statements/set-ansi-padding-transact-sql?view=sql-server-ver15" target="_blank" rel="noopener noreferrer">SET ANSI_PADDING</a> setting does not affect whether SQL Server pads strings before it compares them</strong>. SET ANSI_PADDING only affects whether trailing blanks are trimmed from values being inserted into a table, so it affects storage but not comparisons.</li>
</ol>
<p>There is a simple script to check how it works:</p>
<div class="ng-scope">
<div class="ng-isolate-scope">
<section class="section ng-scope" data-grid="col-12" data-before="" aria-hidden="false">
<div class="section-body ng-scope" style="box-sizing: inherit; outline: none;">
<div class="ng-scope" data-grid="col-12">
<div class="ng-isolate-scope" data-grid="col-12">
<div class="kb-summary-section section ng-scope">
<pre class="EnlighterJSRAW" data-enlighter-language="sql">;WITH s AS
(
	SELECT *, DATALENGTH(String) DataLen FROM (VALUES ('abc'), ('abc ')) a(String)
)
SELECT 'EqualWithSpace' Test, * FROM [s] WHERE String = 'abc ' UNION ALL
SELECT 'EqualNoSpace  ', * FROM s WHERE String = 'abc' UNION ALL
SELECT 'GTWithSpace   ', * FROM s WHERE String &gt; 'ab ' UNION ALL
SELECT 'GTNoSpace     ', * FROM s WHERE String &gt; 'ab' UNION ALL
SELECT 'LTWithSpace   ', * FROM s WHERE String &lt; 'abd ' UNION ALL
SELECT 'LTNoSpace     ', * FROM s WHERE String &lt; 'abd' UNION ALL
SELECT 'LikeWithSpace ', * FROM s WHERE String LIKE 'abc %' UNION ALL
SELECT 'LikeNoSpace   ', * FROM s WHERE String LIKE 'abc%'
GO</pre>
<p><img fetchpriority="high" decoding="async" class="alignnone size-full wp-image-3185" src="https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1.png" alt="" width="227" height="305" srcset="https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1.png 227w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1-74x100.png 74w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1-223x300.png 223w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1-119x160.png 119w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_1-182x245.png 182w" sizes="(max-width: 227px) 100vw, 227px" /></p>
</div>
<p>You can see that SQL Server handles strings &#8216;abc&#8217; and &#8216;abc &#8216; as to be the same. The only exception is  LIKE &#8216;abc %&#8217; where space means search condition that any character can be at the position in searched expression.</p>
<p>How the use of SET ANSI_PADDING ON will affect our script? There will be no difference because this setting is affecting data at the time of writing but not reading. To check this we should modify our script and save data to a temporary table first:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SET ANSI_PADDING OFF
GO

CREATE TABLE #s (String varchar(10))
GO
INSERT INTO #s VALUES ('abc ')
INSERT INTO #s VALUES ('abc')
GO
SELECT 'EqualWithSpace' Test, *, DATALENGTH(String) DataLen FROM #s WHERE String = 'abc ' UNION ALL
SELECT 'EqualNoSpace  ', *, DATALENGTH(String) FROM #s WHERE String = 'abc' UNION ALL
SELECT 'GTWithSpace   ', *, DATALENGTH(String) FROM #s WHERE String &gt; 'ab ' UNION ALL
SELECT 'GTNoSpace     ', *, DATALENGTH(String) FROM #s WHERE String &gt; 'ab' UNION ALL
SELECT 'LTWithSpace   ', *, DATALENGTH(String) FROM #s WHERE String &lt; 'abd ' UNION ALL
SELECT 'LTNoSpace     ', *, DATALENGTH(String) FROM #s WHERE String &lt; 'abd' UNION ALL
SELECT 'LikeWithSpace ', *, DATALENGTH(String) FROM #s WHERE String LIKE 'abc %' UNION ALL
SELECT 'LikeNoSpace   ', *, DATALENGTH(String) FROM #s WHERE String LIKE 'abc%'
GO</pre>
</div>
</div>
</div>
</section>
</div>
</div>
<p><img decoding="async" class="alignnone size-full wp-image-3186" src="https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_2.png" alt="" width="227" height="286" srcset="https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_2.png 227w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_2-79x100.png 79w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_2-127x160.png 127w, https://sqlpowered.com/wp-content/uploads/2019/10/TrailingSpacesComparison_2-194x245.png 194w" sizes="(max-width: 227px) 100vw, 227px" /></p>
<p>The core change is that our DataLen is now 3 everywhere and this means that SQL Server stored our data without padding spaces. We see that all rows for LIKE are gone In our result set because compare conditions don&#8217;t match.</p>
<p>You should remember two other important things:</p>
<ul>
<li>SET ANSI_PADDING is a deprecated feature for Azure SQL and will be deprecated for SQL Server soon</li>
<li>SET ANSI_PADDING should be set to ON always</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/comparing-strings-with-trailing-spaces/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>IDENTITY, transakce a návrh datového modelu</title>
		<link>https://sqlpowered.com/identity-transakce-a-navrh-datoveho-modelu/</link>
					<comments>https://sqlpowered.com/identity-transakce-a-navrh-datoveho-modelu/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sat, 18 Aug 2018 08:02:37 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=2707</guid>

					<description><![CDATA[Při návrhu datového modelu musíme myslet i na správnou velikost datového typu pro primární klíč. Standardem je datový typ INT, ale pokud máme dimenzi s několika prvky, rádi zvolíme TINYINT, je-li dimenze cizím klíčem ve faktové tabulce o velkém množství řádků. Úspora úložiště a vliv na výkon je poté jednoznačně...]]></description>
										<content:encoded><![CDATA[<p>Při návrhu datového modelu musíme myslet i na správnou velikost datového typu pro primární klíč. Standardem je datový typ INT, ale pokud máme dimenzi s několika prvky, rádi zvolíme TINYINT, je-li dimenze cizím klíčem ve faktové tabulce o velkém množství řádků. Úspora úložiště a vliv na výkon je poté jednoznačně pozitivní.</p>
<p>Pokud v našem primární klíči využijeme i IDENTITY, je třeba si dát velký pozor, pokud náš ETL proces pracuje v transakci a není úplně neobvyklé, že dojde k rollbacku transakce. Potom můžeme velice lehce narazit na situaci, kdy se snažíme vložit do naší dimenzionální tabulky nové řádky, ale tabulka nám říká, že hodnota primárního klíče, kterou chceme vložit, přesahuje délku datového typu, ačkoliv po inspekcí tabulky samotné vidíme, že je v ní mnohem méně záznamů, než je maximální velikost datového typu, nebo je dokonce prázdná.</p>
<p><span id="more-2707"></span></p>
<p>Vše si ukážeme na jednoduchém příkladu:</p>
<p>Vytvoříme si tabulku dbo.SampleTable, která má primární klíč ID datového typu TINYINT a nastaveno IDENTITY(1,1). Maximální hodnota, kterou můžeme do TINYINT uložit je 255, tedy naše tabulka může obsahovat maximálně 256 řádků (včetně 0 by to bylo 256).</p>
<pre class="lang:tsql decode:true">CREATE TABLE [dbo].[SampleTable] (
	[ID] TINYINT NOT NULL IDENTITY(1,1),
	[Name] VARCHAR(10)
)
GO</pre>
<p>Nyní budeme do tabulky vkládat řádky, ale vždy provedeme rollback dané transakce. K simulaci využijeme volání GO 255, což je funkce SQL Server Management Studia, nikoliv T-SQL syntax.</p>
<pre class="lang:tsql decode:true">BEGIN TRAN

	INSERT INTO [dbo].[SampleTable] ([Name]) 
		VALUES ('Test')

ROLLBACK
GO 255</pre>
<p>Pokud se nyní podíváme, jaká data naše tabulka obsahuje, je přesně podle očekávání prázdná:</p>
<pre class="lang:tsql decode:true">SELECT * FROM [dbo].[SampleTable] [st]
GO</pre>
<p><img decoding="async" class="alignnone size-full wp-image-2709" src="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_1.png" alt="" width="106" height="37"></p>
<p>Nyní pojďme do tabulky vložit další řádek:</p>
<pre class="lang:tsql decode:true">INSERT INTO [dbo].[SampleTable] ([Name]) 
	VALUES ('Test')
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2710" src="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2.png" alt="" width="436" height="56" srcset="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2.png 436w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2-150x19.png 150w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2-300x39.png 300w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2-160x21.png 160w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_2-320x41.png 320w" sizes="auto, (max-width: 436px) 100vw, 436px" /></p>
<p>A zde již přichází poněkud nečekaně chyba. Řádek se nepodařilo vložit, protože další hodnota, kterou vygenerovalo IDENTITY je 256, což přesahuje velikost datového typu TINYINT. To si můžeme ověřit i takto:</p>
<pre class="lang:tsql decode:true">SELECT IDENT_CURRENT('dbo.SampleTable')
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2711" src="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_3.png" alt="" width="136" height="39"></p>
<p>Vidíme, že poslední přiřazená hodnota je 255 a další následující bude opravdu 256.</p>
<p>Řešení celé situace je na první pohled jednoduché:</p>
<pre class="lang:tsql decode:true">DBCC CHECKIDENT('dbo.SampleTable',RESEED, 0)
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2712" src="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4.png" alt="" width="594" height="44" srcset="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4.png 594w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4-150x11.png 150w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4-300x22.png 300w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4-160x12.png 160w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4-320x24.png 320w, https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_4-520x39.png 520w" sizes="auto, (max-width: 594px) 100vw, 594px" /></p>
<p>Provedli jsme RESEED tabulky a nastavili sekvenci opět od 0. Zkusme nyní vložit další řádek:</p>
<pre class="lang:tsql decode:true">INSERT INTO [dbo].[SampleTable] ([Name]) 
	VALUES ('Test')
GO

SELECT * FROM [dbo].[SampleTable] [st]
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2713" src="https://sqlpowered.com/wp-content/uploads/2018/07/IDENTIY_TRANSACTIONS_DATA_MODEL_5.png" alt="" width="105" height="39"></p>
<p>Vidíme, že vše prošlo bez problémů a můžeme vesele vkládat ještě dalších 254 řádků.</p>
<p>To, co v naše scénáři vypadá triviálně, nás ovšem v reálném provozu může řádně potrápit. Pokud se nám objeví chybová hláška zobrazená výše, stojíme před následujícími otázkami:</p>
<ul>
<li>Na jaké tabulce se chyba vyskytla? V chybové zprávě tuto informaci nenajdeme a pokud naše ETL transakce vkládá do desítek dimenzionálních tabulek, bude hledání bez dobrého logování celého procesu opravu zdlouhavé.</li>
<li>Pokud již identifikujeme tabulku, budeme čekat, že bude obsahovat 255 řádků a my se snažíme vložit další řádek a budeme proto přemýšlet o změně velikosti datového typu. Po zobrazení dat v tabulce ale může přijít velké překvapení: tabulka obsahuje méně než 255 řádků nebo je dokonce prázdná.</li>
<li>V naše případě jsme pracovali v prázdnou tabulkou, ale v reálné scénáři může tabulka obsahovat třeba 20 řádků s různě rozloženými hodnotami primárního klíče, třeba 14, 78, 128, kde mezery v sekvenci vznikly právě každým rollbackem ETL transakce. V tomto případě budeme hledat nějakou rozumnou hodnotu, ke které provedeme RESEED, opravdu těžko a nezbude nám, než pracně provést update všech hodnot primárního klíče na hodnoty začínající od 1. Jak velký to může být úkol v případě rozsáhlé faktové tabulky netřeba zmiňovat.</li>
<li>Pokud skutečně vyčerpáme 255 hodnot a budeme potřebovat zvětšit datový typ, je třeba změnu provést i ve faktového tabulce, což může být při miliardách záznamů opravdu zdlouhavý a komplikovaný úkol.</li>
</ul>
<p>Při návrhu datového modelu a ETL procesu tedy mysleme vždy na následující:</p>
<ul>
<li>Je uvažovaný datový typ skutečně dostačující pro budoucnost? Nebude jeho pozdější změna tak komplikovaná, že momentální volba nejmenšího možného datového typu nestojí a tím získaná úspora místa a lehké zvýšení výkonu nám za to nestojí?</li>
<li>V rámci ETL procesu musíme myslet při rollbacku i na chování IDENTITY() a zvolit RESSED při rollbacku transakce, případně uvažovat o použití sekvencí.</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/identity-transakce-a-navrh-datoveho-modelu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Generátor unikátních ID</title>
		<link>https://sqlpowered.com/generator-unikatnich-id/</link>
					<comments>https://sqlpowered.com/generator-unikatnich-id/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 23 Apr 2017 20:39:25 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=608</guid>

					<description><![CDATA[V době, kdy ještě neexistovaly v SQL serveru sekvence, jsme při vytvoření generátoru unikátních IDček napříč celou databází byli odkázání na lidovou tvořivost. Dnes si ukážeme snad nejjednodušší možné řešení s využitím SCOPE_IDENTITY () a jedné tabulky, která drží naše již použitá IDčka. Není to nic těžkého a vše funguje...]]></description>
										<content:encoded><![CDATA[<p>V době, kdy ještě neexistovaly v SQL serveru <a href="https://sqlpowered.com/sekvence/">sekvence</a>, jsme při vytvoření generátoru unikátních IDček napříč celou databází byli odkázání na lidovou tvořivost. Dnes si ukážeme snad nejjednodušší možné řešení s využitím SCOPE_IDENTITY () a jedné tabulky, která drží naše již použitá IDčka.</p>
<p><span id="more-608"></span></p>
<p>Není to nic těžkého a vše funguje poměrně efektivně, pokud správně pracujeme s transakcemi. Ideální je, že nám nevadí, pokud je nějaké ID vygenerováno, ale následně se nepoužije, protože navazující transakce byla odrolována. Pak můžeme jednoduše volat naši proceduru mimo transakci a nemusíme se bát zámků nebo jiných potíží s výkonem vyjma případu, že bychom narazili na omezení na úrovni úložiště nebo interních zámků SQL Serveru (latches), pokud bychom nová IDčka generovali rychlostí několika tisíc a více položek za vteřinu.</p>
<p>Ve skriptu níže si vytvoříme jednoduchou uloženou proceduru, která zapisuje vždy jeden nový řádek do tabulky [dbo].[IdsTable], která drží naše již vygenerovaná IDčka. Procedura nám vrací hodnotu SCOPE_IDENTITY() posledně vloženého záznamu jako výstupní parametr @Id, se kterým již můžeme dále pracovat.  Skript si v ukázce pustíme 10x pomocí GO 10 a jednotlivá IDčka vidíme ve výstupní okně Management Studia.</p>
<pre class="lang:tsql decode:true">CREATE TABLE [dbo].[IdsTable]
(
    [Id] INT IDENTITY (1,1) PRIMARY KEY
)
GO

CREATE PROCEDURE [dbo].[GetNewID] 
    @NewId BIGINT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO [dbo].[IdsTable] DEFAULT VALUES
    SET @NewId = SCOPE_IDENTITY()
END
GO

DECLARE @Id INT

EXEC [dbo].[GetNewID] @Id OUTPUT

PRINT @Id
GO 10</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2740" src="https://sqlpowered.com/wp-content/uploads/2015/04/Generate_IDs_1.png" alt="" width="290" height="209" srcset="https://sqlpowered.com/wp-content/uploads/2015/04/Generate_IDs_1.png 290w, https://sqlpowered.com/wp-content/uploads/2015/04/Generate_IDs_1-139x100.png 139w, https://sqlpowered.com/wp-content/uploads/2015/04/Generate_IDs_1-160x115.png 160w" sizes="auto, (max-width: 290px) 100vw, 290px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/generator-unikatnich-id/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Vzorová syntaxe CURSORu</title>
		<link>https://sqlpowered.com/vzorova-syntaxe-cursoru/</link>
					<comments>https://sqlpowered.com/vzorova-syntaxe-cursoru/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 03 Mar 2017 09:53:32 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=549</guid>

					<description><![CDATA[Dvě základní šablony kurzoru. Liší se především syntakticky, kdy druhá varianta nevyžaduje opakované kopírování seznamu parametrů v příkazu FETCH, což je výhodné zejména tehdy, načítá-li kurzor velké množství sloupců z deklarativního dotazu. -- Varianta 1 DECLARE @name NVARCHAR(128) DECLARE cur CURSOR FAST_FORWARD FOR SELECT name FROM sys.tables WHERE name LIKE...]]></description>
										<content:encoded><![CDATA[<p>Dvě základní šablony kurzoru. Liší se především syntakticky, kdy druhá varianta nevyžaduje opakované kopírování seznamu parametrů v příkazu FETCH, což je výhodné zejména tehdy, načítá-li kurzor velké množství sloupců z deklarativního dotazu.</p>
<p><span id="more-549"></span></p>
<pre class="lang:tsql decode:true">-- Varianta 1
DECLARE @name NVARCHAR(128)
DECLARE cur CURSOR FAST_FORWARD FOR
    SELECT name
    FROM sys.tables
    WHERE name LIKE 'tbl%'
    ORDER BY name
 
OPEN cur
FETCH NEXT FROM cur INTO @name
WHILE @@FETCH_STATUS = 0
BEGIN
 
    EXECUTE ('delete from ' + @name)
 
    FETCH NEXT FROM cur INTO @name
END

CLOSE cur
DEALLOCATE cur

-- Varianta 2
DECLARE @name NVARCHAR(128)
DECLARE cur CURSOR FAST_FORWARD FOR
    SELECT name
    FROM sys.tables
    WHERE name LIKE 'tbl%'
    ORDER BY name
 
OPEN cur
WHILE 1 = 1
BEGIN
    FETCH NEXT FROM cur INTO @Name
    IF @@FETCH_STATUS &lt;&gt; 0
        BREAK
 
    EXECUTE ('delete from ' + @name)
 
END

CLOSE cur
DEALLOCATE cur</pre>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/vzorova-syntaxe-cursoru/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Nalezení textového řetězce v databázi</title>
		<link>https://sqlpowered.com/nalezeni-textoveho-retezce-v-databazi/</link>
					<comments>https://sqlpowered.com/nalezeni-textoveho-retezce-v-databazi/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Tue, 28 Feb 2017 06:14:06 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=50</guid>

					<description><![CDATA[V případě, že máme neznámou nebo rozsáhlou databázi a snažíme se dohledat výskyt určitých dat, aniž bychom tušili, v které tabulce je máme hledat, stojíme před otázkou, jak prohledat všechny tabulky, zda se v nich určitý řetězec vyskytuje. Stejný problém budeme řešit i v případě, že máme databázi, která nepoužívá...]]></description>
										<content:encoded><![CDATA[<p>V případě, že máme neznámou nebo rozsáhlou databázi a snažíme se dohledat výskyt určitých dat, aniž bychom tušili, v které tabulce je máme hledat, stojíme před otázkou, jak prohledat všechny tabulky, zda se v nich určitý řetězec vyskytuje. Stejný problém budeme řešit i v případě, že máme databázi, která nepoužívá pro zajištění integrity cizí líče nebo část těchto klíčů chybí, a naším úkolem je z databáze odstranit určitá data. Nejprve je potřebujeme v databázi najít a následně ověřit, že data byla smazána ze všech tabulek, kde se vyskytovala.<span id="more-50"></span></p>
<p>Pomocí níže uvedeného skriptu můžeme vyhledat výskyt určitého textového řetězce ve všech tabulkách v databázi, v jejímž kontextu je skript spuštěn. Prohledávat můžeme všechny sloupce, které mají textový datový typ, tedy CHAR, NCHAR, VARCHAR, NVARCHAR, TEXT a NTEXT. V případě unicode datových typů (NCHAR, NVARCHAR, NTEXT) je třeba nezapomenout uvést N před apostrofy s řetězce, který hledáme, jinak nebudou sloupce prohledávány jako unicode a řetězec nebude nalezen, ačkoli v se v databázi vyskytuje.</p>
<p>Skript nejprve naplní cur kursor seznamem všech tabulek v databázi a jejich sloupců, které jsou některého z výše uvedených textových datových typů. Poté je tato vstupní množina iterována a pro každý sloupec je pomocí dynamického sql vytvořen sql dotaz, který hledá pomocí operátoru LIKE náš řetezec ve všech řádcích daného sloupce. Do lokální dočasné tabulky #Result je vložen řádek se jménem tabulky, sloupce a počtem řádků, které obsahují hledaný řetězec. Na konci skriptu jsou z #Result tabulky vybrány ty sloupce, kde se hledaný řetězec vyskytl alespoň jednou.</p>
<pre class="lang:tsql decode:true ">SET NOCOUNT ON

DECLARE @Stmt VARCHAR(MAX), 
        @SchemaName SYSNAME, 
        @TableName SYSNAME, 
        @ColumnName SYSNAME, 
        @What NVARCHAR(200), 
        @NL CHAR(1),
        @Err NVARCHAR(MAX),
        @Cnt INT

CREATE TABLE #Result (TableName SYSNAME, ColumnName SYSNAME, Cnt INT)

SET @What = N'AKU'

SET @NL = CHAR(13)

DECLARE cur CURSOR FOR 
    SELECT sch.name, t.name, c.name 
    FROM sys.columns c 
        INNER JOIN sys.tables t ON t.object_id = c.object_id
        INNER JOIN sys.types tp ON tp.system_type_id = c.system_type_id
        INNER JOIN sys.schemas sch ON sch.schema_id = t.schema_id
    WHERE tp.name IN ('char','nchar','varchar','nvarchar','text','ntext')
      AND t.type_desc = 'USER_TABLE'
    ORDER BY t.name, c.name

OPEN cur
WHILE 1 = 1
BEGIN
    FETCH NEXT FROM cur INTO @SchemaName, @TableName, @ColumnName
    IF @@FETCH_STATUS &lt;&gt; 0
        BREAK

     PRINT @TableName + ' =&gt; ' + @ColumnName
    
    SET @Stmt = 'INSERT INTO #Result' + @NL + 
                '    SELECT ''' +  @TableName + ''', ''' + @ColumnName + ''', COUNT(*)' + @NL +
                '    FROM [' + @SchemaName + '].[' + @TableName + '] (nolock)' + @NL + 
                '    WHERE [' + @ColumnName + '] Like ''%' + @What + '%'''
    BEGIN TRY
        EXEC(@Stmt)
    END TRY
    BEGIN CATCH
    
        SET @Err = '[' + @SchemaName + '].[' + @TableName + '] =&gt; ' + ERROR_MESSAGE() + @NL + @Stmt
    
        RAISERROR(@Err, 16, 1) WITH NOWAIT
    
    END CATCH
        
    END

SELECT * FROM #Result WHERE Cnt &gt; 0

Error:
CLOSE cur
DEALLOCATE cur
DROP TABLE #Result</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/nalezeni-textoveho-retezce-v-databazi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Odstranění duplicitních řádků</title>
		<link>https://sqlpowered.com/odstraneni-duplicitnich-radku/</link>
					<comments>https://sqlpowered.com/odstraneni-duplicitnich-radku/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Tue, 12 Jan 2016 20:34:03 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=20</guid>

					<description><![CDATA[Existuje řada způsobů, jak z tabulky odstranit duplicitní řádky. Jedním z nich je využití analytické funkce spolu s CTE (Common Table Expression). Pomocí analytické funkce ROW_NUMBER() získáme pro každý řádek číslo, které bude unikátní vždy v rámci dané partition, kterou v příkladu níže vytvoříme tak, že pomocí klauzule PARITION BY...]]></description>
										<content:encoded><![CDATA[<p>Existuje řada způsobů, jak z tabulky odstranit duplicitní řádky. Jedním z nich je využití analytické funkce spolu s CTE (Common Table Expression).</p>
<p>Pomocí analytické funkce ROW_NUMBER() získáme pro každý řádek číslo, které bude unikátní vždy v rámci dané partition, kterou v příkladu níže vytvoříme tak, že pomocí klauzule PARITION BY rozdělíme řádky do skupin podle hodnot ve sloupci Value1. V každé takto vytvořené skupině budou potom řádky očíslovány vzestupě od jedné.</p>
<p>Z takto vytvořené virtuální tabulky poté vybereme pouze řádky, které jsou první v každé skupině. Všechny ostatní a tedy duplicitní záznamy budou z výsledkové sady vyjmuty.<span id="more-20"></span></p>
<pre title="Odstranění dulicitníc řádků" class="lang:tsql decode:true">CREATE TABLE dbo.SampleTable (
    Id INT,
    Value1 VARCHAR(10)
)
GO    

INSERT INTO dbo.SampleTable
    SELECT 1, 'Val1' UNION ALL
    SELECT 2, 'Val2' UNION ALL
    SELECT 3, 'Val1' UNION ALL
    SELECT 4, 'Val4'
GO

SELECT * FROM dbo.SampleTable
GO

;WITH cte AS
(
    SELECT
        *, ROW_NUMBER() OVER (PARTITION BY Value1 ORDER BY Id) RN
    FROM dbo.SampleTable
)
DELETE FROM cte
WHERE RN &gt; 1
GO

SELECT * FROM dbo.SampleTable
GO</pre>
<p>V prvním případě jsme odstraňovali duplicitní řádky z tabulky, která neměla žádný primární klíč. Nyní si ukážeme, jak odstranit řádky z tabulky, která má primární klíč s IDENTITY(1,1). Můžeme použít metodu podobnou prvnímu příkladu (CTE)  nebo jednoduchý dotaz s vnitřním dotazem ve WHERE podmínce.</p>
<pre class="lang:tsql decode:true ">-- create SampleTable
CREATE TABLE dbo.tblSampleTable
(
    Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    ObjectId INT NOT NULL,
    ObjectName SYSNAME NOT NULL
)
GO

-- populate sample data
INSERT INTO dbo.tblSampleTable
        ( ObjectId, ObjectName )
    SELECT TOP(1000000) c1.object_id, c1.name
    FROM master.sys.columns c1
        CROSS JOIN master.sys.columns c2
        CROSS JOIN master.sys.columns c3
GO    

-- version 1
;WITH Dupl as
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY ObjectId, ObjectName ORDER BY (SELECT NULL)) as RN 
    from dbo.tblSampleTable
)
DELETE FROM dbo.tblSampleTable
WHERE Id NOT IN (SELECT Id FROM Dupl WHERE RN = 1)
GO

-- version 2
DELETE FROM dbo.tblSampleTable
WHERE Id NOT IN 
(
    SELECT MIN(Id) Id
    FROM dbo.tblSampleTable
    GROUP BY ObjectId, ObjectName
)
GO</pre>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/odstraneni-duplicitnich-radku/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Jednoduchý batch generátor a jeho praktické využití</title>
		<link>https://sqlpowered.com/jednoduchy-batch-generator/</link>
					<comments>https://sqlpowered.com/jednoduchy-batch-generator/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Mon, 13 Jul 2015 22:01:40 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=285</guid>

					<description><![CDATA[V praxi se často můžeme setkat s nutností provádět některé operace po částech či ve více transakcích, protože pokud bychom je vykonali jako jedinou operaci, bude vše trvat neúměrně dlouho, případně si provedení operace může vyžádat systémové prostředky, které nemáme. Nejčastěji se s tímto scénářem setkáme v případě mazání řádků...]]></description>
										<content:encoded><![CDATA[<p>V praxi se často můžeme setkat s nutností provádět některé operace po částech či ve více transakcích, protože pokud bychom je vykonali jako jedinou operaci, bude vše trvat neúměrně dlouho, případně si provedení operace může vyžádat systémové prostředky, které nemáme. Nejčastěji se s tímto scénářem setkáme v případě mazání řádků z velkých tabulek, kdy se při vykonání jediného DELETE příkazu musíme vyrovnat v následujícími omezeními:</p>
<ul>
<li>operace je časově náročná, protože mazání každého jednotlivého řádku se zapisuje do transakčního logu</li>
<li>nárůst transakčního logu může být opravdu enormní a přesáhnout jak velikost původní tabulky tak i dostupné místo na úložišti</li>
<li>po celou dobu provádění transakce jsou nad tabulkou zámky (nad celou tabulkou, případně oddílem tabulky (partition) nebo konkrétními řádky)</li>
<li>celkový výkon systému je snížen, jsou-li intenzivně využívání systémové prostředky, zejména IO</li>
</ul>
<p>Stojíme-li před podobným úkolem, jedním z doporučených postupů je mazat data z tabulky po menších částech pomocí jednoduchého batch generátoru. V článku si ukážeme jednu z možných implementací takového generátoru i její praktické využití.<span id="more-285"></span></p>
<p>Základem generátoru je využití rekurze, kterou umožňuje Common Table Expression (CTE):</p>
<pre class="lang:tsql decode:true">DECLARE @BatchSize INT
DECLARE @BatchCount INT

SET @BatchSize = 50
SET @BatchCount = 8

;WITH Batchlist AS
(
    SELECT 1 BatchId, 1 BatchStart, @BatchSize BatchEnd
    UNION ALL
    SELECT BatchId + 1, @BatchSize + BatchStart, @BatchSize + BatchEnd
    FROM BatchList
    WHERE BatchId &lt;= @BatchCount - 1
)
SELECT *
FROM BatchList
OPTION (MAXRECURSION 0)</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1266" src="https://sqlpowered.com/wp-content/uploads/2015/07/BatchGenerator_Result.png" alt="BatchGenerator_Result" width="214" height="172" /></p>
<p>Nejprve jsme vytvořili dvě proměnné:</p>
<ul>
<li>@BatchSize je velikost jednotlivé dávky, tedy kolik řádků např. budeme později chtít odmazat v jednom kole</li>
<li>@BatchCount je počet dávek</li>
</ul>
<p>V našem příkladě definujeme 8 dávek po 50-ti členech, celkem tedy 400 jednotlivých členů.</p>
<p>Rekurzivní CTE funguje tak, že nejprve nadefinujeme tzv. kotvu:</p>
<pre class="lang:tsql decode:true">SELECT 1 BatchId, 1 BatchStart, @BatchSize BatchEnd</pre>
<p>a k ní postupně pomocí UNION ALL přidáváme řádky, které získáváme rekurzivním voláním již samotné tabulky <em>BatchList</em>:</p>
<pre class="lang:tsql mark:2 decode:true">SELECT BatchId + 1, @BatchSize + BatchStart, @BatchSize + BatchEnd
FROM BatchList
WHERE BatchId &lt;= @BatchCount - 1</pre>
<p>Důležité je nezapomenout připojit k dotazu, který nám vrací data z CTE <em>BatchList,</em> tabulkový hint MAXREXURSION 0, specifikující, kolikrát SQL Server provede rekurzivní operaci, než dojde k jejímu přerušení. MAXRECURSION může nabývat hodnot 0 až 32767, kde 0 znamená neomezený počet cyklů. Pokud nastavení MAXRECURSION neprovedeme, použije se výchozí hodnota 100 a po tomto počty cyklů SQL Server transakci přeruší s chybou a provede její rollback.</p>
<h2>Praktická aplikace: Vymazání velké tabulky</h2>
<p>Nyní si ukážeme, jak můžeme náš batch generátor využít pro smazání dat z velké tabulky po částech a vyhnout se tím možným problém zmíněným výše. Nejprve si vytvoříme testovací tabulku dbo.SampleData, kterou naplníme 100369 řádky. K naplnění využijeme CROSS JOIN mezi systémovými tabulkami <em>spt_values</em> master databáze:</p>
<pre class="lang:tsql decode:true">CREATE TABLE dbo.SampleData
( ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	  Value VARCHAR(100)
)
GO

INSERT INTO dbo.SampleData
	(Value)
	SELECT TOP (100399)
		s1.name + '_' + s2.name
    FROM master..spt_values s1
		CROSS JOIN master..spt_values s2
GO

SELECT COUNT(*) FROM dbo.SampleData
GO</pre>
<p>Kontrolní COUNT(*) na v posledním příkazu nám musí vrátit očekávaný počet řádků.</p>
<p>Nyní rozšíříme batch generátor tak, aby si sám zjistil z tabulky dbo.SampleData, kolik řádků obsahuje, připravil dávky pro mazání o velikosti 30.000 řádků. V následujícím WHILE cyklu provedeme iteraci připravených dávek v tabulce @Batches a samotné mazání.</p>
<pre class="lang:tsql decode:true">SET NOCOUNT ON

DECLARE @BatchSize INT
DECLARE @BatchCount INT
DECLARE @BatchId INT
DECLARE @BatchStart INT
DECLARE @BatchEnd INT
DECLARE @Batches TABLE (BatchId INT NOT NULL PRIMARY KEY,
						BatchStart INT NOT NULL,
						BatchEnd INT NOT NULL)

SET @BatchSize = 30000
SET @BatchCount = (SELECT COUNT(*) / @BatchSize + CASE WHEN COUNT(*) % @BatchSize &gt; 0 THEN 1 ELSE 0 END FROM dbo.SampleData)

;WITH Batchlist AS
(
    SELECT 1 BatchId, 1 BatchStart, @BatchSize BatchEnd
    UNION ALL
    SELECT BatchId + 1, @BatchSize + BatchStart, @BatchSize + BatchEnd
    FROM BatchList
    WHERE BatchId &lt;= @BatchCount-1
)
INSERT INTO @Batches
	(BatchId, BatchStart, BatchEnd)
	SELECT 
		BatchId, BatchStart, BatchEnd
	FROM BatchList
	OPTION (MAXRECURSION 0)

WHILE EXISTS (SELECT * FROM @Batches)
BEGIN

	SELECT TOP(1)
		@BatchId = BatchId,
		@BatchStart = BatchStart,
		@BatchEnd = BatchEnd
	FROM @Batches
	ORDER BY BatchId

	PRINT CAST(@BatchId AS VARCHAR(10)) + ' =&gt; ' + CAST(@BatchStart AS VARCHAR(10)) + ' - ' + CAST(@BatchEnd AS VARCHAR(10))

	DELETE FROM dbo.SampleData WHERE ID BETWEEN @BatchStart AND @BatchEnd

	PRINT CAST(@@ROWCOUNT AS VARCHAR(10))
	
	DELETE FROM @Batches WHERE BatchId = @BatchId

END
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1280" src="https://sqlpowered.com/wp-content/uploads/2015/07/BatchGenerator_DeleteMsg.png" alt="BatchGenerator_DeleteMsg" width="167" height="146" /></p>
<p>Ze stavových zpráv vidíme, že došlo ke smazání všech řádků z tabulek. Generátor správně vytvořil i čtvrtou dávku, která pokryje zbývající řádky po smazání předchozích 90 tisíc řádků.</p>
<p>V našem ideálním testu jsme prováděli vyhledání řádků ke smazání pomocí podmínky, která předpokládá, že sloupec ID v tabulce dbo.SampleData obsahuje unikátní hodnoty číslované od 1 a nejvyšší hodnota je shodná s počtem řádků v tabulce:</p>
<pre class="lang:tsql decode:true">DELETE FROM dbo.SampleData WHERE ID BETWEEN @BatchStart AND @BatchEnd</pre>
<p>Podobný scénář není vždy možný a mnohem jednodušší může být odmazávat data z tabulky vždy po dávce, kdy řádky ke smazání nebudeme vybírat  pomocí selektivní podmínky, ale využijeme klauzuli TOP(<em>počet_řádků</em>), která vždy smaže daný <em>počet_řádků</em>. Pokud nebudeme provádět explitní řazení řádků ke smazání pomocí ORDER BY, můžeme dosáhnout výrazně větší rychlosti a snížit využití systémových prostředků, protože SQL Server nebude nucen vykonávat SORT operaci v exekučním plánu, která patří k těm nejnákladnějším.</p>
<p>Nyní zjednodušíme celý batch generátor takto:</p>
<pre class="lang:tsql decode:true">DECLARE @BatchSize INT
DECLARE @BatchId INT
DECLARE @TotalRows INT
DECLARE @RowsToDelete INT
DECLARE @Batches TABLE (BatchId INT NOT NULL PRIMARY KEY,
			RowsToDelete INT NOT NULL)

SET @BatchSize = 30000
SET @RowsToDelete = (SELECT COUNT(*) FROM dbo.SampleData)

;WITH Batchlist AS
(
    SELECT 1 BatchId
    UNION ALL
    SELECT BatchId + 1
    FROM BatchList
    WHERE BatchId &lt; ( @RowsToDelete / @BatchSize + CASE WHEN @RowsToDelete % @BatchSize &gt; 0 THEN 1 ELSE 0 END )
)
INSERT INTO @Batches
	(BatchId, RowsToDelete)
	SELECT 
		BatchId, CASE
			    WHEN (BatchId * @BatchSize) &lt;= @RowsToDelete THEN @BatchSize
			    ELSE @RowsToDelete % @BatchSize
			 END
	FROM BatchList
	OPTION (MAXRECURSION 0)

SELECT * FROM @Batches
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1283" src="https://sqlpowered.com/wp-content/uploads/2015/07/BatchGenerator_RowsToDelete.png" alt="BatchGenerator_RowsToDelete" width="174" height="96" /></p>
<p>Generátor vygeneroval opět 4 dávky, ale místo rozmezí od &#8211; do určité hodnoty máme nyní ve sloupci RowsToDelete počet řádků, které se mají z tabulky smazat s využitím TOP() klauzule. Naše podmínka, určující data ke smazání v jednotlivé dávce, bude nově vypadat takto:</p>
<pre class="lang:tsql decode:true ">DELETE TOP(@RowsToDelete) FROM dbo.SampleData</pre>
<p>A celý skript včetně WHILE cyklu takto:</p>
<pre class="lang:tsql decode:true">DECLARE @BatchSize INT
DECLARE @BatchId INT
DECLARE @TotalRows INT
DECLARE @RowsToDelete INT
DECLARE @Batches TABLE (BatchId INT NOT NULL PRIMARY KEY,
						RowsToDelete INT NOT NULL)

SET @BatchSize = 30000
SET @RowsToDelete = (SELECT COUNT(*) FROM dbo.SampleData)

;WITH Batchlist AS
(
    SELECT 1 BatchId
    UNION ALL
    SELECT BatchId + 1
    FROM BatchList
    WHERE BatchId &lt; ( @RowsToDelete / @BatchSize + CASE WHEN @RowsToDelete % @BatchSize &gt; 0 THEN 1 ELSE 0 END )
)
INSERT INTO @Batches
	(BatchId, RowsToDelete)
	SELECT 
		BatchId, CASE
					WHEN (BatchId * @BatchSize) &lt;= @RowsToDelete THEN @BatchSize
					ELSE @RowsToDelete % @BatchSize
				 END
	FROM BatchList
	OPTION (MAXRECURSION 0)

WHILE EXISTS (SELECT * FROM @Batches)
BEGIN

	SELECT TOP(1)
		@BatchId = BatchId,
		@RowsToDelete = RowsToDelete
	FROM @Batches
	ORDER BY BatchId

	PRINT CAST(@BatchId AS VARCHAR(10))
	
	DELETE TOP(@RowsToDelete) FROM dbo.SampleData

	PRINT CAST(@@ROWCOUNT AS VARCHAR(10))
	
	DELETE FROM @Batches WHERE BatchId = @BatchId

END
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1285" src="https://sqlpowered.com/wp-content/uploads/2015/07/BatchGenerator_RowsDelete.png" alt="BatchGenerator_RowsDelete" width="84" height="146" /></p>
<p>Všechny řádky z tabulky byly podle očekávání smazány.</p>
<p>První způsob generování dávek se hodí spíše pro speciální případy, kdy potřebujeme mazat data podle určitého rozsahu hodnot, např. objednávky podle data. Druhý způsob je univerzální a ideální pro smazání všech dat v tabulce.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/jednoduchy-batch-generator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Výpis adresářů z T-SQL</title>
		<link>https://sqlpowered.com/vypis-adresaru-z-t-sql/</link>
					<comments>https://sqlpowered.com/vypis-adresaru-z-t-sql/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Mon, 01 Jun 2015 06:23:08 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=678</guid>

					<description><![CDATA[Pokud potřebujeme vypsat v T-SQL seznam adresářů, případně souborů na disku, máme tyto základní možnosti: extended proceduru xp_dirtree, DOS příkazy pomocí xp_cmdshell, CLR nebo OLE (OAAutomation objekty). Dnes se povídáme na možnosti, které nám poskytuje xp_dirtree a xp_cmdshell. Především je nutné zmínit, že xp_dirtree je nedokumentovaná a nepodporovaná procedura a...]]></description>
										<content:encoded><![CDATA[<p>Pokud potřebujeme vypsat v T-SQL seznam adresářů, případně souborů na disku, máme tyto základní možnosti: extended proceduru xp_dirtree, DOS příkazy pomocí xp_cmdshell, CLR nebo OLE (OAAutomation objekty). Dnes se povídáme na možnosti, které nám poskytuje xp_dirtree a xp_cmdshell.<span id="more-678"></span></p>
<p>Především je nutné zmínit, že xp_dirtree je nedokumentovaná a nepodporovaná procedura a její použití může přinést později problémy v případě upgrade SQL Serveru na vyšší verzi.</p>
<p>xp_dirtree má tyto vstupní parametry:</p>
<ul>
<li>Cesta na úložišti, kde chceme zahájit procházení případně vypsat obsah.</li>
<li>Hloubka, do které se mají složky procházet. Ve výchozím nastavení 0 &#8211; bez omezení.</li>
<li>Nastavení, zda zobrazovat pouze složky nebo i soubory. Ve výchozím nastavení 0 &#8211; nezobrazovat soubory.</li>
</ul>
<p>Nyní praktický příklad: necháme si vypsat kompletní obsah adresáře &#8220;c:\Program Files\Microsoft SQL Server\120\SDK&#8221;. Chceme vidět všechny podložky do omezené hloubky a také nás zajímají soubory ve složkách. Výstup procedury uložíme do proměnné @DirList pro případné pozdější použití.</p>
<pre class="lang:tsql decode:true">DECLARE @DirList TABLE ( DirName VARCHAR(128), Depth INT, IsFile BIT )

INSERT @DirList
 EXEC master..xp_dirtree 'c:\Program Files\Microsoft SQL Server\120\SDK',0, 1
 
SELECT * FROM @DirList 
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1394" src="https://sqlpowered.com/wp-content/uploads/2015/06/xp_diftree_ListingFiles.png" alt="xp_diftree_ListingFiles" width="363" height="172" /></p>
<h4>xp_cmdshell</h4>
<p>Druhou možnost nám přináší <a href="http://msdn.microsoft.com/en-us/library/ms175046.aspx">xp_cmdshell</a>, která je na rozdíl od xp_dirtree plně dokumentovaná a podporovaná. Pomocí této procedury můžeme spouštět libovolný kód pro příkazovou řádku, což ovšem není jen výhoda, ale také značné bezpečností riziko. Právě z toho důvodu je procedura ve výchozím stavu zakázána a je potřeba ji povolit pomocí konfigurační procedury sp_configure:</p>
<pre class="lang:tsql decode:true ">EXEC [sys].[sp_configure] 'show advanced options', 1
GO
RECONFIGURE
GO
EXEC [sys].[sp_configure] 'xp_cmdshell', 1
GO
RECONFIGURE
GO</pre>
<p>Volání procedury je jednoduché a pro zobrazení obsahu adresáře použijeme běžné dosové příkazy:</p>
<pre class="lang:tsql decode:true ">EXEC master..xp_cmdshell 'DIR /b c:\Program Files\Microsoft SQL Server\120\'
GO</pre>
<p>Výstup z volání xp_cmdshell můžeme pochopitelně uložit pro pozdější zpracování obdobným způsobem jako v předchozím příkladu.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/vypis-adresaru-z-t-sql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spouštění skriptů ze souboru</title>
		<link>https://sqlpowered.com/spousteni-skriptu-ze-souboru/</link>
					<comments>https://sqlpowered.com/spousteni-skriptu-ze-souboru/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 22 May 2015 06:13:39 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=665</guid>

					<description><![CDATA[Spouštět sql skripty uložené například v adresáři na disku je poměrně častý úkol a my si ukážeme dva jednoduché způsoby jak na to: pomocí OPENROWSET() funkce a pomocí SQLCMD Mode nastavení Sql Server Management Studia. Nejprve si ale musíme vytvořit testovací soubor C:\Test\Test.sql, do kterého umístíme tento jednoduchý skript: SELECT...]]></description>
										<content:encoded><![CDATA[<p>Spouštět sql skripty uložené například v adresáři na disku je poměrně častý úkol a my si ukážeme dva jednoduché způsoby jak na to: pomocí OPENROWSET() funkce a pomocí SQLCMD Mode nastavení Sql Server Management Studia.<span id="more-665"></span></p>
<p>Nejprve si ale musíme vytvořit testovací soubor C:\Test\Test.sql, do kterého umístíme tento jednoduchý skript:</p>
<pre class="lang:tsql decode:true">SELECT 'HELLO WORLD' AS QueryResult</pre>
<p>Při dalším testování musíme mít na paměti, že naše testovací instance musí mít k adresáři a souboru přístup, tzn. účtu, pod kterým instance běží, musíme přidat alespoň Read přístup.</p>
<p><strong>1. OPENROWSET()</strong></p>
<p>Funkce OPENROWSET() představuje ad hoc OLE DB připojení k datovému zdroji, který může být i soubor v souborovém systému:</p>
<pre class="lang:tsql decode:true">DECLARE @Script VARCHAR(MAX)

SELECT
    @Script = f.BulkColumn
FROM OPENROWSET(BULK 'C:\Test\Test.sql', single_clob) f

EXEC (@Script)
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1856" src="https://sqlpowered.com/wp-content/uploads/2015/05/Run-Query-From-File-1.png" alt="Run-Query-From-File-1" width="129" height="39" /></p>
<p>Obsah souboru jsme načetli do proměnné @Script a vykonali jej pomocí EXEC() příkazu.</p>
<p><strong>2. SQLCMD Mode</strong></p>
<p>SQLCMD mode je nastavení Management Studia, které přepne aktuálně otevřené okno do režimu, ve kterém je možné pří používat příkazy, kterým rozumí konzolová aplikace sqlcmd.exe.</p>
<p>Přepnutí provedeme z nabídky <em>Query</em> -&gt; <em>SQLCMD Mode</em>:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1858" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode.png" alt="Management-Studio-SQLCMD-Mode" width="434" height="446" /></p>
<p>Spuštění našeho souboru provedeme použitím příkazu <strong>:r</strong> :</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1859" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script.png" alt="Management-Studio-SQLCMD-Mode-Run-Script" width="250" height="74" /></p>
<p>Pozor ovšem musíme dát na jednu podstatnou věc: Pokud budeme tímto způsobem pouštět více souborů v jedné dávce (batch), chová se SSMS na pozadí tak, že nejprve všechny souboru spojí do jedné dávky a teprve poté je odešle na server jako jeden batch. Abychom mohli uvedené chování demonstrovat, vytvoříme si dva soubory Test1.sql a Test2.sql, do kterých umístíme tyto skripty:</p>
<pre class="lang:tsql decode:true ">-- Soubor Test1.sql
SELECT 'HELLO WORLD Test1.sql' AS QueryResult

-- Soubor Test2.sql
SELECT 'HELLO WORLD Test2.sql' AS QueryResult</pre>
<p>Oba soubory spustíme ze SSMS:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1861" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple" width="251" height="91" /></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1862" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Result.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Result" width="175" height="84" /></p>
<p>To podstatné se ovšem odehrálo na pozadí, kdy jsme zachytili v Profileru následující batch:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1863" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Profiler.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Profiler" width="723" height="241" /></p>
<p>Vidíme, že SSMS nejprve spojilo oba soubory do jednoho a následně odeslalo na server jako jeden batch.</p>
<p>Čemu to vadí? Stačí si například vzpomenout na pravidlo, že proměnné musí být unikátní v rámci jedno batche.</p>
<p>Soubory upravíme takto:</p>
<pre class="lang:tsql decode:true">--Soubor Test1.sql
DECLARE @String NVARCHAR(200)
SET @String = 'HELLO WORLD Test1.sql'
PRINT @String

--Soubor Test2.sql
DECLARE @String NVARCHAR(200)
SET @String = 'HELLO WORLD Test2.sql'
PRINT @String</pre>
<p>A po spuštění ze SSMS dostáváme následující chybovou hlášku:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1864" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error" width="887" height="30" /></p>
<p>Odpověď je opět v Profileru:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1865" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error-Profiler.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error-Profiler" width="266" height="84" /></p>
<p>Obsah obou souborů dorazil jako jeden batch a proměnná @String je tak v jeho kontextu definována dvakrát. Řešení je nasnadě: důsledné použití GO, a to buď ve skriptech samotných:</p>
<pre class="lang:tsql mark:6,11 decode:true">--Soubor Test1.sql
DECLARE @String NVARCHAR(200)
SET @String = 'HELLO WORLD Test1.sql'
PRINT @String
GO

--Soubor Test2.sql
DECLARE @String NVARCHAR(200)
SET @String = 'HELLO WORLD Test2.sql'
PRINT @String
GO</pre>
<p>nebo v SSMS okně:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1866" src="https://sqlpowered.com/wp-content/uploads/2015/05/Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error-Fixed.png" alt="Management-Studio-SQLCMD-Mode-Run-Script-Multiple-Error-Fixed" width="250" height="105" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/spousteni-skriptu-ze-souboru/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Generování dat do HTML pomocí T-SQL</title>
		<link>https://sqlpowered.com/generovani-dat-do-html/</link>
					<comments>https://sqlpowered.com/generovani-dat-do-html/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 15 May 2015 06:08:53 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[development]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=658</guid>

					<description><![CDATA[V praxi se čas od času objeví požadavek, vygenerovat pomocí T-SQL jazyka výstupní data přímo ve formátu HTML, např. pro export do webové aplikace. Náramně užitečné to může být i v případě, pokud si necháváme přímo z SQL Serveru posílat různé emailové notifikace a rádi bychom je doplnili daty tak,...]]></description>
										<content:encoded><![CDATA[<p>V praxi se čas od času objeví požadavek, vygenerovat pomocí T-SQL jazyka výstupní data přímo ve formátu HTML, např. pro export do webové aplikace. Náramně užitečné to může být i v případě, pokud si necháváme přímo z SQL Serveru posílat různé emailové notifikace a rádi bychom je doplnili daty tak, že je emailový klient bude přímo zobrazovat v uživatelsky přívětivém formátu, v tomto případě v tabulce.</p>
<p><span id="more-658"></span></p>
<p>V následujícím skriptu si vše ukážeme na ukázkových datech, kterými bude seznam prvních 10ti tabulek z databáze msdb. Data si jednoduše vložíme do kurzoru a při každé jeho iteraci bude postupně přidávat jednotlivé tagy k proměnné @HTML:</p>
<pre class="lang:tsql decode:true ">USE msdb
GO

DECLARE @name NVARCHAR(128)
DECLARE @HTML NVARCHAR(MAX)
DECLARE @tr NVARCHAR(20)
DECLARE @_tr NVARCHAR(20)
DECLARE @td NVARCHAR(20)
DECLARE @_td NVARCHAR(20)

SET @tr = '   &lt;tr&gt;';
    SET @td = '      &lt;td&gt;'
    SET @_td = '&lt;/td&gt;'
SET @_tr = '   &lt;/tr&gt;';

SET @HTML = '&lt;table border = 1&gt;' + CHAR(13)

DECLARE cur CURSOR FAST_FORWARD FOR
    SELECT TOP 10 name
    FROM sys.tables
    ORDER BY name

OPEN cur
FETCH NEXT FROM cur INTO @name
WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @HTML = @HTML + @tr + CHAR(13) + 
                    @td + ISNULL(@name, 'NULL') + @_td + CHAR(13) +
                    @_tr + CHAR(13)                            
    
        FETCH NEXT FROM cur INTO @name
    END
CLOSE cur
DEALLOCATE cur

SET @HTML = SUBSTRING(@HTML, 0, LEN(@HTML) - 1) + '&gt;' + CHAR(13) + '&lt;/table&gt;'

PRINT @HTML
GO</pre>
<p>Výstup v Management Studiu vypadá takto:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2735" src="https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1.png" alt="" width="297" height="466" srcset="https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1.png 297w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-64x100.png 64w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-191x300.png 191w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-102x160.png 102w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-204x320.png 204w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-156x245.png 156w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-217x340.png 217w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_1-287x450.png 287w" sizes="auto, (max-width: 297px) 100vw, 297px" /></p>
<p>Pokud si data zkopírujeme a uložíme je do souboru v příponou *.html, v prohlížeči uvidíme tuto jednoduchou tabulku (a stejně tak v emailovém klientu, který podporuje zobrazení HTML):</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2736" src="https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2.png" alt="" width="283" height="324" srcset="https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2.png 283w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2-87x100.png 87w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2-262x300.png 262w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2-140x160.png 140w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2-280x320.png 280w, https://sqlpowered.com/wp-content/uploads/2015/05/T-SQL-to-HTML_2-214x245.png 214w" sizes="auto, (max-width: 283px) 100vw, 283px" /></p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/generovani-dat-do-html/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
