SQL Server 游標運用:查看所有數據庫所有表大小信息(Sizes of All Tables in All Database)


一.本文所涉及的內容(Contents)

  1. 本文所涉及的內容(Contents)
  2. 背景(Contexts)
  3. 實現代碼(SQL Codes)
    1. 方法一:游標 + 系統存儲過程sp_MSForEachDB
    2. 方法二:封裝sp_MSforeachtable + sys.databases
    3. 方法三:系統存儲過程sp_MSForEachDB + sp_MSforeachtable
    4. 方法四:擴展sp_MSforeachdb + sp_MSforeachtable
  4. 參考文獻(References)

二.背景(Contexts)

  之前寫了篇關於:SQL Server 游標運用:查看一個數據庫所有表大小信息(Sizes of All Tables in a Database)的文章,它羅列出某個數據所有表的信息,這些信息包括:表的記錄數、數據記錄占用空間、索引占用空間、沒使用的空間等(如Figure1所示),現在我來講述如何獲取整個數據庫實例中所有數據庫所有表的信息(如Figure2所示)。

clip_image001

(Figure1:某數據庫所有表信息)

clip_image003

(Figure2:所有數據庫所有表信息)

三.實現代碼(SQL Codes)

下面內容講述了在實現Figure2過程中遇到的一些問題,如果你對這些問題不感興趣可以直接看最后實現的SQL腳本。下面講述了4種實現方法:

  1. 游標 + 系統存儲過程sp_MSForEachDB,實現腳本為Script3;

  2. 封裝sp_MSforeachtable + sys.databases,實現腳本為Script4和Script5;

  3. 系統存儲過程sp_MSForEachDB + sp_MSforeachtable,實現腳本為Script6;

  4. 擴展sp_MSforeachdb + sp_MSforeachtable,實現腳本為Script7;

 

(一) 我們在SQL Server 游標運用:查看一個數據庫所有表大小信息(Sizes of All Tables in a Database)的SQL腳本中進行改進,結合sp_MSForEachDB系統存儲過程進行實現:

  1) 既然有了獲取某個數據庫所有表信息的腳本,那就可以在外層再套使用sp_MSForEachDB系統存儲過程,下面的Script1腳本可以獲取到所有數據庫的所有表的信息,效果如Figure3所示:

--Script1:
--查看所有數據庫所有表信息
EXEC sp_MSForEachDB 'USE [?];

DECLARE @tablespaceinfo TABLE (
    nameinfo VARCHAR(50),
    rowsinfo INT,
    reserved VARCHAR(20),
    datainfo VARCHAR(20),
    index_size VARCHAR(20),
    unused VARCHAR(20)
)

DECLARE @tablename VARCHAR(255);

DECLARE Info_cursor CURSOR FOR
    SELECT ''[''+[name]+'']'' FROM sys.tables WHERE TYPE=''U'';

OPEN Info_cursor
FETCH NEXT FROM Info_cursor INTO @tablename

WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO @tablespaceinfo EXEC sp_spaceused @tablename
    FETCH NEXT FROM Info_cursor
    INTO @tablename
END

CLOSE Info_cursor
DEALLOCATE Info_cursor

SELECT * FROM @tablespaceinfo
    ORDER BY Cast(Replace(reserved,''KB'','''') AS INT) DESC'

clip_image004

(Figure3:所有數據庫所有表)

  2) 上圖Figure3有兩個缺點,第一是返回的數據太分散,沒有統一表進行管理,第二是需要過濾master、model、msdb和tempdb等系統數據庫,因為我們完全不關心系統數據庫,下面的SQL腳本展示在使用sp_msforeachdb的時候如何排除某個數據庫,效果如Figure4所示:

--sp_msforeachdb排除某個數據庫
EXEC sp_msforeachdb 'IF ''?'' <> ''tempdb'' print ''?'''

clip_image005

(Figure4:sp_msforeachdb排除某個數據庫)

  3) 下面的SQL腳本展示在使用sp_msforeachdb的時候如何排除多個數據庫,效果如Figure5所示:

--sp_msforeachdb排除多個數據庫
EXEC sp_msforeachdb 'IF ''?'' not in(''tempdb'',''master'',''model'',''msdb'') print ''?'''

clip_image006

(Figure5:sp_msforeachdb排除多個數據庫)

  4) 把上面的SQL腳本運用到之前獲取某個數據庫表信息的SQL腳本中,但是執行的過程中出現了Figure6的錯誤信息:

--Script2:
--查看所有數據庫所有表信息
IF NOT EXISTS (SELECT * FROM [tempdb].sys.objects WHERE object_id = OBJECT_ID(N'[tempdb].[dbo].[tablespaceinfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [tempdb].[dbo].[tablespaceinfo](
    [nameinfo] [varchar](255) NULL,
    [rowsinfo] [int] NULL,
    [reserved] [varchar](20) NULL,
    [datainfo] [varchar](20) NULL,
    [index_size] [varchar](20) NULL,
    [unused] [varchar](20) NULL
) ON [PRIMARY]
END
ELSE
    TRUNCATE TABLE tempdb.dbo.tablespaceinfo
EXEC sp_MSForEachDB 'USE [?];

--IF ''?'' not in(''tempdb'',''master'',''model'',''msdb'') 
IF ''?'' in(''AdventureWorksLT2008R2'') 
BEGIN
print ''?''

DECLARE @tablename VARCHAR(255);

DECLARE Info_cursor CURSOR FOR
    SELECT ''[''+[name]+'']'' FROM ?.sys.tables WHERE TYPE=''U'';

OPEN Info_cursor
FETCH NEXT FROM Info_cursor INTO @tablename

WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO tempdb.dbo.tablespaceinfo EXEC ?.dbo.sp_spaceused @tablename
    FETCH NEXT FROM Info_cursor
    INTO @tablename
END

CLOSE Info_cursor
DEALLOCATE Info_cursor
END
'

--返回表
SELECT * FROM tempdb.dbo.tablespaceinfo
    --ORDER BY Cast(Replace(reserved,'KB','') AS INT) DESC
    ORDER BY nameinfo

clip_image008

(Figure6:錯誤信息)

  5) 經過一番查找,最后發現是因為AdventureWorksLT2008R2數據庫的安全中的架構是SalesLT,不是默認的dbo,所以報了Figure6的錯誤信息,但是如果使用sp_MSforeachtable,那就不用理會框架的問題。

clip_image009

(Figure7:SalesLT架構名)

只要我們在表名稱前面加入正確的架構名,那就可以正確執行了,如Figure8所示:

--使用正確的架構名
AdventureWorksLT2008R2.dbo.sp_spaceused 'SalesLT.Address'

clip_image010

(Figure8:正確的架構名)

  6) 經過上面經驗的總結,關於所有數據庫所有表的信息的SQL腳本就水到渠成了,下面就是全部的SQL腳本,注意過濾的方式可以寫成:IF ''?'' like(''A%'') ,執行的效果如Figure2所示:

--Script3:
-- =============================================
-- Author:        <聽風吹雨>
-- Create date:    <2013.05.03>
-- Description:    <查看所有數據庫所有表信息>
-- Blog:        <http://www.cnblogs.com/gaizai/>
-- =============================================
--定義臨時表
IF NOT EXISTS (SELECT * FROM [tempdb].sys.objects WHERE object_id = OBJECT_ID(N'[tempdb].[dbo].[tablespaceinfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [tempdb].[dbo].[tablespaceinfo](
    [db_name] [sysname] NULL,
    [table_name] [sysname] NULL,
    [rows] [bigint] NULL,
    [reserved] [varchar](100) NULL,
    [data] [varchar](100) NULL,
    [index_size] [varchar](100) NULL,
    [unused] [varchar](100) NULL
) ON [PRIMARY]
END
ELSE
    TRUNCATE TABLE tempdb.dbo.tablespaceinfo

DECLARE @SQL NVARCHAR(MAX)
SET @SQL = COALESCE(@SQL,'') + '
USE [?];
--屏蔽掉系統數據庫
IF ''?'' not in(''tempdb'',''master'',''model'',''msdb'')
--IF ''?'' like(''A%'')
BEGIN
PRINT ''?''

DECLARE @schemas_name VARCHAR(255);
DECLARE @table_name VARCHAR(255);

DECLARE Info_cursor CURSOR FOR
    --獲取schemas_name和table_name
    SELECT b.name AS schemas_name,''[''+a.[name]+'']'' AS table_name FROM ?.sys.tables AS a
        LEFT JOIN ?.sys.schemas AS b
        ON a.schema_id = b.schema_id
        WHERE TYPE=''U''

OPEN Info_cursor
FETCH NEXT FROM Info_cursor INTO @schemas_name,@table_name

WHILE @@FETCH_STATUS = 0
BEGIN
    --把表信息插入到臨時表
    SET @table_name = ''[''+@schemas_name+'']''+''.''+@table_name
    INSERT INTO tempdb.dbo.tablespaceinfo([table_name],[rows],[reserved],[data],[index_size],[unused])
        EXEC ?.dbo.sp_spaceused @table_name
    --更新數據庫名稱
    UPDATE tempdb.dbo.tablespaceinfo SET [db_name] = ''?'' WHERE [db_name] IS NULL
    
    FETCH NEXT FROM Info_cursor INTO @schemas_name,@table_name
END

CLOSE Info_cursor
DEALLOCATE Info_cursor
END
'
--循環所有數據庫
PRINT @SQL
EXEC sp_MSForEachDB @SQL

--返回臨時表數據
SELECT * FROM tempdb.dbo.tablespaceinfo
    ORDER BY [db_name],Cast(Replace(reserved,'KB','') AS INT) DESC
--ORDER BY [db_name],[table_name]

DROP TABLE [tempdb].[dbo].[tablespaceinfo]

(二) 還有沒其他方式可以實現Figure2的效果呢?你可以考慮使用sp_MSforeachtable的方式實現,先使用存儲過程sp_spaceused_db簡單封裝sp_MSforeachtable,它簡單實現獲取某個數據庫的所有表,再使用拼湊生成批量的INSERT和UPDATE語句生成表信息數據,Script9與Script10需要分開執行,執行的效果如Figure2所示:

--Script4:
USE [tempdb]
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_spaceused_db]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[sp_spaceused_db]
GO

-- =============================================
-- Author:        <聽風吹雨>
-- Create date: <2013.05.06>
-- Description:    <封裝sp_MSforeachtable>
-- Blog:        <http://www.cnblogs.com/gaizai/>
-- =============================================
CREATE PROCEDURE [dbo].[sp_spaceused_db]
    @db_name nvarchar(776) = null
AS
BEGIN
    DECLARE @SQL NVARCHAR(MAX)
    SELECT @SQL = COALESCE(@SQL,'') + '
        EXEC ['+@db_name+'].dbo.sp_MSforeachtable @command1="sp_spaceused ''?''"'
    
    PRINT(@SQL)
    EXECUTE(@SQL)
END
--Script5:
-- =============================================
-- Author:        <聽風吹雨>
-- Create date: <2013.05.06>
-- Description:    <查看所有數據庫所有表信息>
-- Blog:        <http://www.cnblogs.com/gaizai/>
-- =============================================
CREATE TABLE [tempdb].[dbo].[tablespaceinfo](
    [db_name] [sysname] NULL,
    [table_name] [sysname] NULL,
    [rows] [bigint] NULL,
    [reserved] [varchar](100) NULL,
    [data] [varchar](100) NULL,
    [index_size] [varchar](100) NULL,
    [unused] [varchar](100) NULL
)

DECLARE @SQL NVARCHAR(MAX)
SELECT @SQL = COALESCE(@SQL,'') + '
INSERT INTO [tempdb].[dbo].[tablespaceinfo]([table_name],[rows],[reserved],[data],[index_size],[unused])
    EXEC [tempdb].dbo.sp_spaceused_db ' + QUOTENAME(name,'''') + '
UPDATE [tempdb].[dbo].[tablespaceinfo] SET [db_name] = ' + QUOTENAME(name,'''') + '
    WHERE [db_name] IS NULL'
FROM sys.databases WHERE database_id >4

PRINT(@SQL)
EXECUTE(@SQL)

SELECT * FROM [tempdb].[dbo].[tablespaceinfo]
    ORDER BY [db_name],Cast(Replace(reserved,'KB','') AS INT) DESC

DROP TABLE [tempdb].[dbo].[tablespaceinfo]

(三) 如果你想使用sp_MSForEachDB與sp_MSforeachtable(sp_MSforeach_worker、sp_MStablespace)結合的方式實現Figure2效果,剛開始測試的時候發現這兩個存儲過程在解釋“?”的時候會出現歧義,SQL無法理解它是指數據庫還是表,難道微軟會做那么愚蠢的事情?后來查看了這兩個存儲過程的SQL腳本,發現是有辦法解決上面問題的。

--Script6:
-- =============================================
-- Author:        <聽風吹雨>
-- Create date: <2013.05.08>
-- Description:    <查看所有數據庫所有表信息>
-- Blog:        <http://www.cnblogs.com/gaizai/>
-- =============================================
IF NOT EXISTS (SELECT * FROM [tempdb].sys.objects WHERE object_id = OBJECT_ID(N'[tempdb].[dbo].[tablespaceinfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [tempdb].[dbo].[tablespaceinfo](
    [db_name] [sysname] NULL,
    [table_name] [sysname] NULL,
    [rows] [bigint] NULL,
    [reserved] [varchar](100) NULL,
    [data] [varchar](100) NULL,
    [index_size] [varchar](100) NULL,
    [unused] [varchar](100) NULL
) ON [PRIMARY]
END
ELSE
    TRUNCATE TABLE tempdb.dbo.tablespaceinfo

DECLARE @SQL NVARCHAR(MAX)
SELECT @SQL = COALESCE(@SQL,'') + '
USE [?];
--屏蔽掉系統數據庫
IF ''?'' not in(''tempdb'',''master'',''model'',''msdb'') 
BEGIN
    --插入表信息
    INSERT INTO tempdb.dbo.tablespaceinfo([table_name],[rows],[reserved],[data],[index_size],[unused])
        EXEC [?].sys.sp_MSforeachtable @command1="sp_spaceused N''$''",@replacechar=N''$''
    --更新數據庫名稱
    UPDATE tempdb.dbo.tablespaceinfo SET [db_name] = ''?'' WHERE [db_name] IS NULL

END'
PRINT (@SQL)

--所有數據庫
EXEC sp_MSforeachdb @command1="print '?'",@command2=@SQL, @replacechar=N'?'

--返回臨時表數據
SELECT * FROM tempdb.dbo.tablespaceinfo
    ORDER BY [db_name],Cast(Replace(reserved,'KB','') AS INT) DESC

DROP TABLE [tempdb].[dbo].[tablespaceinfo]

(四) 上面的Script6腳本過濾數據的方式比較麻煩,所以我對sp_MSforeachdb系統存儲過程進行了一些修改,生成一個新的存儲過程sp_MSforeachdb_Filter,詳情請查看:SQL Server 游標運用:查看一個數據庫所有表大小信息(Sizes of All Tables in a Database),在創建了存儲過程sp_MSforeachdb_Filter的情況下執行下面的SQL腳本,你可以隨意修改@whereand參數來滿足你的過濾條件,非常方便。

--Script7:
-- =============================================
-- Author:        <聽風吹雨>
-- Create date: <2013.05.08>
-- Description:    <查看所有數據庫所有表信息>
-- Blog:        <http://www.cnblogs.com/gaizai/>
-- =============================================
IF NOT EXISTS (SELECT * FROM [tempdb].sys.objects WHERE object_id = OBJECT_ID(N'[tempdb].[dbo].[tablespaceinfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [tempdb].[dbo].[tablespaceinfo](
    [db_name] [sysname] NULL,
    [table_name] [sysname] NULL,
    [rows] [bigint] NULL,
    [reserved] [varchar](100) NULL,
    [data] [varchar](100) NULL,
    [index_size] [varchar](100) NULL,
    [unused] [varchar](100) NULL
) ON [PRIMARY]
END
ELSE
    TRUNCATE TABLE tempdb.dbo.tablespaceinfo

DECLARE @SQL NVARCHAR(MAX)
SELECT @SQL = COALESCE(@SQL,'') + '
    --插入表信息
    INSERT INTO tempdb.dbo.tablespaceinfo([table_name],[rows],[reserved],[data],[index_size],[unused])
        EXEC [?].sys.sp_MSforeachtable @command1="sp_spaceused N''$''",@replacechar=N''$''
    --更新數據庫名稱
    UPDATE tempdb.dbo.tablespaceinfo SET [db_name] = ''?'' WHERE [db_name] IS NULL'
PRINT (@SQL)

----過濾數據庫
--EXEC [sp_MSforeachdb_Filter] @command1="print '?'",@command2=@SQL, @replacechar=N'?',
--@whereand=" and [name] not in('tempdb','master','model','msdb') "

--過濾數據庫
EXEC [sp_MSforeachdb_Filter] @command1="print '?'",@command2=@SQL, @replacechar=N'?',
@whereand=" and [dbid] > 4 "

--返回臨時表數據
SELECT * FROM tempdb.dbo.tablespaceinfo
    ORDER BY [db_name],Cast(Replace(reserved,'KB','') AS INT) DESC

DROP TABLE [tempdb].[dbo].[tablespaceinfo]

四.參考文獻(References)

與存儲過程sp_MSforeachdb類似的存儲過程sp_MSforeachdb

SQL Server數據庫開發頂級技巧

sp_MSforeachtable使用方法

How to get information about all databases without a loop

關於quotename的用法

SQL Server 游標運用:查看一個數據庫所有表大小信息(Sizes of All Tables in a Database)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM