2010-03-17 32 views
6

Hola chicos, este es el trato.Cómo ejecutar T-SQL para varias bases de datos cuyos nombres están almacenados en una tabla

Tengo varias bases de datos (SqlServer 2005) en el mismo servidor con el mismo esquema pero diferentes datos.

Tengo una base de datos adicional que tiene una tabla que almacena los nombres de las bases de datos mencionadas.

Entonces, lo que tengo que hacer es iterar sobre el nombre de las bases de datos y realmente "cambiar" a cada uno (usar [nombrebd]) y ejecutar un script T-SQL. ¿Estoy claro?

Déjeme darle un ejemplo (simplificado de la real):

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

Supongamos que DatabaseA, DatabaseB y DatabaseC son bases de datos reales existentes. Digamos que necesito crear un nuevo SP en esos DB. Necesito algún script que pase por encima de esas bases de datos y ejecute el script T-SQL que especifico (tal vez almacenado en una variable varchar o donde sea).

¿Alguna idea?

Gracias!

Respuesta

2

supongo que esto generalmente no será posible en TSQL, ya que, como otros señalaron,

  • primero tiene como sentencia USE para cambiar la base de datos,

  • seguido de la instrucción que desea ejecutar, que es, aunque no se especifica, una instrucción DDL que debe ser la primera en un lote.

  • Además, no puede tener un GO en una cadena para ser ejecutado.

me encontré con una línea de comandos solución invocación 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()" 

Código de ejemplo utiliza la corriente de inicio de sesión de Windows para consultar todas las bases de datos activas del Maestro (sustituir con su propia conexión y la consulta de bases de datos), y ejecuta un comando TSQL literal en cada base de datos así encontrada. (saltos de línea para mayor claridad)

Eche un vistazo a command-line parameters of sqlcmd. También puedes pasarle un archivo TSQL.

Si desea permitir la selección manual de bases de datos, eche un vistazo a SSMS Tools Pack.

+0

¡Gracias! No sabía sobre el paquete de herramientas SSMS. Voy a intentarlo, ya que es la forma más simple de hacerlo sin mucho esfuerzo. – emzero

0

utilizar el comando USE y repetir sus comandos

Sal. Echar un vistazo a cómo utilizar su uso con un parámetro here

+1

¿Cómo puedo utilizar el comando USE con una cadena literal de un conjunto de resultados? Quiero decir que tengo los nombres de la base de datos en una columna varchar. – emzero

+0

@emzero, mira mi edición anterior, ¡pero asegúrate de validar tu entrada! – CResults

3

La forma más sencilla es la siguiente:

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 

Sin embargo, es obvio que no va a funcionar para los estados que necesitan ser la primera instrucción de un lote, como CREAR PROCEDIMIENTO. Para eso puedes usar SQLCLR. Crear e implementar una clase como esta:

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(); 
      } 
     } 
    } 
} 

entonces usted puede hacer

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 
+0

Gracias, pero no está funcionando. Quiero decir, no está cambiando a las bases de datos, está ejecutando lo que escribo "- Haga lo que quiera" varias veces en el mismo DB (el que tiene la tabla DatabaseNames) – emzero

+0

Esto no funcionará. Entrada PER MSDN en la declaración EXEC "Los cambios en el contexto de la base de datos solo duran hasta el final de la sentencia EXECUTE". – JohnFx

+0

Los cursores suelen ser malos, pero a veces son necesarios (como en este ejemplo) +1 para eso. @emzero + JohnFx: la razón por la que esto no funciona es porque tendrá que virar su declaración a la variable @stmt y ejecutarla como un lote. – StingyJack

1

usted debería ser capaz de hacer esto con la sp_MSforeachdb procedimiento almacenado indocumentado.

+0

Esto no da control sobre qué DBs se ejecutan. – Joe

+0

¡Claro que sí! Solo necesita usar la función incorporada DB_NAME() en cualquier consulta que esté utilizando. Puede usarlo para excluir ciertas bases de datos o incluirlas, dependiendo de cuál sea más adecuado para la aplicación. – mwigdahl

1

Este método requiere que ponga su script SQL para que se ejecute en cada DB en una variable, pero debería funcionar.

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 

Además, como algunos han señalado. Este enfoque es problemático si desea ejecutar un comando que debe ser lo único en un lote.

Aquí hay una alternativa para esa situación, pero requiere más permisos de los que muchos DBA podrían desear tener y requiere que coloque su SQL en un archivo de texto aparte.

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 
+0

No funcionará. 1: No existe ninguna declaración como CREATE STORED PROCEDURE 2: CREATE PROCEDURE necesita ser la primera instrucción en un lote. – erikkallen

+0

Respuesta fija para incluir un método que funcione con cosas que deben estar en su propio lote. – JohnFx

0

Sé que esta pregunta tiene 5 años, pero encontré esto a través de Google, por lo que otros también pueden.

Recomiendo sp_msforeachdb system stored procedure. No necesita crear ningún otro procedimiento almacenado o cursores.

Dada la tabla de nombres de bases de datos ya está creada:

EXECUTE sp_msforeachdb ' 
USE ? 

IF DB_NAME() 
    IN(SELECT name DatabaseNames) 
BEGIN 

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

END 
' 

hago esto para resumir los recuentos en muchas bases de datos que he restaurado a partir de varios sitios diferentes con el mismo esquema de la base instalada.

Ejemplo: - Repita la ejecución de Comandos SQL en todas las bases de datos archivadas.

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 
' 

La parte más complicada son las comillas simples '

Cuestiones relacionadas