Salah satu caranya adalah dengan membuat prosedur sistem master
dan kemudian membuat pembungkus dalam database pemeliharaan Anda. Perhatikan bahwa ini hanya akan berfungsi untuk satu basis data pada satu waktu.
Pertama, di master:
USE [master];
GO
CREATE PROCEDURE dbo.sp_GetFragStats -- sp_prefix required
@tableName NVARCHAR(128) = NULL,
@indexID INT = NULL,
@partNumber INT = NULL,
@Mode NVARCHAR(20) = N'DETAILED'
AS
BEGIN
SET NOCOUNT ON;
SELECT
DatabaseName = DB_NAME(),
TableName = t.name,
IndexName = i.name,
IndexID = s.index_id,
PercentFragment = s.avg_fragmentation_in_percent,
TotalFrags = s.fragment_count,
PagesPerFrag = s.avg_fragment_size_in_pages,
NumPages = s.page_count,
IndexType = s.index_type_desc
-- shouldn't s.partition_number be part of the output as well?
FROM sys.tables AS t
INNER JOIN sys.indexes AS i
ON t.[object_id] = i.[object_id]
AND i.index_id = COALESCE(@indexID, i.index_id)
AND t.name = COALESCE(@tableName, t.name)
CROSS APPLY
sys.dm_db_index_physical_stats(DB_ID(), t.[object_id],
i.index_id, @partNumber, @Mode) AS s
WHERE s.avg_fragmentation_in_percent > 10
-- probably also want to filter on minimum page count too
-- do you really care about a table that has 100 pages?
ORDER BY
DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO
-- needs to be marked as a system object:
EXEC sp_MS_MarkSystemObject N'dbo.sp_GetFragStats';
GO
Sekarang, di database pemeliharaan Anda, buat pembungkus yang menggunakan SQL dinamis untuk mengatur konteks dengan benar:
USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
@DatabaseName SYSNAME, -- can't really be NULL, right?
@tableName NVARCHAR(128) = NULL,
@indexID INT = NULL,
@partNumber INT = NULL,
@Mode NVARCHAR(20) = N'DETAILED'
AS
BEGIN
DECLARE @sql NVARCHAR(MAX);
SET @sql = N'USE ' + QUOTENAME(@DatabaseName) + ';
EXEC dbo.sp_GetFragStats @tableName, @indexID, @partNumber, @Mode;';
EXEC sp_executesql
@sql,
N'@tableName NVARCHAR(128),@indexID INT,@partNumber INT,@Mode NVARCHAR(20)',
@tableName, @indexID, @partNumber, @Mode;
END
GO
(Alasan mengapa nama basis data tidak dapat benar-benar NULL
adalah karena Anda tidak dapat bergabung dengan hal-hal seperti sys.objects
dan sys.indexes
karena mereka ada secara independen di setiap basis data. Jadi, mungkin ada prosedur yang berbeda jika Anda menginginkan informasi luas.)
Sekarang Anda dapat memanggil ini untuk basis data lain, mis
EXEC YourMaintenanceDatabase.dbo.GetFragStats
@DatabaseName = N'AdventureWorks2012',
@TableName = N'SalesOrderHeader';
Dan Anda selalu dapat membuat synonym
di setiap basis data sehingga Anda bahkan tidak perlu merujuk nama basis data pemeliharaan:
USE SomeOtherDatabase;`enter code here`
GO
CREATE SYNONYM dbo.GetFragStats FOR YourMaintenanceDatabase.dbo.GetFragStats;
Cara lain adalah dengan menggunakan SQL dinamis, namun ini juga hanya akan bekerja untuk satu database pada suatu waktu:
USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
@DatabaseName SYSNAME,
@tableName NVARCHAR(128) = NULL,
@indexID INT = NULL,
@partNumber INT = NULL,
@Mode NVARCHAR(20) = N'DETAILED'
AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX) = N'SELECT
DatabaseName = @DatabaseName,
TableName = t.name,
IndexName = i.name,
IndexID = s.index_id,
PercentFragment = s.avg_fragmentation_in_percent,
TotalFrags = s.fragment_count,
PagesPerFrag = s.avg_fragment_size_in_pages,
NumPages = s.page_count,
IndexType = s.index_type_desc
FROM ' + QUOTENAME(@DatabaseName) + '.sys.tables AS t
INNER JOIN ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS i
ON t.[object_id] = i.[object_id]
AND i.index_id = COALESCE(@indexID, i.index_id)
AND t.name = COALESCE(@tableName, t.name)
CROSS APPLY
' + QUOTENAME(@DatabaseName) + '.sys.dm_db_index_physical_stats(
DB_ID(@DatabaseName), t.[object_id], i.index_id, @partNumber, @Mode) AS s
WHERE s.avg_fragmentation_in_percent > 10
ORDER BY
DatabaseName, TableName, IndexName, PercentFragment DESC;';
EXEC sp_executesql @sql,
N'@DatabaseName SYSNAME, @tableName NVARCHAR(128), @indexID INT,
@partNumber INT, @Mode NVARCHAR(20)',
@DatabaseName, @tableName, @indexID, @partNumber, @Mode;
END
GO
Namun cara lain adalah dengan membuat tampilan (atau fungsi bernilai tabel) untuk menyatukan tabel dan nama indeks semua database Anda, namun Anda harus meng-hard-code nama-nama database ke dalam tampilan, dan mempertahankannya saat Anda menambahkan / hapus database yang ingin Anda izinkan dimasukkan dalam kueri ini. Ini tidak seperti yang lain, ini memungkinkan Anda untuk mengambil statistik untuk banyak basis data sekaligus.
Pertama, tampilan:
CREATE VIEW dbo.CertainTablesAndIndexes
AS
SELECT
db = N'AdventureWorks2012',
t.[object_id],
[table] = t.name,
i.index_id,
[index] = i.name
FROM AdventureWorks2012.sys.tables AS t
INNER JOIN AdventureWorks2012.sys.indexes AS i
ON t.[object_id] = i.[object_id]
UNION ALL
SELECT
db = N'database2',
t.[object_id],
[table] = t.name,
i.index_id,
[index] = i.name
FROM database2.sys.tables AS t
INNER JOIN database2.sys.indexes AS i
ON t.[object_id] = i.[object_id]
-- ... UNION ALL ...
;
GO
Lalu prosedurnya:
CREATE PROCEDURE dbo.GetFragStats
@DatabaseName NVARCHAR(128) = NULL,
@tableName NVARCHAR(128) = NULL,
@indexID INT = NULL,
@partNumber INT = NULL,
@Mode NVARCHAR(20) = N'DETAILED'
AS
BEGIN
SET NOCOUNT ON;
SELECT
DatabaseName = DB_NAME(s.database_id),
TableName = v.[table],
IndexName = v.[index],
IndexID = s.index_id,
PercentFragment = s.avg_fragmentation_in_percent,
TotalFrags = s.fragment_count,
PagesPerFrag = s.avg_fragment_size_in_pages,
NumPages = s.page_count,
IndexType = s.index_type_desc
FROM dbo.CertainTablesAndIndexes AS v
CROSS APPLY sys.dm_db_index_physical_stats
(DB_ID(v.db), v.[object_id], v.index_id, @partNumber, @Mode) AS s
WHERE s.avg_fragmentation_in_percent > 10
AND v.index_id = COALESCE(@indexID, v.index_id)
AND v.[table] = COALESCE(@tableName, v.[table])
AND v.db = COALESCE(@DatabaseName, v.db)
ORDER BY
DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO