Ads

26 February 2026

RMAN Backups Methodology Full and Incremental

RMAN provides multiple backup types, but the most commonly used in production environments are Full backups and Incremental backups. Understanding their differences helps DBAs design efficient backup strategies that optimize storage, speed, and recovery time.


1. Full Backup

A Full Backup (or Level 0 backup) is a complete backup of the entire database.

Key Characteristics

Backs up all data blocks, regardless of whether they changed.

Forms the baseline for incremental backup strategies.

Larger in size than incremental backups.

Takes more time and requires more storage.

Recovery is straightforward—restore full backup + apply archived logs.

2. Incremental Backups

Incremental backups only capture blocks that have changed since a previous L0 backup.

RMAN supports two types:

Differential Incremental Backup (Level 1 Differential)

Cumulative Incremental Backup (Level 1 Cumulative)

Both rely on a Level L0 full backup as a baseline.

2.1. Differential Incremental Backup

A Differential Level 1 backup captures all blocks changed since the last backup (Level 0 or Level 1).

Key Characteristics

Smaller and faster than full backups.

Captures daily changes.

During recovery, RMAN may use multiple Level 1 backups.

When it comes to recovery we need L0 Backup and all L1 backups including Archive logs need to be apply.


2.2. Cumulative Incremental Backup

A Cumulative Level 1 backup captures all blocks changed since the last Level 0.

Key Characteristics

Slightly larger than differential backups.

Faster recovery (fewer incremental files to apply).

Preferred for large, mission-critical systems.

When it comes to recovery we need L0 and recent L1 Cumulative backup including Archive logs need to be apply.






Understanding RMAN ( Recovery Manager) and Backups

 RMAN Key Points:

    - RMAN (Recovery Manager)

    - Oracle Database backup and recovery automation

    - It Introduced in Oracle 8i version

    - It performs block level backup

    - Parallelism

    - Detecting corruption in datafiles

    - Validating the backup

    - Incremental Backup

    - Recovery Catalog etc

    - Multi Destination Backups

    - Archive log mode is must to use RMAN

RMAN is faster because it takes block level backups.
RMAN is faster because we can initiate parallel processes.
RMAN will data block corruptions and repair it for you.
RMAN stores backup metadata information in the database control file, you can also store the information into deferent database on different server which is called as RECOVERY catalog
RMAN Incremental Recovery
              - Level 0 - Full database backup
              - Level 1 - Backup of only changed blocks taken by referring data block header for updated SCN
              -  
              - As per recovery, we must only restore the database from level 0 and recovery the Database using level 1. When you try to take level 1 backup, RMAN checks if already you have level 0 backup or not. If not then it will take level 0 backup automatically.

Recovery is of two parts:
              - Restore and Recover - We cannot perform recovery without restore. Hence Level 0 is used for restore and level 1 is used for recovery.
RMAN configuration Items
              - show all;

RMAN Utility:

RMAN utility comes with Oracle Binaries
No special installation or license required for using RMAN
At command prompt just type rman
It defaults connects to database environmental variables defined
RMAN utility can be used only when your DB is in at least MOUNT stage
RMAN is used while the DB is up and running and have very little performance impact if the backup is running

RMAN Backup Methodology:

- Full Backup
        - Entire Database backup plus archive logs 
        - Cannot apply Incremental backup on full backup
                            
- Incremental Backup
        - Level 0 - FULL Database backup
        - Level 1 - Backup changes only from last incremental backup
- Differential Backup 
                - Cumulative Backup

Components of RMAN:

  - RMAN Prompt
  - Target Database
  - Recovery Catalog
  - Auxiliary Database --- When cloning, Clone DB is called as auxiliary Database
  - Media Management Layer --- layer between RMAN 3rd part vendor backup tools netbackup,  Veritos and Tape backups
  - RMAN Channels

RMAN Configuration Parameter:

- RETENTION POLICY - tells till what date our backup will be stored which we can use for recovery
               - Redundancy -- How many backups to be retained
               - Recover Window -- How many days backup to be retained
        - Channels -- You can define channel to take backup to disk or tape
        - Control file auto backup -- includes control file and spfile auto backup
        - Parallelism -- Creates multiple processes to speed up backup
        - Encryption -- to secure the backup

25 February 2026

Backups and RMAN Backups ( Recovery Manager)

 

Why we need backup

The primary purpose of taking backups is to protect the database from data loss and ensure the ability to reconstruct the database in case of corruption, errors, or failures.

Categories of Database Failure:

Data loss can occur at any time

       Human errors, accidental deletions, logical corruption, hardware failures, or storage issues can lead to partial or complete data loss.

Backups preserve data integrity

    When data corruption or inconsistencies arise, backups enable you to restore the database to a consistent and trustworthy state.

Support for disaster recovery

    In the event of serious failures—like server crashes, media corruption, or site outages—backups are the foundation for bringing the database back online.

Compliance and business continuity

    Many industries require proper backup policies to meet audit standards and ensure uninterrupted business operations.


Backup Types:

Oracle backups are broadly classified into two categories:

1. Logical Backups

Logical backups extract database objects in a logical format.

Examples: Table-level, Schema-level and Tablespace-level export

Tools Used: Data Pump (expdp / impdp) and  Export/Import (exp / imp)

Logical backups are mainly used for:

    Application-level migrations

    Object-level recoveries

    Copying data between databases


2. Physical Backups

Physical backups involve copying actual database files at the OS level.

Files Included Datafiles, Control files, SPFILE and Archived redo logs

Types of Physical Backups

    RMAN Backups (most recommended)

    Hot Backup (User‑Managed Online Backup)

    Cold Backup (User‑Managed Offline Backup)

Hot and Cold Backups

Hot and Cold backups are known as manual backups, as they are performed manually without using RMAN or any backup tool.

Cold Backup (Consistent Backup)

    Database is shut down.

    All database files are copied at OS level.

    Backup is consistent.

    No recovery required during restore.

    Also referred to as a consistent backup.

Hot Backup (Inconsistent Backup)

    Database is open and available to users.

    Tablespaces are placed in backup mode.

    Datafiles are copied manually at OS level.

    Recovery is required because the backup is inconsistent.

    Archived logs are needed for full recovery.


Backup States / Backup Types in Oracle

Oracle backups fall under two main categories based on the state of the database:

1) Consistent Backup

    A backup is consistent when: The database is shut down cleanly using SHUTDOWN IMMEDIATE / NORMAL / TRANSACTIONAL.

All files (datafiles, control files, redo logs) are synchronized.

2) Inconsistent Backup

A backup is inconsistent when the database is in OPEN or MOUNT mode during the backup.

Files are not synchronized; hence recovery is mandatory.

Examples

    Hot Backup (User‑managed online backup)

    RMAN Backups (always inconsistent unless database is closed)


Oracle RMAN (Recovery Manager)

Oracle Recovery Manager (RMAN) is a powerful, built‑in Oracle database utility used for performing backup, restore, and recovery operations. It is a no‑cost, command‑line tool provided by Oracle to help DBAs protect critical database files—including datafiles, control files, SPFILEs, and archived redo logs.
RMAN integrates tightly with the Oracle database engine, enabling advanced features such as:

Block‑level corruption detection
RMAN scans blocks during backup and restore operations, identifying corrupted blocks early and ensuring data integrity.

Automated backup retention policies
You can define retention periods or redundancy levels, and RMAN will automatically manage obsolete backups.

Backup compression
Built‑in compression reduces storage costs and speeds up backup operations.
Parallelization
File identification
Block recovery
Media recovery

RMAN can run backup and restore operations across multiple channels, improving performance and meeting high‑availability needs.


What to Backup 

Parameter file / SPfile , Control file , archive log files and data files

What file needed in different stages of database 

1) Shutdown - Nothing needed
2) No Mount - Pfile or SPfile
3) Mount - Control file
4) Open - Data and redo log files

Recovery:

       1. Complete Recovery
          --- Recovering the database exactly till the point of failure
                                          
       2. Incomplete Recovery
          --- It cannot recover the database till the point of failure
  --- You can recover the database till the time you had taken the backup

24 August 2025

SQL Server 2022 In place Upgrade - Fails and resumes , succeeded, But SSRS failed to install.

 During in place upgrade of SQL Server 2022, Installation may fail with below error or warning, but it will end with successful message. During SSRS or PBIRS installation it may fail with Second error as shown below -


First Error/Warning During upgrade



Second Error SSRS/PBI RS:


UnexpectedError: setup failed for packageId: SSReportingServicesSetup, package: Microsoft SQL Server Reporting Services, errorCode: -2147483648, errorMessage: Something went wrong, please find details in setup logs.



Resolution :


Sometimes the SQL Server WMI namespace (root\Microsoft\SqlServer\ComputerManagement) is missing or corrupted.

Run this in Command Prompt:

mofcomp "C:\Program Files (x86)\Microsoft SQL Server\150\Shared\sqlmgmproviderxpsp2up.mof"


Note: make sure you are in the right folder and right version of the SQL Server.

27 July 2025

SQL Server Daily Health Check - Script


/* =======================================================================

   SQL Server Health Check – HTML Email (Final)

   - DBA's Heart Beat

   ======================================================================= */

SET NOCOUNT ON;


DECLARE @html    NVARCHAR(MAX) = N'';

DECLARE @newline NVARCHAR(10)  = CHAR(13) + CHAR(10);


-- =========================

-- HTML Header + Styles

-- =========================

SET @html = @html + N'<html><head><style>

body { font-family: Segoe UI, Arial, sans-serif; font-size: 13px; color: #222; }

h3 { margin: 18px 0 8px; }

table { border-collapse: collapse; width: auto; max-width: 100%; }

th, td { border: 1px solid #ddd; padding: 6px 8px; text-align: left; }

th { background-color: #f2f2f2; }

tr.alert { color: #b00020; font-weight: 600; }

.small { color:#666; font-size:12px; }

</style></head><body>' + @newline;


-- =========================

-- SQL Server Version & EOL

-- =========================

DECLARE @Servername NVARCHAR(128) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128));

DECLARE @version    NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));

DECLARE @edition    NVARCHAR(128) = CAST(SERVERPROPERTY('Edition')        AS NVARCHAR(128));

DECLARE @eol        NVARCHAR(256);

DECLARE @MajorVersion NVARCHAR(256);


SET @MajorVersion = CASE

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '8%'    THEN 'SQL2000'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '9%'    THEN 'SQL2005'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '10.0%' THEN 'SQL2008'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '10.5%' THEN 'SQL2008 R2'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '11%'   THEN 'SQL2012'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '12%'   THEN 'SQL2014'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '13%'   THEN 'SQL2016'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '14%'   THEN 'SQL2017'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '15%'   THEN 'SQL2019'

     WHEN CONVERT(VARCHAR(128), SERVERPROPERTY('productversion')) LIKE '16%'   THEN 'SQL2022'

     ELSE 'Unknown/New Version'

END;


SET @eol = CASE

    WHEN @@VERSION LIKE '%2005%'    THEN 'Mainstream: 2011-04-12, Extended: 2016-04-12'

    WHEN @@VERSION LIKE '%2008 R2%' THEN 'Mainstream: 2014-07-08, Extended: 2019-07-09'

    WHEN @@VERSION LIKE '%2008%'    THEN 'Mainstream: 2014-07-08, Extended: 2019-07-09'

    WHEN @@VERSION LIKE '%2012%'    THEN 'Mainstream: 2017-07-11, Extended: 2022-07-12'

    WHEN @@VERSION LIKE '%2014%'    THEN 'Mainstream: 2019-07-09, Extended: 2024-07-09'

    WHEN @@VERSION LIKE '%2016%'    THEN 'Mainstream: 2021-07-13, Extended: 2026-07-14'

    WHEN @@VERSION LIKE '%2017%'    THEN 'Mainstream: 2022-10-11, Extended: 2027-10-12'

    WHEN @@VERSION LIKE '%2019%'    THEN 'Mainstream: 2025-02-28, Extended: 2030-01-08'

    WHEN @@VERSION LIKE '%2022%'    THEN 'Mainstream: 2028-01-11, Extended: 2033-01-11'

    ELSE 'Unknown/New Version'

END;


SET @html = @html + N'<h3>SQL Server Version and EOL</h3><table><tr>

<th>Instance Name</th><th>Version</th><th>PatchLevel</th><th>Edition</th><th>EOL Info</th></tr>';


SET @html = @html + N'<tr><td>' + @Servername + N'</td><td>' + @MajorVersion + N'</td><td>' +

             @version + N'</td><td>' + @edition + N'</td><td>' + @eol + N'</td></tr></table>' + @newline;


-- =========================

-- Uptime (Start + Duration)

-- =========================

SET @html = @html + N'<h3>SQL Server Uptime</h3><table><tr><th>Start Time</th><th>Up Since</th></tr>';


DECLARE @start DATETIME = (SELECT sqlserver_start_time FROM sys.dm_os_sys_info);

DECLARE @now   DATETIME = GETDATE();


DECLARE @diffSeconds BIGINT = DATEDIFF(SECOND, @start, @now);

DECLARE @days   INT    = @diffSeconds / 86400;

DECLARE @hours  INT    = (@diffSeconds % 86400) / 3600;

DECLARE @mins   INT    = (@diffSeconds % 3600) / 60;

DECLARE @secs   INT    = @diffSeconds % 60;


SELECT @html = @html +

    N'<tr><td>' + CONVERT(NVARCHAR(19), @start, 120) + N'</td><td>' +

    CAST(@days AS NVARCHAR(10)) + N' days ' +

    CAST(@hours AS NVARCHAR(10)) + N' hrs ' +

    CAST(@mins AS NVARCHAR(10)) + N' mins ' +

    CAST(@secs AS NVARCHAR(10)) + N' secs</td></tr>';


SET @html = @html + N'</table>' + @newline;


-- =========================

-- Offline Databases

-- =========================

SET @html = @html + N'<h3>Offline Databases</h3>';

IF EXISTS (SELECT 1 FROM sys.databases WHERE state_desc = 'OFFLINE')

BEGIN

    SET @html = @html + N'<table><tr><th>Database</th><th>Status</th></tr>';

    SELECT @html = @html + N'<tr><td>' + name + N'</td><td>' + state_desc + N'</td></tr>'

    FROM sys.databases WHERE state_desc = 'OFFLINE';

    SET @html = @html + N'</table>' + @newline;

END

ELSE

BEGIN

    SET @html = @html + N'<p>All databases are online.</p>' + @newline;

END


/* =======================================================================

   Database Default Locations + All Disks (Fixed Drive Letters only)

   WMI via OLE Automation – decimal math for Free%

   ======================================================================= */


-----------------------------

-- Database Default Locations

-----------------------------

DECLARE @DefaultData   NVARCHAR(4000) = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS NVARCHAR(4000));

DECLARE @DefaultLog    NVARCHAR(4000) = CAST(SERVERPROPERTY('InstanceDefaultLogPath')  AS NVARCHAR(4000));

DECLARE @DefaultBackup NVARCHAR(4000);


BEGIN TRY

    SET @DefaultBackup = CAST(SERVERPROPERTY('InstanceDefaultBackupPath') AS NVARCHAR(4000));

END TRY

BEGIN CATCH

    SET @DefaultBackup = NULL;

END CATCH;


IF @DefaultBackup IS NULL OR LTRIM(RTRIM(@DefaultBackup)) = ''

BEGIN

    DECLARE @bk NVARCHAR(4000);

    EXEC master.dbo.xp_instance_regread

        N'HKEY_LOCAL_MACHINE',

        N'Software\Microsoft\MSSQLServer\MSSQLServer',

        N'BackupDirectory',

        @bk OUTPUT,

        'no_output';

    SET @DefaultBackup = @bk;

END


SET @html = @html + N'<h3>Database Default Locations</h3>'

           + N'<table><tr><th>Setting</th><th>Path</th></tr>'

           + N'<tr><td>Default Data</td><td>' + ISNULL(@DefaultData,'') + N'</td></tr>'

           + N'<tr><td>Default Log</td><td>'  + ISNULL(@DefaultLog,'')  + N'</td></tr>'

           + N'<tr><td>Default Backup</td><td>' + ISNULL(@DefaultBackup,'') + N'</td></tr>'

           + N'</table>' + @newline;


---------------------------------------------

-- All Disks: Fixed Drive Letters only (no \\?\Volume)

---------------------------------------------

IF OBJECT_ID('tempdb..#AllDisks') IS NOT NULL DROP TABLE #AllDisks;

CREATE TABLE #AllDisks

(

    DriveLetter NVARCHAR(10) NOT NULL,  -- 'C:'

    TotalBytes  BIGINT       NOT NULL,

    FreeBytes   BIGINT       NOT NULL

);


DECLARE @hr INT = 0, @locator INT = NULL, @svc INT = NULL, @set INT = NULL, @item INT = NULL, @count INT = 0, @i INT = 0;


BEGIN TRY

    -- Connect to WMI

    EXEC @hr = sp_OACreate 'WbemScripting.SWbemLocator', @locator OUT;

    IF @hr <> 0 BEGIN RAISERROR('Failed to create WMI locator (sp_OACreate).', 16, 1); RETURN; END;


    EXEC @hr = sp_OAMethod @locator, 'ConnectServer', @svc OUT, '.', 'root\cimv2';

    IF @hr <> 0 BEGIN RAISERROR('Failed to connect to WMI root\cimv2.', 16, 1); RETURN; END;


    -- Query: Fixed disks with letters (DriveType=3)

    EXEC @hr = sp_OAMethod @svc, 'ExecQuery', @set OUT,

        'SELECT DeviceID, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType=3';

    IF @hr <> 0 BEGIN RAISERROR('WMI ExecQuery failed for Win32_LogicalDisk.', 16, 1); RETURN; END;


    EXEC @hr = sp_OAGetProperty @set, 'Count', @count OUT;

    SET @i = 0;

    WHILE @i < ISNULL(@count, 0)

    BEGIN

        EXEC @hr = sp_OAMethod @set, 'ItemIndex', @item OUT, @i;

        IF @hr <> 0 BREAK;


        DECLARE @DeviceID NVARCHAR(50), @Size NUMERIC(38,0), @Free NUMERIC(38,0);

        EXEC @hr = sp_OAGetProperty @item, 'DeviceID',  @DeviceID OUT;

        EXEC @hr = sp_OAGetProperty @item, 'Size',      @Size     OUT;

        EXEC @hr = sp_OAGetProperty @item, 'FreeSpace', @Free     OUT;


        -- Insert only if Size is present (>0)

        IF @Size IS NOT NULL AND @Size > 0

        BEGIN

            INSERT INTO #AllDisks(DriveLetter, TotalBytes, FreeBytes)

            VALUES (UPPER(@DeviceID), CONVERT(BIGINT, @Size), CONVERT(BIGINT, @Free));

        END


        SET @i = @i + 1;

    END

END TRY

BEGIN CATCH

    DECLARE @err NVARCHAR(4000) = ERROR_MESSAGE();

    RAISERROR('WMI/OLE disk collection failed: %s', 16, 1, @err);

END CATCH


-- Cleanup WMI objects

IF @set     IS NOT NULL BEGIN EXEC sp_OADestroy @set;     END;

IF @svc     IS NOT NULL BEGIN EXEC sp_OADestroy @svc;     END;

IF @locator IS NOT NULL BEGIN EXEC sp_OADestroy @locator; END;


-- Render All Disks (decimal math for Free%)

SET @html = @html + N'<h3>All Disks</h3>'

           + N'<table><tr>'

           + N'<th>Drive</th><th>Total (GB)</th><th>Free (GB)</th>'

           + N'<th>Used (GB)</th><th>Free (%)</th></tr>';


;WITH calc AS

(

    SELECT

        Drive      = DriveLetter,

        totalGB    = CONVERT(DECIMAL(18,2), TotalBytes / 1024.0 / 1024 / 1024),

        freeGB     = CONVERT(DECIMAL(18,2), FreeBytes  / 1024.0 / 1024 / 1024),

        usedGB     = CONVERT(DECIMAL(18,2), (TotalBytes - FreeBytes) / 1024.0 / 1024 / 1024)

    FROM #AllDisks

),

calc2 AS

(

    SELECT

        Drive, totalGB, freeGB, usedGB,

        freePct = CONVERT(DECIMAL(5,2), CASE WHEN totalGB > 0 THEN (freeGB * 100.00) / totalGB ELSE 0 END)

    FROM calc

)

SELECT @html = @html +

       CASE WHEN freePct < 10 THEN N'<tr class="alert">' ELSE N'<tr>' END +

       N'<td>' + Drive + N'</td><td>' +

       CONVERT(NVARCHAR(50), totalGB) + N'</td><td>' +

       CONVERT(NVARCHAR(50), freeGB)  + N'</td><td>' +

       CONVERT(NVARCHAR(50), usedGB)  + N'</td><td>' +

       CONVERT(NVARCHAR(50), freePct) + N'</td></tr>'

FROM calc2

ORDER BY Drive;


SET @html = @html + N'</table>' + @newline;


DROP TABLE #AllDisks;


-- =========================

-- Backup Status (exclude tempdb & mirrored DBs)

-- =========================

SET @html = @html + N'<h3>Backup Status (Failed or No Backup in Past 2 Days)</h3>';


IF EXISTS (

    SELECT 1

    FROM sys.databases d

    LEFT JOIN msdb.dbo.backupset bs ON bs.database_name = d.name

    LEFT JOIN sys.database_mirroring dm ON d.database_id = dm.database_id

    WHERE d.name <> 'tempdb'

      AND d.is_read_only = 0

      AND dm.mirroring_guid IS NULL

    GROUP BY d.name

    HAVING MAX(bs.backup_finish_date) IS NULL

        OR MAX(bs.backup_finish_date) < DATEADD(DAY, -2, GETDATE())

)

BEGIN

    SET @html = @html + N'<table><tr><th>Database</th><th>Last Backup</th></tr>';

    SELECT @html = @html + N'<tr><td>' + d.name + N'</td><td>' +

           COALESCE(CONVERT(NVARCHAR(19), MAX(bs.backup_finish_date), 120), N'No Backup') + N'</td></tr>'

    FROM sys.databases d

    LEFT JOIN msdb.dbo.backupset bs ON bs.database_name = d.name

    LEFT JOIN sys.database_mirroring dm ON d.database_id = dm.database_id

    WHERE d.name <> 'tempdb'

      AND d.is_read_only = 0

      AND dm.mirroring_guid IS NULL

    GROUP BY d.name

    HAVING MAX(bs.backup_finish_date) IS NULL

        OR MAX(bs.backup_finish_date) < DATEADD(DAY, -2, GETDATE());

    SET @html = @html + N'</table>' + @newline;

END

ELSE

BEGIN

    SET @html = @html + N'<p>All backups successful (within last 2 days).</p>' + @newline;

END


-- =========================

-- Mirroring Status

-- =========================

SET @html = @html + N'<h3>Mirroring Status</h3>';

IF EXISTS (SELECT 1 FROM sys.database_mirroring WHERE mirroring_guid IS NOT NULL)

BEGIN

    SET @html = @html + N'<table><tr><th>Database</th><th>Mirroring State</th><th>Safety Level</th></tr>';

    SELECT @html = @html + N'<tr><td>' + d.name + N'</td><td>' +

           ISNULL(dm.mirroring_state_desc, N'Unknown') + N'</td><td>' +

           ISNULL(dm.mirroring_safety_level_desc, N'Unknown') + N'</td></tr>'

    FROM sys.databases d

    JOIN sys.database_mirroring dm ON d.database_id = dm.database_id

    WHERE dm.mirroring_guid IS NOT NULL;

    SET @html = @html + N'</table>' + @newline;

END

ELSE

BEGIN

    SET @html = @html + N'<p>No mirroring configured.</p>' + @newline;

END




-- =========================

-- Replication Status (GUID-safe + resilient + idempotent)

-- =========================

SET @html = @html + N'<h3>Replication Status</h3>';


IF DB_ID('distribution') IS NOT NULL

BEGIN

    IF OBJECT_ID('distribution.dbo.MSreplication_monitordata') IS NOT NULL

    BEGIN

        -- Clean up any leftovers from previous runs in this session

        IF OBJECT_ID('tempdb..#DA')           IS NOT NULL DROP TABLE #DA;

        IF OBJECT_ID('tempdb..#MM')           IS NOT NULL DROP TABLE #MM;

        IF OBJECT_ID('tempdb..#JobLast')      IS NOT NULL DROP TABLE #JobLast;

        IF OBJECT_ID('tempdb..#MonitorRepl')  IS NOT NULL DROP TABLE #MonitorRepl;


        /* 1) Distribution Agents with publication + subscriber */

        SELECT

            p.publisher_db,

            a.publication,

            a.subscriber_db,

            a.job_id,

            job_id_str = UPPER(CONVERT(NVARCHAR(36), a.job_id))

        INTO #DA

        FROM distribution.dbo.MSdistribution_agents AS a

        INNER JOIN distribution.dbo.MSpublications   AS p

                ON p.publisher_id = a.publisher_id

               AND p.publication  = a.publication;


        /* 2) Monitor data: normalize job_id string (trim, uppercase, remove braces) */

        SELECT

            publisher_db       = m.publisher_db,

            publication        = m.publication,

            job_id_str         = NULLIF(UPPER(REPLACE(REPLACE(LTRIM(RTRIM(m.job_id)), '{', ''), '}', '')), ''),

            [Status]           = m.[Status],

            [Warning]          = m.[Warning],

            cur_latency        = m.cur_latency

        INTO #MM

        FROM distribution.dbo.MSreplication_monitordata AS m

        WHERE m.agent_type = 3  -- distribution agents

          AND NULLIF(LTRIM(RTRIM(m.job_id)), '') IS NOT NULL;


        /* 3) Last job outcome for each agent (fallback when MM has no rows) */

        SELECT

            j.job_id,

            job_id_str = UPPER(CONVERT(NVARCHAR(36), j.job_id)),

            last_status = ISNULL(h_last.run_status, -1)  -- -1 = unknown

        INTO #JobLast

        FROM msdb.dbo.sysjobs AS j

        INNER JOIN #DA        AS da ON da.job_id = j.job_id

        OUTER APPLY (

            SELECT TOP (1) run_status

            FROM msdb.dbo.sysjobhistory

            WHERE job_id = j.job_id AND step_id = 0

            ORDER BY run_date DESC, run_time DESC

        ) AS h_last;


        /* 4) Final projection: prefer MM status; else fallback to job history; else Unknown */

        SELECT DISTINCT

            da.publisher_db,

            da.publication,

            da.subscriber_db,

            Status =

                COALESCE(

                    /* MM status mapping */

                    CASE mm.[Status]

                        WHEN 1 THEN 'Started'

                        WHEN 2 THEN 'Succeeded'

                        WHEN 3 THEN 'In Progress'

                        WHEN 4 THEN 'Idle'

                        WHEN 5 THEN 'Retrying'

                        WHEN 6 THEN 'Failed'

                        ELSE NULL

                    END,

                    /* Job history mapping (fallback) */

                    CASE jl.last_status

                        WHEN 0 THEN 'Failed'

                        WHEN 1 THEN 'Succeeded'

                        WHEN 2 THEN 'Retrying'

                        WHEN 3 THEN 'Canceled'

                        WHEN 4 THEN 'In Progress'

                        ELSE 'Unknown'

                    END,

                    'Unknown'

                )

        INTO #MonitorRepl

        FROM #DA AS da

        LEFT JOIN #MM AS mm

               ON (mm.job_id_str   = da.job_id_str)

              OR (mm.publisher_db  = da.publisher_db AND mm.publication = da.publication)

        LEFT JOIN #JobLast AS jl

               ON jl.job_id        = da.job_id;


        /* 5) Render HTML */

        SET @html = @html + N'<table><tr>'

            + N'<th>Publisher DB</th><th>Publication</th><th>Subscriber DB</th><th>Status</th>'

            + N'</tr>';


        IF EXISTS (SELECT 1 FROM #MonitorRepl)

        BEGIN

            SELECT @html = @html + N'<tr><td>' + publisher_db + N'</td><td>' + publication

                             + N'</td><td>' + subscriber_db + N'</td><td>' + Status + N'</td></tr>'

            FROM #MonitorRepl

            ORDER BY publisher_db, publication, subscriber_db;

        END

        ELSE

        BEGIN

            SET @html = @html + N'<tr><td colspan="4">No replication agent rows found.</td></tr>';

        END


        SET @html = @html + N'</table>' + @newline;


        /* 6) Cleanup */

        IF OBJECT_ID('tempdb..#MonitorRepl') IS NOT NULL DROP TABLE #MonitorRepl;

        IF OBJECT_ID('tempdb..#JobLast')     IS NOT NULL DROP TABLE #JobLast;

        IF OBJECT_ID('tempdb..#MM')          IS NOT NULL DROP TABLE #MM;

        IF OBJECT_ID('tempdb..#DA')          IS NOT NULL DROP TABLE #DA;

    END

    ELSE

    BEGIN

        SET @html = @html + N'<p>No replication activity object (MSreplication_monitordata) found in distribution database.</p>' + @newline;

    END

END

ELSE

BEGIN

    SET @html = @html + N'<p>No replication configured.</p>' + @newline;

END


-- =========================

-- Always On Availability Groups

-- =========================

SET @html = @html + N'<h3>Always On Availability Groups</h3>';

IF EXISTS (SELECT 1 FROM sys.availability_groups)

BEGIN

    SET @html = @html + N'<table><tr>

        <th>Availability Group</th>

        <th>Replica Server</th>

        <th>Role</th>

        <th>Database</th>

        <th>Sync State</th>

        <th>Health</th>

    </tr>';


    ;WITH ags AS (

        SELECT DISTINCT

               ag.name                           AS AGName,

               ar.replica_server_name            AS ReplicaServer,

               ars.role_desc                     AS RoleDesc,

               adc.database_name                 AS DBName,

               dhs.synchronization_state_desc    AS SyncState,

               dhs.synchronization_health_desc   AS Health

        FROM sys.availability_groups ag

        JOIN sys.availability_replicas ar

             ON ag.group_id = ar.group_id

        JOIN sys.dm_hadr_availability_replica_states ars

             ON ar.replica_id = ars.replica_id

        JOIN sys.availability_databases_cluster adc

             ON ag.group_id = adc.group_id

        JOIN sys.dm_hadr_database_replica_states dhs

             ON ars.replica_id = dhs.replica_id

            AND dhs.group_database_id = adc.group_database_id

    )

    SELECT @html = @html + N'<tr><td>' + AGName + N'</td><td>' + ReplicaServer + N'</td><td>' +

           RoleDesc + N'</td><td>' + DBName + N'</td><td>' + SyncState + N'</td><td>' + Health + N'</td></tr>'

    FROM ags

    ORDER BY AGName, ReplicaServer, DBName;


    SET @html = @html + N'</table>' + @newline;

END

ELSE

BEGIN

    SET @html = @html + N'<p>No Always On Availability Groups configured.</p>' + @newline;

END


-- =========================

-- HTML Footer

-- =========================

SET @html = @html + N'</body></html>';


-- =========================

-- Send Email

-- =========================

DECLARE @sub VARCHAR(MAX);

SET @sub = 'Alert: SQL Server Health Check Report - ' + CAST(SERVERPROPERTY('MachineName') AS VARCHAR(128));


EXEC msdb.dbo.sp_send_dbmail

    @profile_name = 'Default',

    @recipients   = 'DBA@sqlserver.com',

    @subject      = @sub,

    @body         = @html,

    @body_format  = 'HTML';


05 May 2025

The server principal owns one or more server role(s) and cannot be dropped. (Microsoft SQL Server, Error: 15141)


Some time we are trying to delete or drop a login from SQL server instance , we encountered with below error.


The server principal owns one or more server role(s) and cannot be dropped. (Microsoft SQL Server, Error: 15141)


Step 1) Find the server roles owned by login using below script 

SELECT sp1.name AS ServerRoleName, 

       sp2.name AS RoleOwnerName

       FROM sys.server_principals AS sp1

       JOIN sys.server_principals As sp2

       ON sp1.owning_principal_id=sp2.principal_id

       WHERE sp2.name='Test' --Change the login name


Step 2 ) Change server role to another login 

USE [master]

GO

ALTER AUTHORIZATION ON SERVER ROLE :: Admin to SQLAdmin


Step 3) Drop login 

USE [master]

GO

Drop Login Test