Its a beautiful Sunday morning. l have a lot of things on my mind to write on the blog but I’ve been very busy lately and it really bothers me sometimes not been able to contribute more important stuff that could help someone out there. Ok, so l’m writing on this stored procedure called sp_spaceused. I actually started using this not that long ago when I faced space issues and l had to track how data was flowing into some databases at work. When data becomes really huge and storage problems become prominent, this procedure can be used to be tracking how fast the storage is being used and you can make decisions such as adding additional storage or implementing some archiving strategies to your systems. So, sp_spaceused gives you information about the size of the database, space used by data and indexes and also space that is reserved but unused and finally unallocated space. Below is the screen shot of the procedure executed against the famous adventureworks database.
sp_spaceused1

 

 

executing sp_spaceused returned the above result set. It is good to note that the procedure can be executed in any database without specifying any object and it will give information about the database. On the other hand, if you are interested in getting the size information about a particular object such as a table, then that table can be provide and the procedure will provide size information on that table. For example if you want to find information on the employee table in adventureworks, you execute

sp_spaceused ‘HumanResources.Employee’ and the result is shown below.

 

sp_spaceused2

 

 

So what is the difference between this screen shot and the one for the database itself. Well, its obvious but let’s get into detail about what all these columns mean in the result set. Starting with the database itself, we see the database_name and database_size which needs no explanation.  There is unallocated_space, what does that mean?  This particular column can easily mislead lot of people. It refers to a space in the database that can not be occupied by any object. Its like useless space unless you fix it. This spaces results from different activities that leave behind a bubble of space that is not available for objects. There are many ways to remove it such has shrinking database files but this has to be done with caution. The next column is reserved, which is actually the total space reserved for objects in the database. The reserved space is the only space available to create objects. The column, data, identifies the space occupied by data in the database.  Index_size specifies the size of all indexes in the database and the unused column identifies the space left in the reserved space after the current storage taken be existing objects and data in the database.

The result set when sp_spaceused is executed for the employee table has similar columns to the ones discussed above. The row column simply tells how many rows of record are available in the table.  You might be wondering why sp_spaceused executed without an object produces multiple result sets but when you provide an object, you get only one result set. This is the other part of the story l want to discuss because when you have multiple result sets, its very difficult to retrieve those columns and information and store it in a table. For example, for reporting or storing it for reference. Well, it is possible to edit the original stored procedure and produce your version which you can use for your work. Lets get into that and see how we can accomplish it.

First of all, All system stored procedures are located in programmability->stored procedures->system stored procedures

sp_spaceused3

 

I am not going to copy and past the entire original procedure here but below  is a modified version that you can use to get a single result set for sp_spaceused when you ran it without specifying an object, such as getting information on a database. To use the script, you simply execute it in your database and produce your own stored procedure which you can call just in the same way you call your procedures.

Create procedure smart_spaceused

@objname nvarchar(776) = null, — The object we want size on.
@updateusage varchar(5) = false — Param. for specifying that
— usage info. should be updated.
as

declare @id int — The object id that takes up space
,@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

/*
** Check to see if user wants usages updated.
*/

if @updateusage is not null
begin
select @updateusage=lower(@updateusage)

if @updateusage not in (‘true’,’false’)
begin
raiserror(15143,-1,-1,@updateusage)
return(1)
end
end
/*
** Check to see that the objname is local.
*/
if @objname IS NOT NULL
begin

select @dbname = parsename(@objname, 3)

if @dbname is not null and @dbname <> db_name()
begin
raiserror(15250,-1,-1)
return (1)
end

if @dbname is null
select @dbname = db_name()

/*
** Try to find the object.
*/
SELECT @id = object_id, @type = type FROM sys.objects WHERE object_id = object_id(@objname)

— Translate @id to internal-table for queue
IF @type = ‘SQ’
SELECT @id = object_id FROM sys.internal_tables WHERE parent_id = @id and internal_type = 201 –ITT_ServiceQueue

/*
** Does the object exist?
*/
if @id is null
begin
raiserror(15009,-1,-1,@objname,@dbname)
return (1)
end

— Is it a table, view or queue?
IF @type NOT IN (‘U ‘,’S ‘,’V ‘,’SQ’,’IT’)
begin
raiserror(15234,-1,-1)
return (1)
end
end

/*
** Update usages if user specified to do so.
*/

if @updateusage = ‘true’
begin
if @objname is null
dbcc updateusage(0) with no_infomsgs
else
dbcc updateusage(0,@objname) with no_infomsgs
print ‘ ‘
end

set nocount on

/*
** If @id is null, then we want summary data.
*/
if @id is null
begin
select @dbsize = sum(convert(bigint,case when status & 64 = 0 then size else 0 end))
, @logsize = sum(convert(bigint,case when status & 64 <> 0 then size else 0 end))
from dbo.sysfiles

select @reservedpages = sum(a.total_pages),
@usedpages = sum(a.used_pages),
@pages = sum(
CASE
— XML-Index and FT-Index internal tables are not considered “data”, but is part of “index_size”
When it.internal_type IN (202,204,211,212,213,214,215,216) Then 0
When a.type <> 1 Then a.used_pages
When p.index_id < 2 Then a.data_pages
Else 0
END
)
from sys.partitions p join sys.allocation_units a on p.partition_id = a.container_id
left join sys.internal_tables it on p.object_id = it.object_id

select
database_name = db_name(),
database_size = convert (dec (15,0),((convert (dec (15,2),@dbsize) + convert (dec (15,2),@logsize))
* 8192 / 1024)),
‘unallocated space’ = convert (dec (15,0),(case when @dbsize >= @reservedpages then
(convert (dec (15,2),@dbsize) – convert (dec (15,2),@reservedpages))
* 8192 / 1024 else 0 end))

,reserved = convert (dec (15,0),(@reservedpages * 8192 / 1024)),
data = convert (dec (15,0),(@pages * 8192 / 1024) ),
index_size = convert (dec (15,0),((@usedpages – @pages) * 8192 / 1024.)),
unused = convert (dec (15,0),((@reservedpages – @usedpages) * 8192 / 1024)),
unit = ‘KB’
end

/*
** We want a particular object.
*/
else
begin
/*
** Now calculate the summary data.
* Note that LOB Data and Row-overflow Data are counted as Data Pages.
*/
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
SELECT
name = OBJECT_NAME (@id),
rows = convert (char(11), @rowCount),
reserved = convert (dec (15,0), (@reservedpages * 8) ),
data =convert (dec (15,0),(@pages * 8)),
index_size = convert (dec (15,0),((CASE WHEN @usedpages > @pages THEN (@usedpages – @pages) ELSE 0 END) * 8)),
unused = convert (dec (15,0), ((CASE WHEN @reservedpages > @usedpages THEN (@reservedpages – @usedpages) ELSE 0 END) * 8) ),
unit = ‘KB’

 

end
return (0) — sp_spaceused

GO

 

 

Here is the output when  l run the edited version.

sp_spaceused4

 

 

sp_spaceused5

 

We can see the edited version gives single result set for both the table and the database and it makes life easy storing this information into a table or for reporting purposes.

I hope this information is useful and will help someone to quickly get the space information about their databases and tables and actually be able to store those information without struggle. Comments, questions and feedback welcome.

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Name *