Determing SQL Server Table Size 

A common problem that I have recently encountered was trying to identify areas of my website database that were taking up the most physical storage space. At work I maintain a DotNetNuke installation that includes a 4.5 Gb reporting database and I was trying to pinpoint the exact sizes of ALL tables in my database and was having a few problems. I started using the "sp_spaceused" stored procedure but I had to run that for every table, I have over 200 tables in this database and it was just not feasiable for me to do it this way. Therefore I wrote a stored procedure that will perform all needed data calls and will return a result set with the data on all tables. In this post I will share the script as well as a few interesting things I learned while writing it.

Environments

This stored procedure has been tested and used on a SQL Server 2000 instance, this may or may not work successfully on other versions of SQL Server, I have not yet had the time to test.

Overview

The process that the stored procedure goes through is very simple, first we declare a cursor that will get the names of all user defined tables in the current database. Then we create a temporary table to store the individual data elements for each table. Then we loop through the created cursor and save the results of the sp_spaceused command to our temporary table. The last steps include closing and deallocating the cursor, selecting all rows from out temp table and lastly dropping that table.

When I created this procedure the one item that I found to be very intersting was the INSERT [TABLE] EXEC [SP] portion. I have used INSERT INTO [TABLE] SELECT ... type statements before where I use a query to load data, but never before had I loaded data into a table directly from the results of a stored procedure execution.

The Script

CREATE PROCEDURE GetAllTableSizes
AS
/*
    Obtains spaced used data for ALL user tables in the database
*/
DECLARE @TableName VARCHAR(100)    --For storing values in the cursor

--Cursor to get the name of all user tables from the sysobjects listing
DECLARE tableCursor CURSOR
FOR 
select 
[name]
from dbo.sysobjects 
where  OBJECTPROPERTY(idN'IsUserTable'1
FOR READ ONLY

--A procedure level temp table to store the results
CREATE TABLE #TempTable
(
    tableName 
varchar(100),
    numberofRows 
varchar(100),
    reservedSize 
varchar(50),
    dataSize 
varchar(50),
    indexSize 
varchar(50),
    unusedSize 
varchar(50)
)

--Open the cursor
OPEN tableCursor

--Get the first table name from the cursor
FETCH NEXT FROM tableCursor INTO @TableName

--Loop until the cursor was not able to fetch
WHILE (@@Fetch_Status >0)
BEGIN
    
--Dump the results of the sp_spaceused query to the temp table
    
INSERT  #TempTable
        
EXEC sp_spaceused @TableName

    
--Get the next table name
    
FETCH NEXT FROM tableCursor INTO @TableName
END

--Get rid of the cursor
CLOSE tableCursor
DEALLOCATE tableCursor

--Select all records so we can use the reults
SELECT 
FROM #TempTable

--Final cleanup!
DROP TABLE #TempTable

GO

The above is the actual stored procedure, this must be created first then you simply call this one procedure to get the list of tables and sizes. At the bottom of this article you will find a zip file available for download that will include this script file.

Usage

Now to get the table sizes for the current database you can simply run the following command.

EXEC GetAllTableSizes

The results of this procedure execution will show you the number of rows and physical sizes of each user defined table in your database.

Downloads

Click here to download a copy of the SQL Statement used to create this procedure.

Posted by Mitchel on Friday, July 27, 2007
 

Comments

Comments from the following blog entry: Determining All Table Sizes in SQL Server, located at: http://www.publicidentity.ca/post/2007/11/Determining-All-Table-Sizes-in-SQL-Server.aspx

By Public Identity on Monday, November 12, 2007 at 5:21 AM

Very Nice Workaround!! Keep up the good work!!

By John Katsiotis on Thursday, February 21, 2008 at 1:36 AM

Great tool, but I would like to see them listed by datasize with the largest on top. How would that be done? ORDER BY does not seem to work correctly.

By Justin on Friday, February 29, 2008 at 4:15 PM

Justin,

The result from SQL Server is a varchar data element. If you wanted to be able to sort it based on the sizes, when inserting the values into the temporary table, you would have to trim off the KB designator at the end of the size, and convert it to a numeric data type column. It is do-able, but you will have to modify the SQL to get it done.

if you want post a request in my Forum and I can try get a modified version of the script out there.

By mitchel.sellers@gmail.com on Saturday, March 01, 2008 at 6:03 AM

hi
there is a simple wayt to get size off table is sql sever by SP_SPACEUSED 'TABLENAME'

By bahman on Saturday, May 31, 2008 at 7:18 PM

Yes, that is the method that this uses, it just combines the info for ALL tables in your database.

By mitchel.sellers@gmail.com on Sunday, June 01, 2008 at 4:51 PM

ORDER BY CAST(LEFT(dataSize,LEN(dataSize)-3) AS NUMERIC(18,0)) DESC

By Mike M on Tuesday, June 10, 2008 at 1:39 PM

Your SP seems to be working for objects with 'dbo' owner but in the case of tables with schema name prefixed results in error. The SP has to be modified according to that.

By Bala on Thursday, July 03, 2008 at 10:49 PM

Nice one! Helped me find what was making an inherited database 16Gb big (15 Gb in a single, unnecessary, stats table!).

By Tony B on Wednesday, July 16, 2008 at 7:39 AM

Many thanks, much appreciated.

By grafeful on Tuesday, August 19, 2008 at 7:18 AM

For SQL2000 (and earlier) you should run DBCC UPDATEUSAGE to make sure you get the correct counts returned. From Books Online:
In earlier (before 2005) versions of SQL Server, the values for the table and index row counts and page counts can become incorrect. Databases that were created on versions prior to SQL Server 2005 may contain incorrect counts. Therefore, we recommend running DBCC UPDATEUSAGE after upgrading to SQL Server 2005 to correct any invalid counts.

By Patrick on Tuesday, September 23, 2008 at 2:07 PM

Yes, that is a good point, the down side, is recalculation is VERY costly if you go to run it on ALL tables inside a database at once, thus why I left that option off in this example.

By mitchel.sellers@gmail.com on Tuesday, September 23, 2008 at 4:18 PM

Superb.....Its Very helpful.

By Raghavendra Nukala on Wednesday, October 01, 2008 at 12:36 AM

I found the following very useful.

http://www.therightstuff.de/2007/11/19/How-To-Obtain-The-Size-Of-All-Tables-In-A-SQL-Server-Database.aspx

Here I can't see any screenshot to see how it works so I was a bit hesitant to use the script.

So, I used the steps from above link instead.

Anyway, thank you for your posting.

By Fiaz on Sunday, October 12, 2008 at 7:33 PM

I think the following statement would suffice

EXEC sp_MSforeachtable @command1="EXEC sp_spaceused '?'"

By sarathy on Wednesday, November 12, 2008 at 12:41 AM

Nice script. Did anyone get it to work with tables within schema?

By FelixL on Thursday, December 18, 2008 at 5:18 PM

Let me do a bit of research, I think I might be able to do it.

By mitchel.sellers@gmail.com on Friday, December 19, 2008 at 3:29 AM

I like taking shortcuts. I opened sp_spaceused and came up with this:


ALTER Procedure [dbo].[spTableSize] AS

Declare @Low float
select @Low = d.low / 1024.
from master.dbo.spt_values d
where d.number = 1
and d.type = 'E'

select o.name, str((sum(i1.dpages) + isnull(sum(i2.used), 0)) * @Low) + ' KB'
from sysindexes i1
inner join sysobjects o on o.id = i1.id
inner join sysindexes i2 on o.id = i2.id
where i1.indid < 2
and i2.indid < 255
group by o.name

Someone said there is even a way to do this as a view and thereby be able to use it in joins.

By Mark Stouffer on Monday, January 05, 2009 at 4:18 PM

Very helpful. Thanks Mitchel. I'd like to second the "sorted" results idea.

Dax

By dax on Wednesday, January 21, 2009 at 5:43 PM

Quick addition to sort the results by the number of rows.

SELECT * FROM #TempTable ORDER BY cast(numberofrows as int) DESC

By Matthew Hardcastle on Monday, January 26, 2009 at 11:23 AM

This creates the USP and order by row counts.

--


CREATE PROCEDURE usp_GetAllTableSizes
AS
/*
Obtains spaced used data for ALL user tables in the database
*/


--A procedure level temp table to store the results
CREATE TABLE #TempTable
(
Name varchar(100),
Rows varchar(100),
reserved varchar(50),
data varchar(50),
index_Size varchar(50),
unused varchar(50)
)


--Dump the results of the sp_spaceused query to the temp table
INSERT #TempTable
Exec sp_MSforeachtable @command1="exec sp_spaceused'?'"

--Select all records so we can use the reults
SELECT *
FROM #TempTable
Order By Rows

--Final cleanup!-

DROP TABLE #TempTable

By Peter on Friday, February 20, 2009 at 3:04 AM

In the create table section it can be a better soulution to use int as datatype in the Rows Coloumn. Gives a better sort.

By Peter on Friday, February 20, 2009 at 3:20 AM

Thanks a lot - helped a lot.

By Gideon on Thursday, March 26, 2009 at 4:59 AM

This works well and can be queried into other views, etc.:

alter function ObjSpaceUsed
(
@id int
)
returns @objInfo table( SchemaName sysname, ObjName sysname, ObjRows int, ObjReservedBytes int, ObjDataBytes int, ObjIndexBytes int, ObjUnusedBytes int )
as
begin

declare @type character(2) -- The object type.
,@pages bigint -- Working variable for size calc.
,@dbname sysname
,@dbsize bigint
,@logsize bigint
,@reservedpages bigint
,@usedpages bigint
,@rowCount bigint
,@schemaName sysname

select @schemaName = SCHEMA_NAME(schema_id)
from sys.objects
where object_id = @id

SELECT
@reservedpages = SUM (reserved_page_count),
@usedpages = SUM (used_page_count),
@pages = SUM (
CASE
WHEN (index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count)
ELSE lob_used_page_count + row_overflow_used_page_count
END
),
@rowCount = SUM (
CASE
WHEN (index_id < 2) THEN row_count
ELSE 0
END
)
FROM sys.dm_db_partition_stats
WHERE object_id = @id;

/*
** Check if table has XML Indexes or Fulltext Indexes which use internal tables tied to this table
*/
IF (SELECT count(*) FROM sys.internal_tables WHERE parent_id = @id AND internal_type IN (202,204,211,212,213,214,215,216)) > 0
BEGIN
/*
** Now calculate the summary data. Row counts in these internal tables don't
** contribute towards row count of original table.
*/
SELECT
@reservedpages = @reservedpages + sum(reserved_page_count),
@usedpages = @usedpages + sum(used_page_count)
FROM sys.dm_db_partition_stats p, sys.internal_tables it
WHERE it.parent_id = @id AND it.internal_type IN (202,204,211,212,213,214,215,216) AND p.object_id = it.object_id;
END

insert @objInfo
SELECT
@schemaName,
object_name(@id),
rows = @rowCount,
reserved = @reservedpages * 8192,
data = @pages * 8192,
index_size = CASE WHEN @usedpages > @pages THEN (@usedpages - @pages) ELSE 0 END * 8192,
unused = CASE WHEN @reservedpages > @usedpages THEN (@reservedpages - @usedpages) ELSE 0 END * 8192

return
end
go
select osu.*
from sys.objects so
CROSS APPLY dbo.ObjSpaceUsed(so.object_id) osu
where so.type='U'

By Dathan on Friday, April 24, 2009 at 9:02 AM

In my just prior comment, to use this function for a single object by name, just call:

select * from dbo.ObjSpaceUsed(object_id('someschema.someobject'))

By Dathan on Friday, April 24, 2009 at 9:06 AM

Great! Thanks a lot. :)

By Devil_De on Thursday, April 30, 2009 at 12:52 AM

If you change 'SELECT * FROM #TempTable' to the following will display the largest reserved space first.

SELECT *, replace(reservedSize, 'KB','')*1 as rSize FROM #TempTable order by rSize desc

By linmic on Sunday, October 11, 2009 at 7:47 PM

Nice thread ! thanks to everyone for the contribution ..saved me a bunch of time !! :)

By BI_Dude on Wednesday, October 28, 2009 at 5:23 PM

Nice! saved me a lot of work.. thanks.

By Victor Yoalli on Tuesday, November 17, 2009 at 4:54 AM

Thanks for all of your help. It saves lot of time

By Masud Parvez on Wednesday, December 09, 2009 at 9:04 PM

You have to alter the script in order to get the schemaname.table name. Otherwise SQL Server will give you only the tables from the dbo schema.

By Feodor on Monday, December 14, 2009 at 8:22 PM

Thanks a lot Mitchel !!!!!! It works like a charm, I had all informations I wanted to have, your article is VERY clear for a novice like me, everything is in to understand and to apply.
CONGRATULATIONS !! One of the best article regarding DNN and how to maintain a database.

Déclic Vidéo FX

By Declic Video on Friday, January 01, 2010 at 9:19 PM

Comments from the following blog entry: Determing SQL Server Table Size, located at: http://pimpthisblog.com/Determing-SQL-Server-Table-Size

By PimpThisBlog.com on Friday, January 08, 2010 at 4:48 PM

Thanks very much. Very clear explanation. Helped me save a lot of time.

By Jeetal on Thursday, February 11, 2010 at 11:20 AM

how about this?
SELECT B.name, SUM(A.reserved_page_count * 8)/1024
FROM sys.dm_db_partition_stats A, sys.OBJECTS B
WHERE A.object_id = B.object_id
GROUP BY B.name
ORDER BY 2 DESC

By YUNA on Tuesday, March 02, 2010 at 6:44 PM

how about this?
SELECT B.name, SUM(A.reserved_page_count * 8)/1024
FROM sys.dm_db_partition_stats A, sys.OBJECTS B
WHERE A.object_id = B.object_id
GROUP BY B.name
ORDER BY 2 DESC

By YUNA on Tuesday, March 02, 2010 at 6:44 PM

how about this?
SELECT B.name, SUM(A.reserved_page_count * 8)/1024
FROM sys.dm_db_partition_stats A, sys.OBJECTS B
WHERE A.object_id = B.object_id
GROUP BY B.name
ORDER BY 2 DESC

By YUNA on Tuesday, March 02, 2010 at 6:44 PM

how about this?
SELECT B.name, SUM(A.reserved_page_count * 8)/1024
FROM sys.dm_db_partition_stats A, sys.OBJECTS B
WHERE A.object_id = B.object_id
GROUP BY B.name
ORDER BY 2 DESC

By YUNA on Tuesday, March 02, 2010 at 6:45 PM

For the order by DataSize I made this simple change:
ORDER BY LEN(dataSize) DESC, LEFT(datasize,1) DESC

By Dennis on Wednesday, March 10, 2010 at 6:15 AM

Hi

I've modified your script. Thanks for your permission to post it on my blog. I've changed and parameterised the ORDER BY clause of the SELECT statement. You can find it <a href="http://bit.ly/AJZA_tblsz">here</a>.

Thank you
Andrew Jackson

By AndrewJacksonZA on Wednesday, April 21, 2010 at 3:53 AM

Comments from the following blog entry: Determine All SQL Server Table Sizes, located at: http://stevesmithblog.com/blog/determine-all-sql-server-table-sizes/

By Community Blogs on Wednesday, April 28, 2010 at 5:21 AM

You can also use Truncate commands to clear Eventlog ScheduleHistory and other troublemaker tables. We used this tip at our <a href="http://www.bookinturkeytours.com" target="_blank" title="book a cultural tour at Turkey">travel blog</a> several times.

By travel bookings on Friday, May 14, 2010 at 7:56 AM

Comments from the following blog entry: Listing the size of all tables in a SQL Server Database, located at: http://dave.harris.net/post/2010/06/02/Listing-the-size-of-all-tables-in-a-SQL-Server-Database.aspx

By dave.harris.net on Wednesday, June 02, 2010 at 6:09 AM

Comments from the following blog entry: Listing the size of all tables in a SQL Server Database, located at: http://dave.harris.net/post/2010/06/02/Listing-the-size-of-all-tables-in-a-SQL-Server-Database.aspx

By dave.harris.net on Wednesday, June 02, 2010 at 6:09 AM

that was a great help ....thanks

By vijay chendur on Monday, June 14, 2010 at 1:04 AM

THANKYOU.
My database had grown to over 25GB.
Thanks to your handy script, I was finally able to discover why. The SearchItemWord & SearchItemWordPosition tables had over 1 Billion rows... (1,084,865,702). GoodLORD.

These two sites also helped with scripts to clear those tables out:
http://www.willstrohl.com/Blog/EntryId/576/Saving-Database-Space-in-DotNetNuke-Clearing-the-Search-Index
http://weblogs.asp.net/christoc/archive/2006/06/26/DotNetNuke-Daily-Tip-_2300_3-6_2F00_26_2F00_06-Clear-Search-Tables.aspx

You rock Mitch!

By cjepp on Saturday, July 24, 2010 at 8:45 AM

Hello All,

I keep my custom tables in a seperate schema, so that they are not mixed in with the dnn tables, to include the schema in the sproc, I have to make a change to retrieve the schema as well as the table name.

Here is my version. Hope it is useful to someone.

CREATE PROCEDURE GetAllTableSizes
AS
/*
Obtains spaced used data for ALL user tables in the database
*/
DECLARE @TableName VARCHAR(100) --For storing values in the cursor

--Cursor to get the name of all user tables from the sysobjects listing
DECLARE tableCursor CURSOR
FOR
SELECT s.name + '.'+ o.name
from dbo.sysobjects o
INNER JOIN sys.schemas s ON o.uid = s.schema_id
where OBJECTPROPERTY(id, N'IsUserTable') = 1
FOR READ ONLY


--A procedure level temp table to store the results
CREATE TABLE #TempTable
(
tableName varchar(100),
numberofRows varchar(100),
reservedSize varchar(50),
dataSize varchar(50),
indexSize varchar(50),
unusedSize varchar(50)
)

--Open the cursor
OPEN tableCursor

--Get the first table name from the cursor
FETCH NEXT FROM tableCursor INTO @TableName

--Loop until the cursor was not able to fetch
WHILE (@@Fetch_Status >= 0)
BEGIN
--Dump the results of the sp_spaceused query to the temp table
INSERT #TempTable
EXEC sp_spaceused @TableName

--Get the next table name
FETCH NEXT FROM tableCursor INTO @TableName
END

--Get rid of the cursor
CLOSE tableCursor
DEALLOCATE tableCursor

--Select all records so we can use the reults
SELECT *
FROM #TempTable

--Final cleanup!
DROP TABLE #TempTable

GO

By Mark Breen on Wednesday, August 18, 2010 at 3:09 AM

Mitchell, buddy you've done a good job. Thanks for this Stored Procedure.
However, I had a suggestion. I'm not sure if it is practically possible. All the fields declared in the #TEMPTABLE are of type VARCHAR. In the end you've used a SELECT * FROM statement on this #TEMPTABLE. I added ORDER BY clause to arrange the output. I wanted to sort this list of tables by the number of rows that they have, with the largest tables at the top. Unfortunately it did not serve the purpose. So my question is whether SP_SPACEUSED returns a numeric value for RowCount. If yes, can we change the datatype of associated field in the #TEMPTABLE. What do you say ... ?
Thanks,
Aashish.

By Aashish Vaghela on Wednesday, August 18, 2010 at 12:54 PM

That should be possible, you will just have to modify the type, and potentially do a cast on the result to get it there.

By mitchel.sellers@gmail.com on Thursday, August 19, 2010 at 3:48 AM

Instead of using a cursor should go with set based operations.

By arun on Wednesday, August 25, 2010 at 7:04 PM
Click here to post a comment

Disclaimer