About Me

Colorado
Paul has 18 years experience with Microsoft SQL Server. He has worked in the roles of production DBA, database developer, database architect, applications developer, business intelligence and data warehouse developer, and instructor for students aspiring for MCDBA certification. He has performed numerous data migrations and supported large databases (3 Terabyte, 1+ billion rows) with high transactions. He is a member of PASS, blogs about lessons learned from a developer’s approach to SQL Server administration, and has been the president of the Boulder SQL Server Users’ Group for 11 years, from January 2009 to 2020.

Wednesday, January 1, 2020

Compare fragmentation before and after running a defrag routine


To compare fragmentation before and after running a defrag, you can use this code.  The vwFragmentation view contains NumPages, Rows, and TotalMB so you have all you need to make your judgement.  Recall that TotalMB is more important that Rows.

Use Admin

-- 1st data point
SELECT *
INTO vwFrag20200101
FROM vwFragmentation
WHERE DB = 'MyDB'

-- 2nd data point
SELECT *
INTO vwFrag20200102
FROM vwFragmentation
WHERE DB = 'MyDB'


-- Compare fragmentation from 1st to 2nd
SELECT  f1.DB, f1.TotalMB,
        f1.PercFrag FragBefore,
        f2.PercFrag FragAfter,
        f1.ObjName,
        f1.IndexName
FROM vwFrag20200101 f1
JOIN vwFrag20200102 f2
    ON  f2.DB = f1.DB
    AND f2.ObjName = f1.ObjName
    AND f2.IndexName = f1.IndexName
    AND F2.IndexLevel = f1.IndexLevel
WHERE f1.PercFrag > 50
ORDER BY f1.TotalMB desc


Tuesday, December 17, 2019

vwFragmentation


This is a comprehensive view for fragmentation, together with size in bytes (from vwTable).  Keep in mind that when assessing fragmentation, we are concerned with larger tables in bytes, not necessarily larger tables in rows.  This is because we are concerned with disk I/O.  Although a tall skinny table with millions of rows may appear as a candidate to scrutinize, it is possible for a table with far fewer rows to incur more disk I/O if it is a wide table.  

This view works well for instances that do not have too many databases (say under 20).  If there are 50-100+ databases, this becomes slower.  For those, I have another solution which will be posted next month.

IF OBJECT_ID('dbo.vwFragmentation') Is Not Null
       DROP VIEW dbo.vwFragmentation
GO
CREATE VIEW dbo.vwFragmentation
AS
/*     DATE          AUTHOR        REMARKS
       12/17/19      PPaiva        Initial creation.
      
       SELECT TOP 1000 *
       FROM vwFragmentation
       WHERE DB = 'MyDB'
         AND TotalMB > 100
       ORDER BY PercFrag desc
      
*/
SELECT  i.Instance,
              i.DB,
              t.Rows,
              t.TotalMB,
              PercFrag,
              i.SchemaName,
              ObjName,
              TotalFrags,
              PagesPerFrag,
              NumPages,
              IndexName,
              PartNum,
              IndexType,
              AllocDesc,
              IsUniq,
              IsPK,
              IsUniqCon,
              IndexDepth,
              IndexLevel,
              IsDisabled,
              AllowPgLocks,
              AllowRowLocks,
              i.object_id,
              database_id,
              i.ViewCreateDate,
              Convert(varchar(16), GetDate(), 120) InsertDate
FROM dbo.vwIndexPhysicalStats i
JOIN vwTable t
       ON t.Instance = i.Instance
       AND t.DB = i.DB
       AND t.TableName = i.ObjName

GO


Thursday, November 1, 2018

vwBackupNever and vwBackupHistory (no dependencies)

Here is vwBackupNever and vwBackupHistory with no dependencies. 

This is created in tempdb but you can optionally put it into your Admin (or similarly named) database. 

Use this as a first check when you need to locate backup files.



/*    DATE        AUTHOR            REMARKS
      11/1/18     PPaiva            Initial creation.

      DESCRIPTION
            Use this code to get a historical list of backups on
                  this instance.
            First query result is Last backup of each database.

      USAGE
            -- Last backup of each database
            SELECT *
            FROM vwBackupHistory         
            WHERE Num = 1
            ORDER BY DateStarted desc

            -- Databases NEVER backed up
            SELECT *
            FROM vwBackupNever

            -- Last backups, all databases
            SELECT *
            FROM vwBackupHistory         
            ORDER BY DateStarted desc

            -- Last 5 backups per DB per Type per Path 
            SELECT *
            FROM vwBackupHistory
            WHERE Num <= 5
                  AND IsSysDB = 0
            ORDER BY Instance, DB, DateStarted desc

*/
Use tempdb
-- Use Admin  -- put this code in your DBA database if you have one
go
IF object_id('dbo.udfGetFilenameOnly') Is Not Null
      DROP FUNCTION dbo.udfGetFilenameOnly
go


CREATE FUNCTION dbo.udfGetFilenameOnly(
      @In varchar(500)
      )
RETURNS varchar(300)
AS
/*    DATE        AUTHOR            REMARKS
      2/2/16      PPaiva            Initial creation.


      USAGE
            SELECT dbo.udfGetFilenameOnly('D:\Program Files\Microsoft SQL Server\Logs\Admin_Log.LDF')
            SELECT dbo.udfGetFilenameOnly('Admin_Log.ldf')

*/

BEGIN
      DECLARE @Out varchar(300),
                  @Pos smallint
           
      -- Get position of last backslash
      SET @Pos = CharIndex('\', Reverse(@In))

      IF @Pos > 1
            BEGIN
                  SET @Pos = Len(@In) - @Pos               
                  SET @Out = Substring(@In, @Pos + 2, 500)
            END
     
      ELSE
            SET @Out = @In


      RETURN @Out

END
go

IF object_id('dbo.udfFormatInteger') Is Not Null
      DROP FUNCTION dbo.udfFormatInteger
go

CREATE FUNCTION dbo.udfFormatInteger(
      @In bigint
      )
RETURNS varchar(20)
AS
/*    DATE              AUTHOR            REMARKS   
      2/22/16           PPaiva            Initial creation.
    
      DESCRIPTION
            Formats a given integer with commas.
                  Examples:
                              IN                OUT
                              123               123
                            12345            12,345
                           -54321           -54,321

      USAGE
            SELECT dbo.udfFormatInteger(1234567890)
            SELECT dbo.udfFormatInteger(12345)
            SELECT dbo.udfFormatInteger(-54321)
            SELECT dbo.udfFormatInteger(0)
*/

BEGIN
      DECLARE @Out varchar(20),
                  @sIn varchar(20),
                  @Balance varchar(20),
                  @CurrTextLen smallint,
                  @IsNegative bit

      IF @In < 0
            BEGIN
                  SET @IsNegative = 1
                  SET @In = Abs(@In)
            END

      SET @sIn = Convert(varchar, @In)
      SET @CurrTextLen = Len(@sIn)
      SET @Out = ''
      SET @Balance = @sIn

      IF @CurrTextLen > 3
            BEGIN
                  WHILE 1 = 1
                        BEGIN
                              SET @Out = ',' + Right(@Balance, 3) + @Out
                              SET @Balance = Substring(@sIn, 1, @CurrTextLen - 3)

                              SET @CurrTextLen = Len(@Balance)
                              IF @CurrTextLen > 3
                                    CONTINUE
                              ELSE
                                    BEGIN
                                          SET @Out = @Balance + @Out
                                          BREAK
                                    END 
                        END
            END

      ELSE
            SET @Out = @sIn

      IF @IsNegative = 1
            SET @Out = '-' + @Out

      RETURN @Out

END
go

IF object_id('dbo.udfGetPathOnly') Is Not Null
      DROP FUNCTION dbo.udfGetPathOnly
go

CREATE FUNCTION dbo.udfGetPathOnly(
      @In varchar(1000)
)
RETURNS varchar(1000)
AS
/*    DATE        AUTHOR            REMARKS
      2/22/16     PPaiva            Initial creation.


      DESCRIPTION
            Returns the path only, with a trailing backslash. 

      USAGE
            SELECT dbo.udfGetPathOnly('C:\Windows\Notepad.exe')
            SELECT dbo.udfGetPathOnly('C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\tempdb.mdf')
            SELECT dbo.udfGetPathOnly('tempdb.ldf')
            SELECT dbo.udfGetPathOnly('C:\asdf')
            SELECT dbo.udfGetPathOnly('C:')
            SELECT dbo.udfGetPathOnly('Z:')

*/
BEGIN
      DECLARE @Out varchar(300),
                  @PosSlash smallint,
                  @PosDot smallint


      -- Get position of last backslash
      SET @PosSlash = CharIndex('\', Reverse(@In))
      -- Get position of last dot
      SET @PosDot = CharIndex('.', Reverse(@In))

      IF @PosDot > 0
            SET @PosDot = Len(@In) - @PosDot + 1

      IF @PosSlash > 0
            BEGIN
                  SET @PosSlash = Len(@In) - @PosSlash + 1
                  IF @PosDot = 0
                        SET @Out = @In
                  ELSE
                        SET @Out = Left(@In, @PosSlash)
            END

      ELSE
            IF Len(@In) = 2 AND Right(@In, 1) = ':'
                  SET @Out = @In + '\'


      RETURN @Out

END
go



IF Object_ID('dbo.vwBackupHistory') Is Not Null
      DROP VIEW dbo.vwBackupHistory
GO

CREATE VIEW dbo.vwBackupHistory
AS
/*  DATE        AUTHOR          REMARKS
       11/29/16    PPaiva          Initial creation.
       11/1/18              PPaiva               Added column IsOnline.

    
       -- Last 10 backups per DB per Type per Path 
       SELECT *
       FROM vwBackupHistory
       WHERE Num <= 10
       AND DB Not In ('master', 'model', 'msdb')
       ORDER BY 1, 2, 3, 4, 5, 6

       SELECT *
       FROM vwBackupHistory
       ORDER BY DateStarted desc

       -- DBs backed up
       SELECT Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName, Count(*) Qty
       FROM vwBackupHistory
       GROUP BY Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName
       ORDER BY Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName

       SELECT *
       FROM vwBackupHistory
       WHERE BackupType <> 'Log'
       ORDER BY DateFinished desc,   DB


       SELECT Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName, Count(*) Qty
       FROM vwBackupHistory
       GROUP BY Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName
       ORDER BY Machine, SqlInstance, DB, BackupType, RecoveryModel, Path, SoftwareName

*/
SELECT  ROW_NUMBER() OVER (PARTITION BY  b.database_name, b.Type, b.Recovery_Model ORDER BY backup_start_date desc) Num,
            ServerProperty('ServerName') Instance,
        b.database_name DB,
              CASE WHEN d.name Is Not Null THEN 1
                           ELSE 0 END IsOnline,
        CASE b.type
              WHEN 'D' THEN 'Database'
              WHEN 'L' THEN 'Log'
              WHEN 'I' THEN 'Differential Database'
              WHEN 'F' THEN 'File or FileGroup'
              WHEN 'G' THEN 'Differential File'
              WHEN 'P' THEN 'Partial'
              WHEN 'Q' THEN 'Differential Partial'
              WHEN Null THEN 'Null'
              ELSE 'Need to define in view'
                    END AS BackupType,
        b.recovery_model RecoveryModel,
        b.backup_start_date DateStarted,
              DATEDIFF(second, b.backup_start_date, b.backup_finish_date) BackupSecs,
              DATEDIFF(minute, b.backup_start_date, b.backup_finish_date) BackupMins,
              dbo.udfFormatInteger((b.backup_size /10000.)) SizeMBFmt,
            CASE WHEN b.database_name In ('master', 'model', 'msdb', 'tempdb', 'distribution', 'Admin')
                        THEN 1 ELSE 0
                              END IsSysDB,
              dbo.udfGetFilenameOnly(f.physical_device_name) Filename,
              dbo.udfGetPathOnly(f.physical_device_name) Path,
            bms.is_compressed IsCompressed,                             -- remove column for 2005 and lower
            b.compressed_backup_size CompressedSizeBytes,   -- remove column for 2005 and lower
             (Convert(decimal(4,1), (100 * (b.compressed_backup_size / b.backup_size))) ) PercComp,  -- remove column for 2005 and lower
        b.backup_size SizeBytes,
        b.is_copy_only IsCopy,
        b.is_damaged IsDamaged,
        b.description,
        b.expiration_date ExpDate,
        f.physical_device_name PathFilename,
        b.name AS BackupSetName,
        f.logical_device_name LogicalDeviceName,
        fg.name FileGroup,
        bms.software_name SoftwareName,
        b.backup_finish_date DateFinished
FROM msdb.dbo.backupmediafamily  f
JOIN msdb.dbo.backupset b
      ON f.media_set_id = b.media_set_id
JOIN msdb.dbo.backupmediaset bms
      ON bms.media_set_id = b.media_set_id
JOIN msdb.dbo.backupfilegroup fg
      ON fg.backup_set_id = b.backup_set_id
LEFT JOIN sys.databases d
       ON d.name = b.database_name
go
IF Object_ID('dbo.vwBackupNever') Is Not Null
      DROP VIEW dbo.vwBackupNever
GO
CREATE VIEW dbo.vwBackupNever
AS
/*     DATE        AUTHOR            REMARKS
       11/1/18              PPaiva            Initial creation.
    
       SELECT *
       FROM vwBackupNever
       ORDER BY Name

*/

-- Databases NEVER backed up
SELECT  s.Name,
              s.state_desc State,
              S.recovery_model_desc RecoveryModel
FROM sys.databases s
LEFT JOIN (SELECT DB FROM vwBackupHistory WHERE Num = 1) as v
        ON v.DB = s.name
WHERE v.DB Is null
go