Una forma sería hacer un procedimiento del sistema mastery luego crear un contenedor en su base de datos de mantenimiento. Tenga en cuenta que esto solo funcionará para una base de datos a la vez.
Primero, en 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
Ahora, en su base de datos de mantenimiento, cree un contenedor que use SQL dinámico para establecer el contexto correctamente:
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
(La razón por la que el nombre de la base de datos no puede ser realmente NULLes porque no puede unirse a cosas como sys.objectsy sys.indexesdado que existen independientemente en cada base de datos. Por lo tanto, tal vez tenga un procedimiento diferente si desea información de toda la instancia).
Ahora puede llamar a esto para cualquier otra base de datos, p. Ej.
EXEC YourMaintenanceDatabase.dbo.GetFragStats
@DatabaseName = N'AdventureWorks2012',
@TableName = N'SalesOrderHeader';
Y siempre puede crear un synonymen cada base de datos para que ni siquiera tenga que hacer referencia al nombre de la base de datos de mantenimiento:
USE SomeOtherDatabase;`enter code here`
GO
CREATE SYNONYM dbo.GetFragStats FOR YourMaintenanceDatabase.dbo.GetFragStats;
Otra forma sería usar SQL dinámico, sin embargo, esto también funcionará solo para una base de datos a la vez:
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
Otra forma sería crear una vista (o función con valores de tabla) para unir los nombres de tabla e índice de todas sus bases de datos, sin embargo, tendría que codificar los nombres de las bases de datos en la vista y mantenerlos a medida que agrega / eliminar las bases de datos que desea permitir que se incluyan en esta consulta. Esto, a diferencia de los demás, le permitirá recuperar estadísticas de varias bases de datos a la vez.
Primero, la vista:
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
Entonces el procedimiento:
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