tags:

views:

70

answers:

4

I need to execute stored procedure sp_spaceused for all the tables in my database. I have used cursor for this, please find the below query.The thing is I need to generate report in a single result set. For the below query I'm getting different results.

Declare @Name Varchar(500)
Declare @GetName Cursor
Set     @Getname = Cursor for
select name from sys.tables
Open @Getname
Fetch Next From @Getname into @Name
While @@Fetch_Status=0
Begin
exec sp_spaceused @Name
Fetch Next From @Getname into @Name
End
Close @GetName
Deallocate @GetName
+4  A: 

You can use something like the below (the data types may well need tweaking).

Edit: Please see Joe's answer for the correct data types to use!

create table #t
(
name sysname,
rows bigint,
reserved varchar(50),
data varchar(50),
index_size varchar(50),
unused varchar(50)
)

EXEC  sp_MSForEachtable 'insert into #t EXEC sp_spaceused ''?'''

select name,rows,reserved,data,index_size,unused 
from #t
Martin Smith
+1 I'm way too slow today! :-) The correct data types per [sp_spaceused documentation](http://msdn.microsoft.com/en-us/library/ms188776.aspx) are in [my answer](http://stackoverflow.com/questions/3711197/execute-stored-procedure-in-a-cursor/3711255#3711255).
Joe Stefanelli
In an email, I pointed user447596 to this site to ask this question where I said I would answer his question. I like this solution, so +1.
Michael J Swart
Hello Joe and Michael Thanks for your prompt response, I have used above query it works fine however im getting double records.
Simhadri
Do you use the same table names in different schemas? That's the only explanation I can think of for double records.
Joe Stefanelli
Yes Joe as you said we have been using same table names in different which leads to duplicate records.Got the result now thanks a bunch for your time.
Simhadri
+1  A: 

Try this:

Create a table (temp or otherwise) that mirrors the result set of the sproc. Then, in the body of your cursor, run this

INSERT INTO <tablename> EXEC sp_spaceused

after you close/deallocate the cursor, select from the table.

Brad
Hello Brad, Thanks for the response I have used your query, I'm getting double records.
Simhadri
I think the set-based process Mike Forman suggested would be the best performing one. I simply wanted to get you up and running without rebuilding the guts of sp_spaceused. One thing I noticed is the absence of schemas in your table names. If you have the same table in two schemas (e.g. dbo) then you would get dupe names (since you aren't showing the schema). When you call the procedure it will assume which schema you want -- both times -- resulting in dupes.
Brad
+2  A: 
create table #Temp (
    name nvarchar(128),
    [rows] char(11),
    reserved varchar(18),
    data varchar(18),
    index_size varchar(18),
    unused varchar(18)
)

insert into #Temp
    exec sp_msforeachtable 'sp_spaceused [?]'

select * from #Temp
Joe Stefanelli
+1 Looks like you took the trouble to find the correct data types!
Martin Smith
@Martin, I did, and those few extra minutes cost me! :-)
Joe Stefanelli
+4  A: 

A faster, set-based solution to this problem is to join sys.dm_db_partition_stats and sys.internal_tables, just like sp_spaceused does. The code below is what runs when you generate the "Disk Usage By Table" report in Management Studio.

In my database, the set-based solution returned in 60 ms, while the cursor ran for 22 seconds.

begin try 
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
    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
end try 
begin catch 
select 
    -100 as l1
,   1 as schemaname 
,       ERROR_NUMBER() as tablename
,       ERROR_SEVERITY() as row_count
,       ERROR_STATE() as reserved
,       ERROR_MESSAGE() as data
,       1 as index_size
,       1 as unused 
end catch
Mike Forman
+1 I agree this way is better.
Martin Smith