<?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>SQL Server &#8211; SQLpowered.com</title>
	<atom:link href="https://sqlpowered.com/category/sql-server/feed/" rel="self" type="application/rss+xml" />
	<link>https://sqlpowered.com</link>
	<description>SQL Server + BI</description>
	<lastBuildDate>Sun, 18 Jan 2026 20:56: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>SQL Server &#8211; SQLpowered.com</title>
	<link>https://sqlpowered.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>SQL Server 2025 vs 2022: What Changed in DMVs</title>
		<link>https://sqlpowered.com/sql-server-2025-vs-2022-what-changed-in-dmvs-download-the-comparison-report/</link>
					<comments>https://sqlpowered.com/sql-server-2025-vs-2022-what-changed-in-dmvs-download-the-comparison-report/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 18 Jan 2026 20:51:06 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5784</guid>

					<description><![CDATA[I recently compared SQL Server system DMVs between SQL Server 2022 (16.0.4225.2) and SQL Server 2025 (17.0.1000.7). I used Redgate SQL Compare to generate an HTML report, and in this post, I’m providing a summary of the changes made, including additions, deletions, and modifications. See the full comparison: SQL_2022_To_2025_DMV_Comparison How...]]></description>
										<content:encoded><![CDATA[<p>I recently compared SQL Server system DMVs between SQL Server 2022 (16.0.4225.2) and SQL Server 2025 (17.0.1000.7). I used Redgate SQL Compare to generate an HTML report, and in this post, I’m providing a summary of the changes made, including additions, deletions, and modifications.</p>
<p>See the full comparison: <a href="https://sqlpowered.com/wp-content/uploads/2026/01/SQL_2022_To_2025_DMV_Comparison.html">SQL_2022_To_2025_DMV_Comparison</a></p>
<h3>How I built the comparison list</h3>
<p>I exported the list of system views from the <code>sys</code> schema using the query below. I also filtered out anything under <code>INFORMATION_SCHEMA</code> because I wanted to focus on the “real” system catalog/DMV surface area.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SELECT * 
FROM [sys].[system_objects] [so] 
    INNER JOIN [sys].[schemas] [sch] ON [sch].[schema_id] = [so].[schema_id] 
WHERE [type] = 'V' AND [sch].[name] = 'sys';</pre>
<h3>Headline numbers</h3>
<ul>
<li>Added: 28</li>
<li>Removed: 6</li>
<li>Modified: 39</li>
<li>Identical: 565</li>
</ul>
<p>These counts are straight from the SQL Compare report.</p>
<h3>New system objects in SQL Server 2025 (added)</h3>
<p>The most interesting additions fall into a few “themes”: newer query processing diagnostics, automatic tuning internals, memory/OS health visibility, governance/security metadata, and new metadata areas like JSON and vector indexing.</p>
<ul>
<li><code>sys_dm_database_backup_lineage</code></li>
<li><code>sys_dm_db_information_protection_label_properties</code></li>
<li><code>sys_dm_db_internal_auto_tuning_create_index_recommendations</code></li>
<li><code>sys_dm_db_internal_auto_tuning_recommendation_impact_query_metrics</code></li>
<li><code>sys_dm_db_internal_auto_tuning_recommendation_metrics</code></li>
<li><code>sys_dm_db_internal_auto_tuning_workflows</code></li>
<li><code>sys_dm_db_internal_automatic_tuning_version</code></li>
<li><code>sys_dm_db_logical_index_corruptions</code></li>
<li><code>sys_dm_db_xtp_undeploy_status</code></li>
<li><code>sys_dm_exec_ce_feedback_cache</code></li>
<li><code>sys_dm_exec_distributed_tasks</code></li>
<li><code>sys_dm_external_governance_synchronizing_objects</code></li>
<li><code>sys_dm_external_policy_excluded_role_members</code></li>
<li><code>sys_dm_hadr_internal_availability_groups</code></li>
<li><code>sys_dm_hadr_internal_availability_replicas</code></li>
<li><code>sys_dm_io_network_traffic_stats</code></li>
<li><code>sys_dm_os_memory_allocations_filtered</code></li>
<li><code>sys_dm_os_memory_health_history</code></li>
<li><code>sys_dm_os_memory_nodes_processor_groups</code></li>
<li><code>sys_dm_os_parent_block_descriptors</code></li>
<li><code>sys_dm_pal_ring_buffers</code></li>
<li><code>sys_dm_server_managed_identities</code></li>
<li><code>sys_external_models</code></li>
<li><code>sys_external_table_schema_changed_mdsync</code></li>
<li><code>sys_information_protection_label_mapping</code></li>
<li><code>sys_json_index_paths</code></li>
<li><code>sys_json_indexes</code></li>
<li><code>sys_vector_indexes</code></li>
</ul>
<h3>System objects that disappeared in SQL Server 2025 (removed)</h3>
<p>Only six objects were flagged as removed.</p>
<ul>
<li><code>sys_dm_dw_locks</code></li>
<li><code>sys_dm_pal_spinlock_stats</code></li>
<li><code>sys_dm_pal_wait_stats</code></li>
<li><code>sys_dm_toad_tuning_zones</code></li>
<li><code>sys_dm_toad_work_item_handlers</code></li>
<li><code>sys_dm_toad_work_items</code></li>
</ul>
<h3>Modified objects (what changed, at a high level)</h3>
<p>The report shows 39 modified objects. In practice, these changes are typically “column-level” updates to internal catalog tables and DMVs to support new features and diagnostics in SQL Server 2025.</p>
<ul>
<li><strong>Query processing and tuning:</strong> changes around query profiling and memory grants, plus new cardinality-estimation feedback exposure (<code>sys_dm_exec_ce_feedback_cache</code> is new, and related surfaces like <code>sys_dm_exec_query_memory_grants</code> show changes).</li>
<li><strong>Query Store &amp; plan feedback:</strong> system objects related to plan feedback/forcing locations are modified (<code>sys_query_store_plan_feedback</code>, <code>sys_query_store_plan_forcing_locations</code>).</li>
<li><strong>Availability groups / HADR:</strong> both catalog views and DMVs around AGs are modified (<code>sys_availability_groups</code>, <code>sys_dm_hadr_availability_replica_states</code>, <code>sys_dm_hadr_database_replica_states</code>), and there are new “internal” HADR DMVs in 2025 (listed above).</li>
<li><strong>Memory/OS internals:</strong> multiple <code>sys_dm_os_*</code> objects are modified, and new memory-health/history DMVs appear in 2025.</li>
<li><strong>Governance and labeling:</strong> new information protection label mapping and governance-sync related surfaces appear, and existing governance DMVs are modified (<code>sys_dm_external_governance_sync_state</code>, <code>sys_dm_database_external_governance_sync_state</code>).</li>
<li><strong>Core catalog internals:</strong> classic internal objects like <code>sys_all_columns</code>, <code>sys_columns</code>, <code>sys_parameters</code>, <code>sys_stats</code>, and “system_*” internals show updates, which is typical when a major version adds metadata flags and new engine behaviors.</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/sql-server-2025-vs-2022-what-changed-in-dmvs-download-the-comparison-report/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server 2025 Upgrade: Linked Servers May Fail Unless You Explicitly Set Encrypt</title>
		<link>https://sqlpowered.com/sql-server-2025-upgrade-gotcha-linked-servers-may-fail-unless-you-explicitly-set-encrypt/</link>
					<comments>https://sqlpowered.com/sql-server-2025-upgrade-gotcha-linked-servers-may-fail-unless-you-explicitly-set-encrypt/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 18 Jan 2026 10:19:07 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5778</guid>

					<description><![CDATA[When I migrated a set of linked servers from SQL Server 2022 to SQL Server 2025, a few of them started failing immediately during connection initialization. The root cause surprised me at first: SQL Server 2025 tightens the encryption defaults for linked servers, so a linked server that worked fine...]]></description>
										<content:encoded><![CDATA[<p>When I migrated a set of linked servers from SQL Server 2022 to SQL Server 2025, a few of them started failing immediately during connection initialization. The root cause surprised me at first: SQL Server 2025 tightens the encryption defaults for linked servers, so a linked server that worked fine on 2022 can fail on 2025 unless you explicitly define the encryption behavior in the provider string.</p>
<h3>What changed in SQL Server 2025</h3>
<ul>
<li>SQL Server 2025 introduces an encryption-related breaking change that can cause linked server connections to fail after an upgrade.</li>
<li>If the connection ends up requiring encryption with certificate validation, and the target server certificate isn’t trusted (or you connect by IP that doesn’t match the certificate name), the linked server can fail.</li>
<li>The quickest “get it working” mitigation I’ve used is to add <code>@provstr = N'Encrypt=Yes;TrustServerCertificate=yes'</code> when creating the linked server.</li>
<li>Best practice is still to use a proper CA-signed certificate and keep certificate validation enabled, but in real migrations you sometimes need the fast, pragmatic fix first.</li>
</ul>
<h3>SQL Server 2022 (working without explicit encryption settings)</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">USE [master]
GO

EXEC master.dbo.sp_addlinkedserver 
    @server = N'MyLinkedServer1', 
    @srvproduct = N'', 
    @provider = N'SQLNCLI', 
    @datasrc = N'123.123.123.123,1433'
GO</pre>
<h3>SQL Server 2025 script (explicit encryption in @provstr is required)</h3>
<p>On SQL Server 2025, adding an explicit provider string makes the intent clear and prevents the upgrade from breaking existing linked servers. I also recommend switching away from the legacy <code>SQLNCLI</code> provider to <code>MSOLEDBSQL</code> if you still have old scripts around.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-highlight="9">USE [master]
GO

EXEC master.dbo.sp_addlinkedserver 
    @server = N'MyLinkedServer1',
    @srvproduct = N'',
    @provider = N'MSOLEDBSQL',
    @datasrc = N'123.123.123.123,1433',
    @provstr = N'Encrypt=Yes;TrustServerCertificate=yes'
GO</pre>
<h3>References</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/sql/database-engine/breaking-changes-to-database-engine-features-in-sql-server-2025?view=sql-server-ver17">Database Engine breaking changes in SQL Server 2025 (linked server encryption)</a></li>
<li><a href="https://learn.microsoft.com/en-us/sql/relational-databases/linked-servers/linked-servers-database-engine?view=sql-server-ver17">Linked servers (Database Engine) – encryption defaults and Encrypt settings</a></li>
<li><a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addlinkedserver-transact-sql?view=sql-server-ver17">sp_addlinkedserver (Transact-SQL) – SQL Server 2025 notes on encrypt in @provstr</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/sql-server-2025-upgrade-gotcha-linked-servers-may-fail-unless-you-explicitly-set-encrypt/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server 2025 on Azure VM failed to deploy: “System Drive returned status not ready for use” (TempDB on uninitialized local NVMe)</title>
		<link>https://sqlpowered.com/sql-server-2025-on-azure-vm-failed-to-deploy-system-drive-returned-status-not-ready-for-use-tempdb-on-uninitialized-local-nvme/</link>
					<comments>https://sqlpowered.com/sql-server-2025-on-azure-vm-failed-to-deploy-system-drive-returned-status-not-ready-for-use-tempdb-on-uninitialized-local-nvme/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 11 Jan 2026 20:03:48 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5773</guid>

					<description><![CDATA[I recently encountered a deployment failure when provisioning a new SQL Server 2025 VM based on a Windows Server 2025 image in Azure. The VM size was E2ds_v6, which includes ~110 GB local storage (NVMe / ephemeral). The deployment failed during provisioning with this error: { "status": "Failed", "error": {...]]></description>
										<content:encoded><![CDATA[<p>I recently encountered a deployment failure when provisioning a new <strong>SQL Server 2025</strong> VM based on a <strong>Windows Server 2025</strong> image in Azure.</p>
<p><img fetchpriority="high" decoding="async" class="alignnone wp-image-5775" src="https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq.png" alt="" width="540" height="240" srcset="https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq.png 1605w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-300x133.png 300w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-1024x455.png 1024w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-150x67.png 150w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-768x341.png 768w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-1536x682.png 1536w, https://sqlpowered.com/wp-content/uploads/2026/01/h1c033I2zq-360x160.png 360w" sizes="(max-width: 540px) 100vw, 540px" /></p>
<p>The VM size was <strong>E2ds_v6</strong>, which includes <strong>~110 GB local storage</strong> (NVMe / ephemeral).</p>
<p>The deployment failed during provisioning with this error:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json">{
  "status": "Failed",
  "error": {
    "code": "Ext_StorageConfigurationSettings_ApplyNewTempDbSettingsError",
    "message": "Error: 'System Drive returned status not ready for use '"
}</pre>
<h2>What was happening</h2>
<p>In my case, the root cause was surprisingly simple: the local NVMe disk was presented to Windows as <strong>RAW / uninitialized</strong>.</p>
<p><img decoding="async" class="alignnone wp-image-5774" src="https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV.png" alt="" width="606" height="398" srcset="https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV.png 1669w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-300x197.png 300w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-1024x672.png 1024w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-150x100.png 150w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-768x504.png 768w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-1536x1009.png 1536w, https://sqlpowered.com/wp-content/uploads/2026/01/bZOpXyVglV-360x236.png 360w" sizes="(max-width: 606px) 100vw, 606px" /></p>
<p>The <strong>SQL Server IaaS extension</strong> (the component Azure uses to apply storage configuration for SQL on a VM) tried to configure <strong>tempdb</strong> on that local disk. Because the disk had no partition, no file system, and no drive letter, the extension step failed, and the entire provisioning process ended as <strong>Failed</strong>.</p>
<p>This is especially annoying because local/ephemeral disks are exactly where you often want tempdb (fast IOPS), but only if the disk is actually usable at boot time.</p>
<h2>The fix: initialize the NVMe temp disk at startup (idempotent script)</h2>
<p>The solution that worked for me was to create a <strong>PowerShell script</strong> that runs on startup and does the following:</p>
<ul>
<li>Find the first <strong>non-OS NVMe disk</strong> ( you can edit it to get the drive based on name or a different attribute, as per your current setup)</li>
<li>If it is <strong>RAW</strong>, initialize it (GPT), create one partition, and format it NTFS</li>
<li>Force the drive letter to <strong>T:</strong> (so my tempdb path is stable)</li>
<li>Create the folder <strong>T:\SQLTemp</strong></li>
<li>Grant permissions to the default instance service SID: <strong>NT SERVICE\MSSQLSERVER</strong></li>
</ul>
<p>Here is the script exactly as I used it:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell"># Pick the temp NVMe disk: non-OS, NVMe bus, and possibly RAW
$disk = Get-Disk | Where-Object {
    $_.IsBoot -eq $false -and $_.IsSystem -eq $false -and $_.BusType -eq 'NVMe'
} | Sort-Object Number | Select-Object -First 1

if ($null -eq $disk) { exit 0 }

# If RAW, initialize + partition + format and force drive letter T:
if ($disk.PartitionStyle -eq 'RAW') {
    Initialize-Disk -Number $disk.Number -PartitionStyle GPT -PassThru | Out-Null
    $part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -DriveLetter T
    Format-Volume -Partition $part -FileSystem NTFS -NewFileSystemLabel "TEMP" -AllocationUnitSize 65536 -Confirm:$false | Out-Null
}

# If it’s already formatted but missing letter T, try to assign it
$vol = Get-Volume | Where-Object { $_.FileSystemLabel -eq 'TEMP' } | Select-Object -First 1
if ($vol -and $vol.DriveLetter -ne 'T') {
    Set-Partition -DriveLetter $vol.DriveLetter -NewDriveLetter T
}

# Ensure TempDB folder exists
$tempFolder = "T:\SQLTemp"
if (!(Test-Path $tempFolder)) { New-Item -ItemType Directory -Path $tempFolder | Out-Null }

# Grant SQL Server service account (default instance service SID) rights
icacls $tempFolder /grant "NT SERVICE\MSSQLSERVER:(OI)(CI)F" /T | Out-Null

# Start SQL
Start-Service "MSSQLSERVER"
Start-Service "SQLSERVERAGENT" -ErrorAction SilentlyContinue</pre>
<h2>Important detail: make sure SQL Server doesn’t start before the disk is ready</h2>
<p>Just initializing the disk is not enough if SQL Server starts too early.</p>
<ul>
<li>Set the <strong>SQL Server (MSSQLSERVER)</strong> service startup type from <strong>Automatic</strong> to <strong>Manual</strong></li>
<li>Set the <strong>SQL Server Agent (MSSQLSERVER)</strong> service startup type from <strong>Automatic</strong> to <strong>Manual</strong></li>
</ul>
<p>Then create a new <strong>Windows Task</strong> (use Task Scheduler) to be run at system startup:</p>
<ul>
<li><strong>Trigger:</strong> At startup</li>
<li><strong>Security options:</strong> Run whether the user is logged on or not</li>
<li><strong>Security options:</strong> Run with highest privileges</li>
<li><strong>Action:</strong> <code>powershell.exe</code></li>
<li><strong>Arguments:</strong> <code>-ExecutionPolicy Bypass -File C:\Scripts\Init-TempDisk-AndStartSql.ps1</code></li>
</ul>
<p>After a reboot, the VM brought up the temp drive first, then SQL Server came online cleanly. The deployment succeeded, and tempdb could be placed where it belongs.</p>
<h2>Alternative options (if you don’t want tempdb on local storage)</h2>
<p>If you want a simpler/safer setup (at the cost of I/O), you can avoid placing tempdb on ephemeral storage entirely:</p>
<ul>
<li>Use a VM size where the temp disk is presented consistently and already formatted</li>
<li>Place tempdb on a managed data disk (Premium SSD / Ultra Disk) and configure it that way from the start.</li>
</ul>
<h3>Important note: How SQL IaaS starts SQL Server (and why you should not edit its startup task)</h3>
<p>When you deploy a SQL Server VM from the Azure Marketplace image, Azure installs the <strong>SQL Server IaaS Agent extension</strong>. One of the things this extension does is create a <strong>Windows Scheduled Task</strong> (running at system startup) that executes<code>SqlIaaSExtension.SqlServerStarter.exe</code>. This starter component is responsible for bringing the SQL Server services online in a predictable way (and for some configurations, it also validates or prepares required paths used by SQL Server).</p>
<p>A key detail is that this scheduled task is <strong>managed by the SQL IaaS extension</strong>. That means it is effectively treated as &#8220;desired state&#8221;: on every VM boot, the extension can <strong>recreate or reset</strong> the task to its default definition. Because of that, any manual edits (for example, adding a PowerShell step into the same task) are <strong>not persistent</strong>. If you add your PowerShell action directly into the SQL IaaS startup task, it will typically be <strong>deleted on the next VM startup</strong>, because the extension rewrites the task back to the default version.</p>
<p>The correct approach is to leave the SQL IaaS task untouched and create <strong>a separate startup scheduled task</strong> for your own script (for example, to initialize and format the ephemeral NVMe disk and create <code>T:\SQLTemp</code>). Your task can run at startup and ensure the drive and folder exist, and then (optionally) start or restart the SQL Server service if needed.</p>
<h3>References</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/troubleshoot/sql/azure-sql/sql-deployment-fails-drive-not-ready" target="_blank" rel="noopener">SQL VM fails to deploy or SQL Server instance can’t come online after restart (Drive not ready)</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/sql-server-iaas-agent-extension-automate-management?view=azuresql" target="_blank" rel="noopener">SQL Server IaaS Agent extension overview</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/storage-configuration?view=azuresql" target="_blank" rel="noopener">Configure storage for SQL Server on Azure VMs</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/sql-server-2025-on-azure-vm-failed-to-deploy-system-drive-returned-status-not-ready-for-use-tempdb-on-uninitialized-local-nvme/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server 2025: Web Edition Is Gone</title>
		<link>https://sqlpowered.com/sql-server-2025-web-edition-is-gone/</link>
					<comments>https://sqlpowered.com/sql-server-2025-web-edition-is-gone/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sun, 04 Jan 2026 22:51:43 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5769</guid>

					<description><![CDATA[As a SQL Server DBA, I usually don’t panic about licensing changes — but the removal of SQL Server Web Edition in SQL Server 2025 is one of those changes that immediately hit a real production project I’m responsible for.Web Edition has been the quiet workhorse for years: small web...]]></description>
										<content:encoded><![CDATA[<article class="sp-article">As a SQL Server DBA, I usually don’t panic about licensing changes — but the removal of <strong>SQL Server Web Edition</strong> in <strong>SQL Server 2025</strong> is one of those changes that immediately hit a real production project I’m responsible for.Web Edition has been the quiet workhorse for years: small web applications, low to moderate workloads, predictable costs. Nothing fancy — just a reliable SQL Server engine at a price that made sense.</p>
<p><strong>With SQL Server 2025, that option is gone.</strong></p>
<p>Microsoft has confirmed that <strong>SQL Server 2022</strong> is the last version supporting Web Edition. From 2025 onward, if you want to stay current, <strong>Standard Edition</strong> becomes the default replacement — and that’s where the real problem starts.</p>
<p>In one of my Azure VM–based projects, we were running SQL Server Web Edition on a VM with <strong>12 vCores and 48 GB RAM</strong>. The SQL licensing cost was roughly <strong>70 USD per month</strong>, fully acceptable for the workload and business value.</p>
<p><img decoding="async" class="alignnone size-full wp-image-5770" src="https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing.png" alt="" width="1875" height="1327" srcset="https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing.png 1875w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-300x212.png 300w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-1024x725.png 1024w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-141x100.png 141w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-768x544.png 768w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-1536x1087.png 1536w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2022_Web_Pricing-360x255.png 360w" sizes="(max-width: 1875px) 100vw, 1875px" /></p>
<p>After switching the same VM to <strong>SQL Server Standard Edition</strong>, the SQL license component alone jumped to <strong>~870 USD per month</strong>.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5771" src="https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing.png" alt="" width="2048" height="1308" srcset="https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing.png 2048w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-300x192.png 300w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-1024x654.png 1024w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-150x96.png 150w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-768x491.png 768w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-1536x981.png 1536w, https://sqlpowered.com/wp-content/uploads/2026/01/SQLServer2025_Standard_Pricing-360x230.png 360w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></p>
<p>Same VM.<br />
Same workload.<br />
Same databases.<br />
<strong>Almost 12× higher SQL cost.</strong></p>
<h2>What changed — in practice</h2>
<ul>
<li><strong>SQL Server Web Edition is discontinued in 2025</strong></li>
<li>SQL Server 2022 Web Edition remains supported, but only as a temporary safe harbor until 2033</li>
<li><strong>Standard Edition becomes the lowest “future-proof” option</strong></li>
<li>Licensing is <strong>per-core</strong>, and costs scale aggressively with CPU count</li>
<li>Even modest VMs suddenly become expensive SQL hosts</li>
</ul>
<h2>My takeaway</h2>
<p>SQL Server Web Edition disappearing is not just a line in the release notes — it’s a <strong>budget-changing event</strong>.</p>
<p>If you’re running Web Edition today, my advice is simple: <strong>audit your environments now</strong> and calculate what Standard Edition will really cost you — before SQL Server 2025 forces the conversation.</p>
<p>Sometimes the most expensive SQL feature is not a feature at all — it’s the edition.</p>
</article>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/sql-server-2025-web-edition-is-gone/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SET STATISTICS PROFILE</title>
		<link>https://sqlpowered.com/set-statistics-profile/</link>
					<comments>https://sqlpowered.com/set-statistics-profile/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Mon, 17 Jun 2024 19:02:27 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5702</guid>

					<description><![CDATA[SQL Server provides various tools and commands to help database administrators and developers analyze and optimize query performance. One of these commands is SET STATISTICS PROFILE. This command is particularly useful for obtaining a detailed execution plan along with the runtime statistics of a query, which can be invaluable for...]]></description>
										<content:encoded><![CDATA[<p>SQL Server provides various tools and commands to help database administrators and developers analyze and optimize query performance. One of these commands is <code>SET STATISTICS PROFILE</code>. This command is particularly useful for obtaining a detailed execution plan along with the runtime statistics of a query, which can be invaluable for performance tuning.</p>
<p><code>SET STATISTICS PROFILE</code> , when enabled, returns detailed information about the execution of a query. This includes the execution plan and the number of rows affected by each operation. The command is helpful for understanding how SQL Server executes a query and where potential bottlenecks might be.</p>
<p>When <code>SET STATISTICS PROFILE</code> is ON, SQL Server returns two result sets. The first is the regular result of the query, and the second is a detailed report showing the actual execution plan with runtime statistics.</p>
<h2>Syntax</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SET STATISTICS PROFILE { ON | OFF }</pre>
<h2>How to Use SET STATISTICS PROFILE</h2>
<p>To use <code>SET STATISTICS PROFILE</code>, you simply turn it ON before running your query and then turn it OFF afterward. Below is a step-by-step guide with a practical example.</p>
<h2>Practical Example</h2>
<p>Consider a scenario where you have a database with the following table:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">CREATE TABLE Employees (
    EmployeeID INT PRIMARY KEY,
    FirstName NVARCHAR(50),
    LastName NVARCHAR(50),
    Department NVARCHAR(50),
    Salary DECIMAL(10, 2)
);</pre>
<p>Let&#8217;s populate this table with some sample data:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">INSERT INTO Employees (EmployeeID, FirstName, LastName, Department, Salary) VALUES
(1, 'John', 'Doe', 'Engineering', 60000),
(2, 'Jane', 'Smith', 'Marketing', 50000),
(3, 'Sam', 'Brown', 'Engineering', 75000),
(4, 'Sue', 'Johnson', 'HR', 45000);</pre>
<p>Now, suppose you want to analyze the performance of a query that retrieves all employees from the Engineering department:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SET STATISTICS PROFILE ON;

SELECT * FROM Employees WHERE Department = 'Engineering';

SET STATISTICS PROFILE OFF;</pre>
<h2>Understanding the Output</h2>
<p>When you run the above commands, SQL Server will return the query results as usual. Additionally, it will provide a second result set with the execution plan and statistics. Here’s an example of what the output might look like:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5706" src="https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2.png" alt="" width="1351" height="60" srcset="https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2.png 3369w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-300x13.png 300w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-1024x45.png 1024w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-150x7.png 150w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-768x34.png 768w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-1536x68.png 1536w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-2048x91.png 2048w, https://sqlpowered.com/wp-content/uploads/2024/06/SET_PROFILE_2-360x16.png 360w" sizes="auto, (max-width: 1351px) 100vw, 1351px" /></p>
<h2>Key Columns in the Output</h2>
<ul>
<li><strong>StmtText:</strong> The text of the SQL statement being executed.</li>
<li><strong>NodeId:</strong> The identifier for the operation node within the execution plan.</li>
<li><strong>PhysicalOp:</strong> The physical operation performed (e.g., Index Scan, Table Scan).</li>
<li><strong>LogicalOp:</strong> The logical operation performed (e.g., Filter, Join).</li>
<li><strong>EstimateRows:</strong> The estimated number of rows that will be processed.</li>
<li><strong>EstimateIO:</strong> The estimated I/O cost.</li>
<li><strong>EstimateCPU:</strong> The estimated CPU cost.</li>
<li><strong>TotalSubtreeCost:</strong> The estimated total cost of the query.</li>
<li><strong>OutputList:</strong> The list of columns output by this operation.</li>
</ul>
<h2>Benefits of Using SET STATISTICS PROFILE</h2>
<ul>
<li><strong>Detailed Insights:</strong> Provides detailed execution plans with actual runtime statistics.</li>
<li><strong>Performance Tuning:</strong> Helps identify bottlenecks and inefficient operations in queries.</li>
<li><strong>Optimization:</strong> Assists in optimizing queries by giving visibility into the execution process.</li>
</ul>
<h2>Conclusion</h2>
<p><code>SET STATISTICS PROFILE</code> is a powerful tool in SQL Server for anyone looking to optimize their queries and improve performance. By providing detailed execution plans and runtime statistics, it allows developers and DBAs to gain a deeper understanding of how their queries are executed and where they can make improvements. Utilizing this command effectively can lead to significant performance gains and more efficient database operations.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/set-statistics-profile/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Temporary Stored Procedures</title>
		<link>https://sqlpowered.com/temporary-stored-procedures/</link>
					<comments>https://sqlpowered.com/temporary-stored-procedures/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Mon, 13 May 2024 22:07:05 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5679</guid>

					<description><![CDATA[Few people know that it is possible to create temporary stored procedures in SQL Server. I often ask about this when recruiting new candidates, and 9 out of 10 ask if I meant temporary tables, or if they start talking about them right away. So yes, we can indeed create...]]></description>
										<content:encoded><![CDATA[<p>Few people know that it is possible to create temporary stored procedures in SQL Server. I often ask about this when recruiting new candidates, and 9 out of 10 ask if I meant temporary tables, or if they start talking about them right away. So yes, we can indeed create temporary stored procedures in SQL Server, but we should do so with a good understanding of their behavior and follow Microsoft&#8217;s recommendations.</p>
<p>Temporary stored procedures are created in the same way as temporary tables &#8211; by adding one or two # before their name. Based on this, they are distinguished as local and global, similar to temporary tables:</p>
<ul>
<li><em>local (#)</em>
<ul>
<li>created by adding one hashtag (#) before their name</li>
<li>visible only in the connection (SPID) in which they were created</li>
<li>automatically removed after the connection is closed</li>
<li>can only be called by their creator &#8211; the owner (logically, the login that owns the active connection)</li>
<li>cannot grant the right to call them to other users</li>
</ul>
</li>
<li><em>global (##)</em>
<ul>
<li>created by adding two hashtags (##) before their name</li>
<li>visible to all connections (SPIDs) of the given instance</li>
<li>automatically removed after the last connection using them is closed</li>
<li>can be called by all users and this right cannot be revoked</li>
</ul>
</li>
</ul>
<p>Temporary stored procedures can be created by all users and this right cannot be revoked.</p>
<p>An example of a simple local stored procedure might look like this:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">CREATE PROCEDURE #PrintMessageUpperCase (
    @Msg NVARCHAR(100)
)
AS
BEGIN
    
     PRINT UPPER(@Msg)

END
GO

EXECUTE [dbo].[#PrintMessageUpperCase] @Msg = N'Test Message'
GO

DROP PROCEDURE [dbo].[#PrintMessageUpperCase]
GO</pre>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3085" src="https://sqlpowered.com/wp-content/uploads/2015/03/Temporary_Stored_Procedures_1.png" alt="" width="120" height="43" /></p>
<p>The use of temporary stored procedures is really useful in various deployment scenarios of database changes, where we need to run more complex scripts or achieve the desired behavior of SQL Server by wrapping the code in a stored procedure. Similarly, they are excellent for testing various optimizations, etc. In the end, we do not need to worry about cleaning the database of temporary objects.</p>
<p>What we really need to be careful about is security: EXECUTE rights cannot be restricted for global temporary stored procedures, so code that a user would not normally have access to can be run from another session.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/temporary-stored-procedures/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Maximum Number of Columns in SELECT Statements</title>
		<link>https://sqlpowered.com/maximum-number-of-columns-in-select-statements/</link>
					<comments>https://sqlpowered.com/maximum-number-of-columns-in-select-statements/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Sat, 13 Apr 2024 06:47:16 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5676</guid>

					<description><![CDATA[SQL Server, like many relational database management systems, has specific limitations that developers need to be aware of when designing and implementing database solutions. One such limitation is the maximum number of expressions that can be specified in the SELECT list of a query. This limit is set at 4096...]]></description>
										<content:encoded><![CDATA[<p>SQL Server, like many relational database management systems, has specific limitations that developers need to be aware of when designing and implementing database solutions. One such limitation is the maximum number of expressions that can be specified in the SELECT list of a query. This limit is set at <strong>4096</strong> expressions. This article explores what this limitation means, why it exists, and demonstrates what happens when this limit is exceeded using a dynamic SQL script.</p>
<h4>What is the Maximum Column Limit?</h4>
<p>In SQL Server, the maximum number of expressions (columns) that can be included in the SELECT list of a query is 4096. This limit is significant because it affects how data can be queried and structured within SQL Server. Exceeding this limit results in an error, preventing the execution of the query.</p>
<h4>Why Does This Limit Exist?</h4>
<p>The limit is primarily in place to ensure performance and manageability. SQL Server is optimized for handling queries that meet certain structural criteria, and overly large queries can degrade performance, not only for the query itself but for the server as a whole. By imposing a limit on the number of expressions in a SELECT list, SQL Server helps ensure that queries are designed efficiently and that system resources are not overwhelmed.</p>
<h4>Demonstrating the Error</h4>
<p>To illustrate what happens when the limit is exceeded, let&#8217;s look at a dynamic SQL script designed to create and execute a SELECT statement with more than 4096 columns. Here&#8217;s the script:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">DECLARE @sql AS NVARCHAR(MAX);

-- Initialize the SQL statement
SET @sql = 'SELECT ';

-- Generate more than 4096 columns
DECLARE @i INT = 1;
WHILE @i &lt;= 4100
BEGIN
    IF @i = 1
        SET @sql = @sql + ' ' + CAST(@i AS VARCHAR) + ' AS Col' + CAST(@i AS VARCHAR)
    ELSE
        SET @sql = @sql + ', ' + CAST(@i AS VARCHAR) + ' AS Col' + CAST(@i AS VARCHAR);
    
    SET @i = @i + 1;
END

PRINT @sql

-- Execute the dynamic SQL
EXEC [sys].[sp_executesql] @sql;
</pre>
<div class="dark bg-gray-950 rounded-md"></div>
<p>A loop runs from 1 to 4100, adding columns to the SQL command. Each iteration adds a new column named <code>Col1</code>, <code>Col2</code>, etc., until <code>Col4100</code>. The SQL command stored in <code>@sql</code> is then executed using <code>sp_executesql</code>. Since the command attempts to select more than 4096 columns, SQL Server will throw an error stating that the maximum number of columns in the SELECT statement has been exceeded:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">Msg 1056, Level 15, State 1, Line 1
The number of elements in the select list exceeds the maximum allowed number of 4096 elements.</pre>
<h4>Conclusion</h4>
<p>Understanding limitations such as the maximum number of expressions in a SELECT list is crucial for SQL Server developers to avoid runtime errors and design efficient queries. This knowledge ensures that database applications are robust, performant, and scalable.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/maximum-number-of-columns-in-select-statements/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Understanding Sparse Columns</title>
		<link>https://sqlpowered.com/understanding-sparse-columns/</link>
					<comments>https://sqlpowered.com/understanding-sparse-columns/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Tue, 09 Apr 2024 11:14:36 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=5673</guid>

					<description><![CDATA[Sparse columns in SQL Server are an intriguing feature designed to optimize storage for columns that contain a significant amount of NULL values. Introduced in SQL Server 2008, sparse columns offer a way to store nulls efficiently, consuming no storage space for NULL values in a column. This feature can...]]></description>
										<content:encoded><![CDATA[<p>Sparse columns in SQL Server are an intriguing feature designed to optimize storage for columns that contain a significant amount of NULL values. Introduced in SQL Server 2008, sparse columns offer a way to store nulls efficiently, consuming no storage space for NULL values in a column. This feature can be particularly useful in scenarios where the table has many columns that are not frequently used or that often contain null values. Sparse columns are ideal for situations where NULL values can constitute 20% to 40% or more of the dataset in a column. Beyond storage optimization, sparse columns also have implications for the performance of data retrieval and manipulation operations.</p>
<h4>Internal Implementation and Metadata</h4>
<p>Internally, SQL Server implements sparse columns by not physically storing NULL values. Non-NULL values in sparse columns consume more storage space than their non-sparse counterparts due to the overhead associated with maintaining sparsity. This trade-off means that while sparse columns can significantly reduce the storage footprint of highly null columns, they might increase the size of columns with a low proportion of NULL values.</p>
<p>To manage sparse columns, SQL Server uses a special kind of row structure that includes a column set, which is an XML representation of all the sparse columns in a table. This column set is not stored physically in the table but can be queried to retrieve data from sparse columns as if they were part of a single XML document.</p>
<p>To explore metadata related to sparse columns in the current database, you can use the following query, which leverages the <code>sys.columns</code> catalog view:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SELECT
    OBJECT_NAME([object_id]) AS [TableName], [name] AS [ColumnName], [is_sparse]
FROM [sys].[columns]
WHERE [is_sparse] = 1</pre>
<p>This query lists all sparse columns in the database, providing insights into which tables and columns are utilizing this feature.</p>
<h4>Test it</h4>
<p>This script is designed to demonstrate the storage efficiency of sparse columns in SQL Server compared to standard nullable columns when dealing with a large volume of NULL values:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SET NOCOUNT ON

DROP TABLE IF EXISTS [dbo].[StandardNulls]
DROP TABLE IF EXISTS [dbo].[SparseNulls]

CREATE TABLE [dbo].[StandardNulls] (
    [StandardNull] INT NULL
)

CREATE TABLE [dbo].[SparseNulls] (
    [SparseNull] INT SPARSE
)

DECLARE @i INT = 0
WHILE @i &lt; 100000
BEGIN
    
	INSERT INTO [StandardNulls] ([StandardNull])
		VALUES (CASE WHEN @i % 1000 = 0 THEN @i ELSE NULL END)
    
	INSERT INTO [SparseNulls] ([SparseNull])
		VALUES (CASE WHEN @i % 1000 = 0 THEN @i ELSE NULL END)

	SET @i = @i + 1
END

EXEC [sp_spaceused] @objname = '[dbo].[StandardNulls]'
EXEC [sp_spaceused] @objname = '[dbo].[SparseNulls]'
GO</pre>
<div class="flex flex-grow flex-col max-w-full">
<div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" dir="auto" data-message-author-role="assistant" data-message-id="d02a20a5-9c1c-4426-b809-e2a169b14b2b">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>The script creates two new tables, <code>[dbo].[StandardNulls]</code> and <code>[dbo].[SparseNulls]</code>. The first table has a standard nullable integer column named <code>[StandardNull]</code>, while the second table has a sparse column named <code>[SparseNull]</code>.</p>
<p>Then Inside the loop, it inserts into both tables. For every 1000th iteration (i.e., when <code>@i % 1000 = 0</code>), it inserts the value of <code>@i</code> into the tables; otherwise, it inserts a NULL. This results in tables where only 1 out of every 1000 rows has a non-NULL value, simulating a high proportion of NULL values which is ideal for demonstrating the efficiency of sparse columns.</p>
<p>After the loop completes and both tables are populated, the script uses the stored procedure <code>sp_spaceused</code> to measure the amount of storage space used by each table. This comparison is crucial as it highlights the storage savings achieved by using a sparse column in scenarios where NULL values dominate. Sparse columns do not use storage space for NULL values, unlike standard columns which allocate minimal storage even for NULLs.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5674" src="https://sqlpowered.com/wp-content/uploads/2024/04/SparseColumns.png" alt="" width="371" height="77" srcset="https://sqlpowered.com/wp-content/uploads/2024/04/SparseColumns.png 742w, https://sqlpowered.com/wp-content/uploads/2024/04/SparseColumns-300x62.png 300w, https://sqlpowered.com/wp-content/uploads/2024/04/SparseColumns-150x31.png 150w, https://sqlpowered.com/wp-content/uploads/2024/04/SparseColumns-360x75.png 360w" sizes="auto, (max-width: 371px) 100vw, 371px" /></p>
<p>Space saved using the SPARSE column is about 200 KB in our example. In real-world applications, this can translate into significant storage savings, especially for large databases with many such columns. However, it&#8217;s also important to remember the overhead associated with sparse columns for non-NULL values, which means they are not universally the best choice for all scenarios.</p>
</div>
</div>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/understanding-sparse-columns/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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 loading="lazy" 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="auto, (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 loading="lazy" 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="auto, (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 loading="lazy" 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="auto, (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>Scalar Functions and Parallel Execution Plans</title>
		<link>https://sqlpowered.com/scalar-functions-and-parallel-execution-plans/</link>
					<comments>https://sqlpowered.com/scalar-functions-and-parallel-execution-plans/#respond</comments>
		
		<dc:creator><![CDATA[Jan Dvořák]]></dc:creator>
		<pubDate>Wed, 27 Jan 2021 08:24:45 +0000</pubDate>
				<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://sqlpowered.com/?p=4771</guid>

					<description><![CDATA[This article is just a simple demonstration of improvements in scalar functions handling implemented in SQL Server 2019. Prior to this version when the scalar function was used in a query, it was blocking parallel plan generation. You can read more on this topic in this article. One of the...]]></description>
										<content:encoded><![CDATA[<p>This article is just a simple demonstration of improvements in scalar functions handling implemented in SQL Server 2019. Prior to this version when the scalar function was used in a query, it was blocking parallel plan generation. You can read more on this topic in this <a href="https://www.sqlskills.com/blogs/jonathan/sql-101-parallelism-inhibitors-scalar-user-defined-functions" target="_blank" rel="noopener">article</a>. One of the most anticipated features in SQL Server 2019 is the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=sql-server-ver15" target="_blank" rel="noopener">Scalar UDF Inlining</a> and I will show you a very simple practical demonstration of how this feature can improve query performance compared to SQL 2017 and older versions.</p>
<p>We will build a scalar function that will replace two HTML tags in the input string with string literals used for tags escaping. We will also build the same function as Inlined because that solution was used before SQL 2019 to address most of the scalar functions related issues. Table dbo.SamleTable will be filled with 100k records to be used in this test.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">USE [tempdb]
GO

-- Scalar Function
CREATE FUNCTION [dbo].[f_Scalar_ReplaceHTMLTags](
	@Data NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
BEGIN
	
	RETURN REPLACE(REPLACE(@Data, '&lt;', '&amp;lt;'), '&gt;', '&amp;gt;')

END
GO

-- Inlined function
CREATE FUNCTION [dbo].[f_Inline_ReplaceHTMLTags](
	@Data NVARCHAR(MAX)
)
RETURNS TABLE
AS
	
	RETURN (SELECT REPLACE(REPLACE(@Data, '&lt;', '&amp;lt;'), '&gt;', '&amp;gt;') [Data])
GO

-- Sample data with 100k records
CREATE TABLE [dbo].[SampleTable] (
	[Data] NVARCHAR(MAX)
)

INSERT INTO [dbo].[SampleTable]
	( [Data] )
SELECT TOP 100000
	REPLICATE('&lt;' + 'HTML_Tag_Name' + '&gt;', 100)
FROM [msdb].[sys].[columns] [c1]
	CROSS JOIN [msdb].[sys].[columns] [c2]
GO</pre>
<p>Let&#8217;s run two queries against the sample data and compare it between SQL Server 2019 and 2017 and review execution plans generated and query statistics. The first query will use the scalar function, the second one is using CROSS APPLY with the inlined function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="sql">SET STATISTICS TIME ON
SET STATISTICS IO ON
GO

SELECT 
	[dbo].[f_Scalar_ReplaceHTMLTags]([Data]) [ReplacedData]
	INTO [#tmp1]
FROM [dbo].[SampleTable]
GO

SELECT 
	[f].[Data] [ReplacedData]
	INTO [#tmp2]
FROM [dbo].[SampleTable] [st]
	CROSS APPLY [dbo].[f_Inline_ReplaceHTMLTags]([st].[Data]) [f]
GO</pre>
<p><strong>SQL 2017</strong><br />
<img loading="lazy" decoding="async" class="alignnone wp-image-4865" src="https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1.png" alt="" width="618" height="220" srcset="https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1.png 1455w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-300x107.png 300w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-1024x364.png 1024w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-150x53.png 150w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-768x273.png 768w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-360x128.png 360w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-160x57.png 160w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-320x114.png 320w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-520x185.png 520w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-720x256.png 720w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-980x348.png 980w, https://sqlpowered.com/wp-content/uploads/2021/01/Scalar_Function_Blocking_Paralell_Plan_Execution_1-1320x469.png 1320w" sizes="auto, (max-width: 618px) 100vw, 618px" /><br />
<strong>SQL 2019</strong><br />
<img loading="lazy" decoding="async" class="alignnone wp-image-4866" src="https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03.png" alt="" width="623" height="226" srcset="https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03.png 1455w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-300x109.png 300w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-1024x372.png 1024w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-150x54.png 150w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-768x279.png 768w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-360x131.png 360w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-160x58.png 160w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-320x116.png 320w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-520x189.png 520w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-720x261.png 720w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-980x356.png 980w, https://sqlpowered.com/wp-content/uploads/2021/01/Screenshot-19.01.2021-8_31_03-1320x479.png 1320w" sizes="auto, (max-width: 623px) 100vw, 623px" /></p>
<p><strong>2017</strong></p>
<p>CPU time = 44797 ms, elapsed time = 55845 ms. Table &#8216;SampleTable&#8217;. Scan count 5, logical reads 50000.<br />
CPU time = 41189 ms, elapsed time = 10511 ms. Table &#8216;SampleTable&#8217;. Scan count 5, logical reads 50000.</p>
<p><strong>2019</strong></p>
<p>CPU time = 34171 ms, elapsed time = 12179 ms. Table &#8216;SampleTable&#8217;. Scan count 5, logical reads 50000.<br />
CPU time = 33828 ms, elapsed time = 15719 ms. Table &#8216;SampleTable&#8217;. Scan count 5, logical reads 50000</p>
<p>As we can see the only difference in execution plans is that in 2017 scalar function was blocking optimizer from parallel plan creation. We can get rid of this issue when an inlined version of the function will be used. This workaround is not necessary for then SQL Server 2019 where the scalar function was successfully inlined and the same parallel plan was generated for both queries (scalar and inlined).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sqlpowered.com/scalar-functions-and-parallel-execution-plans/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
