Friday, 6 March 2026

SQLDBA - Get Space Used by Tables and Indexes in SQL Server

 Get Space Used by Tables and Indexes in SQL Server

Databases can consume significant amounts of storage, so it’s important to understand how that space is distributed across tables and indexes. SQL Server provides system views and metadata that allow us to query detailed storage information and generate reports at varying levels of granularity.

In this article, I will demonstrate several useful queries along with alternative methods for analyzing storage usage, helping you identify how space is allocated within your database and where optimization opportunities may exist.

What requires storage space?

A SQL Server database consists of several components, and its total size includes the actual data, transaction log data, and additional reserved space allocated for future growth. The data is stored in a primary data file (.mdf), optional secondary data files (.ndf), and at least one transaction log file (.ldf). For more information, you can refer to Microsoft’s documentation on database files and filegroups.

Data within the database is organized into tables and indexes.

  • A table may be stored as a heap or have a clustered index, which contains all the table’s data.
  • Nonclustered indexes are separate copies of the data designed to improve query performance but also consume storage.
  • Indexed views can also use storage space.
  • SQL Server also maintains IAM (Index Allocation Map) pages, which track how extents are allocated to objects.

SQL Server structures data using pages and extents:

  • Every page is 8 KB in size and is the fundamental unit of data storage.
  • An extent consists of eight contiguous pages, totaling 64 KB.
    These structures ensure efficient allocation and management of data inside the database.


Getting storage sizes from properties with SSMS

A simple way to view the total size of a database in SQL Server is through SQL Server Management Studio (SSMS).
By right‑clicking the database in Object Explorer and selecting Properties, you can see the Size and Space Available values on the General page.

The Files page provides a detailed list of all database files, including the reserved size for each file.

For individual tables, you can open the table’s Properties window and navigate to the Storage page. There, you will find the Data Space and Index Space values, which show how much storage the table’s data and indexes consume.


Using SSMS Reports

SQL Server Management Studio also offers reports to get the storage size of tables. These can be accessed by right clicking on a database and then go to “Reports”. The most interesting report is “Disk Usage by Table” which uses the following SQL query:


SELECT

    (row_number() over(order by a3.name, a2.name)) % 2 as l1,

    a3.name AS [schemaname],

    a2.name AS [tablename],

    a1.rows as row_count,

    (a1.reserved + ISNULL(a4.reserved,0))* 8 AS reserved,

    a1.data * 8 AS data,

    (CASE WHEN (a1.used + ISNULL(a4.used,0)) > a1.data THEN (a1.used + ISNULL(a4.used,0)) - a1.data ELSE 0 END) * 8 AS index_size,

    (CASE WHEN (a1.reserved + ISNULL(a4.reserved,0)) > a1.used THEN (a1.reserved + ISNULL(a4.reserved,0)) - a1.used ELSE 0 END) * 8 AS unused

FROM (

    SELECT

        ps.object_id,

        SUM(CASE WHEN (ps.index_id < 2) THEN row_count ELSE 0 END) AS [rows],

        SUM(ps.reserved_page_count) AS reserved,

        SUM(CASE WHEN (ps.index_id < 2) THEN (ps.in_row_data_page_count + ps.lob_used_page_count + ps.row_overflow_used_page_count) ELSE (ps.lob_used_page_count + ps.row_overflow_used_page_count) END) AS data,

        SUM(ps.used_page_count) AS used

    FROM sys.dm_db_partition_stats ps

    WHERE ps.object_id NOT IN (SELECT object_id FROM sys.tables WHERE is_memory_optimized = 1)

    GROUP BY ps.object_id

) AS a1

    LEFT OUTER JOIN (SELECT

                         it.parent_id,

                         SUM(ps.reserved_page_count) AS reserved,

                         SUM(ps.used_page_count) AS used

                     FROM sys.dm_db_partition_stats ps

                         INNER JOIN sys.internal_tables it ON (it.object_id = ps.object_id)

                     WHERE it.internal_type IN (202,204)

                     GROUP BY it.parent_id

                    ) AS a4 ON (a4.parent_id = a1.object_id)

    INNER JOIN sys.all_objects a2 ON (a1.object_id = a2.object_id)

    INNER JOIN sys.schemas a3 ON (a2.schema_id = a3.schema_id)

WHERE a2.type <> N'S'

    and a2.type <> N'IT'

ORDER BY a3.name, a2.name

The data is read from the sys.dm_db_partition_stats view. The LFET JOIN with the sub query is used to get the size for columns with the xml type.


The result shows the count of rows, the reserved size for the whole table (or indexed view), the size of the data (this is the size of the cluster index or heap without the indexes), the size of all indexes including xml columns and the size of unused space.


Using sp_spaceused

sp_spaceused is a stored procedure to get the size of an object. You can either provide the name of a table or nothing to get the size of the whole database.


The @updateusage parameter with the value 'true' will run DBCC UPDATEUSAGE for the given object or the whole database. This can be used to update the stats, since the stats are not guaranteed to be up to date.


-- show the storage space of the whole database

EXECUTE sp_spaceused

-- show the storage space of a single table

EXECUTE sp_spaceused 'Person.Address'

-- update the stats for the given table and show the storage space

EXECUTE sp_spaceused 'Person.Address', 'true'

Getting file sizes with SQL query

The sys.database_files view contains all files of the selected database. The table contains the count of pages per file and in combination with the FILEPROPERTY function and 'SpaceUsed' argument we can get the used pages. By multiplying the count of pages with 8 we get the kilobytes. The final query looks like this:

SELECT

    F.file_id AS FileId,

    F.type_desc AS FileType,

    F.name AS FileName,

    F.physical_name AS PhysicalFileName,

    CASE WHEN F.max_size <= 0 THEN -1 ELSE CAST(CAST(F.max_size AS DECIMAL(18, 2)) * 8 / 1024 AS DECIMAL(18, 2)) END AS MaxSizeMb,

    CAST(CAST(F.size AS DECIMAL(18, 2)) * 8 / 1024 AS DECIMAL(18, 2)) AS TotalMb,

    CAST(CAST(FILEPROPERTY(F.name, 'SpaceUsed') AS DECIMAL(18, 2)) * 8 / 1024 AS DECIMAL(18, 2)) AS UsedMb,

    CAST(CAST((F.size - FILEPROPERTY(F.name, 'SpaceUsed')) AS DECIMAL(18, 2)) * 8 / 1024 AS DECIMAL(18, 2)) AS UnusedMb

FROM sys.database_files AS F

Getting index sizes with SQL query

The sys.allocation_units view contains the allocations in the partitions (container_id). It has a column for the total pages and one for the used pages. The view can be joined with sys.partitions to get the index it belongs to.

The sys.partitions view does contain the allocations by index, the sys.dm_db_partition_stats view, which is used by the “Disk Usage by Table”, is only for tables.

The final query looks like this:

SELECT

    S.name AS SchemaName,

    T.name AS TableName,

    ISNULL(I.name, '') AS IndexName,

    T.object_id AS TableObjectId,

    T.type_desc AS TableType,

    I.index_id AS IndexId,

    I.type_desc AS IndexType,

    A.Rows AS Rows,

    A.TotalPages + ISNULL(X.XmlTotalPages, 0) AS TotalPages,

    A.UsedPages + ISNULL(X.XmlUsedPages, 0) AS UsedPages,

    (A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS UnusedPages,

    (A.TotalPages + ISNULL(X.XmlTotalPages, 0)) * 8 AS TotalKb,

    (A.UsedPages + ISNULL(X.XmlUsedPages, 0)) * 8 AS UsedKb,

    ((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0))) * 8 AS UnusedKb,

    CAST(CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS TotalMb,

    CAST(CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS UsedMb,

    CAST(CAST(((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0))) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS UnusedMb,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS DECIMAL(18, 4)) / CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) * 100

         END AS DECIMAL(18, 4)) AS PercentUsed,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 100 - (CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS DECIMAL(18, 4)) / CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) * 100)

         END AS DECIMAL(18, 4)) AS PercentUnused,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) / SUM(CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4))) OVER() * 100

         END AS DECIMAL(18, 4)) AS PercentFromTotal

FROM sys.objects AS T

    INNER JOIN sys.schemas AS S ON S.schema_id = T.schema_id

    INNER JOIN sys.indexes AS I ON I.object_id = T.object_id

    INNER JOIN (SELECT

                    PA.object_id,

                    PA.index_id,

                    SUM(CASE WHEN AU.type_desc = 'IN_ROW_DATA' THEN PA.rows ELSE 0 END) AS Rows,

                    SUM(AU.total_pages) AS TotalPages,

                    SUM(AU.used_pages) AS UsedPages

                FROM sys.allocation_units AS AU

                    INNER JOIN sys.partitions AS PA ON PA.partition_id = AU.container_id

                GROUP BY PA.object_id, PA.index_id

               ) AS A ON A.object_id = I.object_id AND A.index_id = I.index_id

    LEFT JOIN (SELECT

                   IT.parent_id,

                   SUM(AU.total_pages) AS XmlTotalPages,

                   SUM(AU.used_pages) AS XmlUsedPages

               FROM sys.allocation_units AS AU

                   INNER JOIN sys.partitions AS PA ON PA.partition_id = AU.container_id

                   INNER JOIN sys.internal_tables AS IT ON IT.object_id = PA.object_id

               WHERE IT.internal_type_desc = 'XML_INDEX_NODES'

               GROUP BY IT.parent_id

              ) AS X ON X.parent_id = T.object_id AND I.type_desc IN ('HEAP', 'CLUSTERED')

WHERE T.type_desc IN ('USER_TABLE', 'VIEW')

ORDER BY PercentFromTotal DESC, S.name ASC, T.name ASC, I.index_id ASC

The result does contain the storage sizes in different units and percentages. The xml columns are appended to the clustered index or heap. Indexes on views are also included in the result. The PercentFromTotal column can be used to identify big tables or indexes in the database.


Getting table sizes with SQL query

The query for the indexes is the basis for the table size query. The grouping by index is removed in the allocations sub query and the xml size is appended to the entire table:


SELECT

    S.name AS SchemaName,

    T.name AS TableName,

    T.object_id AS TableObjectId,

    T.type_desc AS TableType,

    A.Rows AS Rows,

    A.TotalPages + ISNULL(X.XmlTotalPages, 0) AS TotalPages,

    A.UsedPages + ISNULL(X.XmlUsedPages, 0) AS UsedPages,

    (A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS UnusedPages,

    (A.TotalPages + ISNULL(X.XmlTotalPages, 0)) * 8 AS TotalKb,

    (A.UsedPages + ISNULL(X.XmlUsedPages, 0)) * 8 AS UsedKb,

    ((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0))) * 8 AS UnusedKb,

    CAST(CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS TotalMb,

    CAST(CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS UsedMb,

    CAST(CAST(((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) - (A.UsedPages + ISNULL(X.XmlUsedPages, 0))) * 8 AS DECIMAL(18, 4)) / 1024 AS DECIMAL(18, 4)) AS UnusedMb,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS DECIMAL(18, 4)) / CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) * 100

         END AS DECIMAL(18, 4)) AS PercentUsed,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 100 - (CAST((A.UsedPages + ISNULL(X.XmlUsedPages, 0)) AS DECIMAL(18, 4)) / CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) * 100)

         END AS DECIMAL(18, 4)) AS PercentUnused,

    CAST(CASE

             WHEN A.TotalPages + ISNULL(X.XmlTotalPages, 0) = 0 THEN

                 0

             ELSE

                 CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4)) / SUM(CAST((A.TotalPages + ISNULL(X.XmlTotalPages, 0)) AS DECIMAL(18, 4))) OVER() * 100

         END AS DECIMAL(18, 4)) AS PercentFromTotal

FROM sys.objects AS T

    INNER JOIN sys.schemas AS S ON S.schema_id = T.schema_id

    INNER JOIN (SELECT

                    PA.object_id,

                    SUM(CASE WHEN AU.type_desc = 'IN_ROW_DATA' AND PA.index_id IN (0, 1) THEN PA.rows ELSE 0 END) AS Rows,

                    SUM(AU.total_pages) AS TotalPages,

                    SUM(AU.used_pages) AS UsedPages

                FROM sys.allocation_units AS AU

                    INNER JOIN sys.partitions AS PA ON PA.partition_id = AU.container_id

                GROUP BY PA.object_id

               ) AS A ON A.object_id = T.object_id

    LEFT JOIN (SELECT

                   IT.parent_id,

                   SUM(AU.total_pages) AS XmlTotalPages,

                   SUM(AU.used_pages) AS XmlUsedPages

               FROM sys.allocation_units AS AU

                   INNER JOIN sys.partitions AS PA ON PA.partition_id = AU.container_id

                   INNER JOIN sys.internal_tables AS IT ON IT.object_id = PA.object_id

               WHERE IT.internal_type_desc = 'XML_INDEX_NODES'

               GROUP BY IT.parent_id

              ) AS X ON X.parent_id = T.object_id

WHERE T.type_desc IN ('USER_TABLE', 'VIEW')

ORDER BY PercentFromTotal DESC, S.name ASC, T.name ASC


Sunday, 1 March 2026

SQLDBA - Know the index creation percentage

by passing the  SPID we  will know the index percentage.  


DECLARE @SPID INT = 170;


;WITH agg AS

(

     SELECT SUM(qp.[row_count]) AS [RowsProcessed],

            SUM(qp.[estimate_row_count]) AS [TotalRows],

            MAX(qp.last_active_time) - MIN(qp.first_active_time) AS [ElapsedMS],

            MAX(IIF(qp.[close_time] = 0 AND qp.[first_row_time] > 0,

                    [physical_operator_name],

                    N'<Transition>')) AS [CurrentStep]

     FROM sys.dm_exec_query_profiles qp

     WHERE qp.[physical_operator_name] IN (N'Table Scan', N'Clustered Index Scan',

                                           N'Index Scan',  N'Sort')

     AND   qp.[session_id] = @SPID

), comp AS

(

     SELECT *,

            ([TotalRows] - [RowsProcessed]) AS [RowsLeft],

            ([ElapsedMS] / 1000.0) AS [ElapsedSeconds]

     FROM   agg

)

SELECT [CurrentStep],

       [TotalRows],

       [RowsProcessed],

       [RowsLeft],

       CONVERT(DECIMAL(5, 2),

               (([RowsProcessed] * 1.0) / [TotalRows]) * 100) AS [PercentComplete],

       [ElapsedSeconds],

       (([ElapsedSeconds] / [RowsProcessed]) * [RowsLeft]) AS [EstimatedSecondsLeft],

       DATEADD(SECOND,

               (([ElapsedSeconds] / [RowsProcessed]) * [RowsLeft]),

               GETDATE()) AS [EstimatedCompletionTime]

FROM   comp;

Monday, 23 February 2026

SQLDBA- Get logging activity into sql server

To know who is connected to the SQL server . we need to schedule this  in  SQL agent to run  very  minimal time to capture all the login information. 

USE [DBA]

GO



SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE TABLE [dbo].[chi_logon](

[LoginName] [nvarchar](100) NOT NULL,

[ClientHost] [nvarchar](100) NOT NULL,

[LoginType] [nvarchar](100) NULL,

[LastPostTime] [datetime] NOT NULL,

[ServerName] [nvarchar](100) NOT NULL,

 CONSTRAINT [pk_chi_logon] PRIMARY KEY CLUSTERED 

(

[LoginName] ASC,

[ClientHost] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

GO



USE [DBA]

GO


SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE TABLE [dbo].[chi_logon_log](

[PostTime] [datetime] NULL,

[ServerName] [nvarchar](100) NULL,

[LoginName] [nvarchar](100) NULL,

[LoginType] [nvarchar](100) NULL,

[ClientHost] [nvarchar](100) NULL

) ON [PRIMARY]

GO





USE [msdb]

GO



BEGIN TRANSACTION

DECLARE @ReturnCode INT

SELECT @ReturnCode = 0


IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Administrative' AND category_class=1)

BEGIN

EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Administrative'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


END


DECLARE @jobId BINARY(16)

EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'ADM - Get Logon Activity', 

@enabled=1, 

@notify_level_eventlog=0, 

@notify_level_email=2, 

@notify_level_netsend=0, 

@notify_level_page=0, 

@delete_level=0, 

@description=N'No description available.', 

@category_name=N'Administrative', 

@owner_login_name=N'sa', 

@notify_email_operator_name=N'DBA ALERT', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [Update known connections]    Script Date: 2/23/2026 9:25:36 PM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Update known connections', 

@step_id=1, 

@cmdexec_success_code=0, 

@on_success_action=3, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=0, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'--Update REcords

WITH logon_CTE (logon, client_address, auth_scheme)

AS

(

SELECT DISTINCT s.[loginame] AS [loginame],

e.[client_net_address] AS [client_address],

e.[auth_scheme]

from sys.dm_exec_connections as e with (NOLOCK)

left outer join sys.sysprocesses as s with (NOLOCK)

on s.[spid] = e.session_id

WHERE s.loginame IS NOT NULL

)

UPDATE l

SET        [LoginType] = c.[auth_scheme]

           ,[LastPostTime] = GETDATE()

FROM dba.dbo.[chi_logon] as l

INNER JOIN logon_CTE as c

  ON c.logon = l.LoginName

  AND c.client_address = l.ClientHost

', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [Insert new connections]    Script Date: 2/23/2026 9:25:36 PM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Insert new connections', 

@step_id=2, 

@cmdexec_success_code=0, 

@on_success_action=1, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=0, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'--Insert new records

INSERT INTO dba.dbo.[chi_logon]

           ([LoginName]

           ,[ClientHost]

           ,[LoginType]

           ,[LastPostTime]

           ,[ServerName])

SELECT DISTINCT s.[loginame] AS [loginame],

e.[client_net_address] AS [client_address],

e.[auth_scheme],

GETDATE(),

@@SERVERNAME

from sys.dm_exec_connections as e with (NOLOCK)

left outer join sys.sysprocesses as s with (NOLOCK)

on s.[spid] = e.session_id

WHERE s.loginame IS NOT NULL

AND NOT EXISTS (SELECT 1

FROM dba.dbo.chi_logon as c

WHERE c.LoginName = s.loginame

AND c.ClientHost = e.client_net_address)

ORDER BY [loginame]

', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'1', 

@enabled=1, 

@freq_type=4, 

@freq_interval=1, 

@freq_subday_type=4, 

@freq_subday_interval=5, 

@freq_relative_interval=0, 

@freq_recurrence_factor=0, 

@active_start_date=20130607, 

@active_end_date=99991231, 

@active_start_time=300, 

@active_end_time=235959, 

@schedule_uid=NULL;

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION

GOTO EndSave

QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

EndSave:

GO



Friday, 20 February 2026

SQLDBA - finding out dated statics

 -- Script - Find Details for Statistics of Whole Database

SELECT DISTINCT

OBJECT_NAME(s.[object_id]) AS TableName,

c.name AS ColumnName,

s.name AS StatName,

STATS_DATE(s.[object_id], s.stats_id) AS LastUpdated,

DATEDIFF(d,STATS_DATE(s.[object_id], s.stats_id),getdate()) DaysOld,

dsp.modification_counter,

s.auto_created,

s.user_created,

s.no_recompute,

s.[object_id],

s.stats_id,

sc.stats_column_id,

sc.column_id

FROM sys.stats s

JOIN sys.stats_columns sc

ON sc.[object_id] = s.[object_id] AND sc.stats_id = s.stats_id

JOIN sys.columns c ON c.[object_id] = sc.[object_id] AND c.column_id = sc.column_id

JOIN sys.partitions par ON par.[object_id] = s.[object_id]

JOIN sys.objects obj ON par.[object_id] = obj.[object_id]

CROSS APPLY sys.dm_db_stats_properties(sc.[object_id], s.stats_id) AS dsp

WHERE OBJECTPROPERTY(s.OBJECT_ID,'IsUserTable') = 1

AND (s.auto_created = 1 OR s.user_created = 1)

ORDER BY DaysOld;



--Script 2: Update Statistics for Database


--EXEC sp_updatestats;

--GO

Thursday, 19 February 2026

SQLDBA - High tempdb Usage: Queries Exceeding 100 MB in the Past 24 Hours

 USE [msdb]

GO


/****** Object:  Job [BTCH - High TEMPDB Usage Details]    Script Date: 2/20/2026 2:42:10 AM ******/

BEGIN TRANSACTION

DECLARE @ReturnCode INT

SELECT @ReturnCode = 0

/****** Object:  JobCategory [[Batch - H]]    Script Date: 2/20/2026 2:42:10 AM ******/

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Batch - H]' AND category_class=1)

BEGIN

EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Batch - H]'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


END


DECLARE @jobId BINARY(16)

EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'BTCH - High TEMPDB Usage Details', 

@enabled=1, 

@notify_level_eventlog=0, 

@notify_level_email=0, 

@notify_level_netsend=0, 

@notify_level_page=0, 

@delete_level=0, 

@description=N'High TEMPDB Usage Details', 

@category_name=N'[Batch - H]', 

@owner_login_name=N'sa', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [report]    Script Date: 2/20/2026 2:42:11 AM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report', 

@step_id=1, 

@cmdexec_success_code=0, 

@on_success_action=1, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=0, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'use staging

go


DECLARE @xml1 NVARCHAR(MAX)

DECLARE @body NVARCHAR(MAX)

DECLARE @today DATE = GETDATE()

DECLARE @recipient_list VARCHAR(MAX)

--CREATE TABLE #tmp1 (

--[id] INT IDENTITY(1,1) PRIMARY KEY,

--event_info NVARCHAR(MAX),

--program_name NVARCHAR(256),

--command NVARCHAR(128),

--start_time DATETIME,

--total_used_mb DECIMAL(10,2),

--login_name NVARCHAR(128),

--transaction_isolation_level NVARCHAR(50),

--dbname NVARCHAR(128)

--);



SET NOCOUNT ON

SET @recipient_list = ''ksubbareddy.sap@gmail.com''

;WITH temp1

AS

(

SELECT

EventInfo,ProgramName,start_time,login_name,transaction_isolation_level,[database_name],[TotalUsedMB]

FROM

(

SELECT

SUBSTRING(event_info,1,100) AS EventInfo,

Program_Name AS ProgramName,

--Command,

start_time,

--run_date,

--TotalUsedMB,

login_name,

transaction_isolation_level,

[database_name],

[TotalUsedMB],

ROW_NUMBER() OVER (PARTITION BY SUBSTRING(event_info,1,100) ORDER BY [TotalUsedMB] DESC) AS seq_num

FROM staging.dbo.tempdb_session_usage_detail a

WHERE a.run_date >= GETDATE() -1

AND command NOT LIKE ''%sp_MSforeachdb%''

AND command NOT LIKE ''%BACKUP DATABASE%''

AND command NOT LIKE ''%UPDATE STATISTICS%''

AND command NOT LIKE ''%sp_server_diagnostics%''

AND command NOT LIKE ''%xp_cmdshell%''

AND program_name NOT LIKE ''SQLAgent - TSQL JobStep%''

)a

WHERE a.seq_num = 1

)


SELECT @xml1 = 

''<html><body><H4>High tempdb Usage: Queries Exceeding 100 MB in the Past 24 Hours.</H4>

<table border = 1> 

<tr>

<th> EventInfo </th> <th> ProgramName </th> <th> StartTime </th> <th> LoginName </th> <th> DBName </th><th> MaxTempDBUsageMB </th> 

</tr>'' +

CAST((SELECT a.EventInfo AS ''td'', 

'''',

a.ProgramName AS ''td'',

'''',

REPLACE(CONVERT(VARCHAR(23), start_time, 126), ''T'', '' '') AS ''td'',

'''',

a.login_name AS ''td'',

'''',

ISNULL(CAST(a.[database_name] AS VARCHAR(10)),'''') AS ''td'',

'''',

a.[TotalUsedMB] AS ''td''


FROM temp1 a

ORDER BY a.[TotalUsedMB] DESC, a.EventInfo ASC

FOR XML PATH(''tr''), ELEMENTS) AS NVARCHAR(MAX)) +

N''</table>'';


SET @body = @xml1 + ''</body></html>''


EXEC msdb.dbo.sp_send_dbmail

@subject = ''High TEMPDB Usage Details in the Past 24 Hours'',

@body = @body,

@body_format =''HTML'',

@recipients = @recipient_list;



--DROP TABLE #tmp1

', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'report', 

@enabled=1, 

@freq_type=4, 

@freq_interval=1, 

@freq_subday_type=1, 

@freq_subday_interval=0, 

@freq_relative_interval=0, 

@freq_recurrence_factor=0, 

@active_start_date=20250908, 

@active_end_date=99991231, 

@active_start_time=500, 

@active_end_time=235959, 

@schedule_uid=NULL;

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION

GOTO EndSave

QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

EndSave:

GO





USE [msdb]

GO


/****** Object:  Job [BTCH - High TEMPDB Usage Details - DBA]    Script Date: 2/20/2026 2:42:42 AM ******/

BEGIN TRANSACTION

DECLARE @ReturnCode INT

SELECT @ReturnCode = 0

/****** Object:  JobCategory [[Batch - H]]    Script Date: 2/20/2026 2:42:42 AM ******/

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Batch - H]' AND category_class=1)

BEGIN

EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Batch - H]'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


END


DECLARE @jobId BINARY(16)

EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'BTCH - High TEMPDB Usage Details - DBA', 

@enabled=1, 

@notify_level_eventlog=0, 

@notify_level_email=0, 

@notify_level_netsend=0, 

@notify_level_page=0, 

@delete_level=0, 

@description=N'High TEMPDB Usage Details', 

@category_name=N'[Batch - H]', 

@owner_login_name=N'sa', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [report]    Script Date: 2/20/2026 2:42:42 AM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report', 

@step_id=1, 

@cmdexec_success_code=0, 

@on_success_action=1, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=0, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'use staging

go


DECLARE @xml1 NVARCHAR(MAX)

DECLARE @body NVARCHAR(MAX)

DECLARE @today DATE = GETDATE()

DECLARE @recipient_list VARCHAR(MAX)

--CREATE TABLE #tmp1 (

--[id] INT IDENTITY(1,1) PRIMARY KEY,

--event_info NVARCHAR(MAX),

--program_name NVARCHAR(256),

--command NVARCHAR(128),

--start_time DATETIME,

--total_used_mb DECIMAL(10,2),

--login_name NVARCHAR(128),

--transaction_isolation_level NVARCHAR(50),

--dbname NVARCHAR(128)

--);



SET NOCOUNT ON

SET @recipient_list = ''ksubbareddy.sap@gmail.com''

;WITH temp1

AS

(

SELECT

SUBSTRING(event_info,1,100) AS EventInfo,

Program_Name AS ProgramName,

--Command,

start_time,

--run_date,

--TotalUsedMB,

(SELECT TOP 1 [TotalUsedMB]

FROM staging.dbo.tempdb_session_usage_detail b

WHERE SUBSTRING(b.event_info,1,100) = SUBSTRING(a.event_info,1,100)

AND B.Program_Name=A.Program_Name

--and b.command=a.command

and b.start_time=a.start_time

AND b.run_date >= GETDATE() -1

ORDER BY [TotalUsedMB] DESC) AS [TotalUsedMB],


login_name,

transaction_isolation_level,

[dop],

[parallel_worker_count]

--database_name as DBName

FROM staging.dbo.tempdb_session_usage_detail a

WHERE a.run_date >= GETDATE() -1

AND command NOT LIKE ''%sp_MSforeachdb%''

AND command NOT LIKE ''%BACKUP DATABASE%''

AND command NOT LIKE ''%UPDATE STATISTICS%''

AND command NOT LIKE ''%sp_server_diagnostics%''

AND command NOT LIKE ''%xp_cmdshell%''

AND program_name NOT LIKE ''SQLAgent - TSQL JobStep%''

GROUP BY 

SUBSTRING(event_info,1,100),

Program_Name,

--Command,

start_time,

--run_date,

--TotalUsedMB,

login_name,

transaction_isolation_level,

[dop],

[parallel_worker_count]

--database_name

)


SELECT @xml1 = 

''<html><body><H4>High tempdb Usage: Queries Exceeding 100 MB in the Past 24 Hours.</H4>

<table border = 1> 

<tr>

<th> EventInfo </th> <th> ProgramName </th> <th> StartTime </th> <th> TempDBUsageMB </th> <th> LoginName </th> <th> DOP </th> <th> ParallelWorkerCount </th> 

</tr>'' +

CAST((SELECT a.EventInfo AS ''td'', 

'''',

a.ProgramName AS ''td'',

'''',

REPLACE(CONVERT(VARCHAR(23), start_time, 126), ''T'', '' '') AS ''td'',

'''',

a.[TotalUsedMB] AS ''td'',

'''',

a.login_name AS ''td'',

'''',

ISNULL(CAST(a.[dop] AS VARCHAR(10)),'''') AS ''td'',

'''',

ISNULL(CAST(a.[parallel_worker_count] AS VARCHAR(10)),'''') AS ''td''


FROM temp1 a

ORDER BY a.[TotalUsedMB] DESC, a.EventInfo ASC

FOR XML PATH(''tr''), ELEMENTS) AS NVARCHAR(MAX)) +

N''</table>'';


SET @body = @xml1 + ''</body></html>''


EXEC msdb.dbo.sp_send_dbmail

@subject = ''High TEMPDB Usage Details in the Past 24 Hours'',

@body = @body,

@body_format =''HTML'',

@recipients = @recipient_list;



--DROP TABLE #tmp1

', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'report', 

@enabled=1, 

@freq_type=4, 

@freq_interval=1, 

@freq_subday_type=1, 

@freq_subday_interval=0, 

@freq_relative_interval=0, 

@freq_recurrence_factor=0, 

@active_start_date=20250908, 

@active_end_date=99991231, 

@active_start_time=1000, 

@active_end_time=235959, 

@schedule_uid=NULL;

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION

GOTO EndSave

QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

EndSave:

GO



SQLDBA_ Shrink tempDB


  

create proc dbo.sp_chi_shrinkdata  

(  

 @dbname  sysname = null,  

 @freed_space  int = null, --  the portion of free space (MB) to reclaim, eg. 90% of 1 gb is 900 mb  

 @stats   int = null --  Display status message every @stats MB.  

)  

as  

  

/*  Author: 

**  Created on:

**  Modified on: 

**  Purpose: Shrinks data file (not log).  

**  Features:  

**    1. Can shrink data size down to a specified value (optional). If desired size not provided, it   

**       will shrink all the way down until there's 200 MB free space left;  

**    2. Can report time, data size and cycle number after each run. If echo interval not supplied,   

**       it will report at every 20 MB interval.  

**  3. No need to be in the current database to shrink. It is in master :-)  

**  Test code:  

**    sp_chi_shrinkdata choice, 200, null  

**    (in waste_mgmt)hi  

*/  

begin  

   

 set nocount on  

  

 declare @start_data_total int,   

  @end_data_total int,   

  @message varchar(150),   

  @count int,   

  @start_data_used int,   

  @cmd varchar(500),   

  @string sysname,   

  @start_data_free int  

  

 if object_id ('tempdb..##datafilesize') is not null  

   drop table ##datafilesize  

  

 create table ##datafilesize   

 (   

  Fileid   tinyint,   

  [FileGroup]  tinyint,   

  TotalExtents  dec (8, 1),   

  UsedExtents  dec (8, 1),   

  [Name]   varchar(50),   

  [FileName]  sysname   

 )  

   

 if @dbname is null  

   set @dbname = db_name()  

  

 set @string = 'use ' + @dbname + ' DBCC SHOWFILESTATS with no_infomsgs'  

  

 insert into ##datafilesize exec (@string)  

  

 set @count = 1  

 --set @datafile = (select rtrim(name) from sysfiles (nolock) where fileid = 1)   

 set @start_data_total = (select TotalExtents * 8 * 8192.0 / 1048576.0 from ##datafilesize )  

     --  Trims away trailing blanks or will NOT work!!  

 --set @start_data_total = @datafile  

 set @start_data_used = (select UsedExtents * 8 * 8192.0 / 1048576.0 from ##datafilesize )  

   

 if @stats is null   

   set @stats = 20 -- default to 20 mb at each interval  

 if @freed_space is null  

   set @freed_space = @start_data_total - @start_data_used - 200   

       --  default to 200 MB free space left after shrinking  

 set @end_data_total = @start_data_total - @freed_space   

   

 print''  

 set @start_data_free = (@start_data_total-@start_data_used)  

 print '*** Before shrinking ***' + char(10) +   

       'Data total:   ' + convert(char(5),@start_data_total) + ' MB' + char(10) +  

       'Data used:    ' + convert(char(5),@start_data_used) + ' MB' + char(10) +   

       'Data free:    ' + convert(char(5),@start_data_free) + ' MB' + char(10)  

 print ''  

 print 'Starting shrinking data file for ' + @dbname + '......' + char(10)  

 while @start_data_total > @end_data_total  

   begin  

     set @start_data_total = @start_data_total - @stats  

 --    set @cmd = N' dbcc shrinkfile ('+@datafile+', '+convert(varchar(5), @start_data_total)+') WITH NO_INFOMSGS'  

   set @cmd = N' use ' + @dbname + ' dbcc shrinkfile (1, '+convert(varchar(5), @start_data_total)+') WITH NO_INFOMSGS'  

     exec (@cmd)  

     truncate table ##datafilesize  

     insert into ##datafilesize exec (@string)  

     set @start_data_total = (select TotalExtents * 8 * 8192.0 / 1048576.0 from ##datafilesize )  

 --    set @start_data_used = (select UsedExtents * 8 * 8192.0 / 1048576.0 from ##datafilesize )  

     set @message = convert(varchar(30), getdate(), 20) + '       - cycle ' + convert (varchar(6), @count) +   

       '        - Free space left: ' + convert(char(5),(@start_data_total - @start_data_used)) + ' MB'    

     print @message  

     set @count = @count + 1  

   end  

 print ''  

 print 'Shrinking data complete. Total reclaimed space: ' + cast((@start_data_free - (@start_data_total - @start_data_used)) as varchar(10)) + ' MB'   

       + char(10) + char(10) + '*** After shrinking ***' + char(10) +   

       'Data total:   ' + convert(char(5),@start_data_total) + ' MB' + char(10) +  

       'Data used:    ' + convert(char(5),@start_data_used) + ' MB' + char(10) +   

       'Data free:    ' + convert(char(5),(@start_data_total - @start_data_used)) + ' MB' + char(10)  

  

 drop table ##datafilesize  

  

 return (0)  -- sp_chi_shrinkdata  

end  



USE [msdb]

GO


/****** Object:  Job [ADM - Shrink TempDB]    Script Date: 2/20/2026 2:39:41 AM ******/

BEGIN TRANSACTION

DECLARE @ReturnCode INT

SELECT @ReturnCode = 0

/****** Object:  JobCategory [Database Maintenance]    Script Date: 2/20/2026 2:39:41 AM ******/

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Database Maintenance' AND category_class=1)

BEGIN

EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Database Maintenance'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


END


DECLARE @jobId BINARY(16)

EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'ADM - Shrink TempDB', 

@enabled=1, 

@notify_level_eventlog=2, 

@notify_level_email=0, 

@notify_level_netsend=0, 

@notify_level_page=0, 

@delete_level=0, 

@description=N'Shrinks TempDB', 

@category_name=N'Database Maintenance', 

@owner_login_name=N'sa', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [shink]    Script Date: 2/20/2026 2:39:41 AM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'shink', 

@step_id=1, 

@cmdexec_success_code=0, 

@on_success_action=1, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=1, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'sp_chi_shrinkdata tempdb, 2000, 1000', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION

GOTO EndSave

QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

EndSave:

GO



SQLDBA- Large Temp Trans Notification

USE [msdb]

GO


/****** Object:  Job [ADM - Large Temp Trans Notification]    Script Date: 2/20/2026 2:35:31 AM ******/

BEGIN TRANSACTION

DECLARE @ReturnCode INT

SELECT @ReturnCode = 0

/****** Object:  JobCategory [Database Maintenance]    Script Date: 2/20/2026 2:35:31 AM ******/

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Database Maintenance' AND category_class=1)

BEGIN

EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Database Maintenance'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


END


DECLARE @jobId BINARY(16)

EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'ADM - Large Temp Trans Notification', 

@enabled=1, 

@notify_level_eventlog=2, 

@notify_level_email=0, 

@notify_level_netsend=0, 

@notify_level_page=0, 

@delete_level=0, 

@description=N'No description available.', 

@category_name=N'Database Maintenance', 

@owner_login_name=N'sa', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

/****** Object:  Step [step_one]    Script Date: 2/20/2026 2:35:32 AM ******/

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'step_one', 

@step_id=1, 

@cmdexec_success_code=0, 

@on_success_action=1, 

@on_success_step_id=0, 

@on_fail_action=2, 

@on_fail_step_id=0, 

@retry_attempts=0, 

@retry_interval=1, 

@os_run_priority=0, @subsystem=N'TSQL', 

@command=N'EXEC msdb.dbo.sp_send_dbmail @recipients = ''ksubbareddy.sap@gmail.com'', 

   @query = ''use tempdb; DBCC OPENTRAN'',

   @subject = ''Large TempDB OpenTran Report'',

   @body = ''Open Transaction for TempDB as log size has increased over 3.75GB:''

', 

@database_name=N'master', 

@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION

GOTO EndSave

QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

EndSave:

GO



SQLDBA - Get Space Used by Tables and Indexes in SQL Server

 Get Space Used by Tables and Indexes in SQL Server Databases can consume significant amounts of storage, so it’s important to understand ho...