Preskočiť na obsah
Skript

Porovnanie a synchronizácia databázových rolí medzi loginmi

Automaticky porovná členstvo dvoch SQL loginov v databázových rolách naprieč celou SQL Server inštanciou. Zobrazí chýbajúce aj nadbytočné roly a umožňuje bezpečné dorovnanie pomocou prepínača @Apply (dry-run).

porovnanie-a-synchronizacia-databazovych-roli-medzi-loginmi.sql 556 riadkov · T-SQL
Parametre skriptu doplň a kopíruj hotový skript

/*
============================================================
 DEADLOCK CAFE
 Porovnanie a synchronizacia databazovych roli
============================================================

 SQL Server: 2012+
 Typ: Security / Database Role Synchronization

 REZIMY:
   @Apply = 0 : Iba kontrola (DRY-RUN)
   @Apply = 1 : Doplnenie chybajucich clenstiev

 Skript automaticky vyhlada databazy a databazove role.
 Nekopiruje priame GRANT/DENY ani serverove role.
============================================================
*/

USE [master];
GO

SET NOCOUNT ON;
SET XACT_ABORT ON;

------------------------------------------------------------
-- 1. KONFIGURACIA - DEADLOCK CAFE PARAMETRE
------------------------------------------------------------

DECLARE @ReferenceLogin SYSNAME =
    N'{{ReferenceLogin:SourceLogin|Referenčný SQL login}}';

DECLARE @TargetLogin SYSNAME =
    N'{{TargetLogin:TargetLogin|Cieľový SQL login}}';

DECLARE @Apply INT =
    {{Apply:0|Aplikovať zmeny? (0=nie, 1=áno)}};

DECLARE @IncludeSystemDB INT =
    {{IncludeSystemDB:0|Zahrnúť systémové DB? (0=nie, 1=áno)}};

------------------------------------------------------------
-- 2. VALIDACIA PARAMETROV A LOGINOV
------------------------------------------------------------

IF @Apply NOT IN (0, 1) OR @Apply IS NULL
    THROW 50001, 'Parameter Apply musi byt 0 alebo 1.', 1;

IF @IncludeSystemDB NOT IN (0, 1)
   OR @IncludeSystemDB IS NULL
    THROW 50002, 'IncludeSystemDB musi byt 0 alebo 1.', 1;

IF @ReferenceLogin = @TargetLogin
    THROW 50003, 'Loginy musia byt rozdielne.', 1;

DECLARE @ReferenceSID VARBINARY(85);
DECLARE @TargetSID VARBINARY(85);

SELECT @ReferenceSID = sid
FROM sys.server_principals
WHERE name = @ReferenceLogin
  AND type = 'S';

SELECT @TargetSID = sid
FROM sys.server_principals
WHERE name = @TargetLogin
  AND type = 'S';

IF @ReferenceSID IS NULL
    THROW 50004, 'Referencny SQL login neexistuje.', 1;

IF @TargetSID IS NULL
    THROW 50005, 'Cielovy SQL login neexistuje.', 1;

------------------------------------------------------------
-- 3. PRACOVNE TABULKY
------------------------------------------------------------

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

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

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

CREATE TABLE #RoleDifferences
(
    DatabaseName  SYSNAME NOT NULL,
    RoleName      SYSNAME NOT NULL,
    ReferenceUser SYSNAME NULL,
    TargetUser    SYSNAME NULL,
    ActionType    VARCHAR(10) NOT NULL
);

CREATE TABLE #Warnings
(
    DatabaseName SYSNAME,
    WarningMessage NVARCHAR(4000)
);

CREATE TABLE #Applied
(
    DatabaseName SYSNAME,
    RoleName SYSNAME,
    TargetUser SYSNAME,
    ActionStatus VARCHAR(20)
);

------------------------------------------------------------
-- 4. KONTROLA DOSTUPNOSTI DATABAZ
------------------------------------------------------------

INSERT INTO #Warnings
(
    DatabaseName,
    WarningMessage
)
SELECT
    name,
    CONCAT(
        N'Databaza preskocena. State=',
        state_desc,
        N', ReadOnly=',
        is_read_only,
        N', Access=',
        COALESCE(
            CONVERT(VARCHAR(10), HAS_DBACCESS(name)),
            'NULL'
        )
    )
FROM sys.databases
WHERE
    (database_id > 4 OR @IncludeSystemDB = 1)
    AND database_id <> 2
    AND
    (
        state_desc <> 'ONLINE'
        OR is_read_only = 1
        OR ISNULL(HAS_DBACCESS(name), 0) <> 1
    );

------------------------------------------------------------
-- 5. AUTOMATICKE POROVNANIE DATABAZOVYCH ROLI
------------------------------------------------------------

DECLARE @DB SYSNAME;
DECLARE @SQL NVARCHAR(MAX);

DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR

SELECT name
FROM sys.databases
WHERE state_desc = 'ONLINE'
  AND is_read_only = 0
  AND HAS_DBACCESS(name) = 1
  AND database_id <> 2
  AND (database_id > 4 OR @IncludeSystemDB = 1);

OPEN db_cursor;

FETCH NEXT FROM db_cursor INTO @DB;

WHILE @@FETCH_STATUS = 0
BEGIN

    SET @SQL = N'
    USE ' + QUOTENAME(@DB) + N';

    -- Overenie viditelnosti bezpecnostnych metadat
    IF COALESCE(IS_SRVROLEMEMBER(''sysadmin''), 0) <> 1
       AND COALESCE(IS_MEMBER(''db_owner''), 0) <> 1
       AND COALESCE(IS_MEMBER(''db_securityadmin''), 0) <> 1
       AND COALESCE(
           HAS_PERMS_BY_NAME(
               DB_NAME(), ''DATABASE'', ''VIEW DEFINITION''
           ), 0
       ) <> 1
    BEGIN
        THROW 50010,
              ''Nedostatocna viditelnost databazovych roli.'',
              1;
    END;

    DECLARE @RefID INT;
    DECLARE @TargetID INT;
    DECLARE @RefUser SYSNAME;
    DECLARE @TargetUser SYSNAME;

    SELECT
        @RefID = principal_id,
        @RefUser = name
    FROM sys.database_principals
    WHERE sid = @RefSID
      AND type = ''S'';

    SELECT
        @TargetID = principal_id,
        @TargetUser = name
    FROM sys.database_principals
    WHERE sid = @TargetSID
      AND type = ''S'';

    --------------------------------------------------------
    -- Cielovy databazovy user neexistuje
    --------------------------------------------------------

    IF @RefID IS NOT NULL AND @TargetID IS NULL
    BEGIN
        INSERT INTO #Warnings
        VALUES
        (
            DB_NAME(),
            N''Cielovy database user neexistuje.''
        );
    END
    ELSE
    BEGIN

        ----------------------------------------------------
        -- ROLE, KTORE CIELOVEMU USEROVI CHYBAJU
        ----------------------------------------------------

        IF @RefID IS NOT NULL AND @TargetID IS NOT NULL
        BEGIN

            INSERT INTO #RoleDifferences
            (
                DatabaseName,
                RoleName,
                ReferenceUser,
                TargetUser,
                ActionType
            )
            SELECT
                DB_NAME(),
                r.name,
                @RefUser,
                @TargetUser,
                ''ADD''
            FROM sys.database_role_members rm
            JOIN sys.database_principals r
                ON r.principal_id = rm.role_principal_id
            WHERE rm.member_principal_id = @RefID
              AND NOT EXISTS
              (
                  SELECT 1
                  FROM sys.database_role_members x
                  WHERE x.role_principal_id =
                        rm.role_principal_id
                    AND x.member_principal_id = @TargetID
              );

        END;

        ----------------------------------------------------
        -- ROLE, KTORE MA CIELOVY USER NAVYSE
        ----------------------------------------------------

        IF @TargetID IS NOT NULL
        BEGIN

            INSERT INTO #RoleDifferences
            (
                DatabaseName,
                RoleName,
                ReferenceUser,
                TargetUser,
                ActionType
            )
            SELECT
                DB_NAME(),
                r.name,
                @RefUser,
                @TargetUser,
                ''EXTRA''
            FROM sys.database_role_members rm
            JOIN sys.database_principals r
                ON r.principal_id = rm.role_principal_id
            WHERE rm.member_principal_id = @TargetID
              AND NOT EXISTS
              (
                  SELECT 1
                  FROM sys.database_role_members x
                  WHERE x.role_principal_id =
                        rm.role_principal_id
                    AND x.member_principal_id = @RefID
              );

        END;

    END;
    ';

    BEGIN TRY

        EXEC sys.sp_executesql
            @SQL,
            N'@RefSID VARBINARY(85),
              @TargetSID VARBINARY(85)',
            @RefSID = @ReferenceSID,
            @TargetSID = @TargetSID;

    END TRY
    BEGIN CATCH

        INSERT INTO #Warnings
        (
            DatabaseName,
            WarningMessage
        )
        VALUES
        (
            @DB,
            ERROR_MESSAGE()
        );

    END CATCH;

    FETCH NEXT FROM db_cursor INTO @DB;

END;

CLOSE db_cursor;
DEALLOCATE db_cursor;

------------------------------------------------------------
-- 6. RESULT 1 - ZISTENE ROZDIELY
------------------------------------------------------------

SELECT
    DatabaseName,
    RoleName,
    ReferenceUser,
    TargetUser,
    CASE ActionType
        WHEN 'ADD'   THEN 'MISSING - ADD'
        WHEN 'EXTRA' THEN 'EXTRA - KEEP'
    END AS Status
FROM #RoleDifferences
ORDER BY
    DatabaseName,
    RoleName,
    ActionType;

------------------------------------------------------------
-- 7. RESULT 2 - UPOZORNENIA
------------------------------------------------------------

SELECT
    DatabaseName,
    WarningMessage
FROM #Warnings
ORDER BY DatabaseName;

------------------------------------------------------------
-- 8. APLIKOVANIE ZMIEN
------------------------------------------------------------

IF @Apply = 1
BEGIN

    -- Pri upozorneniach sa nevykona ziadna zmena
    IF EXISTS (SELECT 1 FROM #Warnings)
    BEGIN
        THROW 50011,
              'Kontrola obsahuje upozornenia. Zmeny zablokovane.',
              1;
    END;

    DECLARE @Role SYSNAME;

    DECLARE apply_cursor CURSOR LOCAL FAST_FORWARD FOR

    SELECT
        DatabaseName,
        RoleName
    FROM #RoleDifferences
    WHERE ActionType = 'ADD'
    ORDER BY DatabaseName, RoleName;

    BEGIN TRY

        BEGIN TRANSACTION;

        OPEN apply_cursor;
        FETCH NEXT FROM apply_cursor INTO @DB, @Role;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            SET @SQL = N'
            USE ' + QUOTENAME(@DB) + N';

            DECLARE @RefID INT;
            DECLARE @TargetID INT;
            DECLARE @RoleID INT;
            DECLARE @DbUser SYSNAME;

            SELECT @RefID = principal_id
            FROM sys.database_principals
            WHERE sid = @RefSID
              AND type = ''S'';

            SELECT
                @TargetID = principal_id,
                @DbUser = name
            FROM sys.database_principals
            WHERE sid = @TargetSID
              AND type = ''S'';

            SELECT @RoleID = principal_id
            FROM sys.database_principals
            WHERE name = @Role
              AND type = ''R'';

            IF @RefID IS NULL OR @TargetID IS NULL
                THROW 50012,
                      ''DB user neexistuje.'',
                      1;

            IF @RoleID IS NULL
                THROW 50013,
                      ''Databazova rola neexistuje.'',
                      1;

            -- Referencny user musi stale mat danu rolu
            IF NOT EXISTS
            (
                SELECT 1
                FROM sys.database_role_members
                WHERE role_principal_id = @RoleID
                  AND member_principal_id = @RefID
            )
                THROW 50014,
                      ''Referencny user uz nema tuto rolu.'',
                      1;

            -- Ochrana pred duplicitnym priradenim
            IF NOT EXISTS
            (
                SELECT 1
                FROM sys.database_role_members
                WHERE role_principal_id = @RoleID
                  AND member_principal_id = @TargetID
            )
            BEGIN

                DECLARE @Command NVARCHAR(MAX);

                SET @Command =
                    N''ALTER ROLE ''
                    + QUOTENAME(@Role)
                    + N'' ADD MEMBER ''
                    + QUOTENAME(@DbUser)
                    + N'';'';

                EXEC sys.sp_executesql @Command;

                INSERT INTO #Applied
                (
                    DatabaseName,
                    RoleName,
                    TargetUser,
                    ActionStatus
                )
                VALUES
                (
                    DB_NAME(),
                    @Role,
                    @DbUser,
                    ''ADDED''
                );

            END;
            ';

            EXEC sys.sp_executesql
                @SQL,
                N'@Role SYSNAME,
                  @RefSID VARBINARY(85),
                  @TargetSID VARBINARY(85)',
                @Role = @Role,
                @RefSID = @ReferenceSID,
                @TargetSID = @TargetSID;

            FETCH NEXT FROM apply_cursor INTO @DB, @Role;

        END;

        CLOSE apply_cursor;
        DEALLOCATE apply_cursor;

        COMMIT TRANSACTION;

        ----------------------------------------------------
        -- RESULT 3 - USpesne APLIKOVANE ZMENY
        ----------------------------------------------------

        SELECT
            DatabaseName,
            RoleName,
            TargetUser,
            ActionStatus
        FROM #Applied
        ORDER BY DatabaseName, RoleName;

    END TRY
    BEGIN CATCH

        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        IF CURSOR_STATUS('local', 'apply_cursor') >= 0
            CLOSE apply_cursor;

        IF CURSOR_STATUS('local', 'apply_cursor') > -3
            DEALLOCATE apply_cursor;

        THROW;

    END CATCH;

END;

------------------------------------------------------------
-- 9. ZAVERECNY STATUS
------------------------------------------------------------

SELECT
    @@SERVERNAME AS ServerName,
    @ReferenceLogin AS ReferenceLogin,
    @TargetLogin AS TargetLogin,
    CASE @Apply
        WHEN 0 THEN 'DRY-RUN'
        WHEN 1 THEN 'APPLY COMPLETED'
    END AS ExecutionMode,
    (SELECT COUNT(*)
     FROM #RoleDifferences
     WHERE ActionType = 'ADD') AS MissingMemberships,
    (SELECT COUNT(*)
     FROM #RoleDifferences
     WHERE ActionType = 'EXTRA') AS ExtraMemberships,
    (SELECT COUNT(*)
     FROM #Applied) AS AppliedMemberships,
    (SELECT COUNT(*)
     FROM #Warnings) AS WarningCount;

------------------------------------------------------------
-- 10. CLEANUP
------------------------------------------------------------

DROP TABLE #RoleDifferences;
DROP TABLE #Warnings;
DROP TABLE #Applied;
## Kedy použiť

Skript slúži na **automatické porovnanie a synchronizáciu členstva v databázových rolách** medzi dvoma SQL loginmi na jednej SQL Server inštancii.

Umožňuje zistiť, ktoré databázové roly má referenčný účet a ktoré chýbajú cieľovému účtu. Chýbajúce členstvá dokáže následne automaticky doplniť.

### Typické scenáre použitia

- **Migrácia servisného účtu** – nový účet potrebuje rovnaké databázové roly ako pôvodný.
- **Riešenie Permission denied** – kontrola, či účtu nechýba členstvo v požadovanej role.
- **Migrácia SQL Servera** – kontrola a dorovnanie databázových rolí po presune databáz.
- **Bezpečnostný audit** – identifikácia rozdielov medzi dvoma SQL loginmi.
- **Správa viacerých SQL inštancií** – použitie rovnakého skriptu na serveroch s rozdielnymi databázami.

> [!TIP]
> Skript automaticky vyhľadáva databázy aj roly na aktuálnej SQL inštancii. Nie je potrebné definovať zoznam databáz ani upravovať názvy rolí.

---

## Ako skript funguje

Skript pracuje na princípe **referenčný login → cieľový login**.

- **ReferenceLogin** – účet, ktorého členstvá v rolách predstavujú požadovaný stav.
- **TargetLogin** – účet, ktorému chceme prípadne doplniť chýbajúce členstvá.

Na získanie informácií používa systémové katalógové pohľady:

- `sys.databases` – zoznam databáz na inštancii.
- `sys.server_principals` – kontrola existencie SQL loginov.
- `sys.database_principals` – databázoví používatelia a roly.
- `sys.database_role_members` – členstvo používateľov v databázových rolách.

Databázových používateľov identifikuje podľa SID príslušných SQL loginov. Názov databázového používateľa preto nemusí byť rovnaký ako názov SQL loginu.

### Režimy vykonania

| Režim | Popis |
|---|---|
| `@Apply = 0` | Iba kontrola a výpis rozdielov (dry-run) |
| `@Apply = 1` | Kontrola a automatické doplnenie chýbajúcich členstiev |

> [!IMPORTANT]
> Predvolený režim je `@Apply = 0`. Skript v tomto režime nemení oprávnenia ani členstvá v databázových rolách.

---

## Postup použitia

### 1. Pripojenie na SQL Server

1. Otvor SQL Server Management Studio (SSMS).
2. Pripoj sa na požadovanú SQL Server inštanciu.
3. Otvor nové query okno.
4. Vlož SQL skript zo záložky **Kód**.

Skript sa vykonáva nad aktuálne pripojenou SQL inštanciou.

> [!TIP]
> Na spoľahlivú kontrolu všetkých databáz používaj administrátorský účet s dostatočnými oprávneniami na čítanie bezpečnostných metadát.

### 2. Konfigurácia loginov

V úvodnej časti skriptu uprav:

```sql
DECLARE @ReferenceLogin SYSNAME = N'SourceLogin';
DECLARE @TargetLogin    SYSNAME = N'TargetLogin';
```

**Význam parametrov:**

- `@ReferenceLogin` – zdrojový SQL login.
- `@TargetLogin` – cieľový SQL login, ktorému chceme doplniť členstvá.

**Príklad:**

```sql
DECLARE @ReferenceLogin SYSNAME = N'app_service_old';
DECLARE @TargetLogin    SYSNAME = N'app_service_new';
```

Skript bude porovnávať roly účtu `app_service_new` voči účtu `app_service_old`.

### 3. Nastavenie rozsahu databáz

```sql
DECLARE @IncludeSystemDB BIT = 0;
```

Možnosti:

- `0` – kontrolujú sa iba používateľské databázy.
- `1` – kontrolujú sa aj systémové databázy okrem `tempdb`.

> [!TIP]
> Pre bežnú správu aplikačných a servisných účtov odporúčam ponechať `@IncludeSystemDB = 0`.

### 4. Spustenie kontroly (dry-run)

Nastav:

```sql
DECLARE @Apply BIT = 0;
```

Spusti celý skript pomocou **Execute (F5)**.

Skript vykoná:

1. Kontrolu existencie oboch loginov.
2. Automatické vyhľadanie dostupných databáz.
3. Identifikáciu databázových používateľov.
4. Porovnanie členstva v databázových rolách.
5. Výpis zistených rozdielov a upozornení.

**V tomto režime sa nevykonajú žiadne zmeny oprávnení.**

---

## Interpretácia výsledkov

### Result 1 – Rozdiely v databázových rolách

Príklad výsledku:

| DatabaseName | RoleName | Status |
|---|---|---|
| AppDB | AppWriter | MISSING - ADD |
| ReportingDB | db_datareader | MISSING - ADD |
| ArchiveDB | db_datawriter | EXTRA - KEEP |

#### MISSING - ADD

Referenčný účet je členom databázovej roly, ale cieľový účet nie.

Pri použití `@Apply = 1` bude cieľový používateľ automaticky pridaný do tejto roly.

#### EXTRA - KEEP

Cieľový účet je členom databázovej roly, ktorú referenčný účet nemá.

Skript túto skutočnosť iba zobrazí.

**Existujúce členstvo cieľového používateľa automaticky neodstraňuje.**

> [!NOTE]
> Ak je výsledok prázdny, skript nezistil rozdiely v priamom členstve databázových rolí v úspešne skontrolovaných databázach. Neznamená to však automaticky, že oba účty majú identické efektívne oprávnenia.

### Result 2 – Upozornenia

Druhý výsledok obsahuje informácie o problémoch pri kontrole databáz.

Typické upozornenia:

- Databáza nie je ONLINE.
- Databáza je READ_ONLY.
- Databáza nie je dostupná pre aktuálny účet.
- Cieľový databázový používateľ neexistuje, hoci referenčný má priradené roly.
- Pri kontrole databázy nastala chyba.

> [!WARNING]
> Ak skript zaznamená upozornenia, režim aplikovania sa zablokuje. Pred opakovaným spustením je potrebné zistiť ich príčinu.

---

## Aplikovanie chýbajúcich členstiev

### 5. Povolenie aplikovania

Po kontrole výsledkov a schválení zmeny nastav:

```sql
DECLARE @Apply BIT = 1;
```

Spusti celý skript znova.

Skript automaticky:

1. Zopakuje porovnanie členstva v databázových rolách.
2. Skontroluje prípadné upozornenia.
3. Vyberie rozdiely označené `MISSING - ADD`.
4. Overí aktuálne členstvo pred vykonaním zmeny.
5. Doplní chýbajúce členstvá pomocou `ALTER ROLE`.
6. Vráti zoznam úspešne vykonaných operácií.

Použitý SQL príkaz:

```sql
ALTER ROLE [RoleName]
ADD MEMBER [DatabaseUser];
```

> [!WARNING]
> Režim `@Apply = 1` mení databázové oprávnenia prostredníctvom členstva v rolách. Na produkcii ho používaj až po vyhodnotení rozdielov a schválení požadovaných zmien.

### 6. Výsledok aplikovania

Po úspešnom vykonaní sa zobrazí tretí výsledok.

**Result 3 – Applied Changes**

| DatabaseName | RoleName | TargetUser | ActionStatus |
|---|---|---|---|
| AppDB | AppWriter | app_service_new | ADDED |
| ReportingDB | db_datareader | app_service_new | ADDED |

**Význam stavu:**

- `ADDED` – členstvo v databázovej role bolo úspešne doplnené.

Skript používa transakčné spracovanie a pri chybe počas aplikovania vykoná `ROLLBACK TRANSACTION`.

### 7. Záverečná kontrola

Po dokončení nastav:

```sql
DECLARE @Apply BIT = 0;
```

Spusti celý skript ešte raz.

**Očakávaný výsledok:**

- Žiadne `MISSING - ADD`.
- Žiadne nevyriešené upozornenia.
- Prípadné `EXTRA - KEEP` predstavujú členstvá cieľového účtu navyše.

> [!TIP]
> Výsledky kontroly pred zmenou a po zmene je vhodné uložiť ako súčasť dokumentácie alebo change ticketu.

---

## Bezpečnostné mechanizmy

### Dry-run

Predvolený režim nevykonáva žiadne zmeny.

### Automatické vyhľadávanie databáz

Skript pracuje bez pevného zoznamu databáz a rolí.

### Kontrola existencie členstva

Pred pridaním používateľa kontroluje, či už členom požadovanej roly nie je.

### Transakčné spracovanie

Zmeny sa vykonávajú v transakcii. V prípade chyby počas aplikovania sa vykoná rollback.

### Ochrana pred neúplnou kontrolou

Zistené upozornenia blokujú automatické aplikovanie zmien.

### Bez automatického odoberania oprávnení

Skript pridáva iba chýbajúce členstvá. Existujúce členstvá cieľového používateľa nemení.

---

## Obmedzenia skriptu

> [!IMPORTANT]
> Skript slúži na synchronizáciu **priameho členstva v databázových rolách**, nie na kompletné kopírovanie SQL Server oprávnení.

Skript neporovnáva ani nesynchronizuje:

- Serverové roly (`sysadmin`, `securityadmin` a ďalšie).
- Priame serverové oprávnenia (`GRANT`, `DENY`).
- Priame databázové oprávnenia (`SELECT`, `INSERT`, `UPDATE`, `DELETE`, `EXECUTE`).
- Vlastníctvo databáz a databázových objektov.
- Všetky efektívne oprávnenia zdedené nepriamo.
- Heslá a autentifikačné nastavenia SQL loginov.

Skript nevytvára chýbajúcich databázových používateľov.

Ak referenčný databázový používateľ neexistuje, skript nemá jeho členstvá na porovnanie. Ak cieľový používateľ v tej istej databáze má nejaké roly, zobrazia sa ako `EXTRA - KEEP`.

---

## Bezpečnostné upozornenie

Pridanie používateľa do databázovej roly môže výrazne rozšíriť jeho oprávnenia.

Príklady:

| Rola | Význam |
|---|---|
| `db_datareader` | Čítanie používateľských tabuliek a pohľadov |
| `db_datawriter` | Úprava dát v používateľských tabuľkách |
| `db_ddladmin` | Vykonávanie DDL operácií |
| `db_owner` | Rozsiahle administrátorské oprávnenia v databáze |

Používateľsky definované roly môžu mať vlastné oprávnenia alebo byť členmi ďalších databázových rolí.

> [!CAUTION]
> Nesynchronizuj oprávnenia iba preto, že dva účty majú byť „rovnaké“. Najskôr over, či cieľový účet skutočne potrebuje všetky oprávnenia referenčného účtu. Dodržiavaj princíp najmenších potrebných oprávnení (Least Privilege).

---

## Odporúčaný DBA workflow

1. Pripoj sa na požadovanú SQL Server inštanciu.
2. Nastav referenčný a cieľový login.
3. Spusti kontrolu s `@Apply = 0`.
4. Skontroluj rozdiely a upozornenia.
5. Over, čo umožňujú chýbajúce databázové roly.
6. V prípade potreby získaj schválenie zmeny.
7. Nastav `@Apply = 1` a aplikuj zmeny.
8. Skontroluj zoznam vykonaných operácií.
9. Opakuj kontrolu s `@Apply = 0`.
10. Over funkčnosť aplikácie alebo služby.

---

## Zhrnutie

Skript umožňuje rýchlo identifikovať a kontrolovane doplniť chýbajúce členstvá v databázových rolách medzi dvoma SQL loginmi.

**Hlavné výhody:**

- Automatické vyhľadanie databáz.
- Automatické porovnanie databázových rolí.
- Použiteľnosť na rôznych SQL Server inštanciách.
- Predvolený bezpečný režim kontroly.
- Aplikovanie zmien pomocou jediného prepínača.
- Výpis vykonaných zmien.
- Bez automatického odoberania existujúcich členstiev.

**SQL Server:** 2012+

**Použitie:** Security Audit / Database Role Comparison / Role Synchronization

> [!NOTE]
> Tento nástroj je určený pre DBA na opakovateľné porovnávanie a dorovnávanie databázových rolí. Nenahrádza kompletný audit efektívnych SQL Server oprávnení.