<?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>DML &#8211; SQLpowered.com</title>
	<atom:link href="https://sqlpowered.com/tag/dml/feed/" rel="self" type="application/rss+xml" />
	<link>https://sqlpowered.com</link>
	<description>SQL Server + BI</description>
	<lastBuildDate>Tue, 28 Dec 2021 21:09: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>DML &#8211; SQLpowered.com</title>
	<link>https://sqlpowered.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>DBCC CHECKIDENT RESEED behaves differently when TRUNCATE or DELETE was used on the table</title>
		<link>https://sqlpowered.com/dbcc-checkident-reseed-behaves-differently-when-truncate-or-delete-was-used-on-the-table/</link>
					<comments>https://sqlpowered.com/dbcc-checkident-reseed-behaves-differently-when-truncate-or-delete-was-used-on-the-table/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Tue, 28 Dec 2021 20:41:41 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[DML]]></category>
		<category><![CDATA[tables]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5202</guid>

					<description><![CDATA[Let&#8217;s assume we have some script where tables are filled with data and then DELETED/TRUNCATED again to rerun the script. Some of these tables are large and they don&#8217;t have a foreign key reference so we will decide to use TRUNCATE to remove the data. For tables referenced by foreign...]]></description>
										<content:encoded><![CDATA[<p>Let&#8217;s assume we have some script where tables are filled with data and then DELETED/TRUNCATED again to rerun the script. Some of these tables are large and they don&#8217;t have a foreign key reference so we will decide to use TRUNCATE to remove the data. For tables referenced by foreign keys, we will use DELETE instead (because TRUNCATE can&#8217;t be executed on tables referenced in foreign keys). All the tables are using IDENTITY(1,1) property and we will use DBCC CHECKIDENT to RESEED identity values to be starting again from 1. But what is our surprise if we will discover later, that for some tables the first identity value is 1 and for other tables it&#8217;s starting from 2?</p>
<p>Let&#8217;s demonstrate it.</p>
<p>We will create two tables and fill in some data. These tables are absolutely the same including the IDENTITY specification. The only difference is their name.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">DROP TABLE IF EXISTS [dbo].[To_Be_Deleted]
DROP TABLE IF EXISTS [dbo].[To_Be_Truncated]
GO

CREATE TABLE [dbo].[To_Be_Deleted] (
	Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY
)
GO
CREATE TABLE [dbo].[To_Be_Truncated] (
	Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY
)
GO

INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
GO
INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
GO

SELECT 'To_Be_Deleted', * FROM [dbo].[To_Be_Deleted] UNION ALL
SELECT 'To_Be_Truncated', * FROM [dbo].[To_Be_Truncated]
GO</pre>
<p><img decoding="async" class="alignnone wp-image-5273" src="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1.png" alt="" width="189" height="113" srcset="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1.png 333w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1-300x179.png 300w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1-150x90.png 150w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1-160x96.png 160w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_1-320x191.png 320w" sizes="(max-width: 189px) 100vw, 189px" /></p>
<p>We will also check the current identity value after data is inserted. It&#8217;s the same.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SELECT 'To_Be_Deleted',	'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Deleted]') UNION ALL
SELECT 'To_Be_Truncated', 'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Truncated]')
GO</pre>
<p><img decoding="async" class="alignnone wp-image-5274" src="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2.png" alt="" width="418" height="38" srcset="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2.png 737w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-300x27.png 300w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-150x14.png 150w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-360x33.png 360w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-160x15.png 160w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-320x29.png 320w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-520x47.png 520w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_2-720x65.png 720w" sizes="(max-width: 418px) 100vw, 418px" /></p>
<p>Now is time to remove data from our tables. There is a difference coming: We will empty the first table via the DELETE command and the second one will be emptied using the TRUNCATE command.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">DELETE FROM [dbo].[To_Be_Deleted]
TRUNCATE TABLE [dbo].[To_Be_Truncated]
GO

DBCC CHECKIDENT('[dbo].[To_Be_Deleted]', RESEED, 1)
DBCC CHECKIDENT('[dbo].[To_Be_Truncated]', RESEED, 1)
GO

SELECT 'To_Be_Deleted',	'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Deleted]') UNION ALL
SELECT 'To_Be_Truncated', 'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Truncated]')
GO</pre>
<p>We will check the current identity value now: It&#8217;s the same.</p>
<p><img decoding="async" class="alignnone wp-image-5275" src="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3.png" alt="" width="407" height="37" srcset="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3.png 737w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-300x27.png 300w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-150x14.png 150w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-360x33.png 360w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-160x15.png 160w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-320x29.png 320w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-520x47.png 520w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_3-720x65.png 720w" sizes="(max-width: 407px) 100vw, 407px" /></p>
<p>Next, we will again insert some data into our tables. Exactly as before.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Deleted] DEFAULT VALUES
GO

INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
INSERT INTO [dbo].[To_Be_Truncated] DEFAULT VALUES
GO

SELECT 'To_Be_Deleted', * FROM [dbo].[To_Be_Deleted] UNION ALL
SELECT 'To_Be_Truncated', * FROM [dbo].[To_Be_Truncated]
GO

SELECT 'To_Be_Deleted', 'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Deleted]') UNION ALL
SELECT 'To_Be_Truncated', 'IDENT_CURRENT', IDENT_CURRENT('[dbo].[To_Be_Truncated]')
GO</pre>
<p>If we will check data in both tables, there is the surprise immediately visible:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5276" src="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_4.png" alt="" width="162" height="122" srcset="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_4.png 264w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_4-133x100.png 133w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_4-160x121.png 160w" sizes="auto, (max-width: 162px) 100vw, 162px" /></p>
<p>The table which was emptied using the DELETE command has the first identity value 2 whereas the second table emptied using the TRUNCATE command has the first identity value 1!</p>
<p>This difference is also approved via the IDENT_CURRENT() function.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5277" src="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5.png" alt="" width="363" height="33" srcset="https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5.png 737w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-300x27.png 300w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-150x14.png 150w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-360x33.png 360w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-160x15.png 160w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-320x29.png 320w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-520x47.png 520w, https://sqlpowered.com/wp-content/uploads/2021/12/DBCC_CHECKIDENT_RESEED_Truncate_Delete_5-720x65.png 720w" sizes="auto, (max-width: 363px) 100vw, 363px" /></p>
<p>I know that this behavior isn&#8217;t too intuitive at first look. But what is positive (only:) about it is that it&#8217;s properly documented at the right place in the <a href="https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-checkident-transact-sql?view=sql-server-ver15" target="_blank" rel="noopener">DBCC CHECKIDENT()</a> command description:</p>
<p><em>The current identity value is set to the new_reseed_value. If no rows have been inserted into the table since the table was created, or if all rows have been removed by using the <strong>TRUNCATE TABLE</strong> statement, the first row inserted after you run DBCC CHECKIDENT uses new_reseed_value as the identity. If rows are present in the table, or if all rows have been removed by using the <strong>DELETE</strong> statement, the next row inserted uses new_reseed_value + the current increment value.</em></p>
<p>To be honest, seniors should know, for sure. But juniors are spending hours investigating this based on my experience. There is a good comparison of DELETE vs TRUNCATE on <a href="https://www.sqlshack.com/difference-between-sql-truncate-and-sql-delete-statements-in-sql-server/" target="_blank" rel="noopener">sqlhack.com</a>, but still, the RESEED issue isn&#8217;t clearly explained there.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/dbcc-checkident-reseed-behaves-differently-when-truncate-or-delete-was-used-on-the-table/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Paralelní INSERT… SELECT</title>
		<link>https://sqlpowered.com/paralelni-insert-select/</link>
					<comments>https://sqlpowered.com/paralelni-insert-select/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 30 Oct 2016 22:23:54 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=2232</guid>

					<description><![CDATA[SQL Server 2016 přináší nově paralelní vykonání INSERT&#8230;SELECT kombinace, které za určitých okolností představuje výrazné (10x a více) zlepšení výkonu pro některé typické ETL scénáře. Aby mohl být použit paralelní operátor, musí být splněny především dvě podmínky: level kompatibily databáze musí být nastaven na 130 a je nutné použít TABLOCK...]]></description>
										<content:encoded><![CDATA[<p>SQL Server 2016 přináší nově paralelní vykonání INSERT&#8230;SELECT kombinace, které za určitých okolností představuje výrazné (10x a více) zlepšení výkonu pro některé typické ETL scénáře. Aby mohl být použit paralelní operátor, musí být splněny především dvě podmínky:</p>
<ol>
<li>level kompatibily databáze musí být nastaven na 130 a</li>
<li>je nutné použít TABLOCK hint pro cílovou tabulku.</li>
</ol>
<p>Zda byl skutečně použit paralelní operátor zjistíme jednoduše z exekučního plánu.</p>
<p>Bohužel jako obvykle existuje několik výrazných omezení, která nám prvotní radost mohou zkazit. Paralelní exekuce se nedočkáme pokud:</p>
<ul>
<li>při insertu do row store cílová tabulka obsahuje clusterovaný nebo alespoň jeden neclusterovaný index</li>
<li>tabulka má IDENTITY sloupec nebo využívá sekvence</li>
</ul>
<p>Zvláštní pravidla platí v případě, využíváme-li jako cíl (INSERT) nebo zdroj (SELECT) column store úložiště.</p>
<p><strong>Detaily</strong>:</p>
<ul>
<li><a href="https://blogs.msdn.microsoft.com/sqlcat/2016/07/06/sqlsweet16-episode-3-parallel-insert-select/">https://blogs.msdn.microsoft.com/sqlcat/2016/07/06/sqlsweet16-episode-3-parallel-insert-select/</a></li>
<li><a href="https://blogs.msdn.microsoft.com/sqlcat/2016/07/21/real-world-parallel-insert-what-else-you-need-to-know/">https://blogs.msdn.microsoft.com/sqlcat/2016/07/21/real-world-parallel-insert-what-else-you-need-to-know/</a></li>
</ul>
<p><strong>Poznámka:</strong></p>
<p>Nezaměňovat INSERT&#8230;INTO se SELECT&#8230;INTO, který podporuje paralelní exekuční plány již od SQL Serveru 2014 (compatibility 110 a vyšší).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/paralelni-insert-select/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Column Aliases are Fully Ignored in INSERT Columns List</title>
		<link>https://sqlpowered.com/column-aliases-are-fully-ignored-in-insert-columns-list/</link>
					<comments>https://sqlpowered.com/column-aliases-are-fully-ignored-in-insert-columns-list/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 28 Oct 2016 08:37:02 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=2136</guid>

					<description><![CDATA[Time to time crazy things are happening in SQL Server. T-SQL looks like to be easy and rigid language without any options for playing with it like other languages. I will show you that there is still some space where you can do magic and have some fun. Check out...]]></description>
										<content:encoded><![CDATA[<p>Time to time crazy things are happening in SQL Server. T-SQL looks like to be easy and rigid language without any options for playing with it like other languages. I will show you that there is still some space where you can do magic and have some fun. Check out this sample query a test on our own. Yes, it will really execute successfully:).</p>
<pre class="lang:tsql mark:6,7 decode:true">CREATE TABLE [dbo].[SampleTable]
	([ID] INT, 
	 [Value] NVARCHAR(100))

INSERT INTO [dbo].[Sampletable]
	( ..[Happy]..[New]...[ID], 
	  Another.[Ignored]..Alias.Value )
	SELECT  1, 'Value1'
GO</pre>
<p>How that works? Column aliases are ignored in INSERT INTO command so that we can place any funny string there. I think that an error should be raised rather because this lazy behavior can lead to unwanted confusion.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/column-aliases-are-fully-ignored-in-insert-columns-list/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to override Table Value Constructor limit of maximum 1000 rows in direct INSERT statement</title>
		<link>https://sqlpowered.com/how-to-override-table-value-constructor-limit-of-maximum-1000-rows-in-direct-insert-statement/</link>
					<comments>https://sqlpowered.com/how-to-override-table-value-constructor-limit-of-maximum-1000-rows-in-direct-insert-statement/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 15 Apr 2016 19:08:10 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=1403</guid>

					<description><![CDATA[Regarding MSDN documentation there is a limit that maximum 1000 rows can be constructed using Row Value Constructor when used directly in the INSERT statement. If this limit is reached, error 10738 is generated. There is a simple workaround and we will observe it in detail in this article. First...]]></description>
										<content:encoded><![CDATA[<p>Regarding <a href="http://msdn.microsoft.com/en-us/library/dd776382.aspx">MSDN documentation</a> there is a limit that maximum 1000 rows can be constructed using Row Value Constructor when used directly in the INSERT statement. If this limit is reached, error 10738 is generated. There is a simple workaround and we will observe it in detail in this article.<span id="more-1403"></span></p>
<p>First of all, we will try to check if this limit is really presented regarding the documentation. We will create TestTable and then dynamically create an insert statement with 1005 rows constructed using the VALUES() row constructor. Our final statement generated is visible below in the picture.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">CREATE TABLE TestTable ( Value NVARCHAR(100))
GO

DECLARE @Numbers TABLE (ID INT NOT NULL PRIMARY KEY)

INSERT INTO @Numbers
	SELECT TOP(1005) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM [master].[dbo].[spt_values]

DECLARE @Stmt NVARCHAR(MAX) = ''

SELECT @Stmt = @Stmt + '(''' + CAST(ID AS VARCHAR(10)) + '''),'
FROM @Numbers

SET @Stmt = 'INSERT INTO TestTable VALUES ' + LEFT(@Stmt, LEN(@Stmt)-1) + ''

PRINT @Stmt

EXECUTE(@Stmt)
GO</pre>
<p>After we have executed our @Stmt, the Messages window in Management Studio shows this output:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1408" src="https://sqlpowered.com/wp-content/uploads/2016/04/VALUES-1000-rows-limit-1.png" alt="VALUES-1000-rows-limit-1" width="832" height="42" /></p>
<p>As expected, error 10378 was generated clearly declaring, that we simply can´t ignore the 1000 rows limit for the row constructor.</p>
<p>Luckily there is a simple solution how to skip this limitation: The only thing we need to do is to move the row  constructor from direct INSERT to derived table as is visible on the marked line in this script:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">DECLARE @Numbers TABLE (ID INT NOT NULL PRIMARY KEY)

INSERT INTO @Numbers
	SELECT TOP(1005) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM [master].[dbo].[spt_values]

DECLARE @Stmt NVARCHAR(MAX) = ''

SELECT @Stmt = @Stmt + '(''' + CAST(ID AS VARCHAR(10)) + '''),'
FROM @Numbers

SET @Stmt = 'INSERT INTO TestTable SELECT * FROM (VALUES ' + LEFT(@Stmt, LEN(@Stmt)-1) + ') a (Col1)'

PRINT @Stmt

EXECUTE(@Stmt)
GO</pre>
<p>Rows in TestTable aren´t inserted directly, but with help of FROM(derived table) trick. There is the final result message:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1409" src="https://sqlpowered.com/wp-content/uploads/2016/04/VALUES-1000-rows-limit-2.png" alt="VALUES-1000-rows-limit-2" width="604" height="49" /></p>
<p>Table Row Value constructor is really powerful construct in T-SQL and in <a href="https://sqlpowered.com/table-value-constructor/">the next article</a> we will show other fun we can have with it.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/how-to-override-table-value-constructor-limit-of-maximum-1000-rows-in-direct-insert-statement/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Mazání dat pomocí sp_MSForEachTable</title>
		<link>https://sqlpowered.com/mazani-dat-pomoci-sp_msforeachtable/</link>
					<comments>https://sqlpowered.com/mazani-dat-pomoci-sp_msforeachtable/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 05 Jun 2015 06:48:04 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=682</guid>

					<description><![CDATA[Pomocí nedokumentované procedury sp_MSForEachTable můžeme spouštět dotazy a operace nad všemi tabulkami v databázi. Že je procedura nedokumentovaná znamená, že není zmíněna v SQL Server dokumentaci (BOL) a je oficiálně určena pouze k internímu použití. Na druhou stranu je procedura používání již spoustu let i běžnými uživateli SQL Serveru a...]]></description>
										<content:encoded><![CDATA[<p>Pomocí nedokumentované procedury <em>sp_MSForEachTable </em>můžeme spouštět dotazy a operace nad všemi tabulkami v databázi. Že je procedura nedokumentovaná znamená, že není zmíněna v SQL Server dokumentaci (BOL) a je oficiálně určena pouze k internímu použití. Na druhou stranu je procedura používání již spoustu let i běžnými uživateli SQL Serveru a je možné ji efektivně použít tam, kde bychom jinak museli psát kód s využitím WHILE cyklu nebo kurzorů. Její použití si ukážeme na jednoduchém scénáři: smazání dat ze všech tabulek v databázi.<span id="more-682"></span>Nejprve se podívejme na samotný skript:</p>
<pre class="lang:tsql decode:true">EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
GO

EXEC sp_MSForEachTable '
    IF OBJECTPROPERTY(object_id(''?''), ''TableHasForeignRef'') = 1
        DELETE FROM ?
    ELSE
        TRUNCATE TABLE ?'
GO

EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
GO

EXEC sp_MSForEachTable 'SELECT * FROM ?'
GO</pre>
<p>Skript si umí poradit s mazáním i v případě, že databáze obsahuje cizí klíče, které za normálních okolností brání smazání dat z tabulky, pokud jsou použita jako cizí klíč v jiné tabulce. V prvním kroku se proto volá <em>sp_MSForEachTable</em> s ALTER TABLE NO CHECK CONSTRAINT ALL<em>,</em> který zakáže všechna omezení na tabulkou, pro kterou je volán. Jméno tabulky do skriptu doplňuje automaticky právě <em>sp_MSForEachTable,</em> a to do místa, kde je vložen ?. Po smazání se obdobným způsobem omezení opět povolí.</p>
<p>Samotné mazání lze provést dvěma způsoby: DELETE nebo TRUNCATE. Pro TRUNCATE platí omezení, že tento příkaz nelze použít pro tabulky, které mají cizí klíče, a to ani v případě, že jsme cizí klíče zakázali, protože jejich definice stále zůstává uložena v metadatech. TRUNCATE je samozřejmě výrazně rychlejší a byla by škoda si vystačit jen s DELETE. Proto musíme zjistit, jestli právě mazaná tabulka cizí klíče obsahuje či nikoliv a podle toho rozhodnout, zda se použije DELETE nebo TRUNCATE. Pomocí OBJECTPROPERTY() zjistíme nastavení vlastnosti  <em>TableHasForeignRef,</em> a pokud je 1, zavoláme DELETE, pokud 0, pak TRUNCATE.</p>
<p>V posledním kroku použijeme <em>sp_MSForEachTable</em> ještě jednou pro kontrolu, že v žádné tabulce nezůstaly nějaké řádky.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/mazani-dat-pomoci-sp_msforeachtable/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OUTPUT</title>
		<link>https://sqlpowered.com/output/</link>
					<comments>https://sqlpowered.com/output/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Wed, 29 Apr 2015 20:45:37 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=620</guid>

					<description><![CDATA[OUTPUT klauzule přišla do SQL Serveru již v edici 2008, ale stále je to výrazně nedoceněná funkcionalita a spousta vývojářů raději nejprve vloží data do tabulky a pak se teprve pomocí WHERE snaží přesvědčit, že se vše povedlo. Proto jsem pro Vás připravil krátký přehled všech vychytávek, které s OUTPUT...]]></description>
										<content:encoded><![CDATA[<p>OUTPUT klauzule přišla do SQL Serveru již v edici 2008, ale stále je to výrazně nedoceněná funkcionalita a spousta vývojářů raději nejprve vloží data do tabulky a pak se teprve pomocí WHERE snaží přesvědčit, že se vše povedlo. Proto jsem pro Vás připravil krátký přehled všech vychytávek, které s OUTPUT klauzulí můžeme dělat pro základní DML operace.</p>
<p>Zopakujme si ale nejprve základní fakta o použití OUTPUT:</p>
<ul>
<li>vrací data pro každý řádek, který byl ovlivněn některým z příkazů INSERT, UPDATE, DELETE nebo MERGE (DML operace)</li>
<li>výstup je možné vrátit rovnou jako data klientovi nebo uložit do tabulky, temporární tabulky nebo table variable</li>
<li>OUTPUT je možné použít i jako vnořenou část všech čtyř DML příkazů</li>
<li>s výstupními daty lze přímo pracovat pomocí nejrůznějších výrazů</li>
</ul>
<p>Příklady jsou zpracovány co nejjednodušší formou pro rychlé použíti metodou copy &amp; paste.</p>
<p><strong>INSERT</strong>:</p>
<pre class="lang:tsql decode:true">CREATE TABLE dbo.SampleTable
(Id INT IDENTITY(1,1) PRIMARY KEY,
 Name VARCHAR(50)
)
GO

-- OUTPUT from INSERT into @TableVariable

DECLARE @InsertedRows TABLE (RowId INT)

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.Id
        INTO @InsertedRows
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')

SELECT * FROM @InsertedRows
GO


-- OUTPUT from INSERT into ##TemporaryTable

CREATE TABLE ##tmp (RowId INT)

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.Id
        INTO ##tmp
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')

SELECT * FROM ##tmp
GO

-- OUTPUT from INSERT into UserTable

CREATE TABLE UserTable (RowId INT)
GO

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.Id
        INTO UserTable
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')

SELECT * FROM UserTable
GO

-- OUTPUT from INSERT with *

DECLARE @InsertedRows TABLE (RowId INT, Name VARCHAR(50))

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.*
        INTO @InsertedRows
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')

SELECT * FROM @InsertedRows
GO

-- OUTPUT from multiple INSERTs

DECLARE @InsertedRows TABLE (RowId INT, Name VARCHAR(50))

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.*
        INTO @InsertedRows
VALUES
    ('Name1')

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.*
        INTO @InsertedRows
VALUES
    ('Name2')

SELECT * FROM @InsertedRows
GO

-- OUTPUT from INSERT from SELECT

DECLARE @InsertedRows TABLE (RowId INT, Name VARCHAR(50))

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.*
        INTO @InsertedRows
SELECT 'Name1' 

SELECT * FROM @InsertedRows
GO

-- OUTPUT from INSERT with column names on output table

DECLARE @InsertedRows TABLE (RowId INT, Name VARCHAR(50))

INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.Id
        INTO @InsertedRows (RowId)
SELECT 'Name1' 

SELECT * FROM @InsertedRows
GO

-- OUTPUT from INSERT with SELECT output values only
INSERT INTO dbo.SampleTable
    OUTPUT INSERTED.*
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')</pre>
<p><strong>UPDATE:</strong></p>
<pre class="lang:tsql decode:true">CREATE TABLE dbo.SampleTable
(Id INT IDENTITY(1,1) PRIMARY KEY,
 Name VARCHAR(50)
)
GO

INSERT INTO dbo.SampleTable
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')

SELECT * FROM dbo.SampleTable
GO

-- OUTPUT from UPDATE with SELECT output values only
UPDATE dbo.SampleTable
    SET Name = 'Name3UPDATED'
OUTPUT 
    INSERTED.Id,
    DELETED.Id,
    INSERTED.Name,
    DELETED.Name
WHERE Id = 3
GO

-- OUTPUT from UPDATE into @TableVariable
DECLARE @Updated TABLE (Id INT,
                        NewValue VARCHAR(50),
                        OldValue VARCHAR(50))

UPDATE dbo.SampleTable
    SET Name = 'Name4UPDATED'
OUTPUT 
    INSERTED.Id,
    INSERTED.Name,
    DELETED.Name
    INTO @Updated
WHERE Id = 4

SELECT * FROM @Updated
GO</pre>
<p><strong>DELETE:</strong></p>
<pre class="lang:tsql decode:true ">CREATE TABLE dbo.SampleTable
(Id INT IDENTITY(1,1) PRIMARY KEY,
 Name VARCHAR(50)
)
GO

INSERT INTO dbo.SampleTable
VALUES
    ('Name1'), 
    ('Name2'),
    ('Name3'),
    ('Name4'),
    ('Name5')
GO

-- OUTPUT from DELETE with SELECT output values only
DELETE FROM dbo.SampleTable
    OUTPUT DELETED.Id,
           DELETED.Name
WHERE Id = 3
GO

-- OUTPUT from DELETE into @TableVariable
DECLARE @Deleted TABLE (Id INT)

DELETE FROM dbo.SampleTable
    OUTPUT DELETED.Id
        INTO @Deleted
WHERE Id = 5

SELECT * FROM @Deleted
GO</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/output/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>UPDATE pomocí aliasu tabulky</title>
		<link>https://sqlpowered.com/update-pomoci-aliasu-tabulky/</link>
					<comments>https://sqlpowered.com/update-pomoci-aliasu-tabulky/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Fri, 17 Apr 2015 20:36:20 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=602</guid>

					<description><![CDATA[Napsat správně UPDATE příkaz může vypadat jako trivialita, ale opak je pravdou. Nehod, kdy se někomu nezadařilo a oprava dat ze záloh zabrala několik hodin, jsem zažil dost. Dnes si ukážeme alespoň drobné vylepšení, jak správně na UPDATE a věřím, že to leckomu z juniorů ušetří pár bezesných nocí:) Kouzlo...]]></description>
										<content:encoded><![CDATA[<p>Napsat správně UPDATE příkaz může vypadat jako trivialita, ale opak je pravdou. Nehod, kdy se někomu nezadařilo a oprava dat ze záloh zabrala několik hodin, jsem zažil dost. Dnes si ukážeme alespoň drobné vylepšení, jak správně na UPDATE a věřím, že to leckomu z juniorů ušetří pár bezesných nocí:)</p>
<p>Kouzlo je v tom, že i UPDATE můžeme udělat proti aliasu tabulky, jak ukazuje následující příklad:</p>
<pre class="lang:tsql decode:true">-- create sample table
CREATE TABLE dbo.SampleTable
(
    Id INT IDENTITY(1,1) NOT NULL,
    StrValue VARCHAR(10)
)
GO

-- insert sample data
INSERT dbo.SampleTable VALUES ('Value1')
INSERT dbo.SampleTable VALUES ('Value2')
INSERT dbo.SampleTable VALUES ('Value3')
GO

UPDATE STalias
    SET StrValue = 'Value1Upd' 
FROM dbo.SampleTable STalias
WHERE StrValue = 'Value1'
GO

SELECT * FROM dbo.SampleTable
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3091" src="https://sqlpowered.com/wp-content/uploads/2015/04/Update_Using_Table_Alias_1.png" alt="" width="128" height="77" /></p>
<p>Vytvořili jsme si testovací tabulku a napsali proti ní UPDATE příkaz, kde jsme tabulce přiřadili alias STalias a ten odkazovali v UDPATE hlavičce. Na tom není složitého. A k čemu je to tedy dobré?</p>
<p>Podívejme se na následující UPDATE:</p>
<pre class="lang:tsql decode:true">UPDATE dbo.[Employee]
	SET FirstName = 'AAA'
FROM ems.[Employee] [emp]
	INNER JOIN ems.[EmployeeStatus] [es] ON [es].[EmployeeStatus_ID] = [emp].[EmployeeStatus_ID]
WHERE emp.[EmployeeStatus_ID] = 3
GO</pre>
<p>Napsali jsme si krásný příkaz, kterým chceme nastavit všem zaměstnancům se statusem 3 jejich [FirstName] na &#8216;AAA&#8217;. A co se stane po spuštění? Došlo ke změně dat. Ano, došlo. A dokonce se nastavilo [FirstName] na &#8216;AAA&#8217;. Ovšem nastavilo se všem zaměstnancům bez ohledu na jejich status , a to ještě v úplně jiné tabulce, která leží místo ve schématu [ems] ve schématu [dbo]. Drobné přehlédnutí a průšvih je na světě.</p>
<p>Jak tomu předejít? Velmi jednoduše právě pomocí aliasu tabulky a správného pořadí, v jakém budeme UPDATE sestavovat. Postupovat bychom měli takto:</p>
<ol>
<li>Nejprve si napíšeme SELECT tak, abychom dostali přesně ta data, která se chystáme změnit a provedeme vizuální kontrolu, že data opravdu odpovídají naší představě:</li>
</ol>
<pre class="lang:tsql decode:true">SELECT emp.[Employee_ID], emp.[FirstName]
FROM ems.[Employee] [emp]
	INNER JOIN ems.[EmployeeStatus] [es] ON [es].[EmployeeStatus_ID] = [emp].[EmployeeStatus_ID]
WHERE es.[EmployeeStatus_ID] = 3
GO</pre>
<p style="padding-left: 40px;">2. Vybereme správný alias tabulky a použijeme ho v UPDATE příkazu. SELECT si zakomentujeme pro pozdější opakované spuštění:</p>
<pre class="lang:tsql decode:true ">UPDATE [emp]
	SET [emp].[FirstName] = 'AAA'
--SELECT emp.[Employee_ID], emp.[FirstName]
FROM ems.[Employee] [emp]
	INNER JOIN ems.[EmployeeStatus] [es] ON [es].[EmployeeStatus_ID] = [emp].[EmployeeStatus_ID]
WHERE es.[EmployeeStatus_ID] = 3
GO</pre>
<p style="padding-left: 40px;">3. Spustíme znovu označením do bloku část dotazu se SELECT příkazem a zkontrolujeme, zda data jsou opravdu změněná podle očekávání.</p>
<p style="padding-left: 40px;">4. Pokud pokračujeme ve stejném okně v psaní dalších příkazů, doporučuji zakomentovat si UPDATE a odkomentovat SELECT pro případ, že bychom omylem spustili znovu celý skript poté, co jsme si správnou část zapomněli označit do bloku:</p>
<pre class="lang:tsql decode:true ">--UPDATE [emp]
--	SET [emp].[FirstName] = 'AAA'
SELECT emp.[Employee_ID], emp.[FirstName]
FROM ems.[Employee] [emp]
	INNER JOIN ems.[EmployeeStatus] [es] ON [es].[EmployeeStatus_ID] = [emp].[EmployeeStatus_ID]
WHERE es.[EmployeeStatus_ID] = 3
GO</pre>
<p>Vše jsou to pouze triviální operace, ale osvojit si tyto 4 kroky jako povinné pro každodenní práci znamená výraznou minimalizaci rizika, že to jedno dne nedopadne dobře. Úplně stejně můžeme pochopitelně postupovat v případě DELETE příkazu.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/update-pomoci-aliasu-tabulky/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Explicitní vložení do IDENTITY sloupce</title>
		<link>https://sqlpowered.com/explicitni-vlozeni-do-identity-sloupce/</link>
					<comments>https://sqlpowered.com/explicitni-vlozeni-do-identity-sloupce/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 05 Apr 2015 12:56:29 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=592</guid>

					<description><![CDATA[Pokud máme tabulku, která obsahuje IDENTITY sloupec, generuje SQL Server nového hodnoty pro IDENTITY sloupec automaticky. Pokud přeci jen chceme vložit hodnotu sami a nespoléhat na automatický generátor, stačí použít SET IDENTITY INSERT ON&#124;OFF příkaz, jak ukazuje následující příklad: SET IDENTITY_INSERT SampleTable ON GO INSERT SampleTable (RowID, Name, Value) VALUES...]]></description>
										<content:encoded><![CDATA[<p>Pokud máme tabulku, která obsahuje IDENTITY sloupec, generuje SQL Server nového hodnoty pro IDENTITY sloupec automaticky. Pokud přeci jen chceme vložit hodnotu sami a nespoléhat na automatický generátor, stačí použít SET IDENTITY INSERT ON|OFF příkaz, jak ukazuje následující příklad:</p>
<pre class="lang:tsql decode:true">SET IDENTITY_INSERT SampleTable ON
GO

INSERT SampleTable (RowID, Name, Value) 
    VALUES (1, 'Name1', 'Value1')
GO

SET IDENTITY_INSERT SampleTable OFF
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3104" src="https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert.png" alt="" width="227" height="46" srcset="https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert.png 227w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert-150x30.png 150w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert-160x32.png 160w" sizes="auto, (max-width: 227px) 100vw, 227px" /></p>
<p>SET IDENTITY INSERT je ovšem dobrý sluha, ale zlý pán. Nesmíme totiž nikdy zapomenout, že se jedná o SET příkaz, který nastavuje vlastnosti aktuálního připojení. A platí, že v daném připojení může mít explicitní vkládání IDENTITY hodnot povolena vždy pouze jedna tabulka. Takže následující sekvence příkazů skončí s chybou:</p>
<pre class="lang:tsql mark:13 decode:true ">CREATE TABLE dbo.SampleTable (
	RowID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	Name NVARCHAR(100),
	Value NVARCHAR(100)
)
GO

SET IDENTITY_INSERT SampleTable ON

INSERT SampleTable (RowID, Name, Value) 
    VALUES (1, 'Name1', 'Value1')

--SET IDENTITY_INSERT SampleTable OFF
GO

CREATE TABLE dbo.SampleTable2 (
	RowID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	Name NVARCHAR(100),
	Value NVARCHAR(100)
)
GO

SET IDENTITY_INSERT SampleTable2 ON
GO

INSERT SampleTable2 (RowID, Name, Value) 
    VALUES (10, 'Name1', 'Value1')
GO

SET IDENTITY_INSERT SampleTable2 OFF
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3107" src="https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2.png" alt="" width="890" height="70" srcset="https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2.png 890w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-150x12.png 150w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-300x24.png 300w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-768x60.png 768w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-160x13.png 160w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-320x25.png 320w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-520x41.png 520w, https://sqlpowered.com/wp-content/uploads/2015/04/Explicit_Identity_Insert_2-720x57.png 720w" sizes="auto, (max-width: 890px) 100vw, 890px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/explicitni-vlozeni-do-identity-sloupce/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Composable DML</title>
		<link>https://sqlpowered.com/composable-dml/</link>
					<comments>https://sqlpowered.com/composable-dml/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 27 Apr 2014 10:55:44 +0000</pubDate>
				<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[DML]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=324</guid>

					<description><![CDATA[Composable DML is an extension SQL Server 2008 extension of the OUTPUT clause which one was originally published in SQL Server 2005. OUTPUT is generally returning all rows from the DML where it&#8217;s used without an option to filter the results set. Composable DML extends this concept for INSERT&#8230;SELECT scenarios...]]></description>
										<content:encoded><![CDATA[<p>Composable DML is an extension SQL Server 2008 extension of the OUTPUT clause which one was originally published in SQL Server 2005. OUTPUT is generally returning all rows from the DML where it&#8217;s used without an option to filter the results set. Composable DML extends this concept for INSERT&#8230;SELECT scenarios and brings a limited option to filter/slightly modify the result set provided by the OUTPUT clause. Let&#8217;s see how it&#8217;s working.</p>
<p>We will create a table [dbo].[SampleTable] and fill it with some dummy data first.</p>
<pre class="lang:tsql decode:true">-- Create sample table
CREATE TABLE [dbo].[SampleTable]
(
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [ProductName] VARCHAR(50) NOT NULL,
    [Price] MONEY NOT NULL
)
GO

-- Populate data
INSERT [dbo].[SampleTable] ([ProductName], [Price])
    SELECT 'Product1', 100 UNION ALL
    SELECT 'Product2', 200 UNION ALL
    SELECT 'Product3', 300
GO

-- Review sample data
SELECT [Id], [ProductName],	[Price] FROM dbo.[SampleTable]
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-4487" src="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-1.png" alt="" width="166" height="72" srcset="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-1.png 307w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-1-300x130.png 300w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-1-150x65.png 150w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-1-160x69.png 160w" sizes="auto, (max-width: 166px) 100vw, 166px" /></p>
<p>Table [dbo].[TrackedChanges] will be used to store changes history from DML we will run againts [dbo].[SampleTable]:</p>
<pre class="lang:tsql decode:true">-- Create table for changes history
CREATE TABLE [dbo].[TrackedChanges]
(
    [RowId] INT,
    [OldValue] MONEY,
    [NewValue] MONEY,
    [ChangeDate] DATETIME DEFAULT (GETDATE())
)
GO</pre>
<p>We will run the main Composable DML statement now: It&#8217;s an UPDATE statement to change Product price followed by OUTPUT clause. That all is nested in INSERT&#8230;SELECT statement with WHERE filter condition to track changes in [dbo].[TrackedChanges] table only in case when the updated [Price] is over 300.</p>
<pre class="lang:tsql decode:true">-- Perform Composable DML
INSERT INTO [dbo].[TrackedChanges]
        ( [RowId], [OldValue], [NewValue] )
SELECT 
	[RowId], [OldValue], [NewValue]
FROM 
(
    UPDATE [dbo].[SampleTable]
        SET [Price] = [Price] * 1.5
    OUTPUT
        [INSERTED].[Id] AS [RowId],
        [DELETED].[Price] AS [OldValue],
        [INSERTED].[Price] AS [NewValue] 
) [upd] 
WHERE NewValue &gt; 300
GO

-- Check results
SELECT [RowId], [OldValue], [NewValue],	[ChangeDate] FROM [dbo].[TrackedChanges]
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-4488" src="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2.png" alt="" width="311" height="33" srcset="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2.png 631w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-300x32.png 300w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-150x16.png 150w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-360x38.png 360w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-160x17.png 160w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-320x34.png 320w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-2-520x55.png 520w" sizes="auto, (max-width: 311px) 100vw, 311px" /></p>
<p>This query concept is mainly useful when handling SDC Type II in data warehouse scenarios.</p>
<p>Few limitations exist still and Composable DML is not fully done 10 years after publishing. I.e. we can&#8217;t use JOINs, TOP or GROUP BY clauses. Let&#8217;s demonstrate it on JOIN example:</p>
<pre class="lang:tsql mark:14 decode:true">INSERT INTO [dbo].[TrackedChanges]
        ( [RowId], [OldValue], [NewValue] )
SELECT 
	[RowId], [OldValue], [NewValue]
FROM 
(
    UPDATE [dbo].[SampleTable]
        SET [Price] = [Price] * 1.5
    OUTPUT
        [INSERTED].[Id] AS [RowId],
        [DELETED].[Price] AS [OldValue],
        [INSERTED].[Price] AS [NewValue] 
) [upd] 
	INNER JOIN [dbo].[SampleTable] st ON [st].[Id] = upd.Row_Id
WHERE NewValue &gt; 300
GO
</pre>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-4489" src="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3.png" alt="" width="589" height="27" srcset="https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3.png 938w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-300x14.png 300w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-150x7.png 150w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-768x35.png 768w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-360x17.png 360w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-160x7.png 160w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-320x15.png 320w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-520x24.png 520w, https://sqlpowered.com/wp-content/uploads/2014/04/Composable-DML-3-720x33.png 720w" sizes="auto, (max-width: 589px) 100vw, 589px" /></p>
<p>Besides these limitations, I really like it and have a lot of fun when junior developers are scratching heads what the hell is it for peace of code:)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/composable-dml/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
