Я немного написал что-то вроде 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;