Как выполнить T-SQL для нескольких баз данных, имена которых хранятся в таблице - PullRequest
7 голосов
/ 17 марта 2010

Эй, ребята, вот вам и сделка.

У меня есть несколько баз данных (SqlServer 2005) на одном сервере с одинаковой схемой, но разными данными.

У меня есть одна дополнительная база данных, в которой есть одна таблица с именами упомянутых баз данных.

Итак, мне нужно перебрать имя базы данных и фактически "переключиться" на каждое (использовать [имя_базы]) и выполнить скрипт T-SQL. Я ясно?

Позвольте мне привести пример (упрощенный от реального):

CREATE TABLE DatabaseNames
(
   Id   int,
   Name varchar(50)
)
INSERT INTO DatabaseNames SELECT 'DatabaseA'
INSERT INTO DatabaseNames SELECT 'DatabaseB'
INSERT INTO DatabaseNames SELECT 'DatabaseC'

Предположим, что DatabaseA, DatabaseB и DatabaseC являются реально существующими базами данных. Допустим, мне нужно создать новый SP на этих БД. Мне нужен какой-нибудь сценарий, который зацикливается на этих базах данных и выполняет указанный мной сценарий T-SQL (может храниться в переменной varchar или где-либо еще).

Есть идеи?

Спасибо!

Ответы [ 6 ]

3 голосов
/ 17 марта 2010

Самый простой способ это:

DECLARE @stmt nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT 'USE [' + Name + ']' FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
    FETCH c INTO @stmt
    IF @@fetch_status <> 0 BREAK
    SET @stmt = @stmt + ' ' + @what_you_want_to_do
    EXEC(@stmt)
END
CLOSE c
DEALLOCATE c

Однако, очевидно, он не будет работать для операторов, которые должны быть первыми в пакете, например CREATE PROCEDURE. Для этого вы можете использовать SQLCLR. Создайте и разверните класс следующим образом:

public class StoredProcedures {
    [SqlProcedure(Name="exec_in_db")]
    public static void ExecInDb(string dbname, string sql) {
        using (SqlConnection conn = new SqlConnection("context connection=true")) {
            conn.Open();
            using (SqlCommand cmd = conn.CreateCommand()) {
                cmd.CommandText = "USE [" + dbname + "]";
                cmd.ExecuteNonQuery();
                cmd.CommandText = sql;
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Тогда вы можете сделать

DECLARE @db_name nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT Name FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
    FETCH c INTO @@db_name
    IF @@fetch_status <> 0 BREAK
    EXEC exec_in_db @db_name, @what_you_want_to_do
END
CLOSE c
DEALLOCATE c
2 голосов
/ 17 марта 2010

Я полагаю, что это вообще невозможно в TSQL, так как, как отмечали другие,

  • сначала вы должны использовать оператор USE для изменения базы данных,

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

  • Более того, вы не можете использовать GO в строке для EXECuted.

Я нашел решение командной строки, вызывающее sqlcmd:

for /f "usebackq" %i in 
    (`sqlcmd -h -1 -Q 
     "set nocount on select name from master..sysdatabases where status=16"`)
    do
        sqlcmd -d %i -Q "print db_name()"

В примере кода используется текущий вход в систему Windows для запроса всех активных баз данных от Master (замените его своим собственным соединением и запросом баз данных) и выполняет буквальную команду TSQL для каждой найденной таким образом базы данных. (разрывы строк только для ясности)

Посмотрите на параметры командной строки sqlcmd . Вы также можете передать ему файл TSQL.

Если вы хотите разрешить ручной выбор баз данных, взгляните на SSMS Tools Pack .

2 голосов
/ 17 марта 2010

Этот метод требует, чтобы ваш сценарий SQL выполнялся на каждой БД в переменной, но он должен работать.

DECLARE @SQLcmd varchar(MAX)
SET @SQLcmd ='Your SQL Commands here'

DECLARE @dbName nvarchar(200)
DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT dbName FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
    FETCH c INTO @dbName 
    IF @@fetch_status <> 0 BREAK
    EXEC('USE [' + @dbName + '] ' + @SQLcmd )
END
CLOSE c

Кроме того, как указали некоторые. Этот подход проблематичен, если вы хотите запустить команду, которая должна быть единственной в пакете.

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

DECLARE c CURSOR LOCAL FORWARD_ONLY FOR SELECT dbName FROM DatabaseNames
OPEN c
WHILE 1 <> 0 BEGIN
    FETCH c INTO @dbName 
    IF @@fetch_status <> 0 BREAK
     exec master.dbo.xp_cmdshell 'osql -E -S '+ @@SERVERNAME + ' -d ' + @dbName + '  -i c:\test.sql'
END
CLOSE c
DEALLOCATE c
2 голосов
/ 17 марта 2010

Это можно сделать с помощью недокументированной хранимой процедуры sp_MSforeachdb .

0 голосов
/ 15 октября 2015

Я знаю, что этому вопросу 5 лет, но я нашел его через Google, так что другие тоже могут.

Я рекомендую системную хранимую процедуру sp_msforeachdb. Вам не нужно создавать какие-либо другие хранимые процедуры или курсоры.

Учитывая, что ваша таблица имен баз данных уже создана:

EXECUTE sp_msforeachdb '
USE ? 

IF DB_NAME() 
    IN( SELECT name DatabaseNames )
BEGIN

    SELECT 
              ''?'' as 'Database Name'  
            , COUNT(*)
    FROM
            MyTableName
    ;

END
'

Я делаю это для подведения итогов во многих базах данных, которые я восстановил с нескольких разных сайтов с одной и той же установленной схемой базы данных.

Пример: - Повторите выполнение команд SQL для всех архивных баз данных сайта.

PRINT       'Database Name'
+ ',' +     'Site Name'
+ ',' +     'Site Code'
+ ',' +     '# Users'
+ ',' +     '# Seats'
+ ',' +     '# Rooms'
...  and so on...
+ ',' +     '# of days worked'
;


EXECUTE sp_msforeachdb 'USE ?

IF DB_NAME() 
    IN( SELECT name FROM sys.databases  WHERE name LIKE ''Site_Archive_%'' )
BEGIN

DECLARE  @SiteName      As Varchar(100);
DECLARE  @SiteCode      As Varchar(8);

DECLARE  @NumUsers  As Int
DECLARE  @NumSeats  As Int
DECLARE  @NumRooms  As Int
... and so on ...

SELECT  @SiteName = OfficeBuildingName FROM  Office
...

SELECT @NumUsers = COUNT(*)  FROM   NetworkUsers
...

PRINT       ''?'' 
+ '','' +     @SiteName 
+ '','' +     @SiteCode
+ '','' +     str(@NumUsers)
...
+ '','' +     str(@NumDaysWorked) ;
END
'

Самая хитрая часть - это одинарные кавычки

0 голосов
/ 17 марта 2010

Используйте команду USE и повторите ваши команды

Ps. Посмотрите, как использовать USE с параметром здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...