104

How can you tell what Tables are taking up the most space in a SQL Server 2005 Database?

I am sure there is some System Stored Procedure that shows this information.

I have a TEST database that grew from 1tb to 23tb. We are currently doing a lot of client conversion testing in the database, which entails running the same conversion Stored Procedure multiple times. It does DELETEs which I am sure is increasing the Transaction Log. But this got me thinking to ask this question.

info

the big problem is the dbo.Download table, it creates massive storage that is actually not needed, I had 3GB before truncating it, then 52MB ;)

Community
  • 1
  • 1
Gerhard Weiss
  • 9,343
  • 18
  • 65
  • 67
  • 2
    Marc_S and Barry's answers where just outstanding so I upvoted both of them and was waiting to see which one got the most upvotes so I could reward that one with the "Accepted Answer". But they were both tied at 5 so I just picked one but I used both. Thank you very much Marc_S and Barry! – Gerhard Weiss Oct 14 '10 at 21:36
  • Does this answer your question? [Get size of all tables in database](https://stackoverflow.com/questions/7892334/get-size-of-all-tables-in-database) – TylerH Feb 23 '22 at 19:46

4 Answers4

233

Try this script - it will list the number of rows and the space used by data rows (and the total space used) for all tables in your database:

SELECT 
 t.NAME AS TableName,
 i.name AS indexName,
 SUM(p.rows) AS RowCounts,
 SUM(a.total_pages) AS TotalPages, 
 SUM(a.used_pages) AS UsedPages, 
 SUM(a.data_pages) AS DataPages,
 (SUM(a.total_pages) * 8) / 1024 AS TotalSpaceMB, 
 (SUM(a.used_pages) * 8) / 1024 AS UsedSpaceMB, 
 (SUM(a.data_pages) * 8) / 1024 AS DataSpaceMB
FROM 
 sys.tables t
INNER JOIN  
 sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN 
 sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN 
 sys.allocation_units a ON p.partition_id = a.container_id
WHERE 
 t.NAME NOT LIKE 'dt%' AND
 i.OBJECT_ID > 255 AND  
 i.index_id <= 1
GROUP BY 
 t.NAME, i.object_id, i.index_id, i.name 
ORDER BY 
 OBJECT_NAME(i.object_id) 
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 6
    +1 Brilliant. Note that this doesn't include the size of data indices. For me, though, it got the job done. – Erick Robertson Sep 16 '11 at 14:16
  • 49
    I didn't know this, but if you're using Management Studio you can also right click on the database and go to Reports -> Disk Usage by Table for the same results. – rossisdead Mar 27 '15 at 17:30
  • @rossisdead, that's hilarious piece of information to know. Thanks! – Nickmaovich Feb 08 '17 at 07:53
  • I get 'Table 'sys.tables' doesn't exist' – Sean H Feb 15 '19 at 14:45
  • @Seano: what SQL Server version are you using? (run `SELECT @@VERSION` to find out) What database compatibility level does your database have?? – marc_s Feb 15 '19 at 16:47
  • I had to add `a.type != 3` to the WHERE clause to avoid Row_Overflow rows from being double counted in the row counts. Not sure if this will break the Space counts, as apparently my overflow rows are empty. – Quentin May 26 '22 at 17:44
37

Use sp_spacedUsed

Exec sp_spaceused N'YourTableName'

Or if you want to execute the sp_spaceused for each table in your database then you can use this SQL:

set nocount on
create table #spaceused (
  name nvarchar(120),
  rows char(11),
  reserved varchar(18),
  data varchar(18),
  index_size varchar(18),
  unused varchar(18)
)

declare Tables cursor for
  select name
  from sysobjects where type='U'
  order by name asc

OPEN Tables
DECLARE @table varchar(128)

FETCH NEXT FROM Tables INTO @table

WHILE @@FETCH_STATUS = 0
BEGIN
  insert into #spaceused exec sp_spaceused @table
  FETCH NEXT FROM Tables INTO @table
END

CLOSE Tables
DEALLOCATE Tables 

select * from #spaceused
drop table #spaceused

exec sp_spaceused

The above SQL is from here

Jon Cram
  • 16,609
  • 24
  • 76
  • 107
codingbadger
  • 42,678
  • 13
  • 95
  • 110
  • 7
    For newer versions of SQL Server you can also use `exec sp_msforeachtable 'exec sp_spaceused N''?'''` – JNK Oct 13 '10 at 19:47
  • 1
    @JNK `sp_msforeachtable` exists since at least SQl Server 2000 – SQLMenace Oct 13 '10 at 19:59
  • @SQLMenace - thanks for the info. I didn't research how old it was before posting, but wasn't sure I would find it since it's undocumented. – JNK Oct 13 '10 at 20:06
  • 5
    A _slightly_ simpler example: You can get away with skipping the EXECs and fancy quoting, by doing just `sp_msforeachtable 'sp_spaceused [?]'` if you like. Verified back to SQL2000. – Mark May 24 '12 at 17:36
  • Mark problem with this method is it doesn't return as a single result set – Paul Nov 07 '16 at 08:46
  • If you are using non-default schema, this query won't work. To fix it you can change the cursor query to `select TABLE_SCHEMA + '.' + TABLE_NAME as name from INFORMATION_SCHEMA.TABLES order by TABLE_SCHEMA, TABLE_NAME`. – Jack A. Apr 05 '17 at 14:32
12

Rossisdead's comment answered this question the best for me, I wish it wasn't buried in a comment. This will be useful for people like me not trying to script the solution (the OP did not ask for a code snippet)

If you're using Management Studio you can also right click on the database and go to Reports -> Disk Usage by Table for the same results

Hucker
  • 671
  • 1
  • 8
  • 25
7

Thanks to @marc_s for the answer. I needed to know data vs index space so I went ahead and expanded on the query to include that.

SELECT TableName
    , SUM(DataRowCounts) AS DataRowCounts
    , SUM(DataTotalSpaceGB) AS DataTotalSpaceGB
    , SUM(DataSpaceUsedGB) AS DataSpaceUsedGB
    , SUM(DataUnusedSpaceGB) AS DataUnusedSpaceGB
    , SUM(IndexRowCounts) AS IndexRowCounts
    , SUM(IndexTotalSpaceGB) AS IndexTotalSpaceGB
    , SUM(IndexSpaceUsedGB) AS IndexSpaceUsedGB
    , SUM(IndexUnusedSpaceGB) AS IndexUnusedSpaceGB
    , SUM(DataTotalSpaceGB) + SUM(IndexTotalSpaceGB) AS TotalSpaceGB
FROM
(
SELECT t.NAME AS TableName
    , i.type_desc AS IndexType
    , CASE WHEN i.type_desc IN ('CLUSTERED', 'CLUSTERED COLUMNSTORE', 'HEAP') THEN CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2)/1000 AS NUMERIC(36, 2)) ELSE 0 END AS DataTotalSpaceGB
    , CASE WHEN i.type_desc IN ('CLUSTERED', 'CLUSTERED COLUMNSTORE', 'HEAP') THEN CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2)/1000 AS NUMERIC(36, 2))  ELSE 0 END AS DataSpaceUsedGB    
    , CASE WHEN i.type_desc IN ('CLUSTERED', 'CLUSTERED COLUMNSTORE', 'HEAP') THEN CAST(ROUND(((SUM(a.total_pages) - SUM(a.used_pages)) * 8) / 1024.00, 2)/1000 AS NUMERIC(36, 2)) ELSE 0 END AS DataUnusedSpaceGB
    , CASE WHEN i.type_desc IN ('CLUSTERED', 'CLUSTERED COLUMNSTORE', 'HEAP') THEN SUM(p.Rows) ELSE 0 END AS DataRowCounts
    , CASE WHEN i.type_desc = 'NONCLUSTERED' THEN CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2)/1000 AS NUMERIC(36, 2)) ELSE 0 END AS IndexTotalSpaceGB
    , CASE WHEN i.type_desc = 'NONCLUSTERED' THEN CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2)/1000 AS NUMERIC(36, 2))  ELSE 0 END AS IndexSpaceUsedGB    
    , CASE WHEN i.type_desc = 'NONCLUSTERED' THEN CAST(ROUND(((SUM(a.total_pages) - SUM(a.used_pages)) * 8) / 1024.00, 2)/1000 AS NUMERIC(36, 2)) ELSE 0 END AS IndexUnusedSpaceGB  
    , CASE WHEN i.type_desc = 'NONCLUSTERED' THEN SUM(p.Rows) ELSE 0 END AS IndexRowCounts
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
LEFT JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE t.NAME NOT LIKE 'dt%'
    AND t.is_ms_shipped = 0
    AND i.OBJECT_ID > 255
    AND s.Name = 'dbo' --update this filter
    AND t.Name = 'MyTable'
GROUP BY t.Name
    , i.type_desc
) x
GROUP BY TableName
ORDER BY TotalSpaceGB DESC
kjmerf
  • 4,275
  • 3
  • 21
  • 29