I used this on one of our larger databases. I had to make some changes as I was getting overflow errors.
Changed all the decimal definitions from Decimal(10,2) to Decimal(15,2)
Changed a number of items that were INT to BIGINT
--Revised Code
CREATE PROCEDURE [dbo].[USP_Compression_Savings_By_DB]
@checkdb nvarchar(255) = null-- database we will be checking (by default runs all databases)
, @admindbname nvarchar(255) = null-- administrative database for holding the data (defaults to database where the Procedure is created)
AS
/****************************************************************************************
* THIS IS FOR ESTIMATION PURPOSES ONLY, YOU SHOULD PERFORM FULL TESTS AS REGARDS ACTUAL *
* SAVINGS AND POTENTIAL NEGATIVE IMPACT TO PERFORMANCE PRIOR TO IMPLEMENTING ANY CHANGES*
* IN A PRODUCTION ENVIRONMENT.*
**
* RECOMMENDED TO RUN THIS AGAINST A STAGING OR DEVELOPMENT SYSTEM DUE TO THE POTENTIAL *
* IMPACT UPON THE SERVER WHILE RUNNING. AT THE VERY LEAST RUN THIS DURING YOUR SERVERS *
* QUIETIST PERIOD.*
****************************************************************************************/
/****************************************************************************************
* The procedure runs the procedure sp_estimate_data_compression_savings against all or *
* a selected database with all tables and all schemas.*
* It accepts two paramters, the first @checkdb will be the database that you wish to *
* estimate compression for. The second @admindbname is the database into which you wish *
* to place the results table dbo.estimated_data_compression_savings. By default this *
* will be located in the same database as the procedure was created.*
* We ignore system databases, as they cannot be compressed anyway.*
**
* Limitations:*
**
* cannot examine individual indexes, this could prove useful*
* cannot evaluate different partitions, very useful in DW systems*
* no ability to pass multiple databases to the @checkdb variable*
****************************************************************************************/
SET NOCOUNT ON
--declare all the variables
declare @varint int-- integer value of the database
, @SQLCmd1 NVARCHAR(4000)-- Dynamic SQL statment for sp_execute sql statements
, @ParmDef1 NVARCHAR(4000)-- Dynamic SQL statment for sp_execute sql statements
, @database varchar(200)-- used by the cursor
, @sql varchar(2000)-- holds the query we'll use for running the estimations page
, @sql2 varchar(2000)-- holds the query we'll use for running the estimations row
, @schema varchar(2000)-- allows us to query multiple schemas
, @table sysname-- table we are testing compression estimations again
, @kbsize bigint-- how big the data is
, @loop int-- what we use to loop around the table listing
/****** if there is no @admindbname variable we just use the current database ******/
if @admindbname is null
begin
select @admindbname = DB_NAME()
end
/****** if we are checking just one database check that it exists ******/
if @checkdb is not null
begin
if exists (select DATABASE_id from master.sys.databases where @checkdb = name and state = 0)
begin
select name from master.sys.databases where name = @checkdb and state = 0
end
else
begin
raiserror (N'The Database %s does not exist.', 16, 0, @checkdb)
end
end
/****** if we have a single database to check set the cursor for that one db ******/
if @checkdb is not null
begin
select @varint = DATABASE_id from master.sys.databases where @checkdb = name and state = 0
DECLARE database_names CURSOR
FOR SELECT database_id FROM master.sys.databases
WHERE database_id = @varint
end
else
/****** otherwise set the cursor to work on all databases (non system that is) ******/
begin
DECLARE database_names CURSOR
FOR SELECT database_id FROM master.sys.databases
WHERE database_id > 4 and state = 0
end
DECLARE @database_id int
OPEN database_names
FETCH NEXT FROM database_names INTO @database_id
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
/****** initialize the @checkdb parameter even though defined at the start ******/
select @checkdb = name from master.sys.databases where database_id = @database_id
--print @checkdb
/****** if the table we are dumping data into doesn't exist, let's create it ******/
SET @SQLCmd1 = N'IF NOT EXISTS (SELECT 1 FROM '+@AdminDBName+'.sys.tables
WHERE name = '+char(39)+'estimated_data_compression_savings'+char(39)+')
CREATE TABLE '+@AdminDBName+'.dbo.estimated_data_compression_savings
(database_name sysname
, [object_name] sysname
, [schema_name] sysname
, current_size_KB bigint
, estimated_size_page_KB bigint
, estimated_page_savings_KB bigint
, estimated_page_savings_percent decimal(15,2)
, estimated_size_row_KB bigint
, estimated_row_savings_KB bigint
, estimated_row_savings_percent decimal(15,2)) '
SET @ParmDef1 = N'@AdminDBName VARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName
/****** clear out any existing data from the table for the database we are checking ******/
SET @SQLCmd1 = N'DELETE FROM '+@AdminDBName+'.dbo.estimated_data_compression_savings
WHERE database_name = '+CHAR(39)+@checkdb+char(39)+''
SET @ParmDef1 = N'@AdminDBName VARCHAR(255), @checkdb NVARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName, @checkdb
/****** just in case the temp table exists, get rid of it ******/
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
--IF OBJECT_ID('tempdb..#thedata') IS NOT NULL
-- DROP TABLE #thedata
/****** create the temp table to hold the current size information for each table in the database ******/
CREATE TABLE #temp (
theid INT IDENTITY(1,1),
table_name sysname ,
row_count BIGINT,
reserved_size VARCHAR(50),
data_size VARCHAR(50),
index_size VARCHAR(50),
unused_size VARCHAR(50))
/****** get the list of tables (with their schemas) for this database ******/
SET @SQLCmd1 = N'INSERT #temp (table_name, row_count,reserved_size,data_size,index_size,unused_size)
EXEC ['+@checkdb+']..sp_msforeachtable '+char(39)+'sp_spaceused'+char(39)+char(39)+'?'+char(39)+char(39)+char(39)+''
SET @ParmDef1 = N'@checkdb NVARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@checkdb
/****** and another temp table to create ******/
/****** for some reason this doesn't work with a # table ******/
IF OBJECT_ID('tempdb..thedata') IS NOT NULL
DROP TABLE tempdb..thedata
CREATE TABLE tempdb..thedata (
theidint
,table_namenvarchar (750)
,schemanamesysname
,row_countBIGINT
,col_countINT
,data_sizeBIGINT )
/****** get the information about the tables ******/
SET @SQLCmd1 = N'INSERT INTO tempdb..thedata
SELECT a.theid,
a.table_name,
s.name as schemaname,
a.row_count,
COUNT(*) AS col_count,
CAST(REPLACE(a.data_size, '+CHAR(39)+'KB'+CHAR(39)+','+CHAR(39)++CHAR(39)+') AS integer) as data_size
FROM #temp a
INNER JOIN ['+@checkdb+'].information_schema.columns b
ON a.table_name COLLATE database_default
= b.table_name COLLATE database_default
INNER JOIN ['+@checkdb+'].sys.objects o
ON a.table_name COLLATE database_default
= o.name COLLATE database_default
INNER JOIN ['+@checkdb+'].sys.schemas s
ON o.schema_id = s.schema_id
GROUP BY a.table_name, a.row_count, a.data_size, s.name,a.theid
ORDER BY CAST(REPLACE(a.data_size, '+CHAR(39)+'KB'+CHAR(39)+','+CHAR(39)++CHAR(39)+') AS integer) DESC'
--select * from #thedata
SET @ParmDef1 = N'@checkdb NVARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@checkdb
/****** get rid of the temp table, we don't need it any more ******/
DROP TABLE #temp
/****** create a couple of tables to hold the compression test information ******/
IF OBJECT_ID('tempdb..#data_compression_page') IS NOT NULL
DROP TABLE #data_compression_page
IF OBJECT_ID('tempdb..#data_compression_row') IS NOT NULL
DROP TABLE #data_compression_row
create table #data_compression_page
([object_name] sysname
, [schema_name] sysname
, index_id int
, partition_number int
, [size_with_current_compression_setting(KB)] bigint
, [size_with_requested_compression_setting(KB)] bigint
, [sample_size_with_current_compression_setting(KB)] bigint
, [sample_size_with_requested_compression_setting(KB)] bigint)
create table #data_compression_row
([object_name] sysname
, [schema_name] sysname
, index_id int
, partition_number int
, [size_with_current_compression_setting(KB)] bigint
, [size_with_requested_compression_setting(KB)] bigint
, [sample_size_with_current_compression_setting(KB)] bigint
, [sample_size_with_requested_compression_setting(KB)] bigint)
/****** here we are going to loop through the table/schema list and check the
estimated savings for both row and page compression ******/
select @loop = min(theid) from tempdb..thedata
while @loop > 0 and @loop is not null
begin
select @table = table_name from tempdb..thedata where theid = @loop
select @schema = schemaname from tempdb..thedata where theid = @loop
/****** this is where we build the statement to estimate the savings that compression could provide ******/
/****** at the page level ******/
select @sql = 'insert into #data_compression_page exec ['+@checkdb+']..sp_estimate_data_compression_savings
@schema_name = '''+@schema+'''
, @object_name = '''+@table+'''
, @index_id = null
, @partition_number = null
, @data_compression = ''page'';'
/****** at the row level ******/
select @sql2 = 'insert into #data_compression_row exec ['+@checkdb+']..sp_estimate_data_compression_savings
@schema_name = '''+@schema+'''
, @object_name = '''+@table+'''
, @index_id = null
, @partition_number = null
, @data_compression = ''row'';'
/****** get the data ******/
exec(@sql)
exec(@sql2)
/****** loop around ******/
select @loop = MIN(theid) from tempdb..thedata where theid > @loop and @loop is not null
end
/****** get rid of the data table, we no longer need it ******/
drop table tempdb..thedata
/****** update the main table with the page estimations ******/
SET @SQLCmd1 = N'INSERT INTO '+@AdminDBName+'.dbo.estimated_data_compression_savings
(database_name
, [object_name]
, [schema_name]
, current_size_KB
, estimated_size_page_KB
)
SELECT
'+CHAR(39)+@checkdb+char(39)+'
,[object_name]
,[schema_name]
,sum([size_with_current_compression_setting(KB)])
,sum([size_with_requested_compression_setting(KB)])
FROM #data_compression_page
group by [object_name]
,[schema_name]
'
SET @ParmDef1 = N'@AdminDBName VARCHAR(255), @checkdb NVARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName, @checkdb
/****** we create a table to hold the results of estimated row compression ******/
IF OBJECT_ID('tempdb..#rowupdates') IS NOT NULL
DROP TABLE #rowupdates
CREATE TABLE #rowupdates
(database_name sysname
, [object_name] sysname
, [schema_name] sysname
, current_size_KB bigint
, estimated_size_row_KB bigint
, estimated_row_savings_KB bigint
, estimated_row_savings_percent int)
/****** then we insert the estimated savings based on row compression ******/
SET @SQLCmd1 = N'INSERT INTO #rowupdates
(database_name
, [object_name]
, [schema_name]
, current_size_KB
, estimated_size_row_KB
)
SELECT
'+CHAR(39)+@checkdb+char(39)+'
,[object_name]
,[schema_name]
,sum([size_with_current_compression_setting(KB)])
,sum([size_with_requested_compression_setting(KB)])
FROM #data_compression_row
group by [object_name]
,[schema_name]
'
SET @ParmDef1 = N'@AdminDBName VARCHAR(255), @checkdb NVARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName, @checkdb
/****** now update the master table with the row compression estimations ******/
SET @SQLCmd1 = N'UPDATE '+@AdminDBName+'.dbo.estimated_data_compression_savings
set estimated_size_row_KB = r.estimated_size_row_KB
from #rowupdates r
INNER JOIN '+@AdminDBName+'.dbo.estimated_data_compression_savings e
ON r.object_name = e.object_name
and r.schema_name = e.schema_name
and r.database_name = e.database_name'
SET @ParmDef1 = N'@AdminDBName VARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName
/****** provide diff data between current and estimated ******/
SET @SQLCmd1 = N'UPDATE '+@AdminDBName+'.dbo.estimated_data_compression_savings
set estimated_page_savings_KB = current_size_KB - estimated_size_page_KB
, estimated_row_savings_KB = current_size_KB - estimated_size_row_KB
'
SET @ParmDef1 = N'@AdminDBName VARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName
/****** finally run a percentage calculation to show the estimated savings ******/
SET @SQLCmd1 = N'UPDATE '+@AdminDBName+'.dbo.estimated_data_compression_savings
set estimated_page_savings_percent =
((isnull(CAST(estimated_page_savings_KB AS Decimal(15,2)) / nullif(CAST(current_size_kb AS decimal(15,2)),0),0)) * 100)
, estimated_row_savings_percent =
((isnull(CAST(estimated_row_savings_KB AS Decimal(15,2)) / nullif(CAST(current_size_kb as decimal(15,2)),0),0)) * 100)
'
SET @ParmDef1 = N'@AdminDBName VARCHAR(255)'
EXEC sp_executesql @SQLCmd1
,@parmdef1
,@AdminDBName
END
/****** loop around the cursor ******/
FETCH NEXT FROM database_names INTO @database_id
END
CLOSE database_names
DEALLOCATE database_names
GO
david hay