SQL - удаление пользователя из всех баз данных в экземпляре без курсора или sp_foreach - PullRequest
0 голосов
/ 26 февраля 2020

Цель:

  • Выполнить итерацию по каждой базе данных на сервере и удалить пользователя, указав при этом имя базы данных, а также сообщение об ошибке, если есть ошибка, например если пользователь не существует в этой базе данных.

Проблема:

  • Код выполняется, но я не думаю, что он фактически проверяет каждый сервер. Я говорю это потому, что знаю, что пользователь существует в одной конкретной базе данных, но я получаю сообщение «Пользователь« имя пользователя »не существует.

В столбце DBName показаны все базы данных на сервере. , но почему этот код показывает, что пользователь не существует в БД, где я знаю, что он существует?

Спасибо за помощь!

    USE master;
    SET NOCOUNT ON;
    IF OBJECT_ID ('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp;

    SELECT  name
          , 0 AS completed
    INTO    #tmp
    FROM    sys.databases
    WHERE   name NOT IN ( 'tempdb', 'Training2012' );   -- A list of DBs you don't wish to include

    DECLARE @dbname sysname;

    DECLARE @cmd NVARCHAR(4000);
    DECLARE @cmd2 NVARCHAR(4000) = N'
    DECLARE  @user1 VARCHAR(15) = ''username''
    BEGIN TRY
        IF (EXISTS (SELECT  * FROM  sys.database_principals WHERE   name = @user1)) DROP USER [@user1];
    ELSE SELECT  @dbname AS DBName, ''User '' + @user1 +'' does not exist'' AS DOES_NOT_EXIST;
    END TRY
    BEGIN CATCH
    SELECT  @dbname            AS DBName
          , @user1                AS [User]
          , ERROR_MESSAGE ()      AS ErrorMsg          
    END CATCH;'


    WHILE EXISTS (SELECT 1 FROM #tmp WHERE  completed = 0)
    BEGIN

        SET @dbname = (SELECT TOP 1 name FROM #tmp WHERE  completed = 0 ORDER BY  name); -- You can ORDER BY name if you care about the order       

        BEGIN TRY
            --SET @cmd
            --EXEC sp_executesql @cmd2;
             EXEC sp_executesql @cmd2, N'@dbname sysname',@dbname = @dbname;
            UPDATE  #tmp
            SET     completed = 1
            WHERE   name = @dbname;
        END TRY
        BEGIN CATCH
            SELECT  @@SERVERNAME     AS ServerName
                  , DB_NAME ()       AS DBName
                  , ERROR_MESSAGE () AS ErrorMessage
                  , ERROR_LINE ()    AS ErrorLine;
        END CATCH;
    END;

Ответы [ 2 ]

2 голосов
/ 26 февраля 2020

Я немного написал что-то вроде go, которое могло бы работать для этого, которое не использует CURSOR для запуска команд через базы данных, которое я назвал sp_foreachdatabase: CURSOR бесплатная версия sp_msforeachdb . Ниже приведены определения:

USE master;
GO
IF NOT EXISTS (SELECT 1 FROM sys.types WHERE [name] = N'objectlist')
    CREATE TYPE dbo.objectlist AS table ([name] sysname);

GO

USE master;
GO
CREATE OR ALTER PROC sp_foreachdatabase @Command nvarchar(MAX),
                                        @Delimit_Character nchar(1) = N'?', --Character to be replaced with a delimit identified version of the datbaase name I.e. [master]
                                        @Quote_Character nchar(1) = N'&', --Character to be replaced with a single quoted (') version of the datbaase name I.e. 'master'
                                        @Skip_System bit = 0, --Omits master, msdb, tempdb and model. Ignored if @Database_List has data.
                                        @Skip_User bit = 0, --Omits all user databases. Ignored if @Database_List has data.
                                        @Database_List dbo.objectlist READONLY, --If @Skip_System and @Skip_User equal 1, and this is empty, an error will be thrown
                                        @Auto_Use bit = 0, --Automatically starts each command agaisnt a database with a USE
                                        @Exit_On_Error bit = 1, --If an error is occurs against a single database, the command will still be run against the remainder. Otherwise everything is rolled back
                                                                --This does not effect the @Pre_Command and @Post_Command statements
                                        @Pre_Command nvarchar(MAX) = NULL, --Command to run before @Command. Does not use Character Replacements. Run against master DB.
                                        @Post_Command nvarchar(MAX) = NULL, --Command to run after @Command. Does not use Character Replacements. Run against master DB.
                                        @Command_Run nvarchar(MAX) = NULL OUTPUT  --Returns the generated and replaced command, for trouble shooting
AS BEGIN

    --Do some checking of passed values first
    --Check that @Skip_System, @Skip_User aren't both 0 or that @Database_List has some rows
    IF (@Skip_System = 1 AND @Skip_User = 1 AND NOT EXISTS (SELECT 1 FROM @Database_List))
        THROW 62401, N'System and User databases cannot be skipped if a Database List is not supplied.', 16;

    IF @Delimit_Character IS NULL
        THROW 62402, N'@Delimit_Replace cannot have a value of NULL.', 16;

    IF @Quote_Character IS NULL
        THROW 62403, N'@Quoted_Replace cannot have a value of NULL.', 16; 

    IF @Skip_User IS NULL
        THROW 62404, N'@Skip_User cannot have a value of NULL.', 16;

    IF @Skip_System IS NULL
        THROW 62405, N'@Skip_System cannot have a value of NULL.', 16;

    IF @Auto_Use IS NULL
        PRINT N'@Auto_Use has a value of NULL. Behaviour will be as if the value is 0.';

    DECLARE @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
    DECLARE @RC int;

    --Add the Pre Command to the batch
    SET @Command_Run = ISNULL(N'/* --- Pre Command Begin. --- */' + @CRLF + @CRLF + N'USE master;' + @CRLF + @CRLF + @Pre_Command + @CRLF + @CRLF + N'/* --- Pre Command End. --- */', N'');

    --Get the databases we need to deal with
    --As @Database_List might be empty and it's READONLY, and we're going to do the command in database_id order we need another variable.
    DECLARE @DBs table (database_id int,
                        database_name sysname);
    IF EXISTS (SELECT 1 FROM @Database_List)
        INSERT INTO @DBs (database_id,database_name)
        SELECT d.database_id,
               d.[name]
        FROM sys.databases d
             JOIN @Database_List DL ON d.[name] = DL.[name];
    ELSE
        INSERT INTO @DBs (database_id,database_name)
        SELECT d.database_id,
               d.[name]
        FROM sys.databases d
        WHERE (d.database_id <= 4 AND @Skip_System = 0) OR (d.database_id > 4 AND @Skip_User = 0);

    SET @Command_Run = @Command_Run + @CRLF + @CRLF +
                       N'/* --- Begin command for each database. --- */' + @CRLF + @CRLF +
                       CASE WHEN @Exit_On_Error = 0 THEN N'--Turning XACT_ABORT off due to @Exit_On_Error parameter' + @CRLF + @CRLF + N'SET XACT_ABORT OFF;' + @CRLF + N'DECLARE @Error nvarchar(4000);' ELSE N'SET XACT_ABORT ON;' END +
                       (SELECT @CRLF + @CRLF + 
                               N'/* --- Running @Command against database ' + QUOTENAME(DB.database_name,'''') + N'. --- */' + @CRLF + @CRLF +
                               CASE WHEN @Auto_Use = 1 THEN N'USE ' + QUOTENAME(DB.database_name) + N';' + @CRLF + @CRLF ELSE N'' END +
                               N'BEGIN TRY' + @CRLF + @CRLF +
                               REPLACE(REPLACE(@Command, @Delimit_Character, QUOTENAME(DB.database_name)),@Quote_Character, 'N' + QUOTENAME(DB.database_name,'''')) + @CRLF + @CRLF +
                               'END TRY' + @CRLF +
                               N'BEGIN CATCH' + @CRLF +
                               CASE WHEN @Exit_On_Error = 0 THEN N'    SET @Error = N''The following error occured during the batch, but has been skipped:'' + NCHAR(13) + NCHAR(10) + ' + @CRLF +
                                                                 N'                 N''Msg '' + CONVERT(nvarchar(6),ERROR_NUMBER()) + '', Level '' + CONVERT(nvarchar(6),ERROR_SEVERITY()) + '', State '' + CONVERT(nvarchar(6),ERROR_STATE()) + '', Line '' + CONVERT(nvarchar(6),ERROR_LINE()) + NCHAR(13) + NCHAR(10) +' + @CRLF + 
                                                                 N'                 ERROR_MESSAGE();' + @CRLF +
                                                                 N'    PRINT @Error;' + @CRLF +
                                                                 N'    SET @RC = ERROR_NUMBER();'
                                                            ELSE N'    THROW;'
                               END + @CRLF +
                               N'END CATCH;' + @CRLF +
                               N'/* --- Completed @Command against database ' + QUOTENAME(DB.database_name,'''') + N'. --- */'
                        FROM @DBs DB
                        FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)') + @CRLF + @CRLF +
                        CASE WHEN @Exit_On_Error = 0 THEN N'--Turning XACT_ABORT back on due to @Exit_On_Error parameter' + @CRLF + @CRLF + N'SET XACT_ABORT ON;' ELSE N'' END;

    SET @Command_Run = @Command_Run + ISNULL(@CRLF + @CRLF + N'/* --- Post Command Begin. --- */' + @CRLF + @CRLF + N'USE master;' + @CRLF + @CRLF + @Post_Command + @CRLF + @CRLF + N'/* --- Post Command End. --- */', N'');

    EXEC sp_executesql @Command_Run, N'@RC int OUTPUT', @RC = @RC;

    SET @RC = ISNULL(@RC, 0);
    RETURN @RC;

END;
GO

Часть того, почему я думаю, что это было бы уместно, связана с параметром @Exit_On_Error, где вы специально хотели получить сообщение в случае возникновения проблемы. Процитирую мою статью:

Одно преимущество, которое имел бы Курсор, состоит в том, что каждый оператор для базы данных будет выполняться в своем собственном пакете; Это означает, что если один не удастся, другие не будут выполнены. Параметр @Exit_On_Error добавляет эту функцию, отлавливая ошибку и затем распечатывая ее. SP также возвращает номер ошибки последнего возвращенного номера ошибки. Обратите внимание, что TRY...CATCH не работает для всех типов ошибок (например, недопустимое имя объекта).

Удаление USER, которое не существует, приведет к ошибке, но не приведет к пакетный сбой с этим параметром, установленным на 0. Поэтому вы можете сделать что-то вроде следующего:

USE master;
GO

DECLARE @User sysname = N'YourUser'; --This is your user parameter

DECLARE @SQL nvarchar(MAX) = N'DROP USER ' + QUOTENAME(@User) + N';' --The SQL to run in each database

DECLARE @Command_Run nvarchar(MAX); --This is an OUTPUT parameter
DECLARE @Database_List dbo.objectlist; --Needs to be declared, as required, but nothing will be inserted
EXEC dbo.sp_foreachdatabase @Command = @SQL,
                            @Skip_System = 1, --Assumed skipping system databases
                            @Skip_User = 0, --Assumed we want user databases
                            @Database_List = @Database_List, --This is empty
                            @Auto_Use = 1, --Puts a USE statement at start of each database query
                            @Exit_On_Error = 0, --Causes a PRINT of errors, but doesn't THROW them
                            @Command_Run = @Command_Run OUTPUT; --The SQL that's run, in case you need it.

В моем случае это не DROP пользователей, поэтому выводит следующее:

The following error occured during the batch, but has been skipped:
Msg 15151, Level 1, State 1, Line 16
Cannot drop the user 'testUser', because it does not exist or you do not have permission.
The following error occured during the batch, but has been skipped:
Msg 15151, Level 1, State 1, Line 34
Cannot drop the user 'testUser', because it does not exist or you do not have permission.
The following error occured during the batch, but has been skipped:
Msg 15151, Level 1, State 1, Line 52
Cannot drop the user 'testUser', because it does not exist or you do not have permission.
The following error occured during the batch, but has been skipped:
Msg 15151, Level 1, State 1, Line 70
Cannot drop the user 'testUser', because it does not exist or you do not have permission.

You также можно увидеть прогон SQL от @Command_Run, который показывает следующее:

/* --- Begin command for each database. --- */

--Turning XACT_ABORT off due to @Exit_On_Error parameter

SET XACT_ABORT OFF;
DECLARE @Error nvarchar(4000);

/* --- Running @Command against database 'Sandbox'. --- */

USE [Sandbox];

BEGIN TRY

DROP USER testUser

END TRY
BEGIN CATCH
    SET @Error = N'The following error occured during the batch, but has been skipped:' + NCHAR(13) + NCHAR(10) + 
                 N'Msg ' + CONVERT(nvarchar(6),ERROR_NUMBER()) + ', Level ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', State ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', Line ' + CONVERT(nvarchar(6),ERROR_LINE()) + NCHAR(13) + NCHAR(10) +
                 ERROR_MESSAGE();
    PRINT @Error;
    SET @RC = ERROR_NUMBER();
END CATCH;
/* --- Completed @Command against database 'Sandbox'. --- */

/* --- Running @Command against database 'CaseSensitive'. --- */

USE [CaseSensitive];

BEGIN TRY

DROP USER testUser

END TRY
BEGIN CATCH
    SET @Error = N'The following error occured during the batch, but has been skipped:' + NCHAR(13) + NCHAR(10) + 
                 N'Msg ' + CONVERT(nvarchar(6),ERROR_NUMBER()) + ', Level ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', State ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', Line ' + CONVERT(nvarchar(6),ERROR_LINE()) + NCHAR(13) + NCHAR(10) +
                 ERROR_MESSAGE();
    PRINT @Error;
    SET @RC = ERROR_NUMBER();
END CATCH;
/* --- Completed @Command against database 'CaseSensitive'. --- */

/* --- Running @Command against database 'AdventureWorks2012'. --- */

USE [AdventureWorks2012];

BEGIN TRY

DROP USER testUser

END TRY
BEGIN CATCH
    SET @Error = N'The following error occured during the batch, but has been skipped:' + NCHAR(13) + NCHAR(10) + 
                 N'Msg ' + CONVERT(nvarchar(6),ERROR_NUMBER()) + ', Level ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', State ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', Line ' + CONVERT(nvarchar(6),ERROR_LINE()) + NCHAR(13) + NCHAR(10) +
                 ERROR_MESSAGE();
    PRINT @Error;
    SET @RC = ERROR_NUMBER();
END CATCH;
/* --- Completed @Command against database 'AdventureWorks2012'. --- */

/* --- Running @Command against database 'TestBed'. --- */

USE [TestBed];

BEGIN TRY

DROP USER testUser

END TRY
BEGIN CATCH
    SET @Error = N'The following error occured during the batch, but has been skipped:' + NCHAR(13) + NCHAR(10) + 
                 N'Msg ' + CONVERT(nvarchar(6),ERROR_NUMBER()) + ', Level ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', State ' + CONVERT(nvarchar(6),ERROR_STATE()) + ', Line ' + CONVERT(nvarchar(6),ERROR_LINE()) + NCHAR(13) + NCHAR(10) +
                 ERROR_MESSAGE();
    PRINT @Error;
    SET @RC = ERROR_NUMBER();
END CATCH;
/* --- Completed @Command against database 'TestBed'. --- */

--Turning XACT_ABORT back on due to @Exit_On_Error parameter

SET XACT_ABORT ON;
0 голосов
/ 26 февраля 2020

Думаю, что в данный момент вы не можете найти пользователей.

Вы получаете информацию из sys.database_principals, но запускаете ее от мастера. sys.database_principals отличается для каждой базы данных. Не думайте, что ваши Dynami c sql действительно действительны либо.

Т.е. если у вас есть базы данных

  1. Test
  2. AB C
  3. Production

Вам нужно проверить наличие пользователей в каждой из этих баз данных, запустив их в своем контексте:

USE Test
GO

SELECT * FROM  sys.database_principals WHERE name = @userName

Или запросить его у другого context / db, добавляя базу данных перед схемой.

SELECT * FROM  Test.sys.database_principals WHERE name = @userName
SELECT * FROM  ABC.sys.database_principals WHERE name = @userName
SELECT * FROM  Production.sys.database_principals WHERE name = @userName

В настоящий момент вы просто проверяете базу данных master.

...